[comp.os.minix] Bugs in 'strn' library functions

brucee@runx.ips.oz (Bruce Evans) (05/15/88)

The 'strn' functions in the Minix 1.2 library *all* diverge from the V7
standard. I think that means they are broken.

strncpy( char *t, char *s, int n )
-------

Supposed to copy *exactly* n characters, truncating or *null padding* as
necessary. The target need *not* be null-terminated.

Actually copies at most (n + 1) characters and doesn't null pad more than
once. Always null terminates the string, thereby copying 1 too many in
some cases. Treats zero counts as 64K and negative counts as unsigned.
(I've always thought counts should be unsigned, but that's not the
standard).

strncat( char *t, char *s, int n )
-------

Supposed to copy *at most* n characters. The target need *not* be
null-terminated.

Always null terminates the string, thereby copying 1 too many in some
cases. Treats zero count correctly but negative counts as unsigned.

strncmp( char *s1, char *s2, int n )
-------

Supposed to compare at most n characters.

Treats zero count as 64K and negative counts as unsigned.

---

I can easily post correct versions (in C and 2+ different non-Minix
assembly languages yet), but I'm more worried about the correctness
of the library generally, since there is nothing much easier or
better documented than strncpy. (Perhaps the precise documentation
is the problem?) (:-).

Bruce Evans
Internet: brucee@runx.ips.oz.au    UUCP: uunet!runx.ips.oz.au!brucee

brucee@runx.ips.oz (Bruce Evans) (05/22/88)

Here are my versions of the 'strn' library functions, to fix the problems
mentioned in my previous posting. I said I could 'easily' post corrected
versions, but decided to test them carefully first and spent a long time
on it when a bug showed up. To avoid unnecessary complications, I decided
not to post the assembler versions.

I said that the old 'strncat' sometimes does an incorrect null termination.
This is wrong, since the null is effectively the one moved from the
end of the initial target string.

There is some confusion about what the type of the count arguments are. The
functions here treat them as ints and not unsigneds, according to
  Unix Programmer's Manual 1983,1979 (V7)
  C A Reference Manual (Harbison and Steele, 1984)
but for practical DOS compilers
  Turbo C V1.0:     arguments are declared as int but act as unsigned (wrong)
  Microsoft C V4.0: arguments are declared and act as unsigned
and
  Unofficial ANSI C standard: arguments are size_t (unsigned for Minix).
So it looks like the new standard is that the count arguments are unsigned.
This brings in a new set of problems, as lint will quite rightly complain
about millions of old programs which pass int parameters.

# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by brucee on Sun May 22 23:44:57 GMT+10:00 1988
# Contents:  strncat.c strncmp.c strncpy.c
 
echo x - strncat.c
sed 's/^@//' > "strncat.c" <<'@//E*O*F strncat.c//'
/*
  strncat.c - char *strncat( char *s1, char *s2, int n )

  Strncat  appends up to  n  characters  from  s2  to the end of  s1.
  It returns s1.
*/

char *strncat( s1, s2, n )
char *s1;
register char *s2;
int n;
{
  register char *rs1;

  rs1 = s1;
  if ( n > 0 )
  {
    while ( *rs1++ != 0 )
      ;
    --rs1;
    while ( (*rs1++ = *s2++) != 0 )
      if ( --n == 0 )
      {
        *rs1 = 0;
        break;
      }
  }
  return s1;
}
@//E*O*F strncat.c//
chmod u=rw,g=,o= strncat.c
 
echo x - strncmp.c
sed 's/^@//' > "strncmp.c" <<'@//E*O*F strncmp.c//'
/*
  strncmp.c - int strncmp( char *s1, char *s2, int n )

  Strcmp  compares  s1  to  s2, up to at most n characters
          (lexicographically with native character comparison).
  It returns
    positive  if  s1 > s2
    zero      if  s1 = s2
    negative  if  s1 < s2.
*/

int strncmp( s1, s2, n )
register char *s1;
register char *s2;
int n;
{
  if ( n <= 0 )
    return 0;
  while ( *s1++ == *s2++ )
    if ( s1[-1] == 0 || --n == 0 )
      return 0;
  return s1[-1] - s2[-1];
}
@//E*O*F strncmp.c//
chmod u=rw,g=,o= strncmp.c
 
echo x - strncpy.c
sed 's/^@//' > "strncpy.c" <<'@//E*O*F strncpy.c//'
/*
  strncpy.c - char *strncpy( char *s1, char *s2, int n )

  Strncpy  writes exactly  n  (or 0 if n < 0)  characters to  s1.
  It copies up to  n  characters from  s2, and null-pads the rest.
  The result is null terminated iff  strlen( s2 ) < n.
  It returns the target string.
*/

char *strncpy( s1, s2, n )
char *s1;
register char *s2;
int n;
{
  register char *rs1;

  rs1 = s1;
  if ( n > 0 )
  {
    while ( (*rs1++ = *s2++) != 0 && --n != 0 )  
      ;
	if ( n != 0 )
      while ( --n != 0 )
        *rs1++ = 0;
  }
  return s1;
}
@//E*O*F strncpy.c//
chmod u=rw,g=,o= strncpy.c
 
exit 0

Bruce Evans
Internet: brucee@runx.ips.oz.au    UUCP: uunet!runx.ips.oz.au!brucee

henry@utzoo.uucp (Henry Spencer) (05/26/88)

People tinkering with the string functions might want to note the version
I contributed to comp.sources.unix quite a while ago.  It's highly portable,
although generally not optimal for a specific machine.  More to the point,
though, is that it includes a 650-line test program that turns all the
functions upside-down and shakes vigorously, in hopes that bugs will fall
out.  You may want the test program even if you don't want the portable
string functions (which are a complete SysV/BSD/X3J11 set, although they
are not up-to-date with recent changes to X3J11).
-- 
"For perfect safety... sit on a fence|  Henry Spencer @ U of Toronto Zoology
and watch the birds." --Wilbur Wright| {ihnp4,decvax,uunet!mnetor}!utzoo!henry