Browse Source

(#16) Implement a working pipe functionality on POSIX

rexim 4 years ago
parent
commit
304a7b62de
2 changed files with 170 additions and 1 deletions
  1. 21 1
      examples/pipe.c
  2. 149 0
      nobuild.h

+ 21 - 1
examples/pipe.c

@@ -6,7 +6,27 @@ int main(void)
 #if _WIN32
     WARN("Piping is not implemented on Windows yet");
 #else
-    PIPE(IN("./nobuild.c"), CHAIN("cat"));
+    Cmd chain[] = {
+        // {.args = nobuild_cstr_vargs_to_array(69, "seq", "100", "110", NULL)},
+        {.args = nobuild_cstr_vargs_to_array(69, "sort", "-rn", NULL)},
+        {.args = nobuild_cstr_vargs_to_array(69, "rev", NULL)},
+        {.args = nobuild_cstr_vargs_to_array(69, "base64", NULL)},
+        {.args = nobuild_cstr_vargs_to_array(69, "gzip", "-f", NULL)},
+        {.args = nobuild_cstr_vargs_to_array(69, "hexdump", "-C", NULL)},
+    };
+
+    Pipe my_pipe = {
+        .chain = chain,
+        .chain_size = sizeof(chain) / sizeof(chain[0]),
+        .input_filepath = "./examples/pipe.c",
+        .output_filepath = "output.txt"
+    };
+
+    pid_t *cpids = nobuild_spawn_pipe(&my_pipe);
+
+    for (size_t i = 0; i < my_pipe.chain_size; ++i) {
+        nobuild__posix_wait_for_pid(cpids[i]);
+    }
 #endif
 
     return 0;

+ 149 - 0
nobuild.h

@@ -189,6 +189,28 @@ void nobuild__rm(const char *path);
 void nobuild__posix_wait_for_pid(pid_t pid);
 #endif
 
+typedef struct {
+    const char **args;
+} Cmd;
+
+const char** nobuild_cstr_vargs_to_array(int ignore, ...);
+
+pid_t nobuild_spawn_cmd(const Cmd *cmd,
+                        int *fdin,  // NULL means stdin
+                        int *fdout, // NULL means stdout
+                        int *fderr  // NULL means stderr
+                        );
+
+// TODO: pipes do not allow redirecting stderr
+typedef struct {
+    const char *input_filepath;  // NULL means stdin
+    const char *output_filepath; // NULL means stdout
+    const Cmd *chain;
+    size_t chain_size;
+} Pipe;
+
+pid_t *nobuild_spawn_pipe(const Pipe *pipe);
+
 typedef enum {
     PIPE_ARG_END,
     PIPE_ARG_IN,
@@ -853,4 +875,131 @@ Pipe_Arg nobuild__make_pipe_arg(Pipe_Arg_Type type, ...)
     return result;
 }
 
+const char** nobuild_cstr_vargs_to_array(int ignore, ...)
+{
+    size_t count = 0;
+    va_list args;
+    FOREACH_VARGS_CSTR(ignore, arg, args, {
+        count += 1;
+    });
+
+    const char **result = malloc(sizeof(const char *) * (count + 1));
+
+    count = 0;
+    FOREACH_VARGS_CSTR(ignore, arg, args, {
+        result[count++] = arg;
+    });
+
+    result[count] = NULL;
+    return result;
+}
+
+pid_t nobuild_spawn_cmd(const Cmd *cmd,
+                        int *fdin,  // NULL means stdin
+                        int *fdout, // NULL means stdout
+                        int *fderr  // NULL means stdout
+                        )
+{
+    pid_t cpid = fork();
+
+    if (cpid < 0) {
+        PANIC("could not fork a child: %s", strerror(errno));
+    }
+
+    if (cpid == 0) {
+        if (fdin) {
+            if (dup2(*fdin, STDIN_FILENO) < 0) {
+                PANIC("could not setup stdin for child process: %s", strerror(errno));
+            }
+        }
+
+        if (fdout) {
+            if (dup2(*fdout, STDOUT_FILENO) < 0) {
+                PANIC("could not setup stdout for child process: %s", strerror(errno));
+            }
+        }
+
+        if (fderr) {
+            if (dup2(*fderr, STDERR_FILENO) < 0) {
+                PANIC("could not setup stderr for child process: %s", strerror(errno));
+            }
+        }
+
+        if (execvp(cmd->args[0], (char * const*) cmd->args) < 0) {
+            PANIC("could not exec child process: %s", strerror(errno));
+        }
+    }
+
+    return cpid;
+}
+
+pid_t *nobuild_spawn_pipe(const Pipe *my_pipe)
+{
+    if (my_pipe->chain_size == 0) {
+        return NULL;
+    }
+
+    pid_t *cpids = malloc(sizeof(pid_t) * my_pipe->chain_size);
+
+    int pipefd[2] = {0};
+    int fdin = 0;
+    int *fdprev = NULL;
+
+    if (my_pipe->input_filepath) {
+        fdin = open(my_pipe->input_filepath, O_RDONLY);
+        if (fdin < 0) {
+            PANIC("could not open file %s: %s", my_pipe->input_filepath, strerror(errno));
+        }
+        fdprev = &fdin;
+    }
+
+    for (size_t i = 0; i < my_pipe->chain_size - 1; ++i) {
+        if (pipe(pipefd) < 0) {
+            PANIC("could not create pipe for a child process: %s", strerror(errno));
+        }
+
+        cpids[i] = nobuild_spawn_cmd(
+            &my_pipe->chain[i],
+            fdprev,
+            &pipefd[1],
+            NULL);
+
+        if (fdprev) close(*fdprev);
+        close(pipefd[1]);
+        fdprev = &fdin;
+        fdin = pipefd[0];
+    }
+
+    {
+        int fdout = 0;
+        int *fdnext = NULL;
+
+        if (my_pipe->output_filepath) {
+            fdout = open(my_pipe->output_filepath,
+                         O_WRONLY | O_CREAT | O_TRUNC,
+                         S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+            if (fdout < 0) {
+                PANIC("could not open file %s: %s",
+                      my_pipe->output_filepath,
+                      strerror(errno));
+            }
+            fdnext = &fdout;
+        }
+
+        const size_t last = my_pipe->chain_size - 1;
+        cpids[last] =
+            nobuild_spawn_cmd(
+            &my_pipe->chain[last],
+            fdprev,
+            fdnext,
+            NULL);
+
+        if (fdprev) close(*fdprev);
+        if (fdnext) close(*fdnext);
+    }
+
+    return cpids;
+}
+
+
 #endif // NOBUILD_IMPLEMENTATION