10

So I can run a process in Unix / Linux using POSIX, but is there some way I can store / redirect both the STDOUT and STDERR of the process to a file? The spawn.h header contains a deceleration of posix_spawn_file_actions_adddup2 which looks relevant, but I'm not sure quite how to use it.

The process spawn:

posix_spawn(&processID, (char *)"myprocess", NULL, NULL, args, environ);

The output storage:

...?

2
  • 1
    The third paramater of posix_spwan is a pointer of type posix_spawn_file_actions_t (one you have given as NULL). posix_spawn will open, close or duplicate file descriptors inherited from the calling process as specified by the posix_spawn_file_actions_t object. The posix_spawn_file_actions_{addclose,adddup2} functions are used to indicate what happens to which fd.
    – muru
    Jan 2, 2016 at 21:38
  • @muru - Do you think you could add a working example? I understood the interaction between the functions is done by a "file action", but it's not clear how exactly this fits together, or where the fd location is defined.
    – nbubis
    Jan 2, 2016 at 21:44

2 Answers 2

17

Here's a minimal example of modifying file descriptors of a spawned process, saved as foo.c:

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <spawn.h>

int main(int argc, char* argv[], char *env[])
{
    int ret;
    pid_t child_pid;
    posix_spawn_file_actions_t child_fd_actions;
    if (ret = posix_spawn_file_actions_init (&child_fd_actions))
        perror ("posix_spawn_file_actions_init"), exit(ret);
    if (ret = posix_spawn_file_actions_addopen (&child_fd_actions, 1, "/tmp/foo-log", 
            O_WRONLY | O_CREAT | O_TRUNC, 0644))
        perror ("posix_spawn_file_actions_addopen"), exit(ret);
    if (ret = posix_spawn_file_actions_adddup2 (&child_fd_actions, 1, 2))
        perror ("posix_spawn_file_actions_adddup2"), exit(ret);

    if (ret = posix_spawnp (&child_pid, "date", &child_fd_actions, NULL, argv, env))
        perror ("posix_spawn"), exit(ret);
}

What does it do?

  • The third parameter of posix_spwan is a pointer of type posix_spawn_file_actions_t (one you have given as NULL). posix_spawn will open, close or duplicate file descriptors inherited from the calling process as specified by the posix_spawn_file_actions_t object.
  • So we start with a posix_spawn_file_actions_t object (chiild_fd_actions), and initialize it with posix_spawn_file_actions_init().
  • Now, the posix_spawn_file_actions_{addopen,addclose,addup2} functions can be used to open, close or duplicate file descriptors (after the open(3), close(3) and dup2(3) functions) respectively.
  • So we posix_spawn_file_actions_addopen a file at /tmp/foo-log to file descriptor 1 (aka stdout).
  • Then we posix_spawn_file_actions_adddup2 fd 2 (aka stderr) to fd 1.
  • Note that nothing has been opened or duped yet. The last two functions simply changed the child_fd_actions object to note that these actions are to be taken.
  • And finally we use posix_spawn with the child_fd_actions object.

Testing it out:

$ make foo
cc     foo.c   -o foo
$ ./foo
$ cat /tmp/foo-log 
Sun Jan  3 03:48:17 IST 2016
$ ./foo +'%F %R'  
$ cat /tmp/foo-log
2016-01-03 03:48
$  ./foo -d 'foo'  
$ cat /tmp/foo-log
./foo: invalid date ‘foo’

As you can see, both stdout and stderr of the spawned process went to /tmp/foo-log.

2
  • Note that posix_spawn* don't set errno. Thus, you can't use perror(). Use something like fprintf(stderr, "...: %s\n", strerror(ret)) instead. Also, the main function is missing a return 0 statement. Oct 27, 2018 at 8:13
  • In this case do you need to close the fd also?
    – Joelio
    Feb 26, 2022 at 19:54
1

Yes, you can. Defining the right list of posix spawn file actions definitely is the way to go.

Example:

#include <errno.h>
#include <fcntl.h>
#include <spawn.h>
#include <stdio.h>
#include <string.h>    
#define CHECK_ERROR(R, MSG) do { if (R) { fprintf(stderr, "%s: %s\n",
        (MSG), strerror(R)); return 1; } } while (0)    
extern char **environ;   
int main(int argc, char **argv)
{
    if (argc < 3) {
        fprintf(stderr, "Call: %s OUTFILE COMMAND [ARG]...\n", argv[0]);
        return 2;
    }
    const char *out_filename = argv[1];
    char **child_argv = argv+2;
    posix_spawn_file_actions_t as;
    int r = posix_spawn_file_actions_init(&as);
    CHECK_ERROR(r, "actions init");
    r = posix_spawn_file_actions_addopen(&as, 1, out_filename,
            O_CREAT | O_TRUNC | O_WRONLY, 0644);
    CHECK_ERROR(r, "addopen");
    r = posix_spawn_file_actions_adddup2(&as, 1, 2);
    CHECK_ERROR(r, "adddup2");
    pid_t child_pid;
    r = posix_spawnp(&child_pid, child_argv[0], &as, NULL,
            child_argv, environ);
    CHECK_ERROR(r, "spawnp");
    r = posix_spawn_file_actions_destroy(&as);
    CHECK_ERROR(r, "actions destroy");
    return 0;
}

Compile and test:

$ cc -Wall -g -o spawnp spawnp.c
$ ./spawnp log date -I
$ cat log
2018-11-03
$ ./a.out log dat 
spawnp: No such file or directory

Note that the posix_spawn functions don't set errno, instead, unlike most other UNIX functions, they return an error code. Thus, we can't use perror() but have to use something like strerror().

We use two spawn file actions: addopen and addup2. The addopen is similar to a normal open() but you also specify a file descriptor which is automatically closed if already open (here 1, i.e. stdout). The addup2 has similar effects to dup2(), i.e. the target file descriptor (here 2, i.e. stderr) is atomically closed before 1 is duplicated to 2. Those actions are only executed in the child created by posix_spawn, i.e. right before it execs the specified command.

Like fork(), posix_spawn() and posix_spawnp() immediately return to the parent. Thus, we have to use waitid() or waitpid() to explicitly wait on child_pid's termination.

You must log in to answer this question.

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