Friday, February 15, 2008

arrrggg -- strlcat

So, I am using strlcat and strlcpy in the project I'm working on. We had to implement our own version of these for Linux since it does not come with them. Well today I had a frustrating day of writing unit tests for this code. I developed the tests in cygwin as I wanted to validate that the native implementation and our implementation work the same. However, I find that cygwin doesn't work according to the available doc. So I went home to try it on my Mac and Linux boxes. So what do I find...

Well lets start with the return values section of Apple's man page:
RETURN VALUES
The strlcpy() and strlcat() functions return the total length of the string they tried to create.

Sun even documents this in better detail. The return value is:
min(sz, strlen(dest)) + strlen(src)

Well that seems simple enough, so what are the results of the tests. The dest in my tests is a 5 character array. I will represent the contents of this as a string literal.

0 == strlcat( "", "", 0 ) yes it does

3 == strlcat( "", "bbb", 0 ) no it returns 0

2 == strlcat( "aa", "", 5 ) yes it does

2 == strlcat( "", "bb", 5 ) yes it does

4 == strlcat( "aa", "bb", 5 ) yes it does

5 == strlcat( "", "abcde", 5 ) no this returns 4

9 == strlcat( "aaaa", "abcde", 5 ) no this returns 4

? == strlcat( "aa", "abcd", 1 )
Well what should the last one return. The case of dest not having a NULL within size characters is an error condition. I think that this should return 5 (size + strlen(src) ). But, no matter what I think, it actually returns 0.

So of course this means that the example that they give in the man page does not work.
if ( strlcpy( pname, dir, sizeof( pname ) ) >= sizeof( pname ) )
goto toolong;

As we can see from my example above, strlcat( "aaaa", "abcde", 5) only returns 4. And last I checked 4 is less than 5.

On Monday I'll have to try this out on Solaris to see what it says.

And don't get me started on the topic of the routines not validating their input. This is supposed to be a standard library, it should not cause a core dump when it receives bad input. Would it be so terribly difficult to return 0 and set errno if you got a NULL pointer?

2 comments:

Anonymous said...

You need to understand the parameters in the command.

You say

3 == strlcat( "", "bbb", 0 ) no it returns 0

This is correct with the arguments you pass.

First argument shall be a buffer, where you can add text to. In this case there is no buffer, just a constant sting.

The second argument is the string you want to add.

The third argument is the length of the buffer of the first argument. If you declare the first argument like
char astring[256];
you should probably pass the third argument as sizeof(astring).

A small example of how you could use strlcat is
char astring[256];
strcpy(astring, "Hello ");
while (strlcat(astring, "world!", sizeof(astring)) < sizeof(astring))
{
++count;
}

How many times does the "world" fits in the string?

Steve Hill said...

@Björn

Thanks for you comment.

You are correct the first argument, dest, is a buffer. As I wrote in my post dest is a 5 character buffer. I chose to show the contents of that buffer as a string literal for readability.

Your example unfortunately does not work. It should, and it would, if strlcat worked the way it is documented. Here is a more explicit example.

char dest[15];
int count = 0;
int stat = 0;
printf("\nTesting strlcat\n");
memset(dest, 0, 15);
while( stat < 15 )
{
stat = strlcat(dest, "Hello", 15);
++count;
printf("iter: %d stat: %d strl dest: %d dest: %s\n",
count, stat, strlen(dest), dest );
if( count > 5 ) break;
}

This should exit after the third iteration. But if you compile this and run it you get this.

Testing strlcat
iter: 1 stat: 5 strl dest: 5 dest: Hello
iter: 2 stat: 10 strl dest: 10 dest: HelloHello
iter: 3 stat: 14 strl dest: 14 dest: HelloHelloHell
iter: 4 stat: 14 strl dest: 14 dest: HelloHelloHell
iter: 5 stat: 14 strl dest: 14 dest: HelloHelloHell
iter: 6 stat: 14 strl dest: 14 dest: HelloHelloHell

The problem is that strlcat never returns more than size - 1.