Improve error handling when writing
[mspang/pyceo.git] / src / util.c
1 #define _ATFILE_SOURCE
2 #include <unistd.h>
3 #include <sys/wait.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <stdarg.h>
7 #include <fcntl.h>
8 #include <syslog.h>
9 #include <errno.h>
10 #include <grp.h>
11 #include <pwd.h>
12
13 #include "util.h"
14 #include "strbuf.h"
15
16 static int log_stderr = 1;
17 static int log_maxprio = LOG_DEBUG;
18
19 void init_log(const char *ident, int option, int facility, int lstderr) {
20     openlog(ident, option, facility);
21     log_stderr = lstderr || isatty(STDERR_FILENO);
22 }
23
24 void log_set_maxprio(int prio) {
25     log_maxprio = prio;
26 }
27
28 static void errmsg(int prio, const char *prefix, const char *fmt, va_list args) {
29     struct strbuf msg = STRBUF_INIT;
30
31     strbuf_addf(&msg, "%s: ", prefix);
32     strbuf_vaddf(&msg, fmt, args);
33     strbuf_addch(&msg, '\n');
34
35     syslog(prio, "%s", msg.buf);
36     if (log_stderr && prio <= log_maxprio)
37         fputs(msg.buf, stderr);
38
39     strbuf_release(&msg);
40 }
41
42 static void errmsgpe(int prio, const char *prefix, const char *fmt, va_list args) {
43     struct strbuf msg = STRBUF_INIT;
44
45     strbuf_addf(&msg, "%s: ", prefix);
46     strbuf_vaddf(&msg, fmt, args);
47     strbuf_addf(&msg, ": %s\n", strerror(errno));
48
49     syslog(prio, "%s", msg.buf);
50     if (log_stderr && prio <= log_maxprio)
51         fputs(msg.buf, stderr);
52
53     strbuf_release(&msg);
54 }
55
56 NORETURN static void die(int prio, const char *prefix, const char *msg, va_list args) {
57     errmsg(prio, prefix, msg, args);
58     exit(1);
59 }
60
61 NORETURN static void diepe(int prio, const char *prefix, const char *msg, va_list args) {
62     errmsgpe(prio, prefix, msg, args);
63     exit(1);
64 }
65
66 NORETURN void fatal(const char *msg, ...) {
67     va_list args;
68     va_start(args, msg);
69     die(LOG_CRIT, "fatal", msg, args);
70     va_end(args);
71 }
72
73 void error(const char *msg, ...) {
74     va_list args;
75     va_start(args, msg);
76     errmsg(LOG_ERR, "error", msg, args);
77     va_end(args);
78 }
79
80 void warn(const char *msg, ...) {
81     va_list args;
82     va_start(args, msg);
83     errmsg(LOG_WARNING, "warning", msg, args);
84     va_end(args);
85 }
86
87 void notice(const char *msg, ...) {
88     va_list args;
89     va_start(args, msg);
90     errmsg(LOG_NOTICE, "notice", msg, args);
91     va_end(args);
92 }
93
94 void debug(const char *msg, ...) {
95     va_list args;
96     va_start(args, msg);
97     errmsg(LOG_DEBUG, "debug", msg, args);
98     va_end(args);
99 }
100
101 void logmsg(int priority, const char *msg, ...) {
102     va_list args;
103     va_start(args, msg);
104     vsyslog(priority, msg, args);
105     va_end(args);
106     va_start(args, msg);
107     if (log_stderr && priority <= log_maxprio) {
108         vfprintf(stderr, msg, args);
109         fputc('\n', stderr);
110     }
111     va_end(args);
112 }
113
114 NORETURN void deny(const char *msg, ...) {
115     va_list args;
116     va_start(args, msg);
117     die(LOG_ERR, "denied", msg, args);
118     va_end(args);
119 }
120
121 NORETURN void badconf(const char *msg, ...) {
122     va_list args;
123     va_start(args, msg);
124     die(LOG_CRIT, "configuration error", msg, args);
125     va_end(args);
126 }
127
128 NORETURN void fatalpe(const char *msg, ...) {
129     va_list args;
130     va_start(args, msg);
131     diepe(LOG_CRIT, "fatal", msg, args);
132     va_end(args);
133 }
134
135 void errorpe(const char *msg, ...) {
136     va_list args;
137     va_start(args, msg);
138     errmsgpe(LOG_ERR, "error", msg, args);
139     va_end(args);
140 }
141
142 void warnpe(const char *msg, ...) {
143     va_list args;
144     va_start(args, msg);
145     errmsgpe(LOG_WARNING, "warning", msg, args);
146     va_end(args);
147 }
148
149 int spawnv(const char *path, char *const argv[]) {
150     int pid, status;
151
152     fflush(stdout);
153     fflush(stderr);
154
155     pid = fork();
156     if (pid < 0)
157         fatalpe("fork");
158     else if (pid)
159         waitpid(pid, &status, 0);
160     else
161         exit(execv(path, argv));
162     return status;
163 }
164
165 int full_write(int fd, const void *buf, size_t count) {
166     ssize_t total = 0;
167
168     while (total < count) {
169         ssize_t wcount = write(fd, (char *)buf + total, count - total);
170         if (wcount < 0)
171             return wcount;
172         total += wcount;
173     }
174
175     return 0;
176 }
177
178 int spawnvem(const char *path, char *const *argv, char *const *envp, const struct strbuf *output, struct strbuf *input, int cap_stderr) {
179     return spawnvemu(path, argv, envp, output, input, cap_stderr, NULL);
180 }
181
182 int spawnvemu(const char *path, char *const *argv, char *const *envp, const struct strbuf *output, struct strbuf *input, int cap_stderr, char *user) {
183     int pid, wpid, status;
184     int tochild[2];
185     int fmchild[2];
186
187     if (pipe(tochild))
188         fatalpe("pipe");
189     if (pipe(fmchild))
190         fatalpe("pipe");
191
192     fflush(stdout);
193     fflush(stderr);
194
195     pid = fork();
196     if (pid < 0)
197         fatalpe("fork");
198     if (!pid) {
199         dup2(tochild[0], STDIN_FILENO);
200         dup2(fmchild[1], STDOUT_FILENO);
201         if (cap_stderr)
202             dup2(STDOUT_FILENO, STDERR_FILENO);
203         close(tochild[0]);
204         close(tochild[1]);
205         close(fmchild[0]);
206         close(fmchild[1]);
207
208         if (user) {
209             struct passwd *pw = getpwnam(user);
210             if (!pw)
211                 fatalpe("getpwnam: %s", user);
212             if (initgroups(user, pw->pw_gid))
213                 fatalpe("initgroups: %s", user);
214             if (setregid(pw->pw_gid, pw->pw_gid))
215                 fatalpe("setregid: %s", user);
216             if (setreuid(pw->pw_uid, pw->pw_uid))
217                 fatalpe("setreuid");
218         }
219         execve(path, argv, envp);
220         fatalpe("execve");
221     } else {
222         close(tochild[0]);
223         close(fmchild[1]);
224         full_write(tochild[1], output->buf, output->len);
225         close(tochild[1]);
226
227         if (input)
228             strbuf_read(input, fmchild[0], 0);
229         close(fmchild[0]);
230     }
231
232     wpid = waitpid(pid, &status, 0);
233     if (wpid < 0)
234         fatalpe("waitpid");
235     else if (wpid != pid)
236         fatal("waitpid is broken");
237
238     if (WIFEXITED(status) && WEXITSTATUS(status))
239         notice("child %s exited with status %d", path, WEXITSTATUS(status));
240     else if (WIFSIGNALED(status))
241         notice("child %s killed by signal %d", path, WTERMSIG(status));
242
243     return status;
244 }
245
246 int spawnv_msg(const char *path, char *const *argv, const struct strbuf *output) {
247     return spawnvem(path, argv, environ, output, NULL, 0);
248 }
249
250 int check_group(char *username, char *group) {
251     struct group *grp = getgrnam(group);
252     char **members;
253
254     if (grp)
255         for (members = grp->gr_mem; *members; members++)
256             if (!strcmp(username, *members))
257                 return 1;
258
259     return 0;
260 }
261
262 FILE *fopenat(DIR *d, const char *path, int flags) {
263     int dfd = dirfd(d);
264     if (dfd < 0)
265         return NULL;
266     int fd = openat(dfd, path, flags);
267     if (fd < 0)
268         return NULL;
269     return fdopen(fd, flags & O_RDWR   ? "r+" :
270                       flags & O_WRONLY ? "w" :
271                                          "r");
272 }
273
274 void make_env(char **envp, ...) {
275     const size_t len = 4096;
276     size_t used = 0;
277     int args = 0;
278     char *buf = xmalloc(len);
279     va_list ap;
280     va_start(ap, envp);
281     char *name, *val;
282
283     while ((name = va_arg(ap, char *))) {
284         val = va_arg(ap, char *);
285         if (!val)
286             continue;
287         int n = snprintf(buf + used, len - used, "%s=%s", name, val);
288         if (n < 0)
289             fatalpe("snprintf");
290         if (n >= len - used)
291             fatal("environment too big");
292
293         envp[args++] = buf + used;
294         used += n + 1;
295     }
296
297     if (!args)
298         free(buf);
299
300     envp[args] = NULL;
301 }
302
303 void free_env(char **envp) {
304     free(*envp);
305 }