Get output of `posix_spawn`

8,565

Solution 1

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.

Solution 2

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.

Share:
8,565

Related videos on Youtube

nbubis
Author by

nbubis

Updated on September 18, 2022

Comments

  • nbubis
    nbubis over 1 year

    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:

    ...?

    • Admin
      Admin over 8 years
      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.
    • Admin
      Admin over 8 years
      @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.
  • maxschlepzig
    maxschlepzig over 5 years
    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.
  • Joelio
    Joelio about 2 years
    In this case do you need to close the fd also?