2

I'm having problems understanding why this program

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int iRetval = 0;
    unsigned int uiNum;

    printf("Enter number: ");
    fflush(stdout);
    iRetval = scanf("%u", &uiNum);
    printf("\nThe number is %u, Retval: %i\n", uiNum, iRetval);
    fflush(stdout);
    if( iRetval > 0)
        system("/bin/sh");
    else
        printf("Goodbye!\n");
}

when invoked from my bash shell

echo -e "3\nls\n" | myprogram

does not print the output of ls. It is as if the system("/bin/sh") call does not read from stdin of the caller. I'm not a normal linux user so any help to what to read or experiments to try or commands to run to understand better how the system("/bin/sh"); statement works would be very helpful.

3
  • 3
    You'll have to run it with echo -e '3\nls\n' | stdbuf -i1 ./your_program, ie force it to read its input byte-by-byte, otherwise it may bite more than it can chew, and not leave anything for the command run via system(). That's what bash has to do too when reading a script from a pipe, in order to be able to also use its read builtin on the same stdin.
    – user313992
    Jan 23, 2020 at 8:34
  • 1
    If add a fseek(stdin, 0, SEEK_CUR) to sync the file pointer before the system("..."), you will be able to use it with seekable files, as those created by bash for here-strings: ./your_program <<<$'3\nls\n'.
    – user313992
    Jan 23, 2020 at 8:42
  • Thanks mosvy although I don't understand everything in detail.
    – cosas
    Jan 23, 2020 at 8:53

1 Answer 1

5

The reason is you are using scanf which reads from the stdin stream. It uses buffers, so it will try to read one buffer's worth of data and only then checks to see how much of that data it really needs. Since your ls command is immediately available, it will get read into the buffer too, waiting for another call to scanf (or any other buffered stdio function operating on stdin) to get used. So when sh then tries to read from stdin, there is nothing left since it does not see the buffer which is internal to your C program.

There are at least two ways to solve this.

  1. You make sure the "ls" command is not echoed to stdin before scanf has completed reading. This is a bit tricky since you would need to wait for your program to output evidence of having gotten past that point (not trivial), or then using some fixed delay and hope system will never stall at that point and make the delay too short (i.e. this is a brittle solution). The latter would look something like: (echo 3 ; sleep 1 ; echo ls) | myprogram i.e. the three commands are executed in order and all provide input to myprogram.

  2. You use functions to read from stdin - without buffers - only the minimum number of characters needed. For example the read function does not use buffers. You could write a helper function like

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>

int unbuffered_scanf(const char *fmt, ...) {
  char buffer[100]; // maximum line length
  int i;
  int ret;
  for(i=0; i<sizeof(buffer)-1; ++i) {
    if (!read(0, &buffer[i], 1)) break;
    if (buffer[i] == '\n') break;
  }
  buffer[i] = '\0';
  va_list ap;
  va_start(ap, fmt);
  ret = vsscanf(buffer, fmt, ap);
  va_end(ap);
  return ret;
}

int main()
{
    int iRetval = 0;
    unsigned int uiNum;

    printf("Enter number: ");
    fflush(stdout);
    iRetval = unbuffered_scanf("%u", &uiNum);
    printf("\nThe number is %u, Retval: %i\n", uiNum, iRetval);
    fflush(stdout);
    if( iRetval > 0)
        system("/bin/sh");
    else
        printf("Goodbye!\n");
}

For more info on unbuffered functions, read the info pages for example here: https://www.gnu.org/software/libc/manual/html_node/I_002fO-Primitives.html#I_002fO-Primitives

EDIT: Apparently there is a way to disable the buffering done by scanf and others using e.g. the setbuf function - it probably works internally exactly as my example above:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int iRetval = 0;
    unsigned int uiNum;

    setbuf(stdin, NULL);
    printf("Enter number: ");
    fflush(stdout);
    iRetval = scanf("%u", &uiNum);
    printf("\nThe number is %u, Retval: %i\n", uiNum, iRetval);
    fflush(stdout);
    if( iRetval > 0)
        system("/bin/sh");
    else
        printf("Goodbye!\n");
}
4
  • "read one buffer's worth of data" does that mean that scanf read from stdin until it sees 0x00 in the buffer? Will a second scanf read new stuff from stdin or will it receive the contents not used from the first scanf?
    – cosas
    Jan 23, 2020 at 7:34
  • 1
    It means scanf will have for example a buffer of 4096 bytes (I don't know what it actually is), and then ask the kernel to wait for some data to become available and then return as much of it that it can into the buffer. It does not care what characters it puts there (letters, line feeds or even 0x00 bytes), so it stops only when it temporarily has nothing more to return. scanf will keep the extra characters in its buffer and continue using them next time it's called before reading from stdin again. Jan 23, 2020 at 8:21
  • Basically scanf will also call the same read (as I did) function internally, but the difference I call read with a buffer size of 1 each time to make sure it does not read too much from stdin. Jan 23, 2020 at 8:24
  • 1
    Okay, so not even if it was possible to insert a null-byte into the echo string would help as I understand. Thanks a lot for your answer. That was very helpful.
    – cosas
    Jan 23, 2020 at 8:52

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .