Skip to content

question about strxfrm #141

@Takashiidobe

Description

@Takashiidobe

Take this program that uses strxfrm:

#include <assert.h>
#include <stdio.h>
#include <string.h>

int main(void) {
  char buf[2] = {'X', 'Z'};

  int len = strxfrm(buf, "X", 1); 

  printf("len: %d, buf[0]: %c, buf[1]: %c\n", len, buf[0], buf[1]);
  assert(buf[1] == 'Z'); 
  return 0;
}

I compiled this program with my x86_64's gcc and ran it: This leaves buf[0] and buf[1] as is.

$ gcc strxfrm.c -o strxfrm && ./strxfrm
len: 1, buf[0]: X, buf[1]: Z

If I compile libcmini, link my binary to it, and run it through hatari:

$ make # to build in build dir
$ LIBCMINI=$PWD/build/ m68k-atari-mint-gcc \
    -nostdlib \
    -I"$LIBCMINI" \
    "$LIBCMINI/objs/crt0.o" \
    strxfrm.c atexit_shim.c \
    -L"$LIBCMINI" \
    -lcmini \
    -lgcc \
    -o strxfrm_test.tos # my compiler told me _atexit was undefined, so the atexit_shim just defines it
$ hatari-prg-args -q \
    --tos emutos-512k/etos512us.img \
    --conout 2 \
    -- "$(pwd)/strxfrm_test.tos"

I got this in my terminal:

len: 1, buf[0]: X, buf[1]: .
strxfrm.c:11: Assertion buf[1] == 'Z' failed

So it looks like buf[1] has been replaced with the null terminator.
Looking at the code it makes sense:

// Since we called with char buf[2] = {'X', 'Z'}; + int len = strxfrm(buf, "X", 1); 
size_t strxfrm(char *dest, const char *src, size_t n) {
  if (n > 0) {
    strncpy(dest, src, n);  // since n is one, src is copied to dest, so dest is now 'X'.
    dest[n] = '\0';  // then, dest[1] is overwritten with the null terminator, so dest is now '\0'.
    n = strlen(dest); // the length of dest is returned. This should be strlen(src) I believe.
  }

  return n; // this should also return strlen(src) always
}

If I slightly tweak the test I also get different results than my gcc

#include <assert.h>
#include <stdio.h>
#include <string.h>

int main(void) {
  char buf[2] = {'X', 'Z'};

  int len = strxfrm(buf, "X", 0); 

  printf("len: %d, buf[0]: %c, buf[1]: %c\n", len, buf[0], buf[1]);
  return 0;
}

Compiled with my host gcc prints:

len: 1, buf[0]: X, buf[1]: Z

the return value should always be the strlen of src, even if nothing happens (n is 0). Note that buf's len is 2, src's len is 1, and n is 0, so it is returning strlen(src) here.

libcmini's version prints 0, because the implementation returns 0 if n is 0, otherwise strlen(dest), instead of strlen(src).

len: 0, buf[0]: X, buf[1]: Z

So the fixes:

  1. Always return strlen(src).
  2. If there's space in dest, stick a null terminator. That only happens if n > strlen(dest) + 1. Otherwise, drop the null terminator and copy away.

I assume something like this would work and line up with what I see on gcc.

size_t strxfrm(char *dest, const char *src, size_t n) {
    size_t len = strlen(src);

    if (n > len) {
       memcpy(dest, src, len + 1);
    }

    return len;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions