Haslo Vardos
Haslo Vardos

Reputation: 322

read system call: When input has more bytes than count argument, excess bytes overflow to shell and get executed as next command

I am writing a code to Implement tee command using I/O system calls. This is an exercise in the book The Linux Programming Interface by Michael Kerrisk.

My system is Ubuntu 16.04.

I am inexperienced with linux programming.

  #include <sys/stat.h>                                                                                                                                                                                             
  #include <fcntl.h>
  #include <unistd.h>
  #include "tlpi_hdr.h"
  
  #define MAX_READ 20
  
  int
  main(int argc, char *argv[])
  {
    int fileFd;
    ssize_t numRead;
    char buffer[MAX_READ + 10];
  
    // command example: tee_practice tfile
    if (argc != 2 || strcmp(argv[1], "--help") == 0)
      usageErr("Usage error\nDid you supply the filename?\n");
  
    fileFd = open(argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
  
    if (fileFd == -1)
      errExit("open");
  
    if ((numRead = read(STDIN_FILENO, buffer, MAX_READ)) == -1) // HERE bytes overflow to shell and get executed as next command
      errExit("read");
  
    buffer[numRead] = '\0';
  
    if (write(STDOUT_FILENO, buffer, MAX_READ) == -1)
      errExit("write");
  
    if (write(fileFd, buffer, MAX_READ) == -1)
      errExit("write");
  
    exit(EXIT_SUCCESS);
  }

On input

Hi I am writing a few lines

the program writes first 20 bytes (Hi I am writing a fe) to file and stdout. The remaining bytes (w lines) are executed as next shell command, which I want to prevent from happening.

Where am I going wrong?


EDIT

Not exactly minimal reproducible example, but it works and includes errExit() and usageErro().

#include <sys/stat.h>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       
#include <fcntl.h>
#include <unistd.h>
#include <stdarg.h>

#include <sys/types.h>  /* Type definitions used by many programs */
#include <stdio.h>      /* Standard I/O functions */
#include <stdlib.h>     /* Prototypes of commonly used library functions,
                           plus EXIT_SUCCESS and EXIT_FAILURE constants */
#include <unistd.h>     /* Prototypes for many system calls */
#include <errno.h>      /* Declares errno and defines error constants */
#include <string.h>     /* Commonly used string-handling functions */

#define MAX_READ 20

#ifndef ERROR_FUNCTIONS_H
#define ERROR_FUNCTIONS_H


#ifdef __GNUC__

    /* This macro stops 'gcc -Wall' complaining that "control reaches
       end of non-void function" if we use the following functions to
       terminate main() or some other non-void function. */

#define NORETURN __attribute__ ((__noreturn__))
#else
#define NORETURN
#endif

#endif

void errMsg(const char *format, ...);

void errExit(const char *format, ...) NORETURN ;

void usageErr(const char *format, ...) NORETURN ;

typedef enum { FALSE, TRUE } Boolean;

static char *ename[] = {
    /*   0 */ "", 
    /*   1 */ "EPERM", "ENOENT", "ESRCH", "EINTR", "EIO", "ENXIO", 
    /*   7 */ "E2BIG", "ENOEXEC", "EBADF", "ECHILD", 
    /*  11 */ "EAGAIN/EWOULDBLOCK", "ENOMEM", "EACCES", "EFAULT", 
    /*  15 */ "ENOTBLK", "EBUSY", "EEXIST", "EXDEV", "ENODEV", 
    /*  20 */ "ENOTDIR", "EISDIR", "EINVAL", "ENFILE", "EMFILE", 
    /*  25 */ "ENOTTY", "ETXTBSY", "EFBIG", "ENOSPC", "ESPIPE", 
    /*  30 */ "EROFS", "EMLINK", "EPIPE", "EDOM", "ERANGE", 
    /*  35 */ "EDEADLK/EDEADLOCK", "ENAMETOOLONG", "ENOLCK", "ENOSYS", 
    /*  39 */ "ENOTEMPTY", "ELOOP", "", "ENOMSG", "EIDRM", "ECHRNG", 
    /*  45 */ "EL2NSYNC", "EL3HLT", "EL3RST", "ELNRNG", "EUNATCH", 
    /*  50 */ "ENOCSI", "EL2HLT", "EBADE", "EBADR", "EXFULL", "ENOANO", 
    /*  56 */ "EBADRQC", "EBADSLT", "", "EBFONT", "ENOSTR", "ENODATA", 
    /*  62 */ "ETIME", "ENOSR", "ENONET", "ENOPKG", "EREMOTE", 
    /*  67 */ "ENOLINK", "EADV", "ESRMNT", "ECOMM", "EPROTO", 
    /*  72 */ "EMULTIHOP", "EDOTDOT", "EBADMSG", "EOVERFLOW", 
    /*  76 */ "ENOTUNIQ", "EBADFD", "EREMCHG", "ELIBACC", "ELIBBAD", 
    /*  81 */ "ELIBSCN", "ELIBMAX", "ELIBEXEC", "EILSEQ", "ERESTART", 
    /*  86 */ "ESTRPIPE", "EUSERS", "ENOTSOCK", "EDESTADDRREQ", 
    /*  90 */ "EMSGSIZE", "EPROTOTYPE", "ENOPROTOOPT", 
    /*  93 */ "EPROTONOSUPPORT", "ESOCKTNOSUPPORT", 
    /*  95 */ "EOPNOTSUPP/ENOTSUP", "EPFNOSUPPORT", "EAFNOSUPPORT", 
    /*  98 */ "EADDRINUSE", "EADDRNOTAVAIL", "ENETDOWN", "ENETUNREACH", 
    /* 102 */ "ENETRESET", "ECONNABORTED", "ECONNRESET", "ENOBUFS", 
    /* 106 */ "EISCONN", "ENOTCONN", "ESHUTDOWN", "ETOOMANYREFS", 
    /* 110 */ "ETIMEDOUT", "ECONNREFUSED", "EHOSTDOWN", "EHOSTUNREACH", 
    /* 114 */ "EALREADY", "EINPROGRESS", "ESTALE", "EUCLEAN", 
    /* 118 */ "ENOTNAM", "ENAVAIL", "EISNAM", "EREMOTEIO", "EDQUOT", 
    /* 123 */ "ENOMEDIUM", "EMEDIUMTYPE", "ECANCELED", "ENOKEY", 
    /* 127 */ "EKEYEXPIRED", "EKEYREVOKED", "EKEYREJECTED", 
    /* 130 */ "EOWNERDEAD", "ENOTRECOVERABLE", "ERFKILL", "EHWPOISON"
};

#define MAX_ENAME 133

#ifdef __GNUC__
__attribute__ ((__noreturn__))
#endif

static void
terminate(Boolean useExit3)
{
    char *s;

    /* Dump core if EF_DUMPCORE environment variable is defined and
       is a nonempty string; otherwise call exit(3) or _exit(2),
       depending on the value of 'useExit3'. */

    s = getenv("EF_DUMPCORE");

    if (s != NULL && *s != '\0')
        abort();
    else if (useExit3)
        exit(EXIT_FAILURE);
    else
        _exit(EXIT_FAILURE);
}

static void
outputError(Boolean useErr, int err, Boolean flushStdout,
        const char *format, va_list ap)
{
#define BUF_SIZE 500
    char buf[BUF_SIZE], userMsg[BUF_SIZE], errText[BUF_SIZE];

    vsnprintf(userMsg, BUF_SIZE, format, ap);

    if (useErr)
        snprintf(errText, BUF_SIZE, " [%s %s]",
                (err > 0 && err <= MAX_ENAME) ?
                ename[err] : "?UNKNOWN?", strerror(err));
    else
        snprintf(errText, BUF_SIZE, ":");

    snprintf(buf, BUF_SIZE, "ERROR%s %s\n", errText, userMsg);

    if (flushStdout)
        fflush(stdout);       /* Flush any pending stdout */
    fputs(buf, stderr);
    fflush(stderr);           /* In case stderr is not line-buffered */
}

void
usageErr(const char *format, ...)
{
    va_list argList;

    fflush(stdout);           /* Flush any pending stdout */

    fprintf(stderr, "Usage: ");
    va_start(argList, format);
    vfprintf(stderr, format, argList);
    va_end(argList);

    fflush(stderr);           /* In case stderr is not line-buffered */
    exit(EXIT_FAILURE);
}

void
errExit(const char *format, ...)
{
    va_list argList;

    va_start(argList, format);
    outputError(TRUE, errno, TRUE, format, argList);
    va_end(argList);

    terminate(TRUE);
}

int
main(int argc, char *argv[])
{
  int fileFd;
  ssize_t numRead;
  char buffer[MAX_READ + 10];

  if (argc != 2 || strcmp(argv[1], "--help") == 0)
    usageErr("Usage error\nDid you supply the filename?\n");

  fileFd = open(argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);

  if (fileFd == -1)
    errExit("open");

  if ((numRead = read(STDIN_FILENO, buffer, MAX_READ)) == -1)
    errExit("read");

  buffer[numRead] = '\0';

  if (write(STDOUT_FILENO, buffer, MAX_READ) == -1)
    errExit("write");

  if (write(fileFd, buffer, MAX_READ) == -1)
    errExit("write");

  exit(EXIT_SUCCESS);
}

Upvotes: 0

Views: 760

Answers (1)

ilkkachu
ilkkachu

Reputation: 6527

Your program only reads 20 bytes. Whatever there is to be read after those 20 bytes, from whatever the program's standard input is connected to, stays there. Be that input from the terminal, a pipe buffer, or a file.

If you used the stdio input functions (fgets(), fread() etc.) instead, they would ask the OS for a larger block of data (usually 4096 B with glibc on Linux), so the issue wouldn't come up with such a short input.

To get everything there is, you need to loop, reading everything until EOF and, since you're implementing tee, also copy all of it to standard output and the output file.

i.e. something in this direction:

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

int main(void)
{
    /* setup ... */
    char buf[1024];
    while(1) {
        int n = read(fd, buf, 1024);
        if (n == 0)
            break; /* EOF */
        if (n == -1) {
            perror("read");
            return 1;
        }
        write(STDOUT_FILENO, buf, n);
        write(outfd, buf, n);
    }
    return 0;
}

But check for errors on the write() calls, too. Also, technically, write() may return without writing everything you asked, i.e. write(outfd, buf, n) may write less than the n bytes you asked. But the cases where that happens are somewhat rare.

Upvotes: 2

Related Questions