Remove wheel submodule - will re-add later with new URL
[dmon:dmon.git] / util.c
1 /*
2  * util.c
3  * Copyright (C) 2010 Adrian Perez <aperez@igalia.com>
4  *
5  * Distributed under terms of the MIT license.
6  */
7
8 /* Needed for nanosleep(2) */
9 #define _POSIX_C_SOURCE 199309L
10
11 /* Needed for fsync(2) */
12 #define _BSD_SOURCE 1
13
14 /* Needed for ULLONG_MAX */
15 #define _GNU_SOURCE 1
16
17 #include "util.h"
18 #include "wheel.h"
19 #include <sys/time.h>
20 #include <sys/resource.h>
21 #include <limits.h>
22 #include <stdarg.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <string.h>
26 #include <signal.h>
27 #include <stdio.h>
28 #include <ctype.h>
29 #include <fcntl.h>
30 #include <errno.h>
31 #include <time.h>
32 #include <grp.h>
33 #include <pwd.h>
34
35
36 void
37 fd_cloexec (int fd)
38 {
39     if (fcntl (fd, F_SETFD, FD_CLOEXEC) < 0)
40         w_die ("unable to set FD_CLOEXEC\n");
41 }
42
43
44 void
45 safe_sleep (unsigned seconds)
46 {
47     struct timespec ts;
48     int retval;
49
50     /* No time? Save up a syscall! */
51     if (!seconds) return;
52
53     ts.tv_sec = seconds;
54     ts.tv_nsec = 0;
55
56     do {
57         retval = nanosleep (&ts, &ts);
58     } while (retval == -1 && errno == EINTR);
59 }
60
61
62 void
63 safe_sigaction (const char *name, int signum, struct sigaction *sa)
64 {
65     if (sigaction (signum, sa, NULL) < 0) {
66         w_die ("could not set handler for signal $s ($i): $E\n",
67              name, signum);
68     }
69 }
70
71
72 void
73 safe_setrlimit (int what, long value)
74 {
75     struct rlimit r;
76
77     if (getrlimit (what, &r) < 0)
78         w_die ("getrlimit ($s) failed: $E\n", limit_name (what));
79
80     if (value < 0 || (unsigned long) value > r.rlim_max)
81         r.rlim_cur = r.rlim_max;
82     else
83         r.rlim_cur = value;
84
85     if (setrlimit (what, &r) < 0)
86         w_die ("setrlimit ($s=$l) failed: $E\n", limit_name (what), value);
87 }
88
89
90 int
91 interruptible_sleep (unsigned seconds)
92 {
93     struct timespec ts;
94
95     if (!seconds) return 0;
96
97     ts.tv_sec = seconds;
98     ts.tv_nsec = 0;
99
100     return !(nanosleep (&ts, NULL) == -1 && errno == EINTR);
101 }
102
103
104 int
105 name_to_uidgid (const char *str,
106                 uid_t      *uresult,
107                 gid_t      *gresult)
108 {
109     struct passwd *pw;
110     unsigned long num;
111     char *dummy;
112
113     w_assert (str);
114     w_assert (uresult);
115     w_assert (gresult);
116
117     num = strtoul (str, &dummy, 0);
118     if (num == ULONG_MAX && errno == ERANGE)
119         return 1;
120
121     if (!dummy || *dummy == '\0') {
122         if ((pw = getpwuid ((uid_t) num)) == NULL)
123             return 1;
124     }
125     else {
126         if ((pw = getpwnam (str)) == NULL)
127             return 1;
128     }
129
130     *uresult = pw->pw_uid;
131     *gresult = pw->pw_gid;
132
133     return 0;
134 }
135
136
137 int
138 name_to_gid (const char *str, gid_t *result)
139 {
140     struct group *grp;
141     unsigned long num;
142     char *dummy;
143
144     w_assert (str);
145     w_assert (result);
146
147     num = strtoul (str, &dummy, 0);
148
149     if (num == ULONG_MAX && errno == ERANGE)
150         return 1;
151
152     if (!dummy || *dummy == '\0') {
153         *result = (gid_t) num;
154         return 0;
155     }
156
157     if ((grp = getgrnam (str)) == NULL)
158         return 1;
159
160
161     *result = grp->gr_gid;
162     return 0;
163 }
164
165
166 static int
167 _parse_gids (char     *s,
168              uidgid_t *u)
169 {
170     char *pos = NULL;
171     gid_t gid;
172
173     w_assert (s);
174     w_assert (u);
175
176     if (u->ngid >= DMON_GID_COUNT) {
177         w_io_format (w_stderr,
178                      "more than $L groups given, ignoring additional ones\n",
179                      DMON_GID_COUNT);
180         return 0;
181     }
182
183     pos = strchr (s, ':');
184     if (pos != NULL)
185         *pos = '\0';
186
187     if (name_to_gid (s, &gid)) {
188         if (pos != NULL) *pos = ':';
189         return 1;
190     }
191
192     if (pos != NULL)
193         *pos = ':';
194
195     u->gids[u->ngid++] = gid;
196
197     return (pos == NULL) ? 0 : _parse_gids (pos + 1, u);
198 }
199
200
201 int
202 parse_uidgids (char     *s,
203                uidgid_t *u)
204 {
205     char *pos = NULL;
206
207     w_assert (s);
208     w_assert (u);
209
210     memset (u, 0x00, sizeof (uidgid_t));
211
212     pos = strchr (s, ':');
213     if (pos != NULL)
214         *pos = '\0';
215
216     if (name_to_uidgid (s, &u->uid, &u->gid)) {
217         if (pos != NULL) *pos = ':';
218         return 1;
219     }
220
221     if (pos != NULL)
222         *pos = ':';
223     else
224         return 0;
225
226     return (pos == NULL) ? 0 : _parse_gids (pos + 1, u);
227 }
228
229
230 void
231 become_daemon (void)
232 {
233     pid_t pid;
234     int nullfd = open ("/dev/null", O_RDWR, 0);
235
236     if (nullfd < 0)
237         w_die ("cannot daemonize, unable to open '/dev/null': $E\n");
238
239     fd_cloexec (nullfd);
240
241     if (dup2 (nullfd, STDIN_FILENO) < 0)
242         w_die ("cannot daemonize, unable to redirect stdin: $E\n");
243     if (dup2 (nullfd, STDOUT_FILENO) < 0)
244         w_die ("cannot daemonize, unable to redirect stdout: $E\n");
245     if (dup2 (nullfd, STDERR_FILENO) < 0)
246         w_die ("cannot daemonize, unable to redirect stderr: $E\n");
247
248     pid = fork ();
249
250     if (pid < 0) w_die ("cannot daemonize: $E\n");
251     if (pid > 0) _exit (EXIT_SUCCESS);
252
253     if (setsid () == -1)
254         _exit (111);
255 }
256
257
258 wbool
259 time_period_to_seconds (const char *str, unsigned long long *result)
260 {
261     unsigned long long val = 0;
262     char *endpos;
263
264     w_assert (str);
265     w_assert (result);
266
267     val = strtoull (str, &endpos, 0);
268
269     if (val == ULLONG_MAX && errno == ERANGE)
270         return W_YES;
271
272     if (!endpos || *endpos == '\0')
273         goto save_and_exit;
274
275     switch (*endpos) {
276         case 'y': val *= 60 * 60 * 24 * 365; break; /* years   */
277         case 'M': val *= 60 * 60 * 24 * 30;  break; /* months  */
278         case 'w': val *= 60 * 60 * 24 * 7;   break; /* weeks   */
279         case 'd': val *= 60 * 60 * 24;       break; /* days    */
280         case 'h': val *= 60 * 60;            break; /* hours   */
281         case 'm': val *= 60;                 break; /* minutes */
282         default : return W_YES;
283     }
284
285 save_and_exit:
286     *result = val;
287     return W_NO;
288 }
289
290
291 w_opt_status_t
292 time_period_option (const w_opt_context_t *ctx)
293 {
294     w_assert (ctx);
295     w_assert (ctx->option);
296     w_assert (ctx->option->extra);
297     w_assert (ctx->option->narg == 1);
298
299     return (time_period_to_seconds (ctx->argument[0], ctx->option->extra))
300             ? W_OPT_BAD_ARG
301             : W_OPT_OK;
302 }
303
304
305 wbool
306 storage_size_to_bytes (const char *str, unsigned long long *result)
307 {
308     unsigned long long val = 0;
309     char *endpos;
310
311     w_assert (str);
312     w_assert (result);
313
314     val = strtoull (str, &endpos, 0);
315
316     if (val == ULLONG_MAX && errno == ERANGE)
317         return W_YES;
318
319     if (!endpos || *endpos == '\0')
320         goto save_and_exit;
321
322     switch (*endpos) {
323         case  'g': case 'G': val *= 1024 * 1024 * 1024; break; /* gigabytes */
324         case  'm': case 'M': val *= 1024 * 1024;        break; /* megabytes */
325         case  'k': case 'K': val *= 1024;               break; /* kilobytes */
326         case '\0': break;
327         default  : return W_YES;
328     }
329
330 save_and_exit:
331     *result = val;
332     return W_NO;
333 }
334
335
336 w_opt_status_t
337 storage_size_option (const w_opt_context_t *ctx)
338 {
339     w_assert (ctx);
340     w_assert (ctx->option);
341     w_assert (ctx->option->extra);
342     w_assert (ctx->option->narg == 1);
343
344     return (storage_size_to_bytes (ctx->argument[0], ctx->option->extra))
345             ? W_OPT_BAD_ARG
346             : W_OPT_OK;
347 }
348
349
350 static wbool
351 _parse_limit_time (const char *sval, long *rval)
352 {
353     unsigned long long val;
354     wbool failed;
355
356     w_assert (sval != NULL);
357     w_assert (rval != NULL);
358
359     failed = time_period_to_seconds (sval, &val);
360     *rval = val;
361     return failed || (val > LONG_MAX);
362 }
363
364
365 static wbool
366 _parse_limit_number (const char *sval, long *rval)
367 {
368     w_assert (sval != NULL);
369     w_assert (rval != NULL);
370     return !(sscanf (sval, "%li", rval) == 1);
371 }
372
373
374 static wbool
375 _parse_limit_bytes (const char *sval, long *rval)
376 {
377     unsigned long long val;
378     wbool failed;
379
380     w_assert (sval != NULL);
381     w_assert (rval != NULL);
382
383     failed = storage_size_to_bytes (sval, &val);
384     *rval = val;
385     return failed || (val > LONG_MAX);
386 }
387
388
389 static const struct {
390     const char *name;
391     int         what;
392     wbool     (*parse)(const char*, long*);
393     const char *desc;
394 } rlimit_specs[] = {
395 #ifdef RLIMIT_AS
396     { "vmem",  RLIMIT_AS, _parse_limit_bytes,
397       "Maximum size of process' virtual memory (bytes)" },
398 #endif /* RLIMIT_AS */
399 #ifdef RLIMIT_CORE
400     { "core",  RLIMIT_CORE, _parse_limit_bytes,
401       "Maximum size of core file (bytes)" },
402 #endif /* RLIMIT_CORE */
403 #ifdef RLIMIT_CPU
404     { "cpu",   RLIMIT_CPU, _parse_limit_time,
405       "Maximum CPU time used (seconds)" },
406 #endif /* RLIMIT_CPU */
407 #ifdef RLIMIT_DATA
408     { "data",  RLIMIT_DATA, _parse_limit_bytes,
409       "Maximum size of data segment (bytes)" },
410 #endif /* RLIMIT_DATA */
411 #ifdef RLIMIT_FSIZE
412     { "fsize", RLIMIT_FSIZE, _parse_limit_bytes,
413       "Maximum size of created files (bytes)" },
414 #endif /* RLIMIT_FSIZE */
415 #ifdef RLIMIT_LOCKS
416     { "locks", RLIMIT_LOCKS, _parse_limit_number,
417       "Maximum number of locked files" },
418 #endif /* RLIMIT_LOCKS */
419 #ifdef RLIMIT_MEMLOCK
420     { "mlock", RLIMIT_MEMLOCK, _parse_limit_bytes,
421       "Maximum number of bytes locked in RAM (bytes)" },
422 #endif /* RLIMIT_MEMLOCK */
423 #ifdef RLIMIT_MSGQUEUE
424     { "msgq", RLIMIT_MSGQUEUE, _parse_limit_number,
425       "Maximum number of bytes used in message queues (bytes)" },
426 #endif /* RLIMIT_MSGQUEUE */
427 #ifdef RLIMIT_NICE
428     { "nice", RLIMIT_NICE, _parse_limit_number,
429       "Ceiling for the process nice value" },
430 #endif /* RLIMIT_NICE */
431 #ifdef RLIMIT_NOFILE
432     { "files", RLIMIT_NOFILE, _parse_limit_number,
433       "Maximum number of open files" },
434 #endif /* RLIMIT_NOFILE */
435 #ifdef RLIMIT_NPROC
436     { "nproc", RLIMIT_NPROC, _parse_limit_number,
437       "Maximum number of processes" },
438 #endif /* RLIMIT_NPROC */
439 #ifdef RLIMIT_RSS
440 #warning Building support for RLIMIT_RSS, this may not work on Linux 2.6+
441     { "rss", RLIMIT_RSS, _parse_limit_number,
442       "Maximum number of pages resident in RAM" },
443 #endif /* RLIMIT_RSS */
444 #ifdef RLIMIT_RTPRIO
445     { "rtprio", RLIMIT_RTPRIO, _parse_limit_number,
446       "Ceiling for the real-time priority" },
447 #endif /* RLIMIT_RTPRIO */
448 #ifdef RLIMIT_RTTIME
449     { "rttime", RLIMIT_RTTIME, _parse_limit_time,
450       "Maximum real-time CPU time used (seconds)" },
451 #endif /* RLIMIT_RTTIME */
452 #ifdef RLIMIT_SIGPENDING
453     { "sigpending", RLIMIT_SIGPENDING, _parse_limit_number,
454       "Maximum number of queued signals" },
455 #endif /* RLIMIT_SIGPENDING */
456 #ifdef RLIMIT_STACK
457     { "stack", RLIMIT_STACK, _parse_limit_bytes,
458       "Maximum stack segment size (bytes)" },
459 #endif /* RLIMIT_STACK */
460 };
461
462
463 int
464 parse_limit_arg (const char *str, int *what, long *value)
465 {
466     unsigned i;
467
468     w_assert (str != NULL);
469     w_assert (what != NULL);
470     w_assert (value != NULL);
471
472     if (!strcmp (str, "help")) {
473         for (i = 0; i < w_lengthof (rlimit_specs); i++) {
474             w_io_format (w_stdout, "$s -- $s\n",
475                          rlimit_specs[i].name,
476                          rlimit_specs[i].desc);
477         }
478         return -1;
479     }
480
481     for (i = 0; i < w_lengthof (rlimit_specs); i++) {
482         unsigned nlen = strlen (rlimit_specs[i].name);
483         if (!strncmp (str, rlimit_specs[i].name, nlen) && str[nlen] == '=') {
484             *what = rlimit_specs[i].what;
485             return ((*rlimit_specs[i].parse) (str + nlen + 1, value));
486         }
487     }
488
489     return 1;
490 }
491
492
493
494 const char*
495 limit_name (int what)
496 {
497     unsigned i;
498
499     for (i = 0; i < w_lengthof (rlimit_specs); i++) {
500         if (what == rlimit_specs[i].what)
501             return rlimit_specs[i].name;
502     }
503     return "<unknown>";
504 }
505
506
507 #ifndef REPLACE_ARGS_VCHUNK
508 #define REPLACE_ARGS_VCHUNK 16
509 #endif /* !REPLACE_ARGS_VCHUNK */
510
511 #ifndef REPLACE_ARGS_SCHUNK
512 #define REPLACE_ARGS_SCHUNK 32
513 #endif /* !REPLACE_ARGS_SCHUNK */
514
515 int
516 replace_args_string (const char *str,
517                      int        *pargc,
518                      char     ***pargv)
519 {
520     int ch;
521     char *s = NULL;
522     int maxarg = REPLACE_ARGS_VCHUNK;
523     int numarg = 0;
524     int quotes = 0;
525     int smax = 0;
526     int slen = 0;
527     char **argv = w_alloc (char*, maxarg);
528
529     w_assert (str);
530     w_assert (pargc);
531     w_assert (pargv);
532
533     /* Copy argv[0] pointer */
534     argv[numarg++] = (*pargv)[0];
535
536     while ((ch = *str++) != '\0') {
537         if (!quotes && isspace (ch)) {
538             if (!slen) {
539                 /*
540                  * Got spaces not inside a quote, and the current argument
541                  * is empty: skip spaces at the left side of an argument.
542                  */
543                 continue;
544             }
545
546             /*
547              * Not inside quotes, got space: add '\0', split and reset
548              */
549             if (numarg >= maxarg) {
550                 maxarg += REPLACE_ARGS_VCHUNK;
551                 argv = w_resize (argv, char*, maxarg);
552             }
553
554             /* Add terminating "\0" */
555             if (slen >= smax) {
556                 smax += REPLACE_ARGS_SCHUNK;
557                 s = w_resize (s, char, smax);
558             }
559
560             /* Save string in array. */
561             s[slen] = '\0';
562             argv[numarg++] = s;
563
564             /* Reset stuff */
565             smax = slen = 0;
566             s = NULL;
567             continue;
568         }
569
570         /*
571          * Got a character which is not an space, or *is* an space inside
572          * quotes. When character is the same as used for start quoting,
573          * end quoting, or start quoting if it's a quote; otherwise, just
574          * store the character.
575          */
576         if (quotes && quotes == ch) {
577             quotes = 0;
578         }
579         else if (ch == '"' || ch == '\'') {
580             quotes = ch;
581         }
582         else {
583             if (!isprint (ch)) {
584 #if   defined(EINVAL)
585                 errno = EINVAL;
586 #elif defined(EILSEQ)
587                 errno = EILSEQ;
588 #else
589 #warning Both EINVAL and EILSEQ are undefined, error message will be ambiguous
590                 errno = 0;
591 #endif
592                 return 1;
593             }
594             if (slen >= smax) {
595                 smax += REPLACE_ARGS_SCHUNK;
596                 s = w_resize (s, char, smax);
597             }
598             s[slen++] = ch;
599         }
600     }
601
602     /* If there is still an in-progres string, store it. */
603     if (slen) {
604         /* Add terminating "\0" */
605         if (slen >= smax) {
606             smax += REPLACE_ARGS_SCHUNK;
607             s = w_resize (s, char, smax);
608         }
609
610         /* Save string in array. */
611         s[slen] = '\0';
612         argv[numarg++] = s;
613     }
614
615     /* Copy remaining pointers at the tail */
616     if ((maxarg - numarg) <= *pargc) {
617         maxarg += *pargc;
618         argv = w_resize (argv, char*, maxarg);
619     }
620     for (ch = 1; ch < *pargc; ch++)
621         argv[numarg++] = (*pargv)[ch];
622
623     /* Add terminating NULL */
624     argv[numarg] = NULL;
625
626     *pargc = numarg;
627     *pargv = argv;
628
629     return 0;
630 }
631
632
633 void
634 replace_args_shift (unsigned amount,
635                     int     *pargc,
636                     char  ***pargv)
637 {
638     int    argc = *pargc;
639     char **argv = *pargv;
640     int    i;
641
642     w_assert (pargc);
643     w_assert (pargv);
644     w_assert (amount > 0);
645     w_assert (*pargc > (int) amount);
646
647     while (amount--) {
648         argc--;
649         for (i = 1; i < argc; i++) {
650             argv[i] = argv[i+1];
651         }
652     }
653     *pargc = argc;
654 }
655
656
657 /* vim: expandtab shiftwidth=4 tabstop=4
658  */