lib/system: Add pb_run_cmd_pipe
[petitboot] / lib / system / system.c
1
2 #if defined(HAVE_CONFIG_H)
3 #include "config.h"
4 #endif
5
6 #include <assert.h>
7 #include <errno.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <unistd.h>
11 #include <sys/stat.h>
12 #include <sys/types.h>
13 #include <sys/wait.h>
14
15 #include "log/log.h"
16 #include <talloc/talloc.h>
17 #include "system.h"
18
19 const struct pb_system_apps pb_system_apps = {
20         .prefix         = PREFIX,
21         .cp             = HOST_PROG_CP,
22         .kexec          = HOST_PROG_KEXEC,
23         .mount          = HOST_PROG_MOUNT,
24         .shutdown       = HOST_PROG_SHUTDOWN,
25         .sftp           = HOST_PROG_SFTP,
26         .tftp           = HOST_PROG_TFTP,
27         .umount         = HOST_PROG_UMOUNT,
28         .wget           = HOST_PROG_WGET,
29         .ip             = HOST_PROG_IP,
30         .udhcpc         = HOST_PROG_UDHCPC,
31 };
32
33 int pb_mkdir_recursive(const char *dir)
34 {
35         struct stat statbuf;
36         char *str, *sep;
37         int mode = 0755;
38
39         if (!*dir)
40                 return 0;
41
42         if (!stat(dir, &statbuf)) {
43                 if (!S_ISDIR(statbuf.st_mode)) {
44                         pb_log("%s: %s exists, but isn't a directory\n",
45                                         __func__, dir);
46                         return -1;
47                 }
48                 return 0;
49         }
50
51         str = talloc_strdup(NULL, dir);
52         sep = strchr(*str == '/' ? str + 1 : str, '/');
53
54         while (1) {
55
56                 /* terminate the path at sep */
57                 if (sep)
58                         *sep = '\0';
59
60                 if (mkdir(str, mode) && errno != EEXIST) {
61                         pb_log("mkdir(%s): %s\n", str, strerror(errno));
62                         return -1;
63                 }
64
65                 if (!sep)
66                         break;
67
68                 /* reset dir to the full path */
69                 strcpy(str, dir);
70                 sep = strchr(sep + 1, '/');
71         }
72
73         talloc_free(str);
74
75         return 0;
76 }
77
78 int pb_rmdir_recursive(const char *base, const char *dir)
79 {
80         char *cur, *pos;
81
82         /* sanity check: make sure that dir is within base */
83         if (strncmp(base, dir, strlen(base)))
84                 return -1;
85
86         cur = talloc_strdup(NULL, dir);
87
88         while (strcmp(base, dir)) {
89
90                 rmdir(dir);
91
92                 /* null-terminate at the last slash */
93                 pos = strrchr(dir, '/');
94                 if (!pos)
95                         break;
96
97                 *pos = '\0';
98         }
99
100         talloc_free(cur);
101
102         return 0;
103 }
104
105 static int read_pipe(void *ctx, int fd, char **bufp, int *lenp)
106 {
107         int rc, len, alloc_len;
108         char *buf;
109
110         alloc_len = 4096;
111         len = 0;
112
113         buf = talloc_array(ctx, char, alloc_len);
114
115         for (;;) {
116                 rc = read(fd, buf, alloc_len - len - 1);
117                 if (rc <= 0)
118                         break;
119
120                 len += rc;
121                 if (len == alloc_len - 1) {
122                         alloc_len *= 2;
123                         buf = talloc_realloc(ctx, buf, char, alloc_len);
124                 }
125         }
126
127         if (rc < 0) {
128                 talloc_free(buf);
129                 return rc;
130         }
131
132         buf[len] = '\0';
133         *bufp = buf;
134         *lenp = len;
135
136         return 0;
137 }
138
139 /**
140  * pb_run_cmd - Run the supplied command.
141  * @cmd_argv: An argument list array for execv.
142  * @wait: Wait for the child process to complete before returning.
143  * @dry_run: Don't actually fork and exec.
144  */
145
146 int pb_run_cmd(const char *const *cmd_argv, int wait, int dry_run)
147 {
148         return pb_run_cmd_pipe(cmd_argv, wait, dry_run, NULL, NULL, NULL);
149 }
150
151 int pb_run_cmd_pipe(const char *const *cmd_argv, int wait, int dry_run,
152                 void *ctx, char **stdout_buf, int *stdout_buf_len)
153 {
154 #if defined(DEBUG)
155         enum {do_debug = 1};
156 #else
157         enum {do_debug = 0};
158 #endif
159         int status, pipefd[2];
160         pid_t pid;
161
162         assert(!wait && stdout_buf);
163         assert(!!ctx != !!stdout_buf);
164         assert(!!stdout_buf != !!stdout_buf_len);
165
166         if (do_debug) {
167                 const char *const *p = cmd_argv;
168
169                 pb_log("%s: %s", __func__, (dry_run ? "(dry-run) " : ""));
170
171                 while (*p) {
172                         pb_log("%s ", *p);
173                         p++;
174                 }
175                 pb_log("\n");
176         } else
177                 pb_log("%s: %s%s\n", __func__, (dry_run ? "(dry-run) " : ""),
178                         cmd_argv[0]);
179
180         if (stdout_buf) {
181                 *stdout_buf = NULL;
182                 *stdout_buf_len = 0;
183         }
184
185         if (dry_run)
186                 return 0;
187
188         if (stdout_buf) {
189                 status = pipe(pipefd);
190                 if (status) {
191                         pb_log("pipe failed");
192                         return -1;
193                 }
194         }
195
196         pid = fork();
197
198         if (pid == -1) {
199                 pb_log("%s: fork failed: %s\n", __func__, strerror(errno));
200                 return -1;
201         }
202
203
204         if (pid == 0) {
205                 int log = fileno(pb_log_get_stream());
206
207                 /* Redirect child output to log. */
208
209                 if (stdout_buf) {
210                         status = dup2(pipefd[1], STDOUT_FILENO);
211                 } else {
212                         status = dup2(log, STDOUT_FILENO);
213                 }
214                 assert(status != -1);
215
216                 status = dup2(log, STDERR_FILENO);
217                 assert(status != -1);
218
219                 execvp(cmd_argv[0], (char *const *)cmd_argv);
220                 pb_log("%s: exec failed: %s\n", __func__, strerror(errno));
221                 exit(EXIT_FAILURE);
222         }
223
224         if (!wait && !waitpid(pid, &status, WNOHANG))
225                 return 0;
226
227         if (stdout_buf) {
228                 close(pipefd[1]);
229                 status = read_pipe(ctx, pipefd[0], stdout_buf, stdout_buf_len);
230                 if (status)
231                         return -1;
232         }
233
234         if (waitpid(pid, &status, 0) == -1) {
235                 pb_log("%s: waitpid failed: %s\n", __func__,
236                                 strerror(errno));
237                 return -1;
238         }
239
240         if (do_debug && WIFSIGNALED(status) && WTERMSIG(status) == SIGINT)
241                 pb_log("%s: signaled\n", __func__);
242
243         if (!WIFEXITED(status)) {
244                 pb_log("%s: %s failed\n", __func__, cmd_argv[0]);
245                 return -1;
246         }
247
248         if (WEXITSTATUS(status))
249                 pb_log("%s: WEXITSTATUS %d\n", __func__, WEXITSTATUS(status));
250
251         return WEXITSTATUS(status);
252 }