aboutsummaryrefslogtreecommitdiff
path: root/src/program.c
blob: 00f242be88d859ce4000462944e73790de2fdad3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#include "program.h"
#include "buffer.h"
#include <unistd.h>
#include <sys/wait.h>
#include <sys/prctl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#define READ_END 0
#define WRITE_END 1

int program_buffer_write_callback(char *data, int size, void *userdata) {
    Buffer *buffer = userdata;
    buffer_append(buffer, data, size);
    return 0;
}

static int program_read_output(int process_id, int read_fd, ProgramOutputCallback output_callback, void *userdata) {
    int status;
    char buffer[4097];

    for(;;) {
        ssize_t bytes_read = read(read_fd, buffer, sizeof(buffer) - 1);
        if(bytes_read == 0) {
            break;
        } else if(bytes_read == -1) {
            int err = errno;
            fprintf(stderr, "Failed to read from pipe, error: %s\n", strerror(err));
            return -err;
        }

        buffer[bytes_read] = '\0';
        if(output_callback && output_callback(buffer, bytes_read, userdata) != 0)
            break;
    }

    if(waitpid(process_id, &status, 0) == -1) {
        perror("waitpid failed");
        return -5;
    }

    if(!WIFEXITED(status))
        return -4;

    int exit_status = WEXITSTATUS(status);
    if(exit_status != 0)
        return -exit_status;

    return 0;
}

int program_exec(const char **args, ProgramOutputCallback output_callback, void *userdata) {
    /* 1 argument */
    if(args[0] == NULL)
        return -1;
    
    int fd[2];
    if(pipe(fd) == -1) {
        perror("Failed to open pipe");
        return -2;
    }

    pid_t parent_pid = getpid();

    pid_t pid = fork();
    if(pid == -1) {
        perror("Failed to fork");
        close(fd[READ_END]);
        close(fd[WRITE_END]);
        return -3;
    } else if(pid == 0) { /* child */
        if(prctl(PR_SET_PDEATHSIG, SIGTERM) == -1) {
            perror("prctl(PR_SET_PDEATHSIG, SIGTERM) failed");
            exit(127);
        }

        /* Test if the parent died before the above call to prctl */
        if(getppid() != parent_pid)
            exit(127);

        dup2(fd[WRITE_END], STDOUT_FILENO);
        close(fd[READ_END]);
        close(fd[WRITE_END]);

        execvp(args[0], (char* const*)args);
        perror("execvp");
        exit(127);
    } else { /* parent */
        close(fd[WRITE_END]);

        int result = program_read_output(pid, fd[READ_END], output_callback, userdata);
        /*
        if(result != 0) {
            fprintf(stderr, "Failed to execute program (");
            const char **arg = args;
            while(*arg) {
                if(arg != args)
                    fputc(' ', stderr);
                fprintf(stderr, "'%s'", *arg);
                ++arg;
            }
            fprintf(stderr, ")\n");
        }
        */

        close(fd[READ_END]);
        return result;
    }
}

int program_exec_async(const char **args, int *process_id, int *stdin_file, int *stdout_file) {
    int result = 0;

    /* 1 argument */
    if(args[0] == NULL)
        return -1;

    int stdin_fd[2] = { -1, -1 };
    int stdout_fd[2] = { -1, -1 };

    if(stdin_file) {
        if(pipe(stdin_fd) == -1) {
            perror("Failed to open pipe");
            result = -2;
            goto cleanup;
        }
    }

    if(stdout_file) {
        if(pipe(stdout_fd) == -1) {
            perror("Failed to open pipe");
            result = -2;
            goto cleanup;
        }
    }

    pid_t parent_pid = getpid();

    pid_t pid = fork();
    if(pid == -1) {
        result = -errno;
        perror("failed to fork");
        goto cleanup;
    } else if(pid == 0) { /* child */
        if(prctl(PR_SET_PDEATHSIG, SIGTERM) == -1) {
            perror("prctl(PR_SET_PDEATHSIG, SIGTERM) failed");
            exit(127);
        }

        /* Test if the parent died before the above call to prctl */
        if(getppid() != parent_pid)
            exit(127);

        if(stdin_file) {
            dup2(stdin_fd[READ_END], STDIN_FILENO);
            close(stdin_fd[READ_END]);
            close(stdin_fd[WRITE_END]);
        }

        if(stdout_file) {
            dup2(stdout_fd[WRITE_END], STDOUT_FILENO);
            close(stdout_fd[READ_END]);
            close(stdout_fd[WRITE_END]);
        }

        execvp(args[0], (char* const*)args);
        perror("execvp");
        exit(127);
    } else { /* parent */
        if(process_id)
            *process_id = pid;
        
        if(stdin_file) {
            close(stdin_fd[READ_END]);
            *stdin_file = stdin_fd[WRITE_END];
        }

        if(stdout_file) {
            close(stdout_fd[WRITE_END]);
            *stdout_file = stdout_fd[READ_END];
        }

        return 0;
    }

    cleanup:
    if(stdin_fd[0] != -1) close(stdin_fd[0]);
    if(stdin_fd[1] != -1) close(stdin_fd[1]);
    if(stdout_fd[0] != -1) close(stdout_fd[0]);
    if(stdout_fd[1] != -1) close(stdout_fd[1]);
    return result;
}

int program_wait_until_exit(int process_id, int stdin_file, int stdout_file, ProgramOutputCallback output_callback, void *userdata) {
    if(stdin_file != -1)
        close(stdin_file);

    int result = program_read_output(process_id, stdout_file, output_callback, userdata);

    if(stdout_file != -1)
        close(stdout_file);

    return result;
}