开发者

monitor bytes in stdin, stdout, and stderr in C

开发者 https://www.devze.com 2023-03-08 20:42 出处:网络
I need to count how many bytes are being sent to a child process through stdin, and how many bytes a child process is writing to stdout and stderr. The child process calls execvp, so I have no way to

I need to count how many bytes are being sent to a child process through stdin, and how many bytes a child process is writing to stdout and stderr. The child process calls execvp, so I have no way to monitor those stats from within the process itself. My current tactic involves creating 3 additional child processes, one each to monitor each of the std streams through pipes (or in the case of stdin, just reading from stdin).

This tactic seems really frail at best, and I'm doing something strange which makes it so that the processes monitoring stdout/err cannot read from their respective ends of the pipes (and makes them hang indefinitely). Code below.

This creates the three helper child processes, and should allow them t开发者_运维知识库o count the stats:

void controles(struct fds *des)
{
    int ex[2];
    int err[2];

    int n_in = 0;
    int c_in;

    int n_ex = 0;
    int c_ex;

    int n_err = 0;
    int c_err;

    pipe(ex);
    pipe(err);

    /*has two fields, for the write end of the stdout pipe and the stderr pipe. */
    des->err = err[1];
    des->ex = ex[1];
    switch (fork()) {
    case 0:     /*stdin */
        while (read(0, &c_in, 1) == 1)
            n_in++;
        if (n_in > 0)
            printf("%d bytes to stdin\n", n_in);
        exit(n_in);
    default:
        break;
    }

    switch (fork()) {
    case 0:     /*stdout */
        close(ex[1]);
        /*pretty sure this is wrong */
        while (read(ex[0], &c_ex, 1) == 1) {
            n_ex++;
            write(1, &c_ex, 1);
        }
        if (n_ex > 0)
            printf("%d bytes to stdout\n", n_ex);
        close(ex[0]);
        exit(n_ex);
    default:
        close(ex[0]);
    }
    switch (fork()) {
    case 0:     /*error */
        close(err[1]);
        /*also probably have a problem here */
        while (read(err[0], &c_err, 1) == 1) {
            n_err++;
            write(2, &c_err, 1);
        }
        if (n_err > 0)
            printf("%d bytes to stderr\n", n_err);
        close(err[0]);
        exit(n_err);
    default:
        close(err[0]);
    }
}

and this is a code fragment (within the child process) which sets up the two fd's from the fds struct so that the child process should write to the pipe instead of stdin/stderr.

    dup2(des.ex, 1);
dup2(des.err, 2);
close(des.ex); close(des.err); /*Is this right?*/
execvp(opts->exec, opts->options); /*sure this is working fine*/

I'm lost, any help would be appreciated.


I think your code could be improved by breaking things apart a little; the accounting and copying routines are all basically the same task, and if you choose to continue down the road with multiple processes, can be written simply:

void handle_fd_pair(char *name, int in, int out) {
    char buf[1024];
    int count = 0, n;
    char fn[PATH_MAX];
    snprintf(fn, PATH_MAX - 1, "/tmp/%s_count", name);
    fn[PATH_MAX-1] = '\0';
    FILE *output = fopen(fn, "w");
    /* handle error */

    while((n = read(in, buf, 1024)) > 0) {
        count+=n;
        writen(out, buf, n); /* see below */
    }

    fprintf(output, "%s copied %d bytes\n", name, count);
    fclose(output);
}

Rather than one-char-at-a-time, which is inefficient for moderate amounts of data, we can handle partial writes with the writen() function from the Advanced Programming in the Unix Environment source code:

ssize_t             /* Write "n" bytes to a descriptor  */
writen(int fd, const void *ptr, size_t n)
{
  size_t      nleft;
  ssize_t     nwritten;

  nleft = n;
  while (nleft > 0) {
      if ((nwritten = write(fd, ptr, nleft)) < 0) {
          if (nleft == n)
              return(-1); /* error, return -1 */
          else
              break;      /* error, return amount written so far */
      } else if (nwritten == 0) {
          break;
      }
      nleft -= nwritten;
      ptr   += nwritten;
  }
  return(n - nleft);      /* return >= 0 */
}

With the helper in place, I think the rest can go more easily. Fork a new child for each stream, and give the in[0] read-end, out[1] and err[1] write-ends of the pipes to the child.

All those close() calls in each child are pretty ugly, but trying to write a little wrapper around an array of all the fds, and exempting the ones passed in as arguments, also seems like trouble.

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

#ifndef PATH_MAX
#define PATH_MAX 128
#endif

void handle_fd_pair(char *name, int in, int out) {
    char buf[1024];
    int count = 0, n;
    char fn[PATH_MAX];
    snprintf(fn, PATH_MAX - 1, "/tmp/%s_count", name);
    fn[PATH_MAX-1] = '\0';
    FILE *output = fopen(fn, "w");
    /* handle error */

    while((n = read(in, buf, 1024)) > 0) {
        count+=n;
        writen(out, buf, n); /* see below */
    }

    fprintf(output, "%s copied %d bytes\n", name, count);
    fclose(output);
}

int main(int argc, char* argv[]) {
    int in[2], out[2], err[2];
    pid_t c1, c2, c3;

    pipe(in);
    pipe(out);
    pipe(err);

    if ((c1 = fork()) < 0) {
        perror("can't fork first child");
        exit(1);
    } else if (c1 == 0) {
        close(in[0]);
        close(out[0]);
        close(out[1]);
        close(err[0]);
        close(err[1]);
        handle_fd_pair("stdin", 0, in[1]);
        exit(0);
    }

    if ((c2 = fork()) < 0) {
        perror("can't fork second child");
        exit(1);
    } else if (c2 == 0) {
        close(in[0]);
        close(in[1]);
        close(out[1]);
        close(err[0]);
        close(err[1]);
        handle_fd_pair("stdout", out[0], 1);
        exit(0);
    }

    if ((c3 = fork()) < 0) {
        perror("can't fork third child");
        exit(1);
    } else if (c3 == 0) {
        close(in[0]);
        close(in[1]);
        close(out[0]);
        close(out[1]);
        close(err[1]);
        handle_fd_pair("stderr", err[0], 2);
        exit(0);
    }

    /* parent falls through to here, no children */

   close(in[1]);
   close(out[0]);
   close(err[0]);
   close(0);
   close(1);
   close(2);
   dup2(in[0], 0);
   dup2(out[1], 1);
   dup2(err[1], 2);
   system(argv[1]);
   exit(1); /* can't reach */
}

It seems to work for toy applications anyway :)

$ ./dup cat
hello
hello
$ ls -l *count
-rw-r--r-- 1 sarnold sarnold 22 2011-05-26 17:41 stderr_count
-rw-r--r-- 1 sarnold sarnold 21 2011-05-26 17:41 stdin_count
-rw-r--r-- 1 sarnold sarnold 22 2011-05-26 17:41 stdout_count
$ cat *count
stderr copied 0 bytes
stdin copied 6 bytes
stdout copied 6 bytes

I think it is worth pointing out that you could also implement this program with only one process, and use select(2) to determine which file descriptors need reading and writing.


Overall, I think you're on the right track.

One problem is that in your stderr and stdout handlers, which should be picking bytes off the pipe and writing to the real stderr/stdout, you're writing back to the same pipe.

It would also be helpful to see how you start the child processes. You gave a code fragment to close the real stderr and then dup2 the pipe fd back to stderr's fd, but you probably want this in the parent process (after fork and before exec) so that you don't need to modify the source code to the child process. You should be able to do this generically all from the parent.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号