user191776
user191776

Reputation:

Writing to both stdout & a file

I have a parent process which forks out a child to perform execv().

I need the output given by execv() to stdout to be displayed onscreen as also copied to a log file.

How do I write the same output to both stdout & a file, without using pipes or tees?

Upvotes: 4

Views: 7319

Answers (7)

caf
caf

Reputation: 239041

You can do this entirely within your program, but you will still need to use anonymous pipes as created by the pipe() system call.

Basically, you will need a subprocess that performs the equivalent of tee, which is quite easy:

int child_pipe[2];
pid_t pid_exec_child, pid_output_child;

pipe(child_pipe);

pid_exec_child = fork();
if (pid_exec_child == 0)
{
    dup2(child_pipe[1], STDOUT_FILENO);
    close(child_pipe[0]);
    close(child_pipe[1]);
    execve(/* ... */);
    _exit(127);
}

close(child_pipe[1]);

pid_output_child = fork();
if (pid_output_child == 0)
{
    /* This child just loops around, reading from the other child and writing everything
     * to both stdout and the log file. */
    int logfd = open("logfile", O_WRONLY);
    char buffer[4096];
    ssize_t nread;

    while ((nread = read(child_pipe[0], buffer, sizeof buffer) != 0)
    {
        size_t nwritten_total;
        ssize_t nwritten;

        if (nread < 0)
        {
            if (errno == EINTR)
                continue;

            perror("read");
            _exit(1);
        }

        /* Copy data to stdout */
        nwritten_total = 0;
        while (nwritten_total < nread)
        {
            nwritten = write(STDOUT_FILENO, buffer + nwritten_total, nread - nwritten_total);

            if (nwritten < 0)
            {
                if (errno == EINTR)
                    continue;

                perror("write(stdout)");
                _exit(2);
            }

            nwritten_total += nwritten;
        }

        /* Copy data to logfile */
        nwritten_total = 0;
        while (nwritten_total < nread)
        {
            nwritten = write(logfd, buffer + nwritten_total, nread - nwritten_total);

            if (nwritten < 0)
            {
                if (errno == EINTR)
                    continue;

                perror("write(logfile)");
                _exit(3);
            }

            nwritten_total += nwritten;
        }
    }
    _exit(0);
}

close(child_pipe[0]);

/* Parent continues here */

Of course, it is probably easier to just exec tee in that second child instead...

(Note that the child processes use _exit(), since they have inherited the standard I/O state from the parent).

Upvotes: 3

Taylor Leese
Taylor Leese

Reputation: 52310

Use Tee like this:

./myprog | tee outputfile

Upvotes: 0

DVK
DVK

Reputation: 129403

You can use dup2() - this link provides an example

Upvotes: 2

kagali-san
kagali-san

Reputation: 3082

Also, you can use fifo's. mkfifo my.fifo; in execv: program > my.fifo; and open fifo as a regular file, reading from it. This way you can have your stdout parsed, but it has minor drawbacks with shared access.

Upvotes: 1

idbrii
idbrii

Reputation: 11916

Do you want only the output of the child to go to the log?

The tee unix command does exactly what you describe: you pipe in data and it writes to a log and to stdout.

Upvotes: 0

Byron Whitlock
Byron Whitlock

Reputation: 53861

If you don't want to use a tee, before you write the data, write it to a file, then send it to stdout.

You should write a logging function that does this for you to make it cleaner.

Upvotes: 6

nmichaels
nmichaels

Reputation: 50941

Pipe it through tee.

Upvotes: 2

Related Questions