├── setunbuf.c ├── conf.h ├── package.json ├── nofork.c ├── COPYING ├── xbuild ├── fallback └── reallocarray.c ├── task.h ├── multicall.c ├── deps ├── clog │ ├── clog.h │ └── clog.c ├── cflag │ ├── cflag.h │ └── cflag.c └── dbuf │ ├── dbuf.c │ └── dbuf.h ├── README.md ├── dslog.rst ├── dlog.rst ├── dslog.8 ├── dlog.8 ├── Makefile ├── drlog.rst ├── denv.rst ├── CHANGELOG.md ├── drlog.8 ├── denv.8 ├── util.h ├── task.c ├── dlog.c ├── conf.c ├── dslog.c ├── denv.c ├── dmon.rst ├── drlog.c ├── dmon.8 ├── util.c └── dmon.c /setunbuf.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static void __attribute__((constructor)) setunbuf(void) 4 | { 5 | setbuf(stdout, NULL); 6 | } 7 | -------------------------------------------------------------------------------- /conf.h: -------------------------------------------------------------------------------- 1 | /* 2 | * conf.h 3 | * Copyright (C) 2020 Adrian Perez de Castro 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #ifndef CONF_H 9 | #define CONF_H 10 | 11 | #include 12 | #include 13 | 14 | struct cflag; 15 | struct dbuf; 16 | 17 | extern bool conf_parse(FILE *input, 18 | const struct cflag *specs, 19 | struct dbuf *err); 20 | 21 | #endif /* !CONF_H */ 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dmon", 3 | "repo": "aperezdc/dmon", 4 | "version": "0.6.0", 5 | "license": "MIT", 6 | "description": "Small process daemonization and monitoring tool", 7 | "keywords": ["monitor", "process", "daemonization", "logging"], 8 | "makefile": "Makefile", 9 | "install": "make install", 10 | "dependencies": { 11 | "aperezdc/cflag": "1.1.1", 12 | "aperezdc/clog": "0.1.0", 13 | "aperezdc/dbuf": "0.1.0" 14 | }, 15 | "src": [ 16 | "conf.c", 17 | "conf.h", 18 | "denv.c", 19 | "dlog.c", 20 | "dmon.c", 21 | "drlog.c", 22 | "dslog.c", 23 | "multicall.c", 24 | "task.c", 25 | "task.h", 26 | "util.c", 27 | "util.h" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /nofork.c: -------------------------------------------------------------------------------- 1 | /* 2 | * nofork.c 3 | * Copyright (C) 2010 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #define _POSIX_C_SOURCE 200809L 9 | 10 | #include 11 | #include 12 | #include 13 | #include "util.h" 14 | 15 | 16 | pid_t 17 | fork (void) 18 | { 19 | return 0; 20 | } 21 | 22 | 23 | int 24 | daemon (int nochdir, int noclose) 25 | { 26 | if (!nochdir) { 27 | if (chdir ("/")) 28 | return -1; 29 | } 30 | if (!noclose) { 31 | safe_close (0); 32 | if (safe_openat(AT_FDCWD, "/dev/null", O_RDONLY) == -1) 33 | return -1; 34 | safe_close(1); 35 | if (safe_openat(AT_FDCWD, "/dev/null", O_WRONLY) != 1) 36 | return -1; 37 | safe_close(2); 38 | if (dup (1) != 2) 39 | return -1; 40 | } 41 | return 0; 42 | } 43 | 44 | 45 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Igalia S.L., Adrian Perez 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /xbuild: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | set -e 3 | 4 | declare -a TARGETS=( 5 | x86-linux-musl 6 | x86_64-linux-musl 7 | aarch64-linux-musl 8 | arm-linux-musleabi 9 | arm-linux-musleabihf 10 | mips-linux-musl 11 | mipsel-linux-musl 12 | mips64-linux-musl 13 | mips64el-linux-musl 14 | powerpc-linux-musl 15 | powerpc64-linux-musl 16 | powerpc64le-linux-musl 17 | #riscv32-linux-musl 18 | riscv64-linux-musl 19 | ) 20 | 21 | declare -a CFLAGS=( 22 | -Os 23 | -flto 24 | -fPIE 25 | -D_FORTIFY_SOURCE=2 26 | -pipe 27 | -fstack-protector-strong 28 | -fno-plt 29 | ) 30 | declare -a LDFLAGS=( 31 | -O1 32 | -s 33 | -flto 34 | -fPIE 35 | -Wl,-O1,--sort-common,--as-needed,-z,relro,-z,now,--icf=safe,--gc-sections 36 | ) 37 | 38 | V=$(jq -r .version package.json) 39 | 40 | for T in "${TARGETS[@]}" 41 | do 42 | O="dmon-$V-${T%-musl}" 43 | echo "===== $O ====" 44 | if [[ -x $O ]] ; then 45 | echo ' -> Already built' 46 | continue 47 | fi 48 | 49 | make -s clean 50 | make -sj$(nproc) \ 51 | AR='llvm-ar' \ 52 | CC="zig cc -target $T" \ 53 | CFLAGS="${CFLAGS[*]}" \ 54 | CPPFLAGS="${CPPFLAGS[*]}" 55 | mv dmon "$O" 56 | llvm-strip -x --strip-unneeded "$O" 57 | echo ' -> Done' 58 | done 59 | make -s clean 60 | -------------------------------------------------------------------------------- /fallback/reallocarray.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: reallocarray.c,v 1.3 2015/09/13 08:31:47 guenther Exp $ */ 2 | /* 3 | * Copyright (c) 2008 Otto Moerbeek 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | /* 24 | * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX 25 | * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW 26 | */ 27 | #define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) 28 | 29 | void * 30 | reallocarray(void *optr, size_t nmemb, size_t size) 31 | { 32 | if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && 33 | nmemb > 0 && SIZE_MAX / nmemb < size) { 34 | errno = ENOMEM; 35 | return NULL; 36 | } 37 | return realloc(optr, size * nmemb); 38 | } 39 | DEF_WEAK(reallocarray); 40 | -------------------------------------------------------------------------------- /task.h: -------------------------------------------------------------------------------- 1 | /* 2 | * task.h 3 | * Copyright (C) 2010-2020 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #ifndef __task_h__ 9 | #define __task_h__ 10 | 11 | #include "util.h" 12 | #include 13 | 14 | typedef enum { 15 | A_NONE = 0, 16 | A_START, 17 | A_STOP, 18 | A_SIGNAL, 19 | } action_t; 20 | 21 | 22 | typedef struct { 23 | pid_t pid; 24 | action_t action; 25 | int argc; 26 | char **argv; 27 | int write_fd; 28 | int read_fd; 29 | int signal; 30 | time_t started; 31 | uidgid_t user; 32 | unsigned redir_errfd; 33 | } task_t; 34 | 35 | #define NO_PID (-1) 36 | #define NO_SIGNAL (-1) 37 | #define TASK { NO_PID, \ 38 | A_START, \ 39 | 0, \ 40 | NULL, \ 41 | -1, \ 42 | -1, \ 43 | NO_SIGNAL, \ 44 | 0, \ 45 | UIDGID, \ 46 | 0 } 47 | 48 | #define task_action_queue(task, _action) \ 49 | ((task)->action = (_action)) 50 | 51 | #define task_signal_queue(task, _signal) \ 52 | ((task)->signal = (_signal)) 53 | 54 | task_t* task_new (int argc, const char **argv); 55 | void task_start (task_t *task); 56 | void task_signal_dispatch (task_t *task); 57 | void task_action_dispatch (task_t *task); 58 | void task_signal (task_t *task, int signum); 59 | void task_action (task_t *task, action_t action); 60 | 61 | #endif /* !__task_h__ */ 62 | 63 | /* vim: expandtab tabstop=4 shiftwidth=4 64 | */ 65 | -------------------------------------------------------------------------------- /multicall.c: -------------------------------------------------------------------------------- 1 | /* 2 | * multicall.c 3 | * Copyright (C) 2010-2024 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #if defined(MULTICALL) && MULTICALL 9 | 10 | #include "util.h" 11 | #include 12 | #include 13 | #include 14 | 15 | extern int denv_main(int, char**); 16 | extern int dlog_main(int, char**); 17 | extern int dmon_main(int, char**); 18 | extern int drlog_main(int, char**); 19 | extern int dslog_main(int, char**); 20 | 21 | static const struct { 22 | const char *name; 23 | int (*func)(int, char**); 24 | } applets[] = { 25 | { .name = "denv", .func = denv_main }, 26 | { .name = "dmon", .func = dmon_main }, 27 | { .name = "dlog", .func = dlog_main }, 28 | { .name = "drlog", .func = drlog_main }, 29 | { .name = "dslog", .func = dslog_main }, 30 | { .name = "envdir", .func = denv_main }, 31 | }; 32 | 33 | 34 | int 35 | main(int argc, char **argv) 36 | { 37 | const char *argv0 = strrchr(argv[0], '/'); 38 | if (argv0 == NULL) 39 | argv0 = argv[0]; 40 | else 41 | argv0++; 42 | 43 | const char *env = getenv("DMON_LIST_MULTICALL_APPLETS"); 44 | const bool list = env && *env != '\0' && *env != '0'; 45 | 46 | int (*mainfunc)(int, char**) = NULL; 47 | 48 | for (unsigned i = 0; i < sizeof(applets) / sizeof(applets[0]); i++) { 49 | if (list) { 50 | puts(applets[i].name); 51 | } else if (strcmp(argv0, applets[i].name) == 0) { 52 | mainfunc = applets[i].func; 53 | break; 54 | } 55 | } 56 | 57 | if (list) 58 | return EXIT_SUCCESS; 59 | 60 | if (mainfunc == NULL) 61 | die("%s: No such applet in multicall binary.\n", argv0); 62 | 63 | return (*mainfunc)(argc, argv); 64 | } 65 | 66 | #endif /* !MULTICALL */ 67 | -------------------------------------------------------------------------------- /deps/clog/clog.h: -------------------------------------------------------------------------------- 1 | /* 2 | * clog.h 3 | * Copyright (C) 2020 Adrian Perez de Castro 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #ifndef CLOG_H 9 | #define CLOG_H 10 | 11 | #include 12 | #include 13 | 14 | enum clog_level { 15 | CLOG_LEVEL_INFO, 16 | CLOG_LEVEL_WARNING, 17 | CLOG_LEVEL_ERROR, 18 | CLOG_LEVEL_DEBUG, 19 | }; 20 | 21 | extern bool clog_debug_enabled; 22 | extern bool clog_fatal_errors; 23 | extern bool clog_fatal_warnings; 24 | 25 | extern void clog_init(const char *env_prefix); 26 | extern void clog_format(enum clog_level, 27 | const char *file, 28 | unsigned line, 29 | const char *func, 30 | const char *fmt, 31 | ...); 32 | 33 | #define clog_message(fmt, ...) \ 34 | fprintf(stderr, " ** " fmt "\n", ##__VA_ARGS__) 35 | 36 | #define clog_info(fmt, ...) \ 37 | clog_format(CLOG_LEVEL_INFO, \ 38 | __FILE__, __LINE__, __func__, \ 39 | (fmt), ##__VA_ARGS__) 40 | 41 | #define clog_warning(fmt, ...) \ 42 | clog_format(CLOG_LEVEL_WARNING, \ 43 | __FILE__, __LINE__, __func__, \ 44 | (fmt), ##__VA_ARGS__) 45 | 46 | #define clog_error(fmt, ...) \ 47 | clog_format(CLOG_LEVEL_ERROR, \ 48 | __FILE__, __LINE__, __func__, \ 49 | (fmt), ##__VA_ARGS__) 50 | 51 | #define clog_debug(fmt, ...) \ 52 | do { \ 53 | if (clog_debug_enabled) \ 54 | clog_format(CLOG_LEVEL_DEBUG, \ 55 | __FILE__, __LINE__, __func__, \ 56 | (fmt), ##__VA_ARGS__); \ 57 | } while (false) 58 | 59 | #if defined(CLOG_SHORT_MACROS) && CLOG_SHORT_MACROS 60 | #define cmessage clog_message 61 | #define cinfo clog_info 62 | #define cwarning clog_warning 63 | #define cerror clog_error 64 | #define cdebug clog_debug 65 | #endif /* CLOG_SHORT_MACROS */ 66 | 67 | #endif /* !CLOG_H */ 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DMon - Process Monitoring With Style 2 | 3 | [![builds.sr.ht status](https://builds.sr.ht/~aperezdc/dmon/commits.svg)](https://builds.sr.ht/~aperezdc/dmon/commits?) 4 | 5 | This README contains only some random bits. For more in-depth writing, you 6 | may want to read the articles on DMon: 7 | 8 | * 2010/08/27: [DMon: Process monitoring with style](https://perezdecastro.org/2010/dmon-process-monitoring-with-style.html) 9 | * 2010/10/04: [DMon status report: evolution to 0.3.7](https://perezdecastro.org/2010/dmon-status-report-0-3-7.html) 10 | * 2011/07/25: [DMon 0.4: New guts & new features](https://perezdecastro.org/2011/dmon-0-4-new-guts-and-new-features.html) 11 | * 2012/01/07: [DMon 0.4.2 “Three Wise Men” released](https://perezdecastro.org/2012/dmon-0-4-2-released.html) 12 | 13 | There are also manual pages, so please take a look at them. 14 | 15 | 16 | ## Bulding standalone binaries 17 | 18 | By default all tools are built into a single binary which can be symlinked 19 | with different names to switch between them (àla BusyBox). This is useful 20 | to save space and (to some degree) system memory. 21 | 22 | You can build all the DMon tools as separate binaries passing `MULTICALL=0` 23 | when invoking Make: 24 | 25 | ```sh 26 | make MULTICALL=0 27 | ``` 28 | 29 | Remember to pass the option when doing `make install` as well: 30 | 31 | ```sh 32 | make MULTICALL=0 install 33 | ``` 34 | 35 | 36 | ## Building libnofork.so 37 | 38 | A tiny `LD_PRELOAD`-able “`libnofork.so`” library can be built by using the 39 | `nofork` Make target. This library overrides the `fork(2)` and `daemon(3)` 40 | functions from the system libraries, in such a way that the process under 41 | effect will not be able of forking. This is interesting for running DMon 42 | with programs that have no option to instruct them not to fork. 43 | 44 | ## Building libsetunbuf.so 45 | 46 | A tiny `LD_PRELOAD`-able "`libsetunbuf.so`" library can be built by using the 47 | `setunbuf` Make target. This library uses the `__attribute__((constructor))` 48 | attribute in order to call `setbuf(stdout, NULL);` which turns off the 49 | buffering of stdout on the process running under DMon. This is useful for 50 | viewing the output of your process through DLog in real time. -------------------------------------------------------------------------------- /dslog.rst: -------------------------------------------------------------------------------- 1 | ====== 2 | dlog 3 | ====== 4 | 5 | ------------------------------------------------ 6 | Send lines from standard input to the system log 7 | ------------------------------------------------ 8 | 9 | :Author: Adrian Perez 10 | :Manual section: 8 11 | 12 | 13 | SYNOPSIS 14 | ======== 15 | 16 | ``dslog [options] name`` 17 | 18 | 19 | DESCRIPTION 20 | =========== 21 | 22 | The ``dslog`` program sends lines given as standard input to the system 23 | logger, one line at a time, with a selectable priority, facility and origin 24 | program name. 25 | 26 | 27 | USAGE 28 | ===== 29 | 30 | Command line options: 31 | 32 | -p PRIORITY, --priority PRIORITY 33 | Priority of messages. Refer to `syslog(3)` to see possible 34 | values. Just pass any valid priority without the ``LOG_`` 35 | prefix. Case does not matter. 36 | 37 | -f FACILITY, --facility FACILITY 38 | Logging facility. Refer to `syslog(3)` to see possible values. 39 | Just pass any valid facility without the ``LOG_`` prefix. Case 40 | does not matter. 41 | 42 | -i NUMBER, --input-fd NUMBER 43 | Use file descriptor ``NUMBER`` to read input. By default the 44 | standard input descriptor (number ``0``) is used. 45 | 46 | -c, --console 47 | If a message cannot be sent to the system logger, print a copy 48 | of it to the system console. 49 | 50 | -e, --skip-empty 51 | Ignore empty input lines. An empty line is one that does not 52 | contain any characters; a line which contains whitespace is 53 | **not** considered empty. 54 | 55 | -h, --help Show a summary of available options. 56 | 57 | Albeit it can be used stan-alone, most of the time you will be running 58 | ``dslog`` under a process control tool like `dmon(8)` or `supervise(8)`. 59 | 60 | 61 | ENVIRONMENT 62 | =========== 63 | 64 | Additional options will be picked from the ``DSLOG_OPTIONS`` environment 65 | variable, if defined. Any command line option can be specified this way. 66 | Arguments read from the environment variable will be prepended to the ones 67 | given in the command line, so they may still be overriden. 68 | 69 | 70 | SEE ALSO 71 | ======== 72 | 73 | `dmon(8)`, `dlog(8)`, `rotlog(8)`, `multilog(8)`, `supervise(8)` 74 | 75 | http://cr.yp.to/daemontools.html 76 | 77 | -------------------------------------------------------------------------------- /dlog.rst: -------------------------------------------------------------------------------- 1 | ====== 2 | dlog 3 | ====== 4 | 5 | --------------------------------------------- 6 | Send lines from standard input to a log file 7 | --------------------------------------------- 8 | 9 | :Author: Adrian Perez 10 | :Manual section: 8 11 | 12 | 13 | SYNOPSIS 14 | ======== 15 | 16 | ``dlog [options] [logfile]`` 17 | 18 | 19 | DESCRIPTION 20 | =========== 21 | 22 | The ``dlog`` program sends lines given as standard input to a log file, 23 | one line at a time, optionally adding a timestamp in front of each line. 24 | If the log file is not specified, then lines are printed back to standard 25 | output. The latter may be useful to add timestamps in shell pipelines. 26 | 27 | 28 | USAGE 29 | ===== 30 | 31 | Command line options: 32 | 33 | -p TEXT, --prefix TEXT 34 | Insert the given text as prefix for each logged message. If 35 | adding timestamps is enabled, the text is inserted *after* 36 | the timestamp, but still before the logged text. 37 | 38 | -i NUMBER, --input-fd NUMBER 39 | Use file descriptor ``NUMBER`` to read input. By default the 40 | standard input descriptor (number ``0``) is used. 41 | 42 | -b, --buffered 43 | Buffered operation. If enabled, calls to `fsync(2)` will be 44 | avoided. This improves performance, but may cause messages to 45 | be lost. 46 | 47 | -t, --timestamp 48 | Prepend a timestamp to each saved line. By default 49 | timestamps are disabled. Timestamp format is 50 | ``YYYY-mm-dd/HH:MM:SS``. 51 | 52 | -e, --skip-empty 53 | Ignore empty input lines. An empty line is one that does not 54 | contain any characters; a line which contains whitespace is 55 | **not** considered empty. 56 | 57 | -h, --help Show a summary of available options. 58 | 59 | Albeit it can be used stand-alone, most of the time you will be running 60 | ``dlog`` under a process control tool like `dmon(8)` or `supervise(8)`. 61 | 62 | 63 | ENVIRONMENT 64 | =========== 65 | 66 | Additional options will be picked from the ``DLOG_OPTIONS`` environment 67 | variable, if defined. Any command line option can be specified this way. 68 | Arguments read from the environment variable will be prepended to the ones 69 | given in the command line, so they may still be overriden. 70 | 71 | 72 | SEE ALSO 73 | ======== 74 | 75 | `dmon(8)`, `dslog(8)`, `rotlog(8)`, `multilog(8)`, `supervise(8)` 76 | 77 | http://cr.yp.to/daemontools.html 78 | 79 | -------------------------------------------------------------------------------- /deps/cflag/cflag.h: -------------------------------------------------------------------------------- 1 | /* 2 | * cflag.h 3 | * Copyright (C) 2020 Adrian Perez de Castro 4 | * 5 | * SPDX-License-Identifier: MIT 6 | */ 7 | 8 | #ifndef CFLAG_H 9 | #define CFLAG_H 10 | 11 | #include 12 | 13 | enum cflag_type { 14 | CFLAG_TYPE_BOOL = 0, 15 | CFLAG_TYPE_INT, 16 | CFLAG_TYPE_STRING, 17 | CFLAG_TYPE_CUSTOM, 18 | CFLAG_TYPE_HELP, 19 | }; 20 | 21 | enum cflag_status { 22 | CFLAG_OK = 0, 23 | CFLAG_SHOW_HELP, 24 | CFLAG_UNDEFINED, 25 | CFLAG_BAD_FORMAT, 26 | CFLAG_NEEDS_ARG, 27 | }; 28 | 29 | struct cflag; 30 | 31 | typedef enum cflag_status (*cflag_func) (const struct cflag*, const char *arg); 32 | 33 | struct cflag { 34 | cflag_func func; 35 | const char *name; 36 | int letter; 37 | void *data; 38 | const char *help; 39 | }; 40 | 41 | 42 | #define CFLAG(_t, _name, _letter, _data, _help) \ 43 | ((struct cflag) { \ 44 | .func = cflag_ ## _t, \ 45 | .name = (_name), \ 46 | .letter = (_letter), \ 47 | .data = (_data), \ 48 | .help = (_help), \ 49 | }) 50 | #define CFLAG_HELP \ 51 | CFLAG(help, "help", 'h', NULL, "Prints command line usage help.") 52 | #define CFLAG_END \ 53 | { .name = NULL, .letter = '\0' } 54 | 55 | enum cflag_status cflag_bool (const struct cflag*, const char*); 56 | enum cflag_status cflag_int (const struct cflag*, const char*); 57 | enum cflag_status cflag_uint (const struct cflag*, const char*); 58 | enum cflag_status cflag_float (const struct cflag*, const char*); 59 | enum cflag_status cflag_double (const struct cflag*, const char*); 60 | enum cflag_status cflag_string (const struct cflag*, const char*); 61 | enum cflag_status cflag_bytes (const struct cflag*, const char*); 62 | enum cflag_status cflag_timei (const struct cflag*, const char*); 63 | enum cflag_status cflag_help (const struct cflag*, const char*); 64 | 65 | void cflag_usage(const struct cflag specs[], 66 | const char *progname, 67 | const char *syntax, 68 | FILE *out); 69 | 70 | int cflag_parse(const struct cflag specs[], 71 | int *pargc, 72 | char ***pargv); 73 | 74 | const char* cflag_apply(const struct cflag specs[], 75 | const char *syntax, 76 | int *pargc, 77 | char ***pargv); 78 | 79 | const char* cflag_status_name(enum cflag_status value); 80 | 81 | #endif /* !CFLAG_H */ 82 | -------------------------------------------------------------------------------- /dslog.8: -------------------------------------------------------------------------------- 1 | .\" Man page generated from reStructuredText. 2 | . 3 | .TH DLOG 8 "" "" "" 4 | .SH NAME 5 | dlog \- Send lines from standard input to the system log 6 | . 7 | .nr rst2man-indent-level 0 8 | . 9 | .de1 rstReportMargin 10 | \\$1 \\n[an-margin] 11 | level \\n[rst2man-indent-level] 12 | level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] 13 | - 14 | \\n[rst2man-indent0] 15 | \\n[rst2man-indent1] 16 | \\n[rst2man-indent2] 17 | .. 18 | .de1 INDENT 19 | .\" .rstReportMargin pre: 20 | . RS \\$1 21 | . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] 22 | . nr rst2man-indent-level +1 23 | .\" .rstReportMargin post: 24 | .. 25 | .de UNINDENT 26 | . RE 27 | .\" indent \\n[an-margin] 28 | .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] 29 | .nr rst2man-indent-level -1 30 | .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] 31 | .in \\n[rst2man-indent\\n[rst2man-indent-level]]u 32 | .. 33 | .SH SYNOPSIS 34 | .sp 35 | \fBdslog [options] name\fP 36 | .SH DESCRIPTION 37 | .sp 38 | The \fBdslog\fP program sends lines given as standard input to the system 39 | logger, one line at a time, with a selectable priority, facility and origin 40 | program name. 41 | .SH USAGE 42 | .sp 43 | Command line options: 44 | .INDENT 0.0 45 | .TP 46 | .BI \-p \ PRIORITY\fR,\fB \ \-\-priority \ PRIORITY 47 | Priority of messages. Refer to \fIsyslog(3)\fP to see possible 48 | values. Just pass any valid priority without the \fBLOG_\fP 49 | prefix. Case does not matter. 50 | .TP 51 | .BI \-f \ FACILITY\fR,\fB \ \-\-facility \ FACILITY 52 | Logging facility. Refer to \fIsyslog(3)\fP to see possible values. 53 | Just pass any valid facility without the \fBLOG_\fP prefix. Case 54 | does not matter. 55 | .TP 56 | .BI \-i \ NUMBER\fR,\fB \ \-\-input\-fd \ NUMBER 57 | Use file descriptor \fBNUMBER\fP to read input. By default the 58 | standard input descriptor (number \fB0\fP) is used. 59 | .TP 60 | .B \-c\fP,\fB \-\-console 61 | If a message cannot be sent to the system logger, print a copy 62 | of it to the system console. 63 | .TP 64 | .B \-e\fP,\fB \-\-skip\-empty 65 | Ignore empty input lines. An empty line is one that does not 66 | contain any characters; a line which contains whitespace is 67 | \fBnot\fP considered empty. 68 | .TP 69 | .B \-h\fP,\fB \-\-help 70 | Show a summary of available options. 71 | .UNINDENT 72 | .sp 73 | Albeit it can be used stan\-alone, most of the time you will be running 74 | \fBdslog\fP under a process control tool like \fIdmon(8)\fP or \fIsupervise(8)\fP\&. 75 | .SH ENVIRONMENT 76 | .sp 77 | Additional options will be picked from the \fBDSLOG_OPTIONS\fP environment 78 | variable, if defined. Any command line option can be specified this way. 79 | Arguments read from the environment variable will be prepended to the ones 80 | given in the command line, so they may still be overriden. 81 | .SH SEE ALSO 82 | .sp 83 | \fIdmon(8)\fP, \fIdlog(8)\fP, \fIrotlog(8)\fP, \fImultilog(8)\fP, \fIsupervise(8)\fP 84 | .sp 85 | \fI\%http://cr.yp.to/daemontools.html\fP 86 | .SH AUTHOR 87 | Adrian Perez 88 | .\" Generated by docutils manpage writer. 89 | . 90 | -------------------------------------------------------------------------------- /dlog.8: -------------------------------------------------------------------------------- 1 | .\" Man page generated from reStructuredText. 2 | . 3 | . 4 | .nr rst2man-indent-level 0 5 | . 6 | .de1 rstReportMargin 7 | \\$1 \\n[an-margin] 8 | level \\n[rst2man-indent-level] 9 | level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] 10 | - 11 | \\n[rst2man-indent0] 12 | \\n[rst2man-indent1] 13 | \\n[rst2man-indent2] 14 | .. 15 | .de1 INDENT 16 | .\" .rstReportMargin pre: 17 | . RS \\$1 18 | . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] 19 | . nr rst2man-indent-level +1 20 | .\" .rstReportMargin post: 21 | .. 22 | .de UNINDENT 23 | . RE 24 | .\" indent \\n[an-margin] 25 | .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] 26 | .nr rst2man-indent-level -1 27 | .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] 28 | .in \\n[rst2man-indent\\n[rst2man-indent-level]]u 29 | .. 30 | .TH "DLOG" "8" "" "" 31 | .SH NAME 32 | dlog \- Send lines from standard input to a log file 33 | .SH SYNOPSIS 34 | .sp 35 | \fBdlog [options] [logfile]\fP 36 | .SH DESCRIPTION 37 | .sp 38 | The \fBdlog\fP program sends lines given as standard input to a log file, 39 | one line at a time, optionally adding a timestamp in front of each line. 40 | If the log file is not specified, then lines are printed back to standard 41 | output. The latter may be useful to add timestamps in shell pipelines. 42 | .SH USAGE 43 | .sp 44 | Command line options: 45 | .INDENT 0.0 46 | .TP 47 | .BI \-p \ TEXT\fR,\fB \ \-\-prefix \ TEXT 48 | Insert the given text as prefix for each logged message. If 49 | adding timestamps is enabled, the text is inserted \fIafter\fP 50 | the timestamp, but still before the logged text. 51 | .TP 52 | .BI \-i \ NUMBER\fR,\fB \ \-\-input\-fd \ NUMBER 53 | Use file descriptor \fBNUMBER\fP to read input. By default the 54 | standard input descriptor (number \fB0\fP) is used. 55 | .TP 56 | .B \-b\fP,\fB \-\-buffered 57 | Buffered operation. If enabled, calls to \fIfsync(2)\fP will be 58 | avoided. This improves performance, but may cause messages to 59 | be lost. 60 | .TP 61 | .B \-t\fP,\fB \-\-timestamp 62 | Prepend a timestamp to each saved line. By default 63 | timestamps are disabled. Timestamp format is 64 | \fBYYYY\-mm\-dd/HH:MM:SS\fP\&. 65 | .TP 66 | .B \-e\fP,\fB \-\-skip\-empty 67 | Ignore empty input lines. An empty line is one that does not 68 | contain any characters; a line which contains whitespace is 69 | \fBnot\fP considered empty. 70 | .TP 71 | .B \-h\fP,\fB \-\-help 72 | Show a summary of available options. 73 | .UNINDENT 74 | .sp 75 | Albeit it can be used stand\-alone, most of the time you will be running 76 | \fBdlog\fP under a process control tool like \fIdmon(8)\fP or \fIsupervise(8)\fP\&. 77 | .SH ENVIRONMENT 78 | .sp 79 | Additional options will be picked from the \fBDLOG_OPTIONS\fP environment 80 | variable, if defined. Any command line option can be specified this way. 81 | Arguments read from the environment variable will be prepended to the ones 82 | given in the command line, so they may still be overriden. 83 | .SH SEE ALSO 84 | .sp 85 | \fIdmon(8)\fP, \fIdslog(8)\fP, \fIrotlog(8)\fP, \fImultilog(8)\fP, \fIsupervise(8)\fP 86 | .sp 87 | 88 | .SH AUTHOR 89 | Adrian Perez 90 | .\" Generated by docutils manpage writer. 91 | . 92 | -------------------------------------------------------------------------------- /deps/clog/clog.c: -------------------------------------------------------------------------------- 1 | /* 2 | * clog.c 3 | * Copyright (C) 2020 Adrian Perez de Castro 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #define _POSIX_C_SOURCE 1 9 | 10 | #include "clog.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | bool clog_fatal_errors = true; 17 | bool clog_fatal_warnings = false; 18 | bool clog_debug_enabled = false; 19 | static bool use_colors = false; 20 | 21 | static const struct { 22 | const char *name; 23 | bool *data; 24 | } env_opts[] = { 25 | { "DEBUG", &clog_debug_enabled }, 26 | { "FATAL_ERRORS", &clog_fatal_errors }, 27 | { "FATAL_WARNINGS", &clog_fatal_warnings }, 28 | { "COLOR_MESSAGES", &use_colors }, 29 | }; 30 | 31 | static const char *names_plain[] = { 32 | [CLOG_LEVEL_INFO] = "INFO ", 33 | [CLOG_LEVEL_WARNING] = "WARN ", 34 | [CLOG_LEVEL_ERROR] = "ERROR ", 35 | [CLOG_LEVEL_DEBUG] = "DEBUG ", 36 | }; 37 | static const char *names_colored[] = { 38 | [CLOG_LEVEL_INFO] = "\33[32m" "INFO " "\33[39m", 39 | [CLOG_LEVEL_WARNING] = "\33[91m" "WARN " "\33[39m", 40 | [CLOG_LEVEL_ERROR] = "\33[31m" "ERROR " "\33[39m", 41 | [CLOG_LEVEL_DEBUG] = "\33[35m" "DEBUG " "\33[39m", 42 | }; 43 | static const char **names = names_plain; 44 | 45 | static const char loc_format_plain[] = 46 | " [%s:%d, %s]" 47 | "\n"; 48 | static const char loc_format_colored[] = 49 | "\33[90m" 50 | " [%s:%d, %s]" 51 | "\33[39m" 52 | "\n"; 53 | static const char *loc_format = loc_format_plain; 54 | 55 | void 56 | clog_init(const char *env_prefix) 57 | { 58 | static bool initialized = false; 59 | 60 | if (initialized) 61 | return; 62 | 63 | initialized = true; 64 | use_colors = isatty(fileno(stderr)); 65 | 66 | if (!env_prefix) 67 | env_prefix = "LOG"; 68 | 69 | unsigned env_prefix_len = strlen(env_prefix); 70 | char varname[env_prefix_len + 16]; 71 | memcpy(varname, env_prefix, env_prefix_len); 72 | varname[env_prefix_len] = '_'; 73 | 74 | for (unsigned i = 0; i < sizeof (env_opts) / sizeof (env_opts[0]); i++) { 75 | strcpy(varname + env_prefix_len + 1, env_opts[i].name); 76 | const char *value = getenv(varname); 77 | if (value) 78 | *env_opts[i].data = (*value != '\0' && *value != '0'); 79 | } 80 | 81 | if (use_colors) { 82 | names = names_colored; 83 | loc_format = loc_format_colored; 84 | } 85 | } 86 | 87 | 88 | void 89 | clog_format(enum clog_level level, 90 | const char *file, 91 | unsigned line, 92 | const char *func, 93 | const char *fmt, 94 | ...) 95 | { 96 | fputs(names[level], stderr); 97 | 98 | va_list args; 99 | va_start(args, fmt); 100 | vfprintf(stderr, fmt, args); 101 | va_end(args); 102 | 103 | fprintf(stderr, loc_format, file, line, func); 104 | 105 | if ((level == CLOG_LEVEL_WARNING && clog_fatal_warnings) || 106 | (level == CLOG_LEVEL_ERROR && clog_fatal_errors)) 107 | { 108 | fflush(stderr); 109 | abort(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /deps/dbuf/dbuf.c: -------------------------------------------------------------------------------- 1 | /* 2 | * dbuf.c 3 | * Copyright (C) 2020 Adrian Perez de Castro 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #include "dbuf.h" 9 | #include 10 | #include 11 | #include 12 | 13 | enum { 14 | CHUNKSIZE = 512, /* bytes */ 15 | }; 16 | 17 | /* Wraps malloc(), realloc(), and free(). */ 18 | void* 19 | mrealloc(void *ptr, size_t size) 20 | { 21 | if (size) { 22 | ptr = ptr ? realloc(ptr, size) : malloc(size); 23 | } else if (ptr) { 24 | free(ptr); 25 | ptr = NULL; 26 | } 27 | return ptr; 28 | } 29 | 30 | /* Reallocates a buffer and sets the .alloc member. */ 31 | static inline void 32 | brealloc(struct dbuf *b, size_t size) 33 | { 34 | if (size) { 35 | const size_t new_size = CHUNKSIZE * ((size / CHUNKSIZE) + 1) + 1; 36 | assert(new_size >= size); 37 | if (new_size != b->alloc) 38 | b->data = mrealloc(b->data, (b->alloc = new_size)); 39 | } else if (b->data) { 40 | free(b->data); 41 | b->data = NULL; 42 | b->alloc = 0; 43 | } else { 44 | assert(b->alloc == 0); 45 | } 46 | } 47 | 48 | /* Calls brealloc() and sets the .size member. */ 49 | static inline void 50 | bresize(struct dbuf *b, size_t size) 51 | { 52 | brealloc(b, size); 53 | b->size = size; 54 | } 55 | 56 | struct dbuf* 57 | dbuf_new(size_t prealloc) 58 | { 59 | struct dbuf *b = mrealloc(NULL, sizeof(struct dbuf)); 60 | *b = DBUF_INIT; 61 | bresize(b, prealloc); 62 | return b; 63 | } 64 | 65 | void 66 | dbuf_free(struct dbuf *b) 67 | { 68 | assert(b); 69 | dbuf_clear(b); 70 | mrealloc(b, 0); 71 | } 72 | 73 | void 74 | dbuf_resize(struct dbuf *b, size_t size) 75 | { 76 | assert(b); 77 | bresize(b, size); 78 | } 79 | 80 | void 81 | dbuf_clear(struct dbuf *b) 82 | { 83 | assert(b); 84 | bresize(b, 0); 85 | } 86 | 87 | void 88 | dbuf_addmem(struct dbuf *b, const void *data, size_t size) 89 | { 90 | assert(b); 91 | assert(data); 92 | 93 | const size_t bsize = b->size; 94 | bresize(b, bsize + size); 95 | memcpy(b->data + bsize, data, size); 96 | } 97 | 98 | void 99 | dbuf_addstr(struct dbuf *b, const char *s) 100 | { 101 | assert(b); 102 | assert(s); 103 | 104 | const size_t bsize = b->size; 105 | const size_t slen = strlen(s); 106 | bresize(b, bsize + slen); 107 | memcpy(b->data + bsize, s, slen); 108 | } 109 | 110 | void 111 | dbuf_addfmtv(struct dbuf *b, const char *format, va_list args) 112 | { 113 | assert(b); 114 | assert(format); 115 | 116 | va_list saved; 117 | va_copy(saved, args); 118 | 119 | const size_t available = (b->alloc - b->size); 120 | const size_t needed = available 121 | ? vsnprintf((char*) b->data + b->size, available - 1, format, args) 122 | : vsnprintf(NULL, 0, format, args); 123 | 124 | if (needed >= available) { 125 | brealloc(b, b->alloc + needed); 126 | vsnprintf((char*) b->data + b->size, b->alloc - 1, format, saved); 127 | } 128 | 129 | b->size += needed; 130 | va_end(saved); 131 | } 132 | 133 | char* 134 | dbuf_str(struct dbuf *b) 135 | { 136 | assert(b); 137 | 138 | if (!b->alloc) 139 | brealloc(b, 1); 140 | 141 | b->data[b->size] = '\0'; 142 | return (char*) b->data; 143 | } 144 | -------------------------------------------------------------------------------- /deps/dbuf/dbuf.h: -------------------------------------------------------------------------------- 1 | /* 2 | * dbuf.h 3 | * Copyright (C) 2020 Adrian Perez de Castro 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #ifndef DBUF_H 9 | #define DBUF_H 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #if !(defined(__GNUC__) && __GNUC__ >= 3) && !defined(__attribute__) 18 | # define __attribute__(dummy) 19 | #endif 20 | 21 | struct dbuf { 22 | uint8_t *data; 23 | size_t size; 24 | size_t alloc; 25 | }; 26 | 27 | #define DBUF_INIT ((struct dbuf) { .data = NULL, .size = 0, .alloc = 0 }) 28 | 29 | struct dbuf* dbuf_new(size_t prealloc) 30 | __attribute__((warn_unused_result)); 31 | 32 | void dbuf_free(struct dbuf*) 33 | __attribute__((nonnull(1))); 34 | 35 | void dbuf_resize(struct dbuf*, size_t) 36 | __attribute__((nonnull(1))); 37 | 38 | void dbuf_clear(struct dbuf*) 39 | __attribute__((nonnull(1))); 40 | 41 | void dbuf_addmem(struct dbuf*, const void*, size_t) 42 | __attribute__((nonnull(1, 2))); 43 | 44 | void dbuf_addstr(struct dbuf*, const char*) 45 | __attribute__((nonnull(1, 2))); 46 | 47 | void dbuf_addfmtv(struct dbuf*, const char*, va_list) 48 | __attribute__((nonnull(1, 2))); 49 | 50 | char* dbuf_str(struct dbuf*) 51 | __attribute__((warn_unused_result)) 52 | __attribute__((nonnull(1))); 53 | 54 | 55 | static inline size_t dbuf_size(const struct dbuf*) 56 | __attribute__((warn_unused_result)) 57 | __attribute__((nonnull(1))); 58 | 59 | size_t 60 | dbuf_size(const struct dbuf *b) 61 | { 62 | assert(b); 63 | return b->size; 64 | } 65 | 66 | 67 | static inline uint8_t* dbuf_data(struct dbuf*) 68 | __attribute__((warn_unused_result)) 69 | __attribute__((nonnull(1))); 70 | 71 | uint8_t* 72 | dbuf_data(struct dbuf *b) 73 | { 74 | assert(b); 75 | return b->data; 76 | } 77 | 78 | 79 | static inline const uint8_t* dbuf_cdata(const struct dbuf*) 80 | __attribute__((warn_unused_result)) 81 | __attribute__((nonnull(1))); 82 | 83 | const uint8_t* dbuf_cdata(const struct dbuf *b) 84 | { 85 | assert(b); 86 | return b->data; 87 | } 88 | 89 | 90 | static inline bool dbuf_empty(const struct dbuf*) 91 | __attribute__((warn_unused_result)) 92 | __attribute__((nonnull(1))); 93 | 94 | bool 95 | dbuf_empty(const struct dbuf *b) 96 | { 97 | assert(b); 98 | return b->size == 0; 99 | } 100 | 101 | 102 | static inline void dbuf_addch(struct dbuf*, char) 103 | __attribute__((nonnull(1))); 104 | 105 | void 106 | dbuf_addch(struct dbuf *b, char c) 107 | { 108 | assert(b); 109 | dbuf_addmem(b, &c, 1); 110 | } 111 | 112 | 113 | static inline void dbuf_addbuf(struct dbuf*, const struct dbuf*) 114 | __attribute__((nonnull(1, 2))); 115 | 116 | void 117 | dbuf_addbuf(struct dbuf *b, const struct dbuf *o) 118 | { 119 | assert(b); 120 | assert(o); 121 | 122 | dbuf_addmem(b, dbuf_cdata(o), dbuf_size(o)); 123 | } 124 | 125 | static inline void dbuf_addfmt(struct dbuf*, const char*, ...) 126 | __attribute__((nonnull(1, 2))); 127 | 128 | void 129 | dbuf_addfmt(struct dbuf *b, const char *format, ...) 130 | { 131 | assert(b); 132 | assert(format); 133 | 134 | va_list args; 135 | va_start(args, format); 136 | dbuf_addfmtv(b, format, args); 137 | va_end(args); 138 | } 139 | 140 | #endif /* !DBUF_H */ 141 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MULTICALL = 1 2 | CFLAGS = -Os -g -Wall -W 3 | CFLAGS += -std=c2x 4 | PREFIX = /usr/local 5 | RST2MAN = rst2man 6 | RM = rm -f 7 | 8 | APPLETS = denv dlog drlog dslog 9 | 10 | O = deps/cflag/cflag.o deps/clog/clog.o deps/dbuf/dbuf.o \ 11 | conf.o task.o multicall.o util.o 12 | D = $(O:.o=.d) dmon.d nofork.d setunbuf.d $(APPLETS:=.d) 13 | 14 | all: all-multicall-$(MULTICALL) 15 | 16 | all-multicall-1: 17 | @$(MAKE) programs A='$(APPLETS)' 18 | 19 | all-multicall-0: 20 | @$(MAKE) programs P='$(APPLETS)' 21 | 22 | programs: dmon $(APPLETS) 23 | 24 | .PHONY: all-multicall-0 all-multicall-1 programs 25 | 26 | .c.o: CFLAGS := $(CFLAGS) 27 | setunbuf.o: CFLAGS := $(CFLAGS) -fPIC 28 | 29 | .c.o: 30 | $(CC) -DMULTICALL=$(MULTICALL) $(CFLAGS) -MMD -MF $(@:.o=.d) -c -o $@ $< 31 | 32 | .SUFFIXES: .c .o 33 | 34 | -include $D 35 | 36 | libdmon.a: $O $(A:=.o) 37 | $(AR) rcs $@ $? 38 | 39 | dmon $P: dmon.o $(P:=.o) libdmon.a 40 | $(CC) $(LDFLAGS) -o $@ $@.o libdmon.a $(LDLIBS) 41 | 42 | nofork: libnofork.so 43 | 44 | libnofork.so: nofork.c util.c 45 | $(CC) $(LDFLAGS) -fPIC -shared -o $@ nofork.c util.c $(LDLIBS) 46 | 47 | setunbuf: libsetunbuf.so 48 | 49 | libsetunbuf.so: setunbuf.c 50 | $(CC) $(LDFLAGS) -fPIC -shared -o $@ setunbuf.c $(LDLIBS) 51 | 52 | .PHONY: nofork setunbuf 53 | 54 | $(A:=-symlink): $A 55 | 56 | $A: dmon 57 | ln -sf dmon $@ 58 | 59 | .PHONY: $(A:=-symlink) 60 | 61 | man: denv.8 dmon.8 dlog.8 dslog.8 drlog.8 62 | 63 | .rst.8: 64 | $(RST2MAN) $< $@ 65 | 66 | .PHONY: man 67 | .SUFFIXES: .rst .8 68 | 69 | clean: 70 | $(RM) dmon denv dlog dslog drlog libdmon.a dmon.o denv.o dlog.o dslog.o drlog.o nofork.o libnofork.so setunbuf.o libsetunbuf.so $O 71 | 72 | mrproper: clean 73 | $(RM) $D 74 | 75 | .PHONY: clean mrproper 76 | 77 | install-all: install-all-multicall-$(MULTICALL) 78 | 79 | install-all-multicall-1: install-common 80 | ln -sf dmon $(DESTDIR)$(PREFIX)/bin/denv 81 | ln -sf dmon $(DESTDIR)$(PREFIX)/bin/dlog 82 | ln -sf dmon $(DESTDIR)$(PREFIX)/bin/drlog 83 | ln -sf dmon $(DESTDIR)$(PREFIX)/bin/dslog 84 | 85 | install-all-multicall-0: install-common 86 | install -m 755 $(APPLETS) $(DESTDIR)$(PREFIX)/bin 87 | 88 | install-common: 89 | install -d $(DESTDIR)$(PREFIX)/share/man/man8 90 | install -m 644 denv.8 dmon.8 dlog.8 dslog.8 drlog.8 \ 91 | $(DESTDIR)$(PREFIX)/share/man/man8 92 | install -d $(DESTDIR)$(PREFIX)/bin 93 | install -m 755 dmon $(DESTDIR)$(PREFIX)/bin 94 | 95 | .PHONY: install-common install-all-multicall-0 install-all-multicall-0 96 | 97 | install: install-multicall-$(MULTICALL) 98 | 99 | install-multicall-1: all-multicall-1 100 | @$(MAKE) install-all A='$(APPLETS)' 101 | 102 | install-multicall-0: all-multicall-0 103 | @$(MAKE) install-all P='$(APPLETS)' 104 | 105 | .PHONY: install install-multicall-0 install-multicall-1 106 | 107 | dist: 108 | @$(MAKE) dist-files VERSION=$$(jq -r .version package.json) 109 | 110 | dist-files: \ 111 | dmon-$(VERSION).tar.xz \ 112 | dmon-$(VERSION).tar.xz.asc \ 113 | dmon-$(VERSION).tar.xz.sha512 114 | 115 | dmon-$(VERSION).tar.xz: 116 | git archive --format=tar --prefix=dmon-$(VERSION)/ v$(VERSION) | xz -9c > $@ 117 | 118 | dmon-$(VERSION).tar.xz.asc: dmon-$(VERSION).tar.xz 119 | $(RM) $@ 120 | gpg --armor --detach-sign --output=$@ dmon-$(VERSION).tar.xz 121 | 122 | dmon-$(VERSION).tar.xz.sha512: dmon-$(VERSION).tar.xz 123 | sha512sum --tag dmon-$(VERSION).tar.xz > $@ 124 | 125 | .PHONY: dist dist-files 126 | -------------------------------------------------------------------------------- /drlog.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | drlog 3 | ======= 4 | 5 | ---------------------------------------------------------- 6 | Read lines from stdin and append them to auto-rotated logs 7 | ---------------------------------------------------------- 8 | 9 | :Author: Adrian Perez 10 | :Manual section: 8 11 | 12 | SYNOPSIS 13 | ======== 14 | 15 | ``drlog [options] directory`` 16 | 17 | 18 | DESCRIPTION 19 | =========== 20 | 21 | ``drlog`` will read its standard input, distributing it as output in a set 22 | of named ``log-YYYY-mm-dd-HH:MM:SS`` and a ``current`` file. Output is always 23 | appended to ``current``, but when user-defined maximum file size (``-s``) or 24 | file usage time (``-t``) it will be renamed with a timestamp in its file name, 25 | a new ``current`` file will be opened and, if there are stored more than 26 | a number of timestamped files (``-m``) old ones will be deleted. 27 | 28 | The names of the files are designed to make them appear time-ordered in 29 | output from commands like `ls(1)`. Also, the ``current`` file will appear at 30 | the top of file listings. 31 | 32 | If ``drlog`` receives a *TERM* signal, it will read and process data until 33 | the next newline and then exit, leaving *stdin* at the first byte of data it 34 | has not yet precessed. 35 | 36 | Upon a ``HUP`` signal, ``drlog`` will close and re-open the ``current`` 37 | log file, just in case you want rotate logs using an external tool, though 38 | using it that way is unsupported. 39 | 40 | 41 | USAGE 42 | ===== 43 | 44 | Command line options: 45 | 46 | -m NUMBER, --max-files NUMBER 47 | Maximum amount of maintained log files. When ``drlog`` sees 48 | more than *NUMBER* log files in the log *directory* it will 49 | remove the oldest log file. 50 | 51 | -T TIME, --max-time TIME 52 | Maximum number of time to use a log file. Once ``drlog`` spends 53 | more than *TIME* using a log file it will start writing to a new 54 | one. Suffixes *m* (minutes), *h* (hours), *d* (days), *w* (weeks), 55 | *M* (months) and *y* (years) may be used after the number. If no 56 | suffix is given, it is assummed that *TIME* is in seconds. 57 | 58 | -s SIZE, --max-size SIZE 59 | Maximum size of each log file. When a log file grows over 60 | *SIZE* then ``drlog`` will rotate logs and open a new one. 61 | Suffixes *k* (kilobytes), *m* (megabytes) and *g* (gigabytes) 62 | may be used after the number. If no suffix is given, it is 63 | assumed that ``SIZE`` is in bytes. 64 | 65 | -i NUMBER, --input-fd NUMBER 66 | Use file descriptor ``NUMBER`` to read input. By default the 67 | standard input descriptor (number ``0``) is used. 68 | 69 | -b, --buffered 70 | Buffered operation. If enabled, calls to `fsync(2)` will be 71 | avoided. This improves performance, but may cause messages to 72 | be lost. 73 | 74 | -t, --timestamp 75 | Prepend a timestamp to each line. The timestamp format 76 | is ``YYYY-mm-dd/HH:MM:SS``, following that of rotated log files. 77 | It is easy to parse and sort. And human-readable, too. 78 | 79 | -e, --skip-empty 80 | Ignore empty input lines. An empty line is one that does not 81 | contain any characters; a line which contains whitespace is 82 | **not** considered empty. 83 | 84 | 85 | SEE ALSO 86 | ======== 87 | 88 | `multilog(8)`, `supervise(8)`, `svc(8)`, `dslog(8)`, `dlog(8)`, `dmon(8)` 89 | 90 | -------------------------------------------------------------------------------- /denv.rst: -------------------------------------------------------------------------------- 1 | ====== 2 | denv 3 | ====== 4 | 5 | --------------------------------------- 6 | Run a command in a modified environment 7 | --------------------------------------- 8 | 9 | :Author: Adrian Perez 10 | :Manual section: 8 11 | 12 | SYNOPSIS 13 | ======== 14 | 15 | ``denv [options] command [command-options]`` 16 | 17 | ``envdir d child`` 18 | 19 | The second form mimics the command line interface of the `envdir(8)` 20 | tool included in DJB's daemontools package, see CAVEATS_ for the 21 | differences. 22 | 23 | 24 | DESCRIPTION 25 | =========== 26 | 27 | The ``dlog`` program modifies the environment variables as specified by 28 | the supplied ``options`` and immediately executes the given ``command``. 29 | 30 | By default the environment will be empty. Variables from the parent process 31 | may be incorporated using the ``--inherit-env`` and ``--inherit`` options, 32 | and new variables added or removed using the ``--environ``, ``--direnv`` 33 | and ``--file`` options. 34 | 35 | 36 | USAGE 37 | ===== 38 | 39 | Command line options: 40 | 41 | -I, --inherit-env 42 | Inherit all the environment variables from the parent 43 | process that runs ``denv``. 44 | 45 | -i NAME, --inherit NAME 46 | Inherit and environment variable from the parent process 47 | that runs ``denv`` given its ``NAME``. This option may 48 | be used multiple times. 49 | 50 | -E NAME[=VALUE], --environ NAME[=VALUE] 51 | Define an environment variable with a given ``NAME``, and 52 | assign a ``VALUE`` to it. If the value is not specified, 53 | an existing variable is removed. 54 | 55 | -d PATH, --envdir PATH 56 | Read each file from the directory at ``PATH``, assigning 57 | as variables named after the files, with their values 58 | being the first line of contents. Trailing spaces are 59 | removed from the values. If a file (or its first line) 60 | is empty, the variable named after the file is removed. 61 | Only regular files are taken into account, and in 62 | particular symbolic links are *not* followed. 63 | 64 | -f PATH, --file PATH 65 | Read lines from the file at ``PATH``, each one containing 66 | a ``NAME=[VALUE]`` environment variable assignment. Both 67 | leading and trailing whitespace are removed from the line. 68 | Each ``NAME`` must be a valid variable name. Empty lines 69 | and lines beginning with the comment character ``#`` are 70 | ignored. If a ``VALUE`` is omitted, then the variable is 71 | removed from the environment. The file format is loosely 72 | based on `environment.d(5)`. 73 | 74 | The ``denv`` program is often used in concert with `dmon(8)` to setup 75 | the environment under which supervised programs run. 76 | 77 | 78 | EXIT CODES 79 | ========== 80 | 81 | ``denv`` exits with code **111** if it has trouble preparing the environment 82 | before executing the chained child process, or if the child process cannot 83 | be executed. Otherwise its exit code is that of the child process. 84 | 85 | 86 | SEE ALSO 87 | ======== 88 | 89 | `dmon(8)`, `envdir(8)`, `environment.d(5)` 90 | 91 | http://cr.yp.to/daemontools.html 92 | 93 | 94 | CAVEATS 95 | ======= 96 | 97 | The original `envdir(8)` program included in the daemontools suite converts 98 | null characters (``\0``) into new lines (``\n``). This behaviour is 99 | deliberately left unimplemented in ``denv``, even when it runs in ``envdir`` 100 | mode. 101 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | ## [Unreleased] 8 | 9 | ## [v0.6.0] - 2024-12-10 10 | ### Added 11 | - New `denv` utility, inspired by daemontools' `envdir`, which may be 12 | used to manipulate the environment before executing a chained child 13 | process. 14 | 15 | ### Fixed 16 | - Retry system calls that may return with `errno` set to `EINTR` when 17 | interrupted by signals to indicate they can be restarted from user space. 18 | - Remove leftover, useless `$E` format specifier when reporting errors 19 | during daemonization. 20 | 21 | ### Changed 22 | - The project is now built in C23 mode by default. In practice `-std=c2x` 23 | gets used to cover systems which may have slightly older compilers. 24 | 25 | ## [v0.5.1] - 2021-02-23 26 | ### Added 27 | - The `dlog`, `dslog`, and `drlog` tools now log empty input lines. The 28 | `--skip-empty`/`-e` command line option has been added to disable 29 | logging empty lines. 30 | - The `dmon` tool has gained a `--max-respawns`/`-m` command line option which 31 | can be used to specify how many times to respawn monitored processes 32 | before exiting. (Patch by Matt Schulte <>.) 33 | - New `libsetunbuf.so` helper which can be used to disable buffering of 34 | the standard output stream on arbitrary programs via `LD_PRELOAD`. 35 | (Patch by Matt Schulte <>.) 36 | 37 | ### Fixed 38 | - The exit status of monitored processes is now correctly propagated as 39 | exit code of `dmon` itself. 40 | - The `dlog`, `dslog`, and `drlog` tools will no longer exit unexpectedly 41 | when they receive an empty input line. (Patch by Matt Schulte 42 | <>.) 43 | 44 | ## [v0.5.0] - 2020-08-27 45 | ### Added 46 | - Support listing applets compiled into a multicall `dmon` binary when the 47 | `DMON_LIST_MULTICALL_APPLETS` environment variable is set and non-zero. 48 | 49 | ### Changed 50 | - GNU Make is not required anymore, the included `Makefile` now works with 51 | the BSD variant as well, and probably others. 52 | 53 | ## [v0.4.5] - 2018-08-05 54 | ### Fixed 55 | - Make it possible to build `dmon` with newer GCC versions. 56 | 57 | ## [v0.4.4] - 2016-10-30 58 | ### Fixed 59 | - Correctly forward signals to the log process when using `-S`/`--log-sigs`. 60 | 61 | ## [v0.4.3] - 2016-10-13 62 | ### Added 63 | - Allow setting the work directory with `-W`/`--work-dir` in the command line 64 | (or `work-dir` in a configuration file). 65 | 66 | ## [v0.4.2] - 2012-01-07 67 | ### Added 68 | - Manual pages now also include the long command line options. 69 | - `dlog` can now be instructed to prefix every log message with a given string, 70 | using the `-p`/`--prefix` command line option. 71 | - `dlog`, `drlog`, and `dslog` have gained the `-i`/`--input-fd` command line 72 | option, which allows reading log messages from file descriptors different from 73 | the standard input. 74 | 75 | ### Fixed 76 | - `dlog` and `drlog` now handle the `INT`, `TERM`, and `HUP` signals gracefully. 77 | 78 | [Unreleased]: https://github.com/aperezdc/dmon/compare/v0.6.0...HEAD 79 | [v0.6.0]: https://github.com/aperezdc/dmon/compare/v0.5.1...v0.6.0 80 | [v0.5.1]: https://github.com/aperezdc/dmon/compare/v0.5.0...v0.5.1 81 | [v0.5.0]: https://github.com/aperezdc/dmon/compare/v0.4.5...v0.5.0 82 | [v0.4.5]: https://github.com/aperezdc/dmon/compare/v0.4.4...v0.4.5 83 | [v0.4.4]: https://github.com/aperezdc/dmon/compare/v0.4.3...v0.4.4 84 | [v0.4.3]: https://github.com/aperezdc/dmon/compare/v0.4.2...v0.4.3 85 | [v0.4.2]: https://github.com/aperezdc/dmon/compare/v0.4.1...v0.4.2 86 | -------------------------------------------------------------------------------- /drlog.8: -------------------------------------------------------------------------------- 1 | .\" Man page generated from reStructuredText. 2 | . 3 | .TH DRLOG 8 "" "" "" 4 | .SH NAME 5 | drlog \- Read lines from stdin and append them to auto-rotated logs 6 | . 7 | .nr rst2man-indent-level 0 8 | . 9 | .de1 rstReportMargin 10 | \\$1 \\n[an-margin] 11 | level \\n[rst2man-indent-level] 12 | level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] 13 | - 14 | \\n[rst2man-indent0] 15 | \\n[rst2man-indent1] 16 | \\n[rst2man-indent2] 17 | .. 18 | .de1 INDENT 19 | .\" .rstReportMargin pre: 20 | . RS \\$1 21 | . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] 22 | . nr rst2man-indent-level +1 23 | .\" .rstReportMargin post: 24 | .. 25 | .de UNINDENT 26 | . RE 27 | .\" indent \\n[an-margin] 28 | .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] 29 | .nr rst2man-indent-level -1 30 | .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] 31 | .in \\n[rst2man-indent\\n[rst2man-indent-level]]u 32 | .. 33 | .SH SYNOPSIS 34 | .sp 35 | \fBdrlog [options] directory\fP 36 | .SH DESCRIPTION 37 | .sp 38 | \fBdrlog\fP will read its standard input, distributing it as output in a set 39 | of named \fBlog\-YYYY\-mm\-dd\-HH:MM:SS\fP and a \fBcurrent\fP file. Output is always 40 | appended to \fBcurrent\fP, but when user\-defined maximum file size (\fB\-s\fP) or 41 | file usage time (\fB\-t\fP) it will be renamed with a timestamp in its file name, 42 | a new \fBcurrent\fP file will be opened and, if there are stored more than 43 | a number of timestamped files (\fB\-m\fP) old ones will be deleted. 44 | .sp 45 | The names of the files are designed to make them appear time\-ordered in 46 | output from commands like \fIls(1)\fP\&. Also, the \fBcurrent\fP file will appear at 47 | the top of file listings. 48 | .sp 49 | If \fBdrlog\fP receives a \fITERM\fP signal, it will read and process data until 50 | the next newline and then exit, leaving \fIstdin\fP at the first byte of data it 51 | has not yet precessed. 52 | .sp 53 | Upon a \fBHUP\fP signal, \fBdrlog\fP will close and re\-open the \fBcurrent\fP 54 | log file, just in case you want rotate logs using an external tool, though 55 | using it that way is unsupported. 56 | .SH USAGE 57 | .sp 58 | Command line options: 59 | .INDENT 0.0 60 | .TP 61 | .BI \-m \ NUMBER\fR,\fB \ \-\-max\-files \ NUMBER 62 | Maximum amount of maintained log files. When \fBdrlog\fP sees 63 | more than \fINUMBER\fP log files in the log \fIdirectory\fP it will 64 | remove the oldest log file. 65 | .TP 66 | .BI \-T \ TIME\fR,\fB \ \-\-max\-time \ TIME 67 | Maximum number of time to use a log file. Once \fBdrlog\fP spends 68 | more than \fITIME\fP using a log file it will start writing to a new 69 | one. Suffixes \fIm\fP (minutes), \fIh\fP (hours), \fId\fP (days), \fIw\fP (weeks), 70 | \fIM\fP (months) and \fIy\fP (years) may be used after the number. If no 71 | suffix is given, it is assummed that \fITIME\fP is in seconds. 72 | .TP 73 | .BI \-s \ SIZE\fR,\fB \ \-\-max\-size \ SIZE 74 | Maximum size of each log file. When a log file grows over 75 | \fISIZE\fP then \fBdrlog\fP will rotate logs and open a new one. 76 | Suffixes \fIk\fP (kilobytes), \fIm\fP (megabytes) and \fIg\fP (gigabytes) 77 | may be used after the number. If no suffix is given, it is 78 | assumed that \fBSIZE\fP is in bytes. 79 | .TP 80 | .BI \-i \ NUMBER\fR,\fB \ \-\-input\-fd \ NUMBER 81 | Use file descriptor \fBNUMBER\fP to read input. By default the 82 | standard input descriptor (number \fB0\fP) is used. 83 | .TP 84 | .B \-b\fP,\fB \-\-buffered 85 | Buffered operation. If enabled, calls to \fIfsync(2)\fP will be 86 | avoided. This improves performance, but may cause messages to 87 | be lost. 88 | .TP 89 | .B \-t\fP,\fB \-\-timestamp 90 | Prepend a timestamp to each line. The timestamp format 91 | is \fBYYYY\-mm\-dd/HH:MM:SS\fP, following that of rotated log files. 92 | It is easy to parse and sort. And human\-readable, too. 93 | .TP 94 | .B \-e\fP,\fB \-\-skip\-empty 95 | Ignore empty input lines. An empty line is one that does not 96 | contain any characters; a line which contains whitespace is 97 | \fBnot\fP considered empty. 98 | .UNINDENT 99 | .SH SEE ALSO 100 | .sp 101 | \fImultilog(8)\fP, \fIsupervise(8)\fP, \fIsvc(8)\fP, \fIdslog(8)\fP, \fIdlog(8)\fP, \fIdmon(8)\fP 102 | .SH AUTHOR 103 | Adrian Perez 104 | .\" Generated by docutils manpage writer. 105 | . 106 | -------------------------------------------------------------------------------- /denv.8: -------------------------------------------------------------------------------- 1 | .\" Man page generated from reStructuredText. 2 | . 3 | . 4 | .nr rst2man-indent-level 0 5 | . 6 | .de1 rstReportMargin 7 | \\$1 \\n[an-margin] 8 | level \\n[rst2man-indent-level] 9 | level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] 10 | - 11 | \\n[rst2man-indent0] 12 | \\n[rst2man-indent1] 13 | \\n[rst2man-indent2] 14 | .. 15 | .de1 INDENT 16 | .\" .rstReportMargin pre: 17 | . RS \\$1 18 | . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] 19 | . nr rst2man-indent-level +1 20 | .\" .rstReportMargin post: 21 | .. 22 | .de UNINDENT 23 | . RE 24 | .\" indent \\n[an-margin] 25 | .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] 26 | .nr rst2man-indent-level -1 27 | .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] 28 | .in \\n[rst2man-indent\\n[rst2man-indent-level]]u 29 | .. 30 | .TH "DENV" "8" "" "" 31 | .SH NAME 32 | denv \- Run a command in a modified environment 33 | .SH SYNOPSIS 34 | .sp 35 | \fBdenv [options] command [command\-options]\fP 36 | .sp 37 | \fBenvdir d child\fP 38 | .sp 39 | The second form mimics the command line interface of the \fIenvdir(8)\fP 40 | tool included in DJB\(aqs daemontools package, see CAVEATS for the 41 | differences. 42 | .SH DESCRIPTION 43 | .sp 44 | The \fBdlog\fP program modifies the environment variables as specified by 45 | the supplied \fBoptions\fP and immediately executes the given \fBcommand\fP\&. 46 | .sp 47 | By default the environment will be empty. Variables from the parent process 48 | may be incorporated using the \fB\-\-inherit\-env\fP and \fB\-\-inherit\fP options, 49 | and new variables added or removed using the \fB\-\-environ\fP, \fB\-\-direnv\fP 50 | and \fB\-\-file\fP options. 51 | .SH USAGE 52 | .sp 53 | Command line options: 54 | .INDENT 0.0 55 | .TP 56 | .B \-I\fP,\fB \-\-inherit\-env 57 | Inherit all the environment variables from the parent 58 | process that runs \fBdenv\fP\&. 59 | .TP 60 | .BI \-i \ NAME\fR,\fB \ \-\-inherit \ NAME 61 | Inherit and environment variable from the parent process 62 | that runs \fBdenv\fP given its \fBNAME\fP\&. This option may 63 | be used multiple times. 64 | .UNINDENT 65 | .INDENT 0.0 66 | .TP 67 | .B \-E NAME[=VALUE], \-\-environ NAME[=VALUE] 68 | Define an environment variable with a given \fBNAME\fP, and 69 | assign a \fBVALUE\fP to it. If the value is not specified, 70 | an existing variable is removed. 71 | .UNINDENT 72 | .INDENT 0.0 73 | .TP 74 | .BI \-d \ PATH\fR,\fB \ \-\-envdir \ PATH 75 | Read each file from the directory at \fBPATH\fP, assigning 76 | as variables named after the files, with their values 77 | being the first line of contents. Trailing spaces are 78 | removed from the values. If a file (or its first line) 79 | is empty, the variable named after the file is removed. 80 | Only regular files are taken into account, and in 81 | particular symbolic links are \fInot\fP followed. 82 | .TP 83 | .BI \-f \ PATH\fR,\fB \ \-\-file \ PATH 84 | Read lines from the file at \fBPATH\fP, each one containing 85 | a \fBNAME=[VALUE]\fP environment variable assignment. Both 86 | leading and trailing whitespace are removed from the line. 87 | Each \fBNAME\fP must be a valid variable name. Empty lines 88 | and lines beginning with the comment character \fB#\fP are 89 | ignored. If a \fBVALUE\fP is omitted, then the variable is 90 | removed from the environment. The file format is loosely 91 | based on \fIenvironment.d(5)\fP\&. 92 | .UNINDENT 93 | .sp 94 | The \fBdenv\fP program is often used in concert with \fIdmon(8)\fP to setup 95 | the environment under which supervised programs run. 96 | .SH EXIT CODES 97 | .sp 98 | \fBdenv\fP exits with code \fB111\fP if it has trouble preparing the environment 99 | before executing the chained child process, or if the child process cannot 100 | be executed. Otherwise its exit code is that of the child process. 101 | .SH SEE ALSO 102 | .sp 103 | \fIdmon(8)\fP, \fIenvdir(8)\fP, \fIenvironment.d(5)\fP 104 | .sp 105 | 106 | .SH CAVEATS 107 | .sp 108 | The original \fIenvdir(8)\fP program included in the daemontools suite converts 109 | null characters (\fB\e0\fP) into new lines (\fB\en\fP). This behaviour is 110 | deliberately left unimplemented in \fBdenv\fP, even when it runs in \fBenvdir\fP 111 | mode. 112 | .SH AUTHOR 113 | Adrian Perez 114 | .\" Generated by docutils manpage writer. 115 | . 116 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * util.h 3 | * Copyright (C) 2010-2014 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #ifndef __util_h__ 9 | #define __util_h__ 10 | 11 | #if !(defined(__GNUC__) && __GNUC__ >= 3) 12 | # define __attribute__(dummy) 13 | #endif 14 | 15 | #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L 16 | # define NODISCARD [[nodiscard]] 17 | # define NORETURN [[noreturn]] 18 | #else 19 | # define NODISCARD __attribute__((warn_unused_result)) 20 | # if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L 21 | # define NORETURN _Noreturn 22 | # else 23 | # define NORETURN __attribute__((noreturn)) 24 | # endif 25 | #endif 26 | 27 | #include "deps/dbuf/dbuf.h" 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #ifdef __GLIBC_PREREQ 34 | # if __GLIBC_PREREQ(2, 29) 35 | # define USE_LIBC_REALLOCARRAY 1 36 | # endif 37 | #endif 38 | 39 | #ifndef USE_LIBC_REALLOCARRAY 40 | # define USE_LIBC_REALLOCARRAY 0 41 | #endif 42 | 43 | #if !USE_LIBC_REALLOCARRAY 44 | void* util_reallocarray(void*, size_t, size_t); 45 | #define reallocarray util_reallocarray 46 | #endif /* !LIBC_HAS_REALLOCARRAY */ 47 | 48 | #define DMON_GID_COUNT 76 49 | 50 | #define ERRSTR (strerror (errno)) 51 | 52 | struct uidgid_s { 53 | uid_t uid; 54 | gid_t gid; 55 | unsigned ngid; 56 | gid_t gids[DMON_GID_COUNT]; 57 | }; 58 | typedef struct uidgid_s uidgid_t; 59 | 60 | #define UIDGID { 0, 0, 0, {0} } 61 | 62 | /* Forward declaration */ 63 | struct sigaction; 64 | 65 | /* uid[:gid[:gid...]] */ 66 | int parse_uidgids (char*, uidgid_t*); 67 | 68 | int name_to_uidgid (const char*, uid_t*, gid_t*); 69 | int name_to_gid (const char*, gid_t*); 70 | 71 | int safe_close(int fd); 72 | int safe_fsync(int fd); 73 | NODISCARD int safe_openat(int fd, const char*, int flags); 74 | NODISCARD int safe_openatm(int fd, const char*, int flags, mode_t); 75 | NODISCARD ssize_t safe_read(int fd, void*, size_t); 76 | NODISCARD ssize_t safe_writev(int fd, const struct iovec*, int iovcnt); 77 | NODISCARD FILE* safe_fdopen(int fd, const char *mode); 78 | void safe_sleep (unsigned); 79 | void safe_sigaction (const char*, int, struct sigaction*); 80 | void safe_setrlimit (int what, long value); 81 | 82 | void fd_cloexec (int); 83 | void become_daemon (void); 84 | int interruptible_sleep (unsigned); 85 | const char* limit_name (int); 86 | 87 | int parse_limit_arg (const char *str, int *what, long *value); 88 | 89 | bool time_period_to_seconds (const char *str, 90 | unsigned long long *result); 91 | bool storage_size_to_bytes (const char *str, 92 | unsigned long long *result); 93 | 94 | int replace_args_string (const char *str, 95 | int *argc, 96 | char ***argv); 97 | 98 | void replace_args_shift(unsigned amount, 99 | int *pargc, 100 | char ***pargv); 101 | 102 | ssize_t freaduntil(int fd, 103 | struct dbuf *buffer, 104 | struct dbuf *overflow, 105 | int delimiter, 106 | size_t readbytes); 107 | 108 | static inline ssize_t 109 | freadline(int fd, 110 | struct dbuf *buffer, 111 | struct dbuf *overflow, 112 | size_t readbytes) 113 | { 114 | return freaduntil(fd, buffer, overflow, '\n', readbytes); 115 | } 116 | 117 | NORETURN void errexit(int code, const char *format, ...) 118 | __attribute__((format (printf, 2, 3))); 119 | 120 | NORETURN void die(const char *format, ...) 121 | __attribute__((format (printf, 1, 2))); 122 | 123 | NORETURN void fatal(const char *format, ...) 124 | __attribute__((format (printf, 1, 2))); 125 | 126 | static inline struct iovec 127 | iov_from_data (void *data, size_t size) 128 | { 129 | return (struct iovec) { .iov_base = data, .iov_len = size }; 130 | } 131 | 132 | static inline struct iovec 133 | iov_from_buffer (struct dbuf *buffer) 134 | { 135 | return iov_from_data (dbuf_data(buffer), dbuf_size(buffer)); 136 | } 137 | 138 | static inline struct iovec 139 | iov_from_string (char *str) 140 | { 141 | return iov_from_data (str, strlen (str)); 142 | } 143 | 144 | #define iov_from_literal(_s) \ 145 | (iov_from_data ((_s), (sizeof (_s)) - 1)) 146 | 147 | #endif /* !__util_h__ */ 148 | 149 | /* vim: expandtab shiftwidth=4 tabstop=4 150 | */ 151 | -------------------------------------------------------------------------------- /task.c: -------------------------------------------------------------------------------- 1 | /* 2 | * task.c 3 | * Copyright (C) 2010-2024 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #ifndef __linux 9 | #define _BSD_SOURCE 10 | #endif 11 | 12 | #define _DEFAULT_SOURCE 13 | #define _POSIX_C_SOURCE 199309L 14 | 15 | #include "task.h" 16 | #include "deps/clog/clog.h" 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include /* setgroups() */ 26 | 27 | 28 | void 29 | task_start (task_t *task) 30 | { 31 | assert (task != NULL); 32 | 33 | time_t now = time (NULL); 34 | unsigned sleep_time = (difftime (now, task->started) > 1) ? 0 : 1; 35 | 36 | clog_debug("Last start %us ago, will wait for %us", 37 | (unsigned) difftime(now, task->started), sleep_time); 38 | memcpy (&task->started, &now, sizeof (time_t)); 39 | 40 | if ((task->pid = fork ()) < 0) 41 | die ("fork failed: %s\n", ERRSTR); 42 | 43 | task->action = A_NONE; 44 | 45 | /* We got a valid PID, return */ 46 | if (task->pid > 0) { 47 | clog_debug("Child pid = %i", task->pid); 48 | return; 49 | } 50 | 51 | /* 52 | * Sleep before exec'ing the child: this is needed to avoid performing 53 | * the classical "continued fork-exec without child reaping" DoS attack. 54 | * We do this here, as soon as the child has been forked. 55 | */ 56 | safe_sleep (sleep_time); 57 | 58 | /* Execute child */ 59 | if (task->write_fd >= 0) { 60 | clog_debug("Redirecting write_fd = %i -> %i", task->write_fd, STDOUT_FILENO); 61 | if (dup2 (task->write_fd, STDOUT_FILENO) < 0) { 62 | fprintf (stderr, "dup2() redirection failed: %s\n", ERRSTR); 63 | _exit (111); 64 | } 65 | } 66 | if (task->read_fd >= 0) { 67 | clog_debug("Redirecting read_fd = %i -> %i", task->read_fd, STDIN_FILENO); 68 | if (dup2 (task->read_fd, STDIN_FILENO) < 0) { 69 | fprintf (stderr, "dup2() redirection failed: %s\n", ERRSTR); 70 | _exit (111); 71 | } 72 | } 73 | 74 | if (task->redir_errfd) { 75 | clog_debug("Redirecting stderr -> stdout"); 76 | if (dup2 (STDOUT_FILENO, STDERR_FILENO) < 0) { 77 | fprintf (stderr, "dup2() failed for stderr: %s\n", ERRSTR); 78 | _exit (111); 79 | } 80 | } 81 | 82 | /* Groups must be changed first, while we have privileges */ 83 | if (task->user.gid > 0) { 84 | clog_debug("Set group id %i", task->pid); 85 | if (setgid (task->user.gid)) 86 | die ("could not set group id: %s\n", ERRSTR); 87 | } 88 | 89 | if (task->user.ngid > 0) { 90 | clog_debug("Calling setgroups (%u groups)\n", task->user.ngid); 91 | if (setgroups (task->user.ngid, task->user.gids)) 92 | die ("could not set additional groups: %s\n", ERRSTR); 93 | } 94 | 95 | /* Now drop privileges */ 96 | if (task->user.uid > 0) { 97 | clog_debug("Set user id %u", (unsigned) task->user.uid); 98 | if (setuid (task->user.uid)) 99 | die ("could not set user id: %s\n", ERRSTR); 100 | } 101 | 102 | execvp (task->argv[0], task->argv); 103 | _exit (111); 104 | } 105 | 106 | 107 | void 108 | task_signal_dispatch (task_t *task) 109 | { 110 | assert (task != NULL); 111 | 112 | if (task->signal == NO_SIGNAL) /* Invalid signal, nothing to do */ 113 | return; 114 | 115 | clog_debug("Signal %i to process %i", task->signal, task->pid); 116 | 117 | if (kill (task->pid, task->signal) < 0) { 118 | die ("cannot send signal %i to process %lu: %s\n", 119 | task->signal, (unsigned long) task->pid, ERRSTR); 120 | } 121 | task_signal_queue (task, NO_SIGNAL); 122 | } 123 | 124 | 125 | void 126 | task_signal (task_t *task, int signum) 127 | { 128 | assert (task != NULL); 129 | 130 | /* Dispatch pending signal first if needed */ 131 | task_signal_dispatch (task); 132 | 133 | /* Then send our own */ 134 | task_signal_queue (task, signum); 135 | task_signal_dispatch (task); 136 | } 137 | 138 | 139 | void 140 | task_action_dispatch (task_t *task) 141 | { 142 | assert (task != NULL); 143 | 144 | switch (task->action) { 145 | case A_NONE: /* Nothing to do */ 146 | return; 147 | case A_START: 148 | task_start (task); 149 | break; 150 | case A_STOP: 151 | task_action_queue (task, A_NONE); 152 | if (task->pid != NO_PID) { 153 | task_signal (task, SIGTERM); 154 | task_signal (task, SIGCONT); 155 | } 156 | break; 157 | case A_SIGNAL: 158 | task_action_queue (task, A_NONE); 159 | task_signal_dispatch (task); 160 | break; 161 | } 162 | } 163 | 164 | 165 | void 166 | task_action (task_t *task, action_t action) 167 | { 168 | assert (task != NULL); 169 | 170 | /* Dispatch pending action. */ 171 | task_action_dispatch (task); 172 | 173 | /* Send our own action. */ 174 | task_action_queue (task, action); 175 | task_action_dispatch (task); 176 | } 177 | 178 | 179 | /* vim: expandtab tabstop=4 shiftwidth=4 180 | */ 181 | -------------------------------------------------------------------------------- /dlog.c: -------------------------------------------------------------------------------- 1 | /* 2 | * dlog.c 3 | * Copyright (C) 2010-2020 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #define _POSIX_C_SOURCE 200809L 9 | 10 | #include "deps/cflag/cflag.h" 11 | #include "deps/clog/clog.h" 12 | #include "deps/dbuf/dbuf.h" 13 | #include "util.h" 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #if !(defined(MULTICALL) && MULTICALL) 25 | # define dlog_main main 26 | #endif /* MULTICALL */ 27 | 28 | #ifndef TSTAMP_FMT 29 | #define TSTAMP_FMT "%Y-%m-%d/%H:%M:%S" 30 | #endif /* !TSTAMP_FMT */ 31 | 32 | #ifndef TSTAMP_LEN 33 | #define TSTAMP_LEN (5 + 3 + 3 + 3 + 3 + 3) 34 | #endif /* !TSTAMP_LEN */ 35 | 36 | 37 | static bool timestamp = false; 38 | static bool skip_empty = false; 39 | static bool buffered = false; 40 | static char *prefix = NULL; 41 | static int log_fd = -1; 42 | static int in_fd = STDIN_FILENO; 43 | 44 | 45 | static const struct cflag dlog_options[] = { 46 | CFLAG(string, "prefix", 'p', &prefix, 47 | "Insert the given prefix string between timestamps and logged text."), 48 | CFLAG(int, "input-fd", 'i', &in_fd, 49 | "File descriptor to read input from (default: stdin)."), 50 | CFLAG(bool, "buffered", 'b', &buffered, 51 | "Buffered operation, do not use flush to disk after each line."), 52 | CFLAG(bool, "timestamp", 't', ×tamp, 53 | "Prepend a timestamp in YYYY-MM-DD/HH:MM:SS format to each line."), 54 | CFLAG(bool, "skip-empty", 'e', &skip_empty, 55 | "Ignore empty lines with no characters."), 56 | CFLAG_HELP, 57 | CFLAG_END 58 | }; 59 | 60 | 61 | static void 62 | handle_signal (int signum) 63 | { 64 | if (log_fd >= 0 && log_fd != STDOUT_FILENO && log_fd != STDERR_FILENO && !isatty(log_fd)) { 65 | if (safe_fsync(log_fd) == -1) 66 | clog_warning("Flushing log: %s", strerror(errno)); 67 | if (safe_close(log_fd) == -1) 68 | clog_warning("Closing log: %s", strerror(errno)); 69 | log_fd = -1; 70 | } 71 | 72 | /* 73 | * When handling HUP, we just sync and close the log file; in 74 | * other cases (INT, TERM) we have to exit gracefully, too. 75 | */ 76 | if (signum != SIGHUP) { 77 | exit (EXIT_SUCCESS); 78 | } 79 | } 80 | 81 | 82 | int 83 | dlog_main (int argc, char **argv) 84 | { 85 | clog_init(NULL); 86 | 87 | struct dbuf overflow = DBUF_INIT; 88 | struct dbuf linebuf = DBUF_INIT; 89 | char *env_opts = NULL; 90 | struct sigaction sa; 91 | 92 | if ((env_opts = getenv ("DLOG_OPTIONS")) != NULL) 93 | replace_args_string (env_opts, &argc, &argv); 94 | 95 | const char *argv0 = cflag_apply(dlog_options, "[options] [logfile-path]", &argc, &argv); 96 | 97 | if (!argc) { 98 | log_fd = STDOUT_FILENO; 99 | } else if (safe_close(STDOUT_FILENO) == -1) { 100 | clog_warning("Closing stdout: %s", strerror(errno)); 101 | } 102 | 103 | sigemptyset (&sa.sa_mask); 104 | sa.sa_flags = 0; 105 | 106 | sa.sa_handler = handle_signal; 107 | safe_sigaction ("HUP", SIGHUP, &sa); 108 | 109 | sa.sa_handler = handle_signal; 110 | safe_sigaction ("INT", SIGINT, &sa); 111 | 112 | sa.sa_handler = handle_signal; 113 | safe_sigaction ("TERM", SIGTERM, &sa); 114 | 115 | for (;;) { 116 | ssize_t bytes = freadline (in_fd, &linebuf, &overflow, 0); 117 | if (bytes == 0) 118 | break; /* EOF */ 119 | 120 | if (bytes < 0) 121 | die ("%s: error reading input: %s\n", argv0, ERRSTR); 122 | 123 | if (!skip_empty || dbuf_size(&linebuf) > 1) { 124 | struct iovec iov[5]; 125 | int n_iov = 0; 126 | 127 | char timebuf[TSTAMP_LEN+1]; 128 | size_t timebuf_len; 129 | if (timestamp) { 130 | time_t now = time(NULL); 131 | struct tm *time_gm = gmtime (&now); 132 | 133 | if ((timebuf_len = strftime (timebuf, TSTAMP_LEN+1, TSTAMP_FMT, time_gm)) == 0) 134 | die ("%s: cannot format timestamp: %s\n", argv0, ERRSTR); 135 | 136 | iov[n_iov++] = iov_from_data (timebuf, timebuf_len); 137 | iov[n_iov++] = iov_from_literal (" "); 138 | } 139 | 140 | if (prefix) { 141 | iov[n_iov++] = iov_from_string (prefix); 142 | iov[n_iov++] = iov_from_literal (" "); 143 | } 144 | 145 | iov[n_iov++] = iov_from_buffer (&linebuf); 146 | 147 | assert ((unsigned) n_iov <= (sizeof (iov) / sizeof (iov[0]))); 148 | 149 | if (log_fd < 0) { 150 | if ((log_fd = safe_openatm(AT_FDCWD, argv[0], O_CREAT | O_APPEND | O_WRONLY, 0666)) < 0) 151 | die ("%s: cannot open '%s': %s\n", argv0, argv[0], ERRSTR); 152 | } 153 | 154 | if (safe_writev(log_fd, iov, n_iov) < 0) 155 | clog_warning("Writing to log: %s", strerror(errno)); 156 | 157 | if (!buffered && log_fd != STDOUT_FILENO && log_fd != STDERR_FILENO && !isatty(log_fd)) { 158 | if (safe_fsync(log_fd) != 0) 159 | clog_warning("Flushing log: %s", strerror(errno)); 160 | } 161 | } 162 | dbuf_clear(&linebuf); 163 | } 164 | 165 | if (log_fd >= 0 && safe_close(log_fd) == -1) 166 | clog_warning("Closing log: %s", strerror(errno)); 167 | 168 | exit (EXIT_SUCCESS); 169 | } 170 | 171 | -------------------------------------------------------------------------------- /conf.c: -------------------------------------------------------------------------------- 1 | /* 2 | * conf.c 3 | * Copyright (C) 2020 Adrian Perez de Castro 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #include "deps/dbuf/dbuf.h" 9 | #include "deps/cflag/cflag.h" 10 | #include "deps/clog/clog.h" 11 | #include "conf.h" 12 | #include 13 | #include 14 | #include 15 | 16 | struct parser { 17 | FILE *input; 18 | int look; 19 | unsigned line, col; 20 | struct dbuf *errmsg; 21 | }; 22 | 23 | enum { 24 | COMMENTCHAR = '#', 25 | }; 26 | 27 | static bool 28 | err(struct parser *p, const char *fmt, ...) 29 | { 30 | dbuf_addfmt(p->errmsg, "%u:%u ", p->line, p->col); 31 | 32 | va_list args; 33 | va_start(args, fmt); 34 | dbuf_addfmtv(p->errmsg, fmt, args); 35 | va_end(args); 36 | 37 | return false; 38 | } 39 | 40 | static inline int 41 | isnotcr(int c) 42 | { 43 | return c != '\n'; 44 | } 45 | 46 | static inline int 47 | isnotspace(int c) 48 | { 49 | return !isspace(c); 50 | } 51 | 52 | static int 53 | nextch(struct parser *p) 54 | { 55 | int c = fgetc(p->input); 56 | if (c == '\n') { 57 | p->col = 0; 58 | p->line++; 59 | } 60 | p->col++; 61 | return c; 62 | } 63 | 64 | static void 65 | skipwhile(struct parser *p, int (*cond)(int)) 66 | { 67 | while (p->look != EOF && (*cond)(p->look)) 68 | p->look = nextch(p); 69 | } 70 | 71 | static inline void 72 | skipws(struct parser *p) 73 | { 74 | skipwhile(p, isspace); 75 | } 76 | 77 | static void 78 | advance(struct parser *p) 79 | { 80 | do { 81 | if ((p->look = nextch(p)) == COMMENTCHAR) 82 | skipwhile(p, isnotcr); 83 | } while (p->look != EOF && p->look == COMMENTCHAR); 84 | } 85 | 86 | static void 87 | takewhile(struct parser *p, int (*cond)(int), struct dbuf *result) 88 | { 89 | dbuf_clear(result); 90 | while (p->look != EOF && (*cond)(p->look)) { 91 | dbuf_addch(result, p->look); 92 | advance(p); 93 | } 94 | } 95 | 96 | static bool 97 | parse_word(struct parser *p, struct dbuf *result) 98 | { 99 | takewhile(p, isnotspace, result); 100 | advance(p); 101 | skipws(p); 102 | return dbuf_size(result) > 0; 103 | } 104 | 105 | static bool 106 | parse_string(struct parser *p, struct dbuf *result) 107 | { 108 | dbuf_clear(result); 109 | 110 | int c = nextch(p); 111 | for (; c != '"' && c != EOF; c = nextch(p)) { 112 | if (c == '\\') { 113 | /* escaped sequences */ 114 | switch ((c = nextch(p))) { 115 | case 'n': c = '\n'; break; /* carriage return */ 116 | case 'r': c = '\r'; break; /* line feed */ 117 | case 'b': c = '\b'; break; /* backspace */ 118 | case 'e': c = 0x1b; break; /* escape */ 119 | case 'a': c = '\a'; break; /* bell */ 120 | case 't': c = '\t'; break; /* tab */ 121 | case 'v': c = '\v'; break; /* vertical tab */ 122 | case 'X': /* hex number */ 123 | case 'x': { char num[3]; 124 | num[0] = nextch(p); 125 | num[1] = nextch(p); 126 | num[2] = '\0'; 127 | if (!isxdigit(num[0]) || !isxdigit(num[1])) 128 | return err(p, "Invalid hex sequence"); 129 | c = strtol(num, NULL, 16); 130 | } break; 131 | } 132 | } 133 | 134 | dbuf_addch(result, c); 135 | } 136 | 137 | /* Premature end of string. */ 138 | if (c == EOF) 139 | return err(p, "Unterminated string"); 140 | 141 | advance(p); 142 | skipws(p); 143 | 144 | return true; 145 | } 146 | 147 | static const struct cflag* 148 | find_flag(const struct cflag *specs, const char *name) 149 | { 150 | for (size_t i = 0; specs[i].name; i++) 151 | if (strcmp(specs[i].name, name) == 0) 152 | return &specs[i]; 153 | return NULL; 154 | } 155 | 156 | static bool 157 | parse_input(struct parser *p, const struct cflag *specs) 158 | { 159 | struct dbuf option = DBUF_INIT; 160 | struct dbuf argument = DBUF_INIT; 161 | 162 | bool ok = true; 163 | 164 | while (p->look != EOF) { 165 | if (!parse_word(p, &option)) { 166 | ok = err(p, "Identifier expected"); 167 | break; 168 | } 169 | 170 | const struct cflag *spec = find_flag(specs, dbuf_str(&option)); 171 | if (!spec) { 172 | ok = err(p, "No such option %s", dbuf_str(&option)); 173 | break; 174 | } 175 | 176 | enum cflag_status status; 177 | if ((*spec->func)(NULL, NULL) == CFLAG_NEEDS_ARG) { 178 | if (p->look == '"') 179 | ok = parse_string(p, &argument); 180 | else 181 | ok = parse_word(p, &argument); 182 | if (!ok) { 183 | err(p, "Expected argument for option %s", spec->name); 184 | break; 185 | } 186 | 187 | /* FIXME: Can we avoid this leak? */ 188 | char *arg = strdup(dbuf_str(&argument)); 189 | status = (*spec->func)(spec, arg); 190 | clog_debug("Config: %s \"%s\"", spec->name, arg); 191 | } else { 192 | status = (*spec->func)(spec, NULL); 193 | clog_debug("Config: %s", spec->name); 194 | } 195 | 196 | if (status != CFLAG_OK) { 197 | ok = err(p, "Argument '%s' for option %s is invalid", 198 | dbuf_str(&argument), spec->name); 199 | break; 200 | } 201 | } 202 | 203 | dbuf_clear(&option); 204 | dbuf_clear(&argument); 205 | 206 | return ok; 207 | } 208 | 209 | bool 210 | conf_parse(FILE *input, 211 | const struct cflag *specs, 212 | struct dbuf *err) 213 | { 214 | struct parser p = { 215 | .errmsg = err, 216 | .input = input, 217 | .line = 1, 218 | }; 219 | 220 | dbuf_clear(p.errmsg); 221 | 222 | advance(&p); 223 | skipws(&p); 224 | 225 | return parse_input(&p, specs); 226 | } 227 | -------------------------------------------------------------------------------- /dslog.c: -------------------------------------------------------------------------------- 1 | /* 2 | * dslog.c 3 | * Copyright (C) 2010-2020 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #define _DEFAULT_SOURCE 9 | 10 | #include "deps/cflag/cflag.h" 11 | #include "deps/dbuf/dbuf.h" 12 | #include "util.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | 21 | #if !(defined(MULTICALL) && MULTICALL) 22 | # define dslog_main main 23 | #endif /* MULTICALL */ 24 | 25 | 26 | #ifndef DEFAULT_FACILITY 27 | #define DEFAULT_FACILITY "daemon" 28 | #endif /* !DEFAULT_FACILITY */ 29 | 30 | 31 | #ifndef DEFAULT_PRIORITY 32 | #define DEFAULT_PRIORITY "warning" 33 | #endif /* !DEFAULT_PRIORITY */ 34 | 35 | 36 | static int 37 | name_to_facility (const char *name) 38 | { 39 | static const struct { 40 | const char *name; 41 | int code; 42 | } map[] = { 43 | #ifdef LOG_AUTHPRIV 44 | { "auth", LOG_AUTHPRIV }, 45 | #else /* LOG_AUTHPRIV */ 46 | { "auth", LOG_AUTH }, 47 | #endif /* LOG_AUTHPRIV */ 48 | { "cron", LOG_CRON }, 49 | { "daemon", LOG_DAEMON }, 50 | { "ftp", LOG_FTP }, 51 | { "kern", LOG_KERN }, 52 | { "kernel", LOG_KERN }, 53 | { "local0", LOG_LOCAL0 }, 54 | { "local1", LOG_LOCAL1 }, 55 | { "local2", LOG_LOCAL2 }, 56 | { "local3", LOG_LOCAL3 }, 57 | { "local4", LOG_LOCAL4 }, 58 | { "local5", LOG_LOCAL5 }, 59 | { "local6", LOG_LOCAL6 }, 60 | { "local7", LOG_LOCAL7 }, 61 | { "lpr", LOG_LPR }, 62 | { "print", LOG_LPR }, 63 | { "printer", LOG_LPR }, 64 | { "mail", LOG_MAIL }, 65 | { "news", LOG_NEWS }, 66 | { "user", LOG_USER }, 67 | { "uucp", LOG_UUCP }, 68 | { NULL, 0 }, 69 | }; 70 | unsigned i = 0; 71 | 72 | while (map[i].name) { 73 | if (strcasecmp (name, map[i].name) == 0) 74 | return map[i].code; 75 | i++; 76 | } 77 | 78 | return -1; 79 | } 80 | 81 | 82 | static int 83 | name_to_priority (const char *name) 84 | { 85 | static const struct { 86 | const char *name; 87 | int code; 88 | } map[] = { 89 | { "emerg", LOG_EMERG }, 90 | { "emergency", LOG_EMERG }, 91 | { "alert", LOG_ALERT }, 92 | { "crit", LOG_CRIT }, 93 | { "critical", LOG_CRIT }, 94 | { "err", LOG_ERR }, 95 | { "error", LOG_ERR }, 96 | { "warn", LOG_WARNING }, 97 | { "warning", LOG_WARNING }, 98 | { "notice", LOG_NOTICE }, 99 | { "info", LOG_INFO }, 100 | { "debug", LOG_DEBUG }, 101 | { NULL, 0 }, 102 | }; 103 | unsigned i = 0; 104 | 105 | while (map[i].name) { 106 | if (strcasecmp (map[i].name, name) == 0) 107 | return map[i].code; 108 | i++; 109 | } 110 | 111 | return -1; 112 | } 113 | 114 | 115 | static bool running = true; 116 | 117 | 118 | static enum cflag_status 119 | _facility_option(const struct cflag *spec, const char *arg) 120 | { 121 | if (!spec) 122 | return CFLAG_NEEDS_ARG; 123 | 124 | int facility = name_to_facility(arg); 125 | if (facility == -1) 126 | return CFLAG_BAD_FORMAT; 127 | 128 | *((int*) spec->data) = facility; 129 | return CFLAG_OK; 130 | } 131 | 132 | 133 | static enum cflag_status 134 | _priority_option(const struct cflag *spec, const char *arg) 135 | { 136 | if (!spec) 137 | return CFLAG_NEEDS_ARG; 138 | 139 | int priority = name_to_priority(arg); 140 | if (priority == -1) 141 | return CFLAG_BAD_FORMAT; 142 | 143 | *((int*) spec->data) = priority; 144 | return CFLAG_OK; 145 | } 146 | 147 | 148 | int 149 | dslog_main (int argc, char **argv) 150 | { 151 | int flags = 0; 152 | int in_fd = STDIN_FILENO; 153 | int facility = name_to_facility (DEFAULT_FACILITY); 154 | int priority = name_to_priority (DEFAULT_PRIORITY); 155 | bool console = false; 156 | bool skip_empty = false; 157 | char *env_opts = NULL; 158 | struct dbuf linebuf = DBUF_INIT; 159 | struct dbuf overflow = DBUF_INIT; 160 | 161 | struct cflag dslog_options[] = { 162 | { 163 | .name = "facility", .letter = 'f', 164 | .help = "Log facility (default: daemon).", 165 | .func = _facility_option, 166 | .data = &facility, 167 | }, 168 | { 169 | .name = "priority", .letter = 'p', 170 | .help = "Log priority (default: warning).", 171 | .func = _priority_option, 172 | .data = &priority, 173 | }, 174 | CFLAG(int, "input-fd", 'i', &in_fd, 175 | "File descriptor to read input from (default: stdin)."), 176 | CFLAG(bool, "console", 'c', &console, 177 | "Log to console if sending messages to logger fails."), 178 | CFLAG(bool, "skip-empty", 'e', &skip_empty, 179 | "Ignore empty lines with no characters."), 180 | CFLAG_HELP, 181 | CFLAG_END 182 | }; 183 | 184 | if ((env_opts = getenv ("DSLOG_OPTIONS")) != NULL) 185 | replace_args_string (env_opts, &argc, &argv); 186 | 187 | const char *argv0 = cflag_apply(dslog_options, "[options] name", &argc, &argv); 188 | 189 | if (console) 190 | flags |= LOG_CONS; 191 | 192 | /* We will be no longer using standard output. */ 193 | if (safe_close(STDOUT_FILENO) == -1) 194 | syslog(priority, "dlog: cannot close stdout (%s), continuing anyway.", strerror(errno)); 195 | 196 | if (!argc) 197 | die ("%s: process name not specified.\n", argv0); 198 | 199 | openlog (argv[0], flags, facility); 200 | 201 | while (running) { 202 | ssize_t bytes = freadline (in_fd, &linebuf, &overflow, 0); 203 | if (bytes == 0) 204 | break; /* EOF */ 205 | 206 | if (bytes < 0) { 207 | fprintf (stderr, "%s: error reading input: %s\n", argv0, ERRSTR); 208 | exit (111); 209 | } 210 | 211 | if (!skip_empty || dbuf_size(&linebuf) > 1) 212 | syslog(priority, "%s", dbuf_str(&linebuf)); 213 | 214 | dbuf_clear(&linebuf); 215 | } 216 | 217 | closelog (); 218 | 219 | exit (EXIT_SUCCESS); 220 | } 221 | 222 | /* vim: expandtab shiftwidth=4 tabstop=4 223 | */ 224 | -------------------------------------------------------------------------------- /denv.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024 Adrian Perez de Castro 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef __linux 8 | #define _BSD_SOURCE 9 | #endif 10 | 11 | #define _DEFAULT_SOURCE 12 | #define _GNU_SOURCE 13 | #define _POSIX_C_SOURCE 200809L 14 | 15 | #include "deps/cflag/cflag.h" 16 | #include "deps/clog/clog.h" 17 | #include "deps/dbuf/dbuf.h" 18 | #include "util.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #if !(defined(MULTICALL) && MULTICALL) 31 | # define denv_main main 32 | #endif /* MULTICALL */ 33 | 34 | extern char **environ; 35 | 36 | static char *argv0 = "denv"; 37 | static char **env = NULL; 38 | static size_t env_a = 0; 39 | static size_t env_n = 0; 40 | 41 | static void 42 | env_del(const char* name, size_t namelen) 43 | { 44 | if (!env) 45 | return; 46 | 47 | size_t i; 48 | for (i = 0; env[i]; i++) { 49 | const char *equalsign = strchr(env[i], '='); 50 | assert(equalsign); 51 | 52 | size_t envlen = equalsign - env[i]; 53 | if (envlen != namelen) 54 | continue; 55 | 56 | if (memcmp(env[i], name, namelen) == 0) { 57 | for (; env[i]; i++) { 58 | env[i] = env[i + 1]; 59 | } 60 | assert(env_n > 0); 61 | env_n--; 62 | return; 63 | } 64 | } 65 | } 66 | 67 | static void 68 | env_add(char *entry) 69 | { 70 | assert(entry); 71 | 72 | const char *equalsign = strchr(entry, '='); 73 | assert(equalsign); 74 | 75 | if (env_n + 1 >= env_a) { 76 | env_a = env_a * 2 + 5; 77 | if (env) { 78 | env = reallocarray(env, env_a, sizeof(char*)); 79 | } else { 80 | env = calloc(env_a, sizeof(char*)); 81 | } 82 | if (!env) 83 | fatal("%s: Cannot allocate memory.\n", argv0); 84 | } 85 | assert(env_a > (env_n + 1)); 86 | 87 | env_del(entry, equalsign - entry); 88 | env[env_n++] = entry; 89 | env[env_n] = NULL; 90 | } 91 | 92 | static void 93 | env_inherit_all(void) 94 | { 95 | for (size_t i = 0; environ[i]; i++) { 96 | assert(strchr(environ[i], '=')); 97 | env_add(environ[i]); 98 | } 99 | } 100 | 101 | static enum cflag_status 102 | opt_inherit_env(const struct cflag *spec, const char *arg) 103 | { 104 | (void) arg; 105 | if (!spec) 106 | return CFLAG_OK; 107 | 108 | env_inherit_all(); 109 | return CFLAG_OK; 110 | } 111 | 112 | static enum cflag_status 113 | opt_inherit(const struct cflag *spec, const char *arg) 114 | { 115 | if (!spec) 116 | return CFLAG_NEEDS_ARG; 117 | 118 | for (size_t i = 0; environ[i]; i++) { 119 | const char *equalsign = strchr(environ[i], '='); 120 | assert(equalsign); 121 | if (strncmp(arg, environ[i], equalsign - environ[i]) == 0) { 122 | env_add(environ[i]); 123 | return CFLAG_OK; 124 | } 125 | } 126 | 127 | clog_debug("Cannot inherit undefined variable '%s'", arg); 128 | return CFLAG_OK; 129 | } 130 | 131 | static enum cflag_status 132 | opt_environ(const struct cflag *spec, const char *arg) 133 | { 134 | if (!spec) 135 | return CFLAG_NEEDS_ARG; 136 | 137 | const char *equalsign; 138 | if ((equalsign = strchr(arg, '=')) == NULL) { 139 | env_del(arg, strlen(arg)); 140 | } else { 141 | env_add((char*) arg); 142 | } 143 | return CFLAG_OK; 144 | } 145 | 146 | #ifndef AT_NO_AUTOMOUNT 147 | #define AT_NO_AUTOMOUNT 0 148 | #endif /* !AT_NO_AUTOMOUNT */ 149 | 150 | static inline bool 151 | is_trim_char(int c) 152 | { 153 | switch (c) { 154 | case '\r': 155 | case '\n': 156 | case '\v': 157 | case '\t': 158 | case '\f': 159 | case ' ': 160 | return true; 161 | default: 162 | return false; 163 | } 164 | } 165 | 166 | static void 167 | env_envdir(const char *path) 168 | { 169 | assert(path); 170 | 171 | DIR *d = opendir(path); 172 | if (!d) 173 | fatal("%s: Cannot open directory '%s': %s.\n", argv0, path, ERRSTR); 174 | 175 | struct dirent *de; 176 | while ((de = readdir(d))) { 177 | if (de->d_name[0] == '.') 178 | continue; 179 | 180 | struct stat st; 181 | if (fstatat(dirfd(d), de->d_name, &st, AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW) == -1) 182 | fatal("%s: Cannot stat '%s/%s': %s.", argv0, path, de->d_name, ERRSTR); 183 | 184 | if (!S_ISREG(st.st_mode)) 185 | continue; 186 | 187 | /* Remove from the environment if the file is empty. */ 188 | if (st.st_size == 0) { 189 | env_del(de->d_name, strlen(de->d_name)); 190 | continue; 191 | } 192 | 193 | int fd = safe_openat(dirfd(d), de->d_name, O_RDONLY); 194 | if (fd < 0) 195 | fatal("%s: Cannot open '%s/%s': %s.\n", argv0, path, de->d_name, ERRSTR); 196 | 197 | struct dbuf linebuf = DBUF_INIT; 198 | struct dbuf overflow = DBUF_INIT; 199 | 200 | dbuf_addstr(&linebuf, de->d_name); 201 | dbuf_addch(&linebuf, '='); 202 | 203 | ssize_t bytes = freadline(fd, &linebuf, &overflow, 0); 204 | if (bytes < 1) 205 | fatal("%s: Cannot read '%s': %s.\n", argv0, de->d_name, ERRSTR); 206 | 207 | /* Chomp spaces around the value. */ 208 | char* entry = dbuf_str(&linebuf); 209 | while (--bytes && is_trim_char(entry[bytes])) 210 | /* empty */; 211 | 212 | if (entry[bytes++] == '=') 213 | env_del(entry, bytes - 1); 214 | else 215 | env_add(strndup(entry, bytes)); 216 | 217 | dbuf_clear(&overflow); 218 | dbuf_clear(&linebuf); 219 | 220 | if (safe_close(fd) == -1) 221 | clog_warning("Cannot close '%s/%s: %s (ignored).", path, de->d_name, ERRSTR); 222 | } 223 | closedir(d); 224 | } 225 | 226 | static enum cflag_status 227 | opt_envdir(const struct cflag *spec, const char *arg) 228 | { 229 | if (!spec) 230 | return CFLAG_NEEDS_ARG; 231 | 232 | env_envdir(arg); 233 | return CFLAG_OK; 234 | } 235 | 236 | static enum cflag_status 237 | opt_file(const struct cflag *spec, const char *arg) 238 | { 239 | if (!spec) 240 | return CFLAG_NEEDS_ARG; 241 | 242 | int fd = safe_openat(AT_FDCWD, arg, O_RDONLY); 243 | if (fd < 0) 244 | fatal("%s: Cannot open '%s': %s.\n", argv0, arg, ERRSTR); 245 | 246 | struct dbuf linebuf = DBUF_INIT; 247 | struct dbuf overflow = DBUF_INIT; 248 | 249 | for (;;) { 250 | ssize_t bytes = freadline(fd, &linebuf, &overflow, 0); 251 | if (bytes == 0) 252 | break; /* EOF */ 253 | 254 | if (bytes < 0) 255 | fatal("%s: Cannot read '%s': %s.\n", argv0, arg, ERRSTR); 256 | 257 | /* Chomp spaces around the entry. */ 258 | char *entry = dbuf_str(&linebuf); 259 | 260 | while (bytes && is_trim_char(entry[0])) 261 | bytes--, entry++; 262 | while (bytes && is_trim_char(entry[bytes])) 263 | bytes--; 264 | 265 | if (bytes-- && entry[0] != '#') { 266 | const char *equalsign = strchr(entry, '='); 267 | assert(equalsign); 268 | if (equalsign - entry + 1 == bytes) 269 | env_del(entry, equalsign - entry); 270 | else 271 | env_add(strndup(entry, bytes)); 272 | } 273 | 274 | dbuf_clear(&linebuf); 275 | } 276 | 277 | dbuf_clear(&overflow); 278 | dbuf_clear(&linebuf); 279 | if (safe_close(fd) == -1) 280 | clog_warning("Cannot close '%s: %s (ignored).\n", arg, ERRSTR); 281 | return CFLAG_OK; 282 | } 283 | 284 | static const struct cflag denv_options[] = { 285 | { 286 | .name = "inherit-env", .letter = 'I', 287 | .func = opt_inherit_env, 288 | .help = "Inherit all environment variables of the calling process.", 289 | }, 290 | { 291 | .name = "inherit", .letter = 'i', 292 | .func = opt_inherit, 293 | .help = "Inherit an environment variable of the calling process.", 294 | }, 295 | { 296 | .name = "environ", .letter = 'E', 297 | .func = opt_environ, 298 | .help = 299 | "Define an environment variable, or if no value is given, " 300 | "delete it. This option can be specified multiple times.", 301 | }, 302 | { 303 | .name = "envdir", .letter = 'd', 304 | .func = opt_envdir, 305 | .help = 306 | "Add environment variables from the contents of files in " 307 | "a directory.", 308 | }, 309 | { 310 | .name = "file", .letter = 'f', 311 | .func = opt_file, 312 | .help = 313 | "Add environment variables from a file in the environment.d(5)" 314 | " format. Note: $VARIABLE expansions are not supported.", 315 | }, 316 | CFLAG_HELP, 317 | CFLAG_END 318 | }; 319 | 320 | int 321 | denv_main(int argc, char **argv) 322 | { 323 | clog_init(NULL); 324 | 325 | /* XXX: It would be neat to have e.g. a cflag_argv0() function that did this. */ 326 | argv0 = strrchr(argv[0], '/'); 327 | if (argv0 == NULL) 328 | argv0 = argv[0]; 329 | else 330 | argv0++; 331 | 332 | if (strcmp(argv0, "envdir") == 0) { 333 | if (argc < 3) 334 | fatal("%s: usage: %s d child\n", argv0, argv0); 335 | 336 | env_inherit_all(); 337 | env_envdir(argv[1]); 338 | 339 | /* Adjust directly because replace_args_shift() does not touch argv[0]. */ 340 | argv += 2; 341 | argc -= 2; 342 | } else { 343 | cflag_apply(denv_options, "[path] command [command-options...]", &argc, &argv); 344 | if (!argc) 345 | fatal("%s: No command specified.\n", argv0); 346 | } 347 | 348 | execvpe(argv[0], argv, env); 349 | fatal("%s: Cannot execute '%s': %s.\n", argv0, argv[0], ERRSTR); 350 | } 351 | -------------------------------------------------------------------------------- /dmon.rst: -------------------------------------------------------------------------------- 1 | ====== 2 | dmon 3 | ====== 4 | 5 | ------------------------------- 6 | Daemonize and monitor processes 7 | ------------------------------- 8 | 9 | :Author: Adrian Perez 10 | :Manual section: 8 11 | 12 | 13 | SYNOPSIS 14 | ======== 15 | 16 | ``dmon [options] cmd [cmdoptions] [-- logcmd [logcmdoptions]]`` 17 | 18 | 19 | DESCRIPTION 20 | =========== 21 | 22 | The ``dmon`` program will launch a program and re-launch it whenever it 23 | dies. Optionally, the standard output streams of the programs may be piped 24 | into a second program (named *log command*), which will receive the output 25 | of the program in its standard input stream. The log command will be also 26 | monitored and re-launched when it dies. 27 | 28 | 29 | USAGE 30 | ===== 31 | 32 | Command line options: 33 | 34 | -C PATH, --config PATH 35 | Read contents from *PATH* as if they were command line options. 36 | Those will be parsed after the options picked from the 37 | ``DMON_OPTIONS`` environment variable and before the options 38 | given in the command line. If given, this option **must** be 39 | the first one passed to to ``dmon``. 40 | 41 | -I PATH, --write-info PATH 42 | Write status changes of monitored processes to *PATH*, one 43 | status message per line. See the `status file format`_ section 44 | for details on the format. 45 | 46 | -p PATH, --pid-file PATH 47 | Write the PID of the master ``dmon`` process to a file in the 48 | specified *PATH*. You can signal the process to interact with 49 | it. (See SIGNALS_ below.) 50 | 51 | -W PATH, --work-dir PATH 52 | Change to the directory located at *PATH* and use it as working 53 | directory. Note that all other paths passed to ``dmon`` (except 54 | the configuration file) will be interpreted as relative to the 55 | working directory. 56 | 57 | -i TIME, --interval TIME 58 | When execution of the process ends with a successful (zero) 59 | exit status, wait for *TIME* seconds before respawning the 60 | process instead of doing it immediately. This can be used to 61 | make ``dmon`` behave as el-cheapo `cron(8)` replacement. This 62 | option cannot be used along with ``-1``. 63 | 64 | -t TIME, --timeout TIME 65 | If the process takes longer than *TIME* seconds to complete, 66 | terminate it by sending the *TERM*/*CONT* signal combo. Then 67 | the process will be respawned again. This is useful to ensure 68 | that potentially locking processes which should take less than 69 | some known time limit do not hog the computer. Most likely, 70 | this flag is useful in conjunction with ``-1``, and with 71 | ``-n`` e.g. when using it in a `cron(8)` job. 72 | 73 | -L NUMBER, --load-high NUMBER 74 | Enable tracking the system's load average, and suspend the 75 | execution of the command process when the system load goes 76 | over *NUMBER*. To pause the process, *STOP* signal will be 77 | sent to it. You may want to use ``-l`` as well to specify 78 | under which load value the process is resumed, otherwise 79 | when the system load falls below *NUMBER/2* the process will 80 | be resumed. 81 | 82 | -l NUMBER, --load-low NUMBER 83 | When using ``-L``, the command process execution will be 84 | resumed when the system load falls below *NUMBER*, instead of 85 | using the default behavior of resuming the process when the 86 | load falls below half the limit specified with ``-L``. 87 | 88 | -E ENVVAR, --environ ENVVAR 89 | Manipulates environment variables. Specifying just a variable 90 | name (e.g. ``-E foo``) as *ENVVAR* will clear it and remove 91 | the variable from the environment. Adding a value will define 92 | the variable (e.g. ``-E foo=bar``). This option may be 93 | specified multiple times. Environment variables will affect 94 | *both* the ``dmon`` and the child process; this is intended 95 | behaviour. 96 | 97 | -u UIDGID, --cmd-user UIDGID 98 | Executes the command with the credentials of user *UID*, 99 | and additional group *GID* specified separated with 100 | semicolons. Both user and group identifiers might be given 101 | as strings or numerically. 102 | 103 | -U UIDGID, --log-user UIDGID 104 | Executes the **log** command with the credentials of user 105 | *UID*, and additional group *GID* specified separated with 106 | semicolons. Both user and group identifiers might be given 107 | as strings or numerically. 108 | 109 | -n, --no-daemon 110 | Do not daemonize: ``dmon`` will keep working in foreground, 111 | without detaching and without closing its standard input and 112 | output streams. This is useful for debugging and, to a limited 113 | extent, to run interactive programs. 114 | 115 | -1, --once Run command only once: if the command exits with a success 116 | status (i.e. exit code is zero), then ``dmon`` will exit and 117 | stop the logging process. If the program dies due to a signal 118 | or with a non-zero exit status, it is respawned. This option 119 | tends to be used in conjunction with ``-n``, and cannot be 120 | used with ``-i``. 121 | 122 | -m COUNT, --max-respawns COUNT 123 | Respawn command m number of times. ``dmon`` does not check the 124 | exit status on each respawn. For example, if you want the 125 | command to never respawn, set ``-m 0``. After the set number of 126 | respawns have passed ``dmon`` will NOT respawn the cmd. 127 | Instead, ``dmon`` will exit and stop the logging process. 128 | 129 | -e, --stderr-redir 130 | Redirect both the standard error and standard output streams 131 | to the log command. If not specified, only the standard output 132 | is redirected. 133 | 134 | -s, --cmd-sigs 135 | Forward signals *CONT*, *ALRM*, *QUIT*, *USR1*, *USR2* and 136 | *HUP* to the monitored command when ``dmon`` receives them. 137 | 138 | -S, --log-sigs 139 | Forward signals *CONT*, *ALRM*, *QUIT*, *USR1*, *USR2* and 140 | *HUP* to the log command when ``dmon`` receives them. 141 | 142 | -r LIMIT, --limit LIMIT 143 | Set *LIMIT* for process execution. Limits are specified as 144 | ``name=value`` strings, and multiple limits may be set by 145 | using ``-r`` multiple times. The available set of limits 146 | depends on the current operating system, to get a list 147 | ``-r help`` can be used. 148 | 149 | -h, --help Show a summary of available options. 150 | 151 | Usual log commands include `dlog(8)` and `dslog(8)`, which are part of the 152 | ``dmon`` suite. Other log commands like `rotlog(8)` or `multilog(8)` may be 153 | used as long as they consume data from standard input and do not detach 154 | themsemlves from the controlling process. 155 | 156 | As a convenience, time values passed to ``-i``, ``-t`` and values of limits 157 | specified with ``-r`` may be given with the following suffixes: 158 | 159 | - ``m``: Minutes, e.g. ``30m`` means "30 minutes". 160 | - ``h``: Hours, e.g. ``4h`` means "4 hours". 161 | - ``d``: Days, e.g. ``3d`` means "3 days". 162 | - ``w``: Weeks, e.g. ``1w`` means "1 week". 163 | 164 | For size values (bytes) the strings passed to ``-r`` as limits may have the 165 | following suffixes: 166 | 167 | - ``k``: Kilobytes. 168 | - ``m``: Megabytes. 169 | - ``g``: Gigabytes. 170 | 171 | 172 | SIGNALS 173 | ======= 174 | 175 | Signals may be used to interact with the monitored processes and ``dmon`` 176 | itself. 177 | 178 | The ``TERM`` and ``INT`` signals are catched by ``dmon``, and they will 179 | make it shut down gracefully: both the main command and the log command 180 | will receive a ``TERM`` signal followed by a ``CONT`` and they will be 181 | waited for. 182 | 183 | When at least one of ``-s`` or ``-S`` are used, the ``CONT``, ``ALRM``, 184 | ``QUIT``, ``USR1``, ``USR2`` and ``HUP`` signals are forwarded to the 185 | managed processes. By default, if none of the options are used, those 186 | signals are ignored. 187 | 188 | 189 | EXAMPLES 190 | ======== 191 | 192 | The following command will supervise a shell which prints a string each 193 | fifth second, and the output is logged to a file with timestamps:: 194 | 195 | dmon -n sh -c 'while echo "Hello World" ; do sleep 5 ; done' \ 196 | -- dlog logfile 197 | 198 | In order to turn the previous example into a daemon, we only need to 199 | remove the ``-n``. I may be convenient to specify a PID file path:: 200 | 201 | dmon -p example.pid \ 202 | sh -c 'while echo "Hello dmon" ; do sleep 5 ; done' \ 203 | -- dlog logfile 204 | 205 | The following example launches the `cron(8)` daemon with the logging 206 | process running as user and group ``log:wheel``:: 207 | 208 | dmon -p /var/run/crond.pid -u log:wheel -e cron -f 209 | -- dlog /var/log/cron.log 210 | 211 | This example will run a (probably lengthy) backup process, pausing it when 212 | the system load goes above 3.5 and resuming it when the load drops below 213 | 1.0:: 214 | 215 | dmon -1 -n -l 1 -L 3.5 rsync -avz ~/ /backup/homedir 216 | 217 | If you have a PID file, terminating the daemon is an easy task:: 218 | 219 | kill $(cat example.pid) 220 | 221 | 222 | STATUS FILE FORMAT 223 | ================== 224 | 225 | When using the ``-I`` *PATH* option, status updates are written to *PATH*, 226 | one line per update. The following line formats may be used: 227 | 228 | A process was started by ``dmon``: 229 | 230 | :: 231 | 232 | cmd start 233 | log start 234 | 235 | 236 | A process is about to be stopped by ``dmon``: 237 | 238 | :: 239 | 240 | cmd stop 241 | log stop 242 | 243 | 244 | A process has exited by its own means, or was terminated by the other means 245 | different than ``dmon`` itself (e.g. by the kernel or the user): 246 | 247 | :: 248 | 249 | cmd exit 250 | log exit 251 | 252 | The ```` field is numeric, and must be interpreted the same as the 253 | *status* argument to the `waitpid(2)` system call. Most of the time this is 254 | the expected integer code passed to `exit(2)`, but this may not be true if 255 | the process exits forcibly. 256 | 257 | 258 | A signal is about to be sent to a process: 259 | 260 | :: 261 | 262 | cmd signal 263 | log signal 264 | 265 | 266 | The main monitored process timed out (when ``-t`` is in effect): 267 | 268 | :: 269 | 270 | cmd timeout 271 | 272 | 273 | Process was paused or resumed due to system load constraints (when the 274 | ``-l`` and ``-L`` options are in effect): 275 | 276 | :: 277 | 278 | cmd pause 279 | cmd resume 280 | 281 | 282 | 283 | ENVIRONMENT 284 | =========== 285 | 286 | Additional options will be picked from the ``DMON_OPTIONS`` environment 287 | variable, if defined. Any command line option can be specified this way. 288 | Arguments read from the environment variable will be prepended to the ones 289 | given in the command line, so they may still be overriden. 290 | 291 | If the ``DMON_LIST_MULTICALL_APPLETS`` is defined and has a non-zero value, 292 | the list of applets compiled into a multicall binary will be printed out, 293 | and the program will exit immediately. 294 | 295 | 296 | SEE ALSO 297 | ======== 298 | 299 | `dlog(8)`, `dslog(8)`, `rotlog(8)`, `multilog(8)`, `supervise(8)`, `cron(8)` 300 | 301 | http://cr.yp.to/daemontools.html 302 | 303 | -------------------------------------------------------------------------------- /deps/cflag/cflag.c: -------------------------------------------------------------------------------- 1 | /* cflag.c 2 | * Copyright (C) 2020-2024 Adrian Perez de Castro 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #define _POSIX_C_SOURCE 199309L 8 | 9 | #include "cflag.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #if defined(linux) || defined(__linux) || defined(__linux__) || \ 19 | defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__) 20 | # define HAVE_TIOCGWINZS 1 21 | # include 22 | #else 23 | # define HAVE_TIOCGWINZS 0 24 | #endif 25 | 26 | static unsigned 27 | term_columns(FILE *f) 28 | { 29 | #if HAVE_TIOCGWINZS 30 | struct winsize ws; 31 | int fd = fileno(f); 32 | if (fd >= 0 && ioctl(fd, TIOCGWINSZ, &ws) == 0) 33 | return ws.ws_col; 34 | #endif /* HAVE_TIOCGWINZS */ 35 | 36 | return 80; 37 | } 38 | 39 | static bool 40 | findent(FILE *out, unsigned rcol, const char *text) 41 | { 42 | if (rcol < 20) 43 | return false; 44 | 45 | if (!(text && *text)) 46 | return true; 47 | 48 | fputs(" ", out); 49 | for (unsigned col = 3; text && *text;) { 50 | const char *spc = strchr(text, ' '); 51 | const size_t len = spc ? (++spc - text) : strlen(text); 52 | 53 | if (col + len > rcol) { 54 | /* Move over to the next line. */ 55 | fputc('\n', out); 56 | return findent(out, rcol, text); 57 | } 58 | 59 | fwrite(text, sizeof(char), len, out); 60 | col += len; 61 | text = spc; 62 | } 63 | 64 | return true; 65 | } 66 | 67 | static inline bool 68 | is_long_flag(const char *s) 69 | { 70 | return (s[0] == '-' && s[1] == '-' && s[2] != '\0'); 71 | } 72 | 73 | static inline bool 74 | is_short_flag(const char *s) 75 | { 76 | return (s[0] == '-' && s[1] != '\0' && s[2] == '\0'); 77 | } 78 | 79 | static inline bool 80 | is_negated(const char *s) 81 | { 82 | return strncmp("--no-", s, 5) == 0; 83 | } 84 | 85 | static const struct cflag* 86 | find_short(const struct cflag specs[], 87 | int letter) 88 | { 89 | for (unsigned i = 0; specs[i].name != NULL || specs[i].letter != '\0'; ++i) { 90 | if (specs[i].letter == '\0') 91 | continue; 92 | if (specs[i].letter == letter) 93 | return &specs[i]; 94 | } 95 | return NULL; 96 | } 97 | 98 | static const struct cflag* 99 | find_long(const struct cflag specs[], 100 | const char *name) 101 | { 102 | for (unsigned i = 0; specs[i].name != NULL || specs[i].letter != '\0'; ++i) { 103 | if (!specs[i].name) 104 | continue; 105 | if (strcmp(specs[i].name, name) == 0) 106 | return &specs[i]; 107 | } 108 | return NULL; 109 | } 110 | 111 | static inline bool 112 | needs_arg(const struct cflag *spec) 113 | { 114 | return (*spec->func)(NULL, NULL) == CFLAG_NEEDS_ARG; 115 | } 116 | 117 | void 118 | cflag_usage(const struct cflag specs[], 119 | const char *progname, 120 | const char *usage, 121 | FILE *out) 122 | { 123 | assert(specs); 124 | assert(progname); 125 | assert(usage); 126 | 127 | if (!out) 128 | out = stderr; 129 | { 130 | const char *slash = strrchr(progname, '/'); 131 | if (slash) 132 | progname = slash + 1; 133 | } 134 | 135 | const unsigned rcol = term_columns(out) - 3; 136 | 137 | fprintf(out, "Usage: %s %s\n", progname, usage); 138 | fprintf(out, "Command line options:\n\n"); 139 | 140 | for (unsigned i = 0;; ++i) { 141 | const struct cflag *spec = &specs[i]; 142 | 143 | const bool has_letter = spec->letter != '\0'; 144 | const bool has_name = spec->name != NULL; 145 | 146 | if (!(has_name || has_letter)) 147 | break; 148 | 149 | if (has_letter && has_name) 150 | fprintf(out, "-%c, --%s", spec->letter, spec->name); 151 | else if (has_name) 152 | fprintf(out, "--%s", spec->name); 153 | else 154 | fprintf(out, "-%c", spec->letter); 155 | 156 | if (needs_arg(spec)) 157 | fprintf(out, " "); 158 | 159 | fputc('\n', out); 160 | if (!findent(out, rcol, spec->help)) { 161 | fputs(" ", out); 162 | fputs(spec->help, out); 163 | } 164 | fputc('\n', out); 165 | fputc('\n', out); 166 | } 167 | } 168 | 169 | int 170 | cflag_parse(const struct cflag specs[], 171 | int *pargc, 172 | char ***pargv) 173 | { 174 | assert(specs); 175 | assert(pargc); 176 | assert(pargv); 177 | 178 | int argc = *pargc; 179 | char **argv = *pargv; 180 | 181 | for (; argc > 0; --argc, ++argv) { 182 | const char *arg = *argv; 183 | 184 | bool negated = false; 185 | const struct cflag *spec; 186 | if (is_short_flag(arg)) { 187 | if (arg[1] == '-') /* -- stop processing command line flags */ 188 | break; 189 | spec = find_short(specs, arg[1]); 190 | } else if (is_long_flag(arg)) { 191 | spec = find_long(specs, &arg[2]); 192 | if (!spec && is_negated(arg)) { 193 | const struct cflag *negspec = find_long(specs, &arg[5]); 194 | if (negspec->func == cflag_bool) { 195 | spec = negspec; 196 | negated = true; 197 | } 198 | } 199 | } else { 200 | *pargc = argc; *pargv = argv; 201 | return CFLAG_OK; 202 | } 203 | 204 | if (!spec) { 205 | *pargc = argc; *pargv = argv; 206 | return CFLAG_UNDEFINED; 207 | } 208 | 209 | arg = NULL; 210 | if (needs_arg(spec)) { 211 | if (argc == 1) { 212 | *pargc = argc; *pargv = argv; 213 | return CFLAG_NEEDS_ARG; 214 | } 215 | arg = *(++argv); 216 | --argc; 217 | } 218 | 219 | const enum cflag_status status = (*spec->func)(spec, arg); 220 | if (status != CFLAG_OK) { 221 | *pargc = argc; *pargv = argv; 222 | return status; 223 | } 224 | 225 | /* 226 | * XXX: This fixup here is ugly, but avoids needing to pass 227 | * additional parameters to cflag_ functions. 228 | */ 229 | if (spec->func == cflag_bool && negated) 230 | *((bool*) spec->data) = false; 231 | } 232 | 233 | *pargc = argc; *pargv = argv; 234 | return CFLAG_OK; 235 | } 236 | 237 | const char* 238 | cflag_status_name(enum cflag_status value) 239 | { 240 | switch (value) { 241 | case CFLAG_OK: return "success"; 242 | case CFLAG_SHOW_HELP: return "help requested"; 243 | case CFLAG_UNDEFINED: return "no such option"; 244 | case CFLAG_BAD_FORMAT: return "argument has invalid format"; 245 | case CFLAG_NEEDS_ARG: return "missing argument"; 246 | } 247 | assert(!"Unreachable"); 248 | abort(); 249 | } 250 | 251 | const char* 252 | cflag_apply(const struct cflag specs[], 253 | const char *syntax, 254 | int *pargc, 255 | char ***pargv) 256 | { 257 | assert(specs); 258 | assert(syntax); 259 | assert(pargc); 260 | assert(pargv); 261 | 262 | int argc = *pargc; 263 | char **argv = *pargv; 264 | 265 | const char *argv0 = *argv++; argc--; 266 | { 267 | const char *slash = strrchr(argv0, '/'); 268 | if (slash) argv0 = slash + 1; 269 | } 270 | 271 | const enum cflag_status status = cflag_parse(specs, &argc, &argv); 272 | switch (status) { 273 | case CFLAG_SHOW_HELP: 274 | cflag_usage(specs, argv0, syntax, stdout); 275 | exit(EXIT_SUCCESS); 276 | case CFLAG_OK: 277 | *pargc = argc; 278 | *pargv = argv; 279 | return argv0; 280 | default: 281 | break; 282 | } 283 | 284 | fprintf(stderr, "%s: %s: '%s'\n", argv0, cflag_status_name(status), *argv); 285 | exit(EXIT_FAILURE); 286 | } 287 | 288 | enum cflag_status 289 | cflag_bool(const struct cflag *spec, 290 | const char *arg) 291 | { 292 | (void) arg; 293 | 294 | if (!spec) 295 | return CFLAG_OK; 296 | 297 | *((bool*) spec->data) = true; 298 | return CFLAG_OK; 299 | } 300 | 301 | enum cflag_status 302 | cflag_int(const struct cflag *spec, 303 | const char *arg) 304 | { 305 | if (!spec) 306 | return CFLAG_NEEDS_ARG; 307 | 308 | return (sscanf(arg, "%d", (int*) spec->data) == 1) ? CFLAG_OK : CFLAG_BAD_FORMAT; 309 | } 310 | 311 | enum cflag_status 312 | cflag_uint(const struct cflag *spec, 313 | const char *arg) 314 | { 315 | if (!spec) 316 | return CFLAG_NEEDS_ARG; 317 | 318 | return (sscanf(arg, "%u", (unsigned*) spec->data) == 1) ? CFLAG_OK : CFLAG_BAD_FORMAT; 319 | } 320 | 321 | enum cflag_status 322 | cflag_float(const struct cflag *spec, 323 | const char *arg) 324 | { 325 | if (!spec) 326 | return CFLAG_NEEDS_ARG; 327 | 328 | char *endptr; 329 | float v = strtof(arg, &endptr); 330 | if (errno == ERANGE || *endptr != '\0') 331 | return CFLAG_BAD_FORMAT; 332 | 333 | *((float*) spec->data) = v; 334 | return CFLAG_OK; 335 | } 336 | 337 | enum cflag_status 338 | cflag_double(const struct cflag *spec, 339 | const char *arg) 340 | { 341 | if (!spec) 342 | return CFLAG_NEEDS_ARG; 343 | 344 | char *endptr; 345 | double v = strtod(arg, &endptr); 346 | if (errno == ERANGE || *endptr != '\0') 347 | return CFLAG_BAD_FORMAT; 348 | 349 | *((double*) spec->data) = v; 350 | return CFLAG_OK; 351 | } 352 | 353 | enum cflag_status 354 | cflag_string(const struct cflag *spec, 355 | const char *arg) 356 | { 357 | if (!spec) 358 | return CFLAG_NEEDS_ARG; 359 | 360 | *((const char**) spec->data) = arg; 361 | return CFLAG_OK; 362 | } 363 | 364 | enum cflag_status 365 | cflag_bytes(const struct cflag *spec, 366 | const char *arg) 367 | { 368 | if (!spec) 369 | return CFLAG_NEEDS_ARG; 370 | 371 | char *endpos; 372 | unsigned long long v = strtoull(arg, &endpos, 0); 373 | if (v == ULLONG_MAX && errno == ERANGE) 374 | return CFLAG_BAD_FORMAT; 375 | 376 | if (endpos) { 377 | switch (*endpos) { 378 | case 'g': case 'G': v *= 1024 * 1024 * 1024; break; /* gigabytes */ 379 | case 'm': case 'M': v *= 1024 * 1024; break; /* megabytes */ 380 | case 'k': case 'K': v *= 1024; break; /* kilobytes */ 381 | case 'b': case 'B': case '\0': break; /* bytes */ 382 | } 383 | } 384 | 385 | *((size_t*) spec->data) = v; 386 | return CFLAG_OK; 387 | } 388 | 389 | enum cflag_status 390 | cflag_timei(const struct cflag *spec, 391 | const char *arg) 392 | { 393 | if (!spec) 394 | return CFLAG_NEEDS_ARG; 395 | 396 | char *endpos; 397 | unsigned long long v = strtoull(arg, &endpos, 0); 398 | 399 | if (v == ULLONG_MAX && errno == ERANGE) 400 | return CFLAG_BAD_FORMAT; 401 | 402 | if (endpos) { 403 | switch (*endpos) { 404 | case 'y': v *= 60 * 60 * 24 * 365; break; /* years */ 405 | case 'M': v *= 60 * 60 * 24 * 30; break; /* months */ 406 | case 'w': v *= 60 * 60 * 24 * 7; break; /* weeks */ 407 | case 'd': v *= 60 * 60 * 24; break; /* days */ 408 | case 'h': v *= 60 * 60; break; /* hours */ 409 | case 'm': v *= 60; break; /* minutes */ 410 | case 's': case '\0': break; /* seconds */ 411 | default : return CFLAG_BAD_FORMAT; 412 | } 413 | } 414 | 415 | *((unsigned long long*) spec->data) = v; 416 | return CFLAG_OK; 417 | } 418 | 419 | enum cflag_status 420 | cflag_help(const struct cflag *spec, 421 | const char *arg) 422 | { 423 | (void) spec; 424 | (void) arg; 425 | 426 | return CFLAG_SHOW_HELP; 427 | } 428 | -------------------------------------------------------------------------------- /drlog.c: -------------------------------------------------------------------------------- 1 | /* 2 | * drlog.c 3 | * Copyright © 2010-2020 Adrián Pérez 4 | * Copyright © 2008-2010 Adrián Pérez 5 | * 6 | * Distributed under terms of the MIT license. 7 | */ 8 | 9 | #define _POSIX_C_SOURCE 200809L 10 | 11 | #include "deps/cflag/cflag.h" 12 | #include "deps/dbuf/dbuf.h" 13 | #include "util.h" 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #if !(defined(MULTICALL) && MULTICALL) 30 | # define drlog_main main 31 | #endif /* MULTICALL */ 32 | 33 | #ifndef LOGDIR_PERMS 34 | #define LOGDIR_PERMS 0750 35 | #endif /* !LOGDIR_PERMS */ 36 | 37 | #ifndef LOGDIR_TSTAMP 38 | #define LOGDIR_TSTAMP ".timestamp" 39 | #endif /* !LOGDIR_TSTAMP */ 40 | 41 | #ifndef LOGFILE_PERMS 42 | #define LOGFILE_PERMS 0640 43 | #endif /* !LOGFILE_PERMS */ 44 | 45 | #ifndef LOGFILE_PREFIX 46 | #define LOGFILE_PREFIX "log-" 47 | #endif /* !LOGFILE_PREFIX */ 48 | 49 | #ifndef LOGFILE_CURRENT 50 | #define LOGFILE_CURRENT "current" 51 | #endif /* !LOGFILE_CURRENT */ 52 | 53 | #ifndef LOGFILE_DEFMAX 54 | #define LOGFILE_DEFMAX 10 55 | #endif /* !LOGFILE_DEFMAX */ 56 | 57 | #ifndef LOGFILE_DEFSIZE 58 | #define LOGFILE_DEFSIZE (150 * 1024) /* 150 kB */ 59 | #endif /* !LOGFILE_DEFSIZE */ 60 | 61 | #ifndef LOGFILE_DEFTIME 62 | #define LOGFILE_DEFTIME (60 * 60 * 24 * 5) /* Five days */ 63 | #endif /* !LOGFILE_DEFTIME */ 64 | 65 | #ifndef TSTAMP_FMT 66 | #define TSTAMP_FMT "%Y-%m-%d/%H:%M:%S " 67 | #endif /* !TSTAMP_FMT */ 68 | 69 | #ifndef TSTAMP_LEN 70 | #define TSTAMP_LEN (5 + 3 + 3 + 3 + 3 + 3) 71 | #endif /* !TSTAMP_LEN */ 72 | 73 | 74 | static char *directory = NULL; 75 | static int out_fd = -1; 76 | static int in_fd = -1; 77 | static unsigned maxfiles = LOGFILE_DEFMAX; 78 | static unsigned long long maxtime = LOGFILE_DEFTIME; 79 | static size_t maxsize = LOGFILE_DEFSIZE; 80 | static unsigned long long curtime = 0; 81 | static unsigned long long cursize = 0; 82 | static bool timestamp = false; 83 | static bool buffered = false; 84 | static bool skip_empty = false; 85 | static int returncode = 0; 86 | static struct dbuf line = DBUF_INIT; 87 | static struct dbuf overflow = DBUF_INIT; 88 | 89 | 90 | static int 91 | rotate_log (void) 92 | { 93 | char old_name[MAXPATHLEN]; 94 | char path[MAXPATHLEN]; 95 | const char *name; 96 | DIR *dir; 97 | struct dirent *dirent; 98 | unsigned foundlogs; 99 | int year, mon, mday, hour, min, sec; 100 | int older_year, older_mon = INT_MAX, older_mday = INT_MAX, 101 | older_hour = INT_MAX, older_min = INT_MAX, older_sec = INT_MAX; 102 | 103 | rescan: 104 | foundlogs = 0; 105 | *old_name = 0; 106 | older_year = INT_MAX; 107 | 108 | if ((dir = opendir (directory)) == NULL) { 109 | fprintf (stderr, "Unable to open directory '%s' for rotation (%s).\n", 110 | directory, ERRSTR); 111 | return -1; 112 | } 113 | 114 | while ((dirent = readdir (dir)) != NULL) { 115 | name = dirent->d_name; 116 | if (!strncmp (name, LOGFILE_PREFIX, sizeof (LOGFILE_PREFIX) - 1U)) { 117 | if (sscanf (name, LOGFILE_PREFIX "%d-%d-%d-%d:%d:%d", 118 | &year, &mon, &mday, &hour, &min, &sec) != 6) 119 | continue; 120 | 121 | foundlogs++; 122 | if (year < older_year || (year == older_year && 123 | (mon < older_mon || (mon == older_mon && 124 | (mday < older_mday || (mday == older_mday && 125 | (hour < older_hour || (hour == older_hour && 126 | (min < older_min || (min == older_min && 127 | (sec < older_sec))))))))))) /* Oh yeah! */ 128 | { 129 | older_year = year; 130 | older_mon = mon ; 131 | older_mday = mday; 132 | older_hour = hour; 133 | older_min = min ; 134 | older_sec = sec ; 135 | strncpy (old_name, name, sizeof (old_name)); 136 | old_name[sizeof (old_name) - 1] = 0; 137 | } 138 | } 139 | } 140 | 141 | closedir (dir); 142 | 143 | if (foundlogs >= maxfiles) { 144 | if (*old_name == 0) { 145 | return -3; 146 | } 147 | if (snprintf (path, sizeof (path), "%s/%s", directory, old_name) < 0) { 148 | fprintf (stderr, "Path too long to unlink: %s/%s\n", directory, old_name); 149 | return -4; 150 | } 151 | if (unlink (path) < 0) { 152 | return -2; 153 | } 154 | foundlogs--; 155 | if (foundlogs >= maxfiles) { 156 | goto rescan; 157 | } 158 | } 159 | return 0; 160 | } 161 | 162 | 163 | static void 164 | flush_line (void) 165 | { 166 | time_t now = time (NULL); 167 | 168 | if (out_fd < 0) { 169 | /* We need to open the output file. */ 170 | char path[MAXPATHLEN]; 171 | struct stat st; 172 | time_t ts; 173 | FILE *ts_file; 174 | 175 | testdir: 176 | if (stat (directory, &st) < 0 || !S_ISDIR (st.st_mode)) 177 | die ((errno == ENOENT) 178 | ? "Output directory does not exist: %s\n" 179 | : "Output path is not a directory: %s\n", 180 | directory); 181 | 182 | if (snprintf (path, sizeof (path), "%s/" LOGFILE_CURRENT, directory) < 0) 183 | die ("Path name too long: %s\n", directory); 184 | 185 | if ((out_fd = safe_openatm(AT_FDCWD, path, O_APPEND | O_CREAT | O_WRONLY, LOGFILE_PERMS)) < 0) 186 | die ("Cannot open '%s': %s\n", path, ERRSTR); 187 | 188 | if (snprintf (path, sizeof (path), "%s/" LOGDIR_TSTAMP, directory) < 0) { 189 | /* TODO: Warn on close failures. */ 190 | safe_close(out_fd); 191 | die ("Path name too long: %s\n", directory); 192 | } 193 | 194 | if ((ts_file = fopen (path, "r")) == NULL) { 195 | int ts_fd; 196 | recreate_ts: 197 | ts = time (NULL); 198 | if ((ts_fd = safe_openatm(AT_FDCWD, path, O_WRONLY | O_CREAT | O_TRUNC, LOGFILE_PERMS)) < 0) { 199 | /* TODO: Warn in close failures. */ 200 | safe_close(out_fd); 201 | die ("Unable to write timestamp to '%s', %s\n", directory, ERRSTR); 202 | } 203 | ts_file = safe_fdopen(ts_fd, "w"); 204 | assert (ts_file != NULL); 205 | 206 | if (fprintf (ts_file, "%lu\n", (unsigned long) ts) < 0) 207 | die ("Unable to write to '%s': %s\n", path, ERRSTR); 208 | } 209 | else { 210 | unsigned long long ts_; 211 | 212 | if (fscanf (ts_file, "%llu", &ts_) != 1 || ferror (ts_file)) { 213 | fclose (ts_file); 214 | goto recreate_ts; 215 | } 216 | ts = ts_; 217 | } 218 | /* TODO: Warn on errors. */ 219 | fclose (ts_file); 220 | ts_file = NULL; 221 | 222 | curtime = ts; 223 | if (maxtime) { 224 | curtime -= (curtime % maxtime); 225 | } 226 | cursize = (unsigned long long) lseek (out_fd, 0, SEEK_CUR); 227 | } 228 | 229 | if ((maxsize != 0) && (maxtime != 0)) { 230 | if (cursize >= maxsize || (unsigned long long) now > (curtime + maxtime)) { 231 | struct tm *time_gm; 232 | char path[MAXPATHLEN]; 233 | char newpath[MAXPATHLEN]; 234 | 235 | if (out_fd < 0) { 236 | die ("Internal inconsistency at %s:%i\n", __FILE__, __LINE__); 237 | assert (!"unreachable"); 238 | } 239 | 240 | if ((time_gm = gmtime(&now)) == NULL) 241 | die ("Unable to get current date: %s\n", ERRSTR); 242 | 243 | if (snprintf (newpath, sizeof (newpath), "%s/" LOGFILE_PREFIX, directory) < 0) 244 | die ("Path name too long: %s\n", directory); 245 | 246 | if (strftime (newpath + strlen (newpath), 247 | sizeof (newpath) - strlen(newpath), 248 | "%Y-%m-%d-%H:%M:%S", 249 | time_gm) == 0) 250 | die ("Path name too long: '%s'\n", directory); 251 | 252 | if (snprintf(path, sizeof (path), "%s/" LOGFILE_CURRENT, directory) < 0) 253 | die ("Path name too long: %s\n", directory); 254 | 255 | rotate_log (); 256 | 257 | /* TODO: Warn on close errors. */ 258 | safe_close(out_fd); 259 | out_fd = -1; 260 | 261 | if (rename (path, newpath) < 0 && unlink (path) < 0) 262 | die ("Unable to rename '%s' to '%s'\n", path, newpath); 263 | 264 | if (snprintf (path, sizeof (path), "%s/" LOGDIR_TSTAMP, directory) < 0) 265 | die ("Path name too long: %s\n", directory); 266 | 267 | unlink (path); 268 | goto testdir; 269 | } 270 | } 271 | 272 | if (dbuf_empty(&line) || (skip_empty && dbuf_size(&line) == 1)) 273 | return; 274 | 275 | char timebuf[TSTAMP_LEN+1]; 276 | struct iovec iov[2]; 277 | int n_iov = 0; 278 | 279 | if (timestamp) { 280 | struct tm *time_gm = gmtime (&now); 281 | size_t timebuf_len; 282 | 283 | if ((timebuf_len = strftime (timebuf, TSTAMP_LEN+1, TSTAMP_FMT, time_gm)) == 0) 284 | die ("Cannot format timestamp\n"); 285 | 286 | iov[n_iov++] = iov_from_data (timebuf, timebuf_len); 287 | } 288 | 289 | iov[n_iov++] = iov_from_buffer (&line); 290 | assert ((unsigned) n_iov <= (sizeof (iov) / sizeof (iov[0]))); 291 | 292 | for (;;) { 293 | ssize_t r = safe_writev(out_fd, iov, n_iov); 294 | if (r > 0) { 295 | cursize += r; 296 | break; 297 | } 298 | 299 | fprintf (stderr, "Cannot write to logfile: %s.\n", ERRSTR); 300 | safe_sleep (5); 301 | } 302 | 303 | if (!buffered) { 304 | /* TODO: Warn on errors. */ 305 | safe_fsync(out_fd); 306 | } 307 | 308 | dbuf_clear(&line); 309 | } 310 | 311 | 312 | static void 313 | close_log (void) 314 | { 315 | flush_line (); 316 | 317 | for (;;) { 318 | if (safe_close(out_fd) == 0) { 319 | out_fd = -1; 320 | break; 321 | } 322 | 323 | fprintf (stderr, "Unable to close logfile at directory '%s', %s\n", 324 | directory, ERRSTR); 325 | safe_sleep (5); 326 | } 327 | } 328 | 329 | 330 | static void 331 | roll_handler (int signum) 332 | { 333 | (void) signum; 334 | close_log (); 335 | } 336 | 337 | 338 | __attribute__((noreturn)) 339 | static void 340 | quit_handler (int signum) 341 | { 342 | (void) signum; 343 | 344 | flush_line (); 345 | dbuf_addbuf(&line, &overflow); 346 | close_log (); 347 | exit (returncode); 348 | } 349 | 350 | 351 | static const struct cflag drlog_options[] = { 352 | CFLAG(uint, "max-files", 'm', &maxfiles, 353 | "Maximum number of log files to keep."), 354 | CFLAG(timei, "max-time", 'T', &maxtime, 355 | "Maximum time to use a log file (suffixes: mhdwMy)."), 356 | CFLAG(bytes, "max-size", 's', &maxsize, 357 | "Maximum size of each log file (suffixes: kmg)."), 358 | CFLAG(int, "input-fd", 'i', &in_fd, 359 | "File descriptor to read input from (default: stdin)."), 360 | CFLAG(bool, "buffered", 'b', &buffered, 361 | "Buffered operation, do not flush to disk after each line."), 362 | CFLAG(bool, "timestamp", 't', ×tamp, 363 | "Prepend a timestamp in YYYY-MM-DD/HH:MM:SS format to each line."), 364 | CFLAG(bool, "skip-empty", 'e', &skip_empty, 365 | "Ignore empty lines with no characters."), 366 | CFLAG_HELP, 367 | CFLAG_END 368 | }; 369 | 370 | int drlog_main (int argc, char **argv) 371 | { 372 | int in_fd = STDIN_FILENO; 373 | char *env_opts = NULL; 374 | struct sigaction sa; 375 | 376 | if ((env_opts = getenv ("DRLOG_OPTIONS")) != NULL) 377 | replace_args_string (env_opts, &argc, &argv); 378 | 379 | const char *argv0 = cflag_apply(drlog_options, "[options] logdir-path", &argc, &argv); 380 | 381 | if (!argc) 382 | die ("%s: No log directory path was specified.\n", argv0); 383 | 384 | /* TODO: Open a fd handle to the directory to use *at() functions afterwards. */ 385 | directory = argv[0]; 386 | 387 | sigemptyset (&sa.sa_mask); 388 | sa.sa_flags = 0; 389 | 390 | sa.sa_handler = roll_handler; 391 | safe_sigaction ("HUP", SIGHUP, &sa); 392 | 393 | sa.sa_handler = quit_handler; 394 | safe_sigaction ("INT", SIGINT, &sa); 395 | 396 | sa.sa_handler = quit_handler; 397 | safe_sigaction ("TERM", SIGTERM, &sa); 398 | 399 | for (;;) { 400 | ssize_t bytes = freadline (in_fd, &line, &overflow, 0); 401 | if (bytes == 0) 402 | break; /* EOF */ 403 | 404 | if (bytes < 0) { 405 | fprintf (stderr, "Unable to read from standard input: %s.\n", ERRSTR); 406 | returncode = 1; 407 | quit_handler (0); 408 | } 409 | 410 | flush_line (); 411 | } 412 | 413 | quit_handler (0); 414 | return 0; /* Keep compiler happy */ 415 | } 416 | 417 | -------------------------------------------------------------------------------- /dmon.8: -------------------------------------------------------------------------------- 1 | .\" Man page generated from reStructuredText. 2 | . 3 | .TH DMON 8 "" "" "" 4 | .SH NAME 5 | dmon \- Daemonize and monitor processes 6 | . 7 | .nr rst2man-indent-level 0 8 | . 9 | .de1 rstReportMargin 10 | \\$1 \\n[an-margin] 11 | level \\n[rst2man-indent-level] 12 | level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] 13 | - 14 | \\n[rst2man-indent0] 15 | \\n[rst2man-indent1] 16 | \\n[rst2man-indent2] 17 | .. 18 | .de1 INDENT 19 | .\" .rstReportMargin pre: 20 | . RS \\$1 21 | . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] 22 | . nr rst2man-indent-level +1 23 | .\" .rstReportMargin post: 24 | .. 25 | .de UNINDENT 26 | . RE 27 | .\" indent \\n[an-margin] 28 | .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] 29 | .nr rst2man-indent-level -1 30 | .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] 31 | .in \\n[rst2man-indent\\n[rst2man-indent-level]]u 32 | .. 33 | .SH SYNOPSIS 34 | .sp 35 | \fBdmon [options] cmd [cmdoptions] [\-\- logcmd [logcmdoptions]]\fP 36 | .SH DESCRIPTION 37 | .sp 38 | The \fBdmon\fP program will launch a program and re\-launch it whenever it 39 | dies. Optionally, the standard output streams of the programs may be piped 40 | into a second program (named \fIlog command\fP), which will receive the output 41 | of the program in its standard input stream. The log command will be also 42 | monitored and re\-launched when it dies. 43 | .SH USAGE 44 | .sp 45 | Command line options: 46 | .INDENT 0.0 47 | .TP 48 | .BI \-C \ PATH\fR,\fB \ \-\-config \ PATH 49 | Read contents from \fIPATH\fP as if they were command line options. 50 | Those will be parsed after the options picked from the 51 | \fBDMON_OPTIONS\fP environment variable and before the options 52 | given in the command line. If given, this option \fBmust\fP be 53 | the first one passed to to \fBdmon\fP\&. 54 | .TP 55 | .BI \-I \ PATH\fR,\fB \ \-\-write\-info \ PATH 56 | Write status changes of monitored processes to \fIPATH\fP, one 57 | status message per line. See the \fI\%status file format\fP section 58 | for details on the format. 59 | .TP 60 | .BI \-p \ PATH\fR,\fB \ \-\-pid\-file \ PATH 61 | Write the PID of the master \fBdmon\fP process to a file in the 62 | specified \fIPATH\fP\&. You can signal the process to interact with 63 | it. (See \fI\%SIGNALS\fP below.) 64 | .TP 65 | .BI \-W \ PATH\fR,\fB \ \-\-work\-dir \ PATH 66 | Change to the directory located at \fIPATH\fP and use it as working 67 | directory. Note that all other paths passed to \fBdmon\fP (except 68 | the configuration file) will be interpreted as relative to the 69 | working directory. 70 | .TP 71 | .BI \-i \ TIME\fR,\fB \ \-\-interval \ TIME 72 | When execution of the process ends with a successful (zero) 73 | exit status, wait for \fITIME\fP seconds before respawning the 74 | process instead of doing it immediately. This can be used to 75 | make \fBdmon\fP behave as el\-cheapo \fIcron(8)\fP replacement. This 76 | option cannot be used along with \fB\-1\fP\&. 77 | .TP 78 | .BI \-t \ TIME\fR,\fB \ \-\-timeout \ TIME 79 | If the process takes longer than \fITIME\fP seconds to complete, 80 | terminate it by sending the \fITERM\fP/\fICONT\fP signal combo. Then 81 | the process will be respawned again. This is useful to ensure 82 | that potentially locking processes which should take less than 83 | some known time limit do not hog the computer. Most likely, 84 | this flag is useful in conjunction with \fB\-1\fP, and with 85 | \fB\-n\fP e.g. when using it in a \fIcron(8)\fP job. 86 | .TP 87 | .BI \-L \ NUMBER\fR,\fB \ \-\-load\-high \ NUMBER 88 | Enable tracking the system\(aqs load average, and suspend the 89 | execution of the command process when the system load goes 90 | over \fINUMBER\fP\&. To pause the process, \fISTOP\fP signal will be 91 | sent to it. You may want to use \fB\-l\fP as well to specify 92 | under which load value the process is resumed, otherwise 93 | when the system load falls below \fINUMBER/2\fP the process will 94 | be resumed. 95 | .TP 96 | .BI \-l \ NUMBER\fR,\fB \ \-\-load\-low \ NUMBER 97 | When using \fB\-L\fP, the command process execution will be 98 | resumed when the system load falls below \fINUMBER\fP, instead of 99 | using the default behavior of resuming the process when the 100 | load falls below half the limit specified with \fB\-L\fP\&. 101 | .TP 102 | .BI \-E \ ENVVAR\fR,\fB \ \-\-environ \ ENVVAR 103 | Manipulates environment variables. Specifying just a variable 104 | name (e.g. \fB\-E foo\fP) as \fIENVVAR\fP will clear it and remove 105 | the variable from the environment. Adding a value will define 106 | the variable (e.g. \fB\-E foo=bar\fP). This option may be 107 | specified multiple times. Environment variables will affect 108 | \fIboth\fP the \fBdmon\fP and the child process; this is intended 109 | behaviour. 110 | .TP 111 | .BI \-u \ UIDGID\fR,\fB \ \-\-cmd\-user \ UIDGID 112 | Executes the command with the credentials of user \fIUID\fP, 113 | and additional group \fIGID\fP specified separated with 114 | semicolons. Both user and group identifiers might be given 115 | as strings or numerically. 116 | .TP 117 | .BI \-U \ UIDGID\fR,\fB \ \-\-log\-user \ UIDGID 118 | Executes the \fBlog\fP command with the credentials of user 119 | \fIUID\fP, and additional group \fIGID\fP specified separated with 120 | semicolons. Both user and group identifiers might be given 121 | as strings or numerically. 122 | .TP 123 | .B \-n\fP,\fB \-\-no\-daemon 124 | Do not daemonize: \fBdmon\fP will keep working in foreground, 125 | without detaching and without closing its standard input and 126 | output streams. This is useful for debugging and, to a limited 127 | extent, to run interactive programs. 128 | .TP 129 | .B \-1\fP,\fB \-\-once 130 | Run command only once: if the command exits with a success 131 | status (i.e. exit code is zero), then \fBdmon\fP will exit and 132 | stop the logging process. If the program dies due to a signal 133 | or with a non\-zero exit status, it is respawned. This option 134 | tends to be used in conjunction with \fB\-n\fP, and cannot be 135 | used with \fB\-i\fP\&. 136 | .TP 137 | .BI \-m \ COUNT\fR,\fB \ \-\-max\-respawns \ COUNT 138 | Respawn command m number of times. \fBdmon\fP does not check the 139 | exit status on each respawn. For example, if you want the 140 | command to never respawn, set \fB\-m 0\fP\&. After the set number of 141 | respawns have passed \fBdmon\fP will NOT respawn the cmd. 142 | Instead, \fBdmon\fP will exit and stop the logging process. 143 | .TP 144 | .B \-e\fP,\fB \-\-stderr\-redir 145 | Redirect both the standard error and standard output streams 146 | to the log command. If not specified, only the standard output 147 | is redirected. 148 | .TP 149 | .B \-s\fP,\fB \-\-cmd\-sigs 150 | Forward signals \fICONT\fP, \fIALRM\fP, \fIQUIT\fP, \fIUSR1\fP, \fIUSR2\fP and 151 | \fIHUP\fP to the monitored command when \fBdmon\fP receives them. 152 | .TP 153 | .B \-S\fP,\fB \-\-log\-sigs 154 | Forward signals \fICONT\fP, \fIALRM\fP, \fIQUIT\fP, \fIUSR1\fP, \fIUSR2\fP and 155 | \fIHUP\fP to the log command when \fBdmon\fP receives them. 156 | .TP 157 | .BI \-r \ LIMIT\fR,\fB \ \-\-limit \ LIMIT 158 | Set \fILIMIT\fP for process execution. Limits are specified as 159 | \fBname=value\fP strings, and multiple limits may be set by 160 | using \fB\-r\fP multiple times. The available set of limits 161 | depends on the current operating system, to get a list 162 | \fB\-r help\fP can be used. 163 | .TP 164 | .B \-h\fP,\fB \-\-help 165 | Show a summary of available options. 166 | .UNINDENT 167 | .sp 168 | Usual log commands include \fIdlog(8)\fP and \fIdslog(8)\fP, which are part of the 169 | \fBdmon\fP suite. Other log commands like \fIrotlog(8)\fP or \fImultilog(8)\fP may be 170 | used as long as they consume data from standard input and do not detach 171 | themsemlves from the controlling process. 172 | .sp 173 | As a convenience, time values passed to \fB\-i\fP, \fB\-t\fP and values of limits 174 | specified with \fB\-r\fP may be given with the following suffixes: 175 | .INDENT 0.0 176 | .IP \(bu 2 177 | \fBm\fP: Minutes, e.g. \fB30m\fP means "30 minutes". 178 | .IP \(bu 2 179 | \fBh\fP: Hours, e.g. \fB4h\fP means "4 hours". 180 | .IP \(bu 2 181 | \fBd\fP: Days, e.g. \fB3d\fP means "3 days". 182 | .IP \(bu 2 183 | \fBw\fP: Weeks, e.g. \fB1w\fP means "1 week". 184 | .UNINDENT 185 | .sp 186 | For size values (bytes) the strings passed to \fB\-r\fP as limits may have the 187 | following suffixes: 188 | .INDENT 0.0 189 | .IP \(bu 2 190 | \fBk\fP: Kilobytes. 191 | .IP \(bu 2 192 | \fBm\fP: Megabytes. 193 | .IP \(bu 2 194 | \fBg\fP: Gigabytes. 195 | .UNINDENT 196 | .SH SIGNALS 197 | .sp 198 | Signals may be used to interact with the monitored processes and \fBdmon\fP 199 | itself. 200 | .sp 201 | The \fBTERM\fP and \fBINT\fP signals are catched by \fBdmon\fP, and they will 202 | make it shut down gracefully: both the main command and the log command 203 | will receive a \fBTERM\fP signal followed by a \fBCONT\fP and they will be 204 | waited for. 205 | .sp 206 | When at least one of \fB\-s\fP or \fB\-S\fP are used, the \fBCONT\fP, \fBALRM\fP, 207 | \fBQUIT\fP, \fBUSR1\fP, \fBUSR2\fP and \fBHUP\fP signals are forwarded to the 208 | managed processes. By default, if none of the options are used, those 209 | signals are ignored. 210 | .SH EXAMPLES 211 | .sp 212 | The following command will supervise a shell which prints a string each 213 | fifth second, and the output is logged to a file with timestamps: 214 | .INDENT 0.0 215 | .INDENT 3.5 216 | .sp 217 | .nf 218 | .ft C 219 | dmon \-n sh \-c \(aqwhile echo "Hello World" ; do sleep 5 ; done\(aq \e 220 | \-\- dlog logfile 221 | .ft P 222 | .fi 223 | .UNINDENT 224 | .UNINDENT 225 | .sp 226 | In order to turn the previous example into a daemon, we only need to 227 | remove the \fB\-n\fP\&. I may be convenient to specify a PID file path: 228 | .INDENT 0.0 229 | .INDENT 3.5 230 | .sp 231 | .nf 232 | .ft C 233 | dmon \-p example.pid \e 234 | sh \-c \(aqwhile echo "Hello dmon" ; do sleep 5 ; done\(aq \e 235 | \-\- dlog logfile 236 | .ft P 237 | .fi 238 | .UNINDENT 239 | .UNINDENT 240 | .sp 241 | The following example launches the \fIcron(8)\fP daemon with the logging 242 | process running as user and group \fBlog:wheel\fP: 243 | .INDENT 0.0 244 | .INDENT 3.5 245 | .sp 246 | .nf 247 | .ft C 248 | dmon \-p /var/run/crond.pid \-u log:wheel \-e cron \-f 249 | \-\- dlog /var/log/cron.log 250 | .ft P 251 | .fi 252 | .UNINDENT 253 | .UNINDENT 254 | .sp 255 | This example will run a (probably lengthy) backup process, pausing it when 256 | the system load goes above 3.5 and resuming it when the load drops below 257 | 1.0: 258 | .INDENT 0.0 259 | .INDENT 3.5 260 | .sp 261 | .nf 262 | .ft C 263 | dmon \-1 \-n \-l 1 \-L 3.5 rsync \-avz ~/ /backup/homedir 264 | .ft P 265 | .fi 266 | .UNINDENT 267 | .UNINDENT 268 | .sp 269 | If you have a PID file, terminating the daemon is an easy task: 270 | .INDENT 0.0 271 | .INDENT 3.5 272 | .sp 273 | .nf 274 | .ft C 275 | kill $(cat example.pid) 276 | .ft P 277 | .fi 278 | .UNINDENT 279 | .UNINDENT 280 | .SH STATUS FILE FORMAT 281 | .sp 282 | When using the \fB\-I\fP \fIPATH\fP option, status updates are written to \fIPATH\fP, 283 | one line per update. The following line formats may be used: 284 | .sp 285 | A process was started by \fBdmon\fP: 286 | .INDENT 0.0 287 | .INDENT 3.5 288 | .INDENT 0.0 289 | .INDENT 3.5 290 | .sp 291 | .nf 292 | .ft C 293 | cmd start 294 | log start 295 | .ft P 296 | .fi 297 | .UNINDENT 298 | .UNINDENT 299 | .UNINDENT 300 | .UNINDENT 301 | .sp 302 | A process is about to be stopped by \fBdmon\fP: 303 | .INDENT 0.0 304 | .INDENT 3.5 305 | .INDENT 0.0 306 | .INDENT 3.5 307 | .sp 308 | .nf 309 | .ft C 310 | cmd stop 311 | log stop 312 | .ft P 313 | .fi 314 | .UNINDENT 315 | .UNINDENT 316 | .UNINDENT 317 | .UNINDENT 318 | .sp 319 | A process has exited by its own means, or was terminated by the other means 320 | different than \fBdmon\fP itself (e.g. by the kernel or the user): 321 | .INDENT 0.0 322 | .INDENT 3.5 323 | .INDENT 0.0 324 | .INDENT 3.5 325 | .sp 326 | .nf 327 | .ft C 328 | cmd exit 329 | log exit 330 | .ft P 331 | .fi 332 | .UNINDENT 333 | .UNINDENT 334 | .UNINDENT 335 | .UNINDENT 336 | .sp 337 | The \fB\fP field is numeric, and must be interpreted the same as the 338 | \fIstatus\fP argument to the \fIwaitpid(2)\fP system call. Most of the time this is 339 | the expected integer code passed to \fIexit(2)\fP, but this may not be true if 340 | the process exits forcibly. 341 | .sp 342 | A signal is about to be sent to a process: 343 | .INDENT 0.0 344 | .INDENT 3.5 345 | .INDENT 0.0 346 | .INDENT 3.5 347 | .sp 348 | .nf 349 | .ft C 350 | cmd signal 351 | log signal 352 | .ft P 353 | .fi 354 | .UNINDENT 355 | .UNINDENT 356 | .UNINDENT 357 | .UNINDENT 358 | .sp 359 | The main monitored process timed out (when \fB\-t\fP is in effect): 360 | .INDENT 0.0 361 | .INDENT 3.5 362 | .INDENT 0.0 363 | .INDENT 3.5 364 | .sp 365 | .nf 366 | .ft C 367 | cmd timeout 368 | .ft P 369 | .fi 370 | .UNINDENT 371 | .UNINDENT 372 | .UNINDENT 373 | .UNINDENT 374 | .sp 375 | Process was paused or resumed due to system load constraints (when the 376 | \fB\-l\fP and \fB\-L\fP options are in effect): 377 | .INDENT 0.0 378 | .INDENT 3.5 379 | .INDENT 0.0 380 | .INDENT 3.5 381 | .sp 382 | .nf 383 | .ft C 384 | cmd pause 385 | cmd resume 386 | .ft P 387 | .fi 388 | .UNINDENT 389 | .UNINDENT 390 | .UNINDENT 391 | .UNINDENT 392 | .SH ENVIRONMENT 393 | .sp 394 | Additional options will be picked from the \fBDMON_OPTIONS\fP environment 395 | variable, if defined. Any command line option can be specified this way. 396 | Arguments read from the environment variable will be prepended to the ones 397 | given in the command line, so they may still be overriden. 398 | .sp 399 | If the \fBDMON_LIST_MULTICALL_APPLETS\fP is defined and has a non\-zero value, 400 | the list of applets compiled into a multicall binary will be printed out, 401 | and the program will exit immediately. 402 | .SH SEE ALSO 403 | .sp 404 | \fIdlog(8)\fP, \fIdslog(8)\fP, \fIrotlog(8)\fP, \fImultilog(8)\fP, \fIsupervise(8)\fP, \fIcron(8)\fP 405 | .sp 406 | \fI\%http://cr.yp.to/daemontools.html\fP 407 | .SH AUTHOR 408 | Adrian Perez 409 | .\" Generated by docutils manpage writer. 410 | . 411 | -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * util.c 3 | * Copyright (C) 2010-2020 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #define _GNU_SOURCE 1 9 | 10 | #include "deps/cflag/cflag.h" 11 | #include "util.h" 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #if !USE_LIBC_REALLOCARRAY 30 | #define DEF_WEAK(dummy) 31 | #include "fallback/reallocarray.c" 32 | #endif /* !LIBC_HAS_REALLOCARRAY */ 33 | 34 | void 35 | fd_cloexec (int fd) 36 | { 37 | if (fcntl (fd, F_SETFD, FD_CLOEXEC) < 0) 38 | die ("unable to set FD_CLOEXEC\n"); 39 | } 40 | 41 | int 42 | safe_fsync(int fd) 43 | { 44 | int ret; 45 | do { 46 | ret = fsync(fd); 47 | } while (ret == -1 && errno == EINTR); 48 | return ret; 49 | } 50 | 51 | int 52 | safe_close(int fd) 53 | { 54 | int ret; 55 | do { 56 | ret = close(fd); 57 | } while (ret == -1 && errno == EINTR); 58 | return ret; 59 | } 60 | 61 | int 62 | safe_openat(int dirfd, const char *name, int flags) 63 | { 64 | int ret; 65 | do { 66 | ret = openat(dirfd, name, flags); 67 | } while (ret == -1 && errno == EINTR); 68 | return ret; 69 | } 70 | 71 | int 72 | safe_openatm(int dirfd, const char *name, int flags, mode_t mode) 73 | { 74 | int ret; 75 | do { 76 | ret = openat(dirfd, name, flags, mode); 77 | } while (ret == -1 && errno == EINTR); 78 | return ret; 79 | } 80 | 81 | FILE* 82 | safe_fdopen(int fd, const char *mode) 83 | { 84 | FILE *ret; 85 | do { 86 | ret = fdopen(fd, mode); 87 | } while (!ret && errno == EINTR); 88 | return ret; 89 | } 90 | 91 | ssize_t 92 | safe_read(int fd, void *buf, size_t count) 93 | { 94 | ssize_t ret; 95 | do { 96 | ret = read(fd, buf, count); 97 | } while (ret == -1 && errno == EINTR); 98 | return ret; 99 | } 100 | 101 | ssize_t 102 | safe_writev(int fd, const struct iovec *iov, int iovcnt) 103 | { 104 | ssize_t ret; 105 | do { 106 | ret = writev(fd, iov, iovcnt); 107 | } while (ret == -1 && errno == EINTR); 108 | return ret; 109 | } 110 | 111 | void 112 | safe_sleep (unsigned seconds) 113 | { 114 | struct timespec ts; 115 | int retval; 116 | 117 | /* No time? Save up a syscall! */ 118 | if (!seconds) return; 119 | 120 | ts.tv_sec = seconds; 121 | ts.tv_nsec = 0; 122 | 123 | do { 124 | retval = nanosleep (&ts, &ts); 125 | } while (retval == -1 && errno == EINTR); 126 | } 127 | 128 | 129 | void 130 | safe_sigaction (const char *name, int signum, struct sigaction *sa) 131 | { 132 | if (sigaction (signum, sa, NULL) < 0) { 133 | die ("could not set handler for signal %s (%i): %s\n", 134 | name, signum, ERRSTR); 135 | } 136 | } 137 | 138 | 139 | void 140 | safe_setrlimit (int what, long value) 141 | { 142 | struct rlimit r; 143 | 144 | if (getrlimit (what, &r) < 0) 145 | die ("getrlimit (%s) failed: %s\n", limit_name (what), ERRSTR); 146 | 147 | if (value < 0 || (unsigned long) value > r.rlim_max) 148 | r.rlim_cur = r.rlim_max; 149 | else 150 | r.rlim_cur = value; 151 | 152 | if (setrlimit (what, &r) < 0) 153 | die ("setrlimit (%s=%li) failed: %s\n", limit_name (what), value, ERRSTR); 154 | } 155 | 156 | 157 | int 158 | interruptible_sleep (unsigned seconds) 159 | { 160 | struct timespec ts; 161 | 162 | if (!seconds) return 0; 163 | 164 | ts.tv_sec = seconds; 165 | ts.tv_nsec = 0; 166 | 167 | return !(nanosleep (&ts, NULL) == -1 && errno == EINTR); 168 | } 169 | 170 | 171 | int 172 | name_to_uidgid (const char *str, 173 | uid_t *uresult, 174 | gid_t *gresult) 175 | { 176 | struct passwd *pw; 177 | unsigned long num; 178 | char *dummy; 179 | 180 | assert (str); 181 | assert (uresult); 182 | assert (gresult); 183 | 184 | num = strtoul (str, &dummy, 0); 185 | if (num == ULONG_MAX && errno == ERANGE) 186 | return 1; 187 | 188 | if (!dummy || *dummy == '\0') { 189 | if ((pw = getpwuid ((uid_t) num)) == NULL) 190 | return 1; 191 | } 192 | else { 193 | if ((pw = getpwnam (str)) == NULL) 194 | return 1; 195 | } 196 | 197 | *uresult = pw->pw_uid; 198 | *gresult = pw->pw_gid; 199 | 200 | return 0; 201 | } 202 | 203 | 204 | int 205 | name_to_gid (const char *str, gid_t *result) 206 | { 207 | struct group *grp; 208 | unsigned long num; 209 | char *dummy; 210 | 211 | assert (str); 212 | assert (result); 213 | 214 | num = strtoul (str, &dummy, 0); 215 | 216 | if (num == ULONG_MAX && errno == ERANGE) 217 | return 1; 218 | 219 | if (!dummy || *dummy == '\0') { 220 | *result = (gid_t) num; 221 | return 0; 222 | } 223 | 224 | if ((grp = getgrnam (str)) == NULL) 225 | return 1; 226 | 227 | 228 | *result = grp->gr_gid; 229 | return 0; 230 | } 231 | 232 | 233 | static int 234 | _parse_gids (char *s, 235 | uidgid_t *u) 236 | { 237 | char *pos = NULL; 238 | gid_t gid; 239 | 240 | assert (s); 241 | assert (u); 242 | 243 | if (u->ngid >= DMON_GID_COUNT) { 244 | fprintf (stderr, 245 | "More than %d groups given, ignoring additional ones\n", 246 | DMON_GID_COUNT); 247 | return 0; 248 | } 249 | 250 | pos = strchr (s, ':'); 251 | if (pos != NULL) 252 | *pos = '\0'; 253 | 254 | if (name_to_gid (s, &gid)) { 255 | if (pos != NULL) *pos = ':'; 256 | return 1; 257 | } 258 | 259 | if (pos != NULL) 260 | *pos = ':'; 261 | 262 | u->gids[u->ngid++] = gid; 263 | 264 | return (pos == NULL) ? 0 : _parse_gids (pos + 1, u); 265 | } 266 | 267 | 268 | int 269 | parse_uidgids (char *s, 270 | uidgid_t *u) 271 | { 272 | char *pos = NULL; 273 | 274 | assert (s); 275 | assert (u); 276 | 277 | memset (u, 0x00, sizeof (uidgid_t)); 278 | 279 | pos = strchr (s, ':'); 280 | if (pos != NULL) 281 | *pos = '\0'; 282 | 283 | if (name_to_uidgid (s, &u->uid, &u->gid)) { 284 | if (pos != NULL) *pos = ':'; 285 | return 1; 286 | } 287 | 288 | if (pos != NULL) 289 | *pos = ':'; 290 | else 291 | return 0; 292 | 293 | return (pos == NULL) ? 0 : _parse_gids (pos + 1, u); 294 | } 295 | 296 | static int 297 | safe_dup2(int oldfd, int newfd) 298 | { 299 | int ret; 300 | do { 301 | ret = dup2(oldfd, newfd); 302 | } while (ret == -1 && errno == EINTR); 303 | return ret; 304 | } 305 | 306 | void 307 | become_daemon (void) 308 | { 309 | pid_t pid; 310 | int nullfd = safe_openat(AT_FDCWD, "/dev/null", O_RDWR); 311 | if (nullfd < 0) 312 | die ("cannot daemonize, unable to open '/dev/null': %s\n", ERRSTR); 313 | 314 | fd_cloexec (nullfd); 315 | 316 | if (safe_dup2(nullfd, STDIN_FILENO) < 0) 317 | die ("cannot daemonize, unable to redirect stdin: %s\n", ERRSTR); 318 | if (safe_dup2(nullfd, STDOUT_FILENO) < 0) 319 | die ("cannot daemonize, unable to redirect stdout: %s\n", ERRSTR); 320 | if (safe_dup2(nullfd, STDERR_FILENO) < 0) 321 | die ("cannot daemonize, unable to redirect stderr: %s\n", ERRSTR); 322 | 323 | pid = fork (); 324 | 325 | if (pid < 0) die ("cannot daemonize: %s\n", ERRSTR); 326 | if (pid > 0) _exit (EXIT_SUCCESS); 327 | 328 | if (setsid () == -1) 329 | _exit (111); 330 | } 331 | 332 | 333 | static bool 334 | _parse_limit_time(const char *sval, long *rval) 335 | { 336 | assert(sval != NULL); 337 | assert(rval != NULL); 338 | 339 | unsigned long long val; 340 | const struct cflag spec = { .data = &val }; 341 | const bool failed = cflag_timei(&spec, sval) != CFLAG_OK; 342 | *rval = val; 343 | return failed || (val > LONG_MAX); 344 | } 345 | 346 | 347 | static bool 348 | _parse_limit_number(const char *sval, long *rval) 349 | { 350 | assert(sval != NULL); 351 | assert(rval != NULL); 352 | return !(sscanf(sval, "%li", rval) == 1); 353 | } 354 | 355 | 356 | static bool 357 | _parse_limit_bytes (const char *sval, long *rval) 358 | { 359 | assert(sval != NULL); 360 | assert(rval != NULL); 361 | 362 | unsigned long long val; 363 | const struct cflag spec = { .data = &val }; 364 | const bool failed = cflag_bytes(&spec, sval) != CFLAG_OK; 365 | *rval = val; 366 | return failed || (val > LONG_MAX); 367 | } 368 | 369 | 370 | static const struct { 371 | const char *name; 372 | int what; 373 | bool (*parse)(const char*, long*); 374 | const char *desc; 375 | } rlimit_specs[] = { 376 | #ifdef RLIMIT_AS 377 | { "vmem", RLIMIT_AS, _parse_limit_bytes, 378 | "Maximum size of process' virtual memory (bytes)" }, 379 | #endif /* RLIMIT_AS */ 380 | #ifdef RLIMIT_CORE 381 | { "core", RLIMIT_CORE, _parse_limit_bytes, 382 | "Maximum size of core file (bytes)" }, 383 | #endif /* RLIMIT_CORE */ 384 | #ifdef RLIMIT_CPU 385 | { "cpu", RLIMIT_CPU, _parse_limit_time, 386 | "Maximum CPU time used (seconds)" }, 387 | #endif /* RLIMIT_CPU */ 388 | #ifdef RLIMIT_DATA 389 | { "data", RLIMIT_DATA, _parse_limit_bytes, 390 | "Maximum size of data segment (bytes)" }, 391 | #endif /* RLIMIT_DATA */ 392 | #ifdef RLIMIT_FSIZE 393 | { "fsize", RLIMIT_FSIZE, _parse_limit_bytes, 394 | "Maximum size of created files (bytes)" }, 395 | #endif /* RLIMIT_FSIZE */ 396 | #ifdef RLIMIT_LOCKS 397 | { "locks", RLIMIT_LOCKS, _parse_limit_number, 398 | "Maximum number of locked files" }, 399 | #endif /* RLIMIT_LOCKS */ 400 | #ifdef RLIMIT_MEMLOCK 401 | { "mlock", RLIMIT_MEMLOCK, _parse_limit_bytes, 402 | "Maximum number of bytes locked in RAM (bytes)" }, 403 | #endif /* RLIMIT_MEMLOCK */ 404 | #ifdef RLIMIT_MSGQUEUE 405 | { "msgq", RLIMIT_MSGQUEUE, _parse_limit_number, 406 | "Maximum number of bytes used in message queues (bytes)" }, 407 | #endif /* RLIMIT_MSGQUEUE */ 408 | #ifdef RLIMIT_NICE 409 | { "nice", RLIMIT_NICE, _parse_limit_number, 410 | "Ceiling for the process nice value" }, 411 | #endif /* RLIMIT_NICE */ 412 | #ifdef RLIMIT_NOFILE 413 | { "files", RLIMIT_NOFILE, _parse_limit_number, 414 | "Maximum number of open files" }, 415 | #endif /* RLIMIT_NOFILE */ 416 | #ifdef RLIMIT_NPROC 417 | { "nproc", RLIMIT_NPROC, _parse_limit_number, 418 | "Maximum number of processes" }, 419 | #endif /* RLIMIT_NPROC */ 420 | #ifdef RLIMIT_RSS 421 | #warning Building support for RLIMIT_RSS, this may not work on Linux 2.6+ 422 | { "rss", RLIMIT_RSS, _parse_limit_number, 423 | "Maximum number of pages resident in RAM" }, 424 | #endif /* RLIMIT_RSS */ 425 | #ifdef RLIMIT_RTPRIO 426 | { "rtprio", RLIMIT_RTPRIO, _parse_limit_number, 427 | "Ceiling for the real-time priority" }, 428 | #endif /* RLIMIT_RTPRIO */ 429 | #ifdef RLIMIT_RTTIME 430 | { "rttime", RLIMIT_RTTIME, _parse_limit_time, 431 | "Maximum real-time CPU time used (seconds)" }, 432 | #endif /* RLIMIT_RTTIME */ 433 | #ifdef RLIMIT_SIGPENDING 434 | { "sigpending", RLIMIT_SIGPENDING, _parse_limit_number, 435 | "Maximum number of queued signals" }, 436 | #endif /* RLIMIT_SIGPENDING */ 437 | #ifdef RLIMIT_STACK 438 | { "stack", RLIMIT_STACK, _parse_limit_bytes, 439 | "Maximum stack segment size (bytes)" }, 440 | #endif /* RLIMIT_STACK */ 441 | }; 442 | 443 | 444 | int 445 | parse_limit_arg(const char *str, int *what, long *value) 446 | { 447 | assert(str != NULL); 448 | assert(what != NULL); 449 | assert(value != NULL); 450 | 451 | const unsigned n_rlimit_specs = sizeof(rlimit_specs) / sizeof(rlimit_specs[0]); 452 | 453 | if (!strcmp(str, "help")) { 454 | for (unsigned i = 0; i < n_rlimit_specs; i++) 455 | printf("%s -- %s\n", rlimit_specs[i].name, rlimit_specs[i].desc); 456 | return -1; 457 | } 458 | 459 | for (unsigned i = 0; i < n_rlimit_specs; i++) { 460 | unsigned nlen = strlen(rlimit_specs[i].name); 461 | if (!strncmp(str, rlimit_specs[i].name, nlen) && str[nlen] == '=') { 462 | *what = rlimit_specs[i].what; 463 | return ((*rlimit_specs[i].parse)(str + nlen + 1, value)); 464 | } 465 | } 466 | 467 | return 1; 468 | } 469 | 470 | 471 | const char* 472 | limit_name(int what) 473 | { 474 | const unsigned n_rlimit_specs = sizeof(rlimit_specs) / sizeof(rlimit_specs[0]); 475 | 476 | for (unsigned i = 0; i < n_rlimit_specs; i++) { 477 | if (what == rlimit_specs[i].what) 478 | return rlimit_specs[i].name; 479 | } 480 | return ""; 481 | } 482 | 483 | 484 | #ifndef REPLACE_ARGS_VCHUNK 485 | #define REPLACE_ARGS_VCHUNK 16 486 | #endif /* !REPLACE_ARGS_VCHUNK */ 487 | 488 | #ifndef REPLACE_ARGS_SCHUNK 489 | #define REPLACE_ARGS_SCHUNK 32 490 | #endif /* !REPLACE_ARGS_SCHUNK */ 491 | 492 | int 493 | replace_args_string(const char *str, int *pargc, char ***pargv) 494 | { 495 | assert(str); 496 | assert(pargc); 497 | assert(pargv); 498 | 499 | int ch; 500 | char *s = NULL; 501 | int maxarg = REPLACE_ARGS_VCHUNK; 502 | int numarg = 0; 503 | int quotes = 0; 504 | int smax = 0; 505 | int slen = 0; 506 | char **argv = calloc(maxarg, sizeof(char*)); 507 | 508 | /* Copy argv[0] pointer */ 509 | argv[numarg++] = (*pargv)[0]; 510 | 511 | while ((ch = *str++) != '\0') { 512 | if (!quotes && isspace (ch)) { 513 | if (!slen) { 514 | /* 515 | * Got spaces not inside a quote, and the current argument 516 | * is empty: skip spaces at the left side of an argument. 517 | */ 518 | continue; 519 | } 520 | 521 | /* 522 | * Not inside quotes, got space: add '\0', split and reset 523 | */ 524 | if (numarg >= maxarg) { 525 | maxarg += REPLACE_ARGS_VCHUNK; 526 | argv = reallocarray(argv, maxarg, sizeof(char*)); 527 | } 528 | 529 | /* Add terminating "\0" */ 530 | if (slen >= smax) { 531 | smax += REPLACE_ARGS_SCHUNK; 532 | s = s ? calloc(smax, sizeof(char)) 533 | : reallocarray(s, smax, sizeof(char)); 534 | } 535 | 536 | /* Save string in array. */ 537 | s[slen] = '\0'; 538 | argv[numarg++] = s; 539 | 540 | /* Reset stuff */ 541 | smax = slen = 0; 542 | s = NULL; 543 | continue; 544 | } 545 | 546 | /* 547 | * Got a character which is not an space, or *is* an space inside 548 | * quotes. When character is the same as used for start quoting, 549 | * end quoting, or start quoting if it's a quote; otherwise, just 550 | * store the character. 551 | */ 552 | if (quotes && quotes == ch) { 553 | quotes = 0; 554 | } 555 | else if (ch == '"' || ch == '\'') { 556 | quotes = ch; 557 | } 558 | else { 559 | if (!isprint (ch)) { 560 | for (unsigned i = 0; argv[i]; i++) 561 | free(argv[i]); 562 | free(argv); 563 | if (s) { 564 | free(s); 565 | s = NULL; 566 | } 567 | #if defined(EINVAL) 568 | errno = EINVAL; 569 | #elif defined(EILSEQ) 570 | errno = EILSEQ; 571 | #else 572 | #warning Both EINVAL and EILSEQ are undefined, error message will be ambiguous 573 | errno = 0; 574 | #endif 575 | return 1; 576 | } 577 | if (slen >= smax) { 578 | smax += REPLACE_ARGS_SCHUNK; 579 | s = s ? calloc(smax, sizeof(char)) 580 | : reallocarray(s, smax, sizeof(char)); 581 | } 582 | s[slen++] = ch; 583 | } 584 | } 585 | 586 | /* If there is still an in-progres string, store it. */ 587 | if (slen) { 588 | /* Add terminating "\0" */ 589 | if (slen >= smax) { 590 | smax += REPLACE_ARGS_SCHUNK; 591 | s = reallocarray(s, smax, sizeof(char)); 592 | } 593 | 594 | /* Save string in array. */ 595 | s[slen] = '\0'; 596 | argv[numarg++] = s; 597 | } 598 | 599 | /* Copy remaining pointers at the tail */ 600 | if ((maxarg - numarg) <= *pargc) { 601 | maxarg += *pargc; 602 | argv = reallocarray(argv, maxarg, sizeof(char*)); 603 | } 604 | for (ch = 1; ch < *pargc; ch++) 605 | argv[numarg++] = (*pargv)[ch]; 606 | 607 | /* Add terminating NULL */ 608 | argv[numarg] = NULL; 609 | 610 | *pargc = numarg; 611 | *pargv = argv; 612 | 613 | return 0; 614 | } 615 | 616 | void 617 | replace_args_shift(unsigned amount, 618 | int *pargc, 619 | char ***pargv) 620 | { 621 | assert(pargc); 622 | assert(pargv); 623 | assert(amount > 0); 624 | assert(*pargc > (int) amount); 625 | 626 | int argc = *pargc; 627 | char **argv = *pargv; 628 | int i; 629 | 630 | while (amount--) { 631 | argc--; 632 | for (i = 1; i < argc; i++) { 633 | argv[i] = argv[i+1]; 634 | } 635 | } 636 | *pargc = argc; 637 | } 638 | 639 | ssize_t 640 | freaduntil(int fd, 641 | struct dbuf *buffer, 642 | struct dbuf *overflow, 643 | int delimiter, 644 | size_t readbytes) 645 | { 646 | assert(fd >= 0); 647 | assert(buffer); 648 | assert(overflow); 649 | 650 | if (!readbytes) { 651 | static const size_t default_readbytes = 4096; 652 | readbytes = default_readbytes; 653 | } 654 | 655 | for (;;) { 656 | char *pos = memchr(dbuf_cdata (overflow), 657 | delimiter, 658 | dbuf_size (overflow)); 659 | 660 | if (pos) { 661 | /* 662 | * The delimiter has been found in the overflow buffer: remove 663 | * it from there, and copy the data to the result buffer. 664 | */ 665 | size_t len = pos - (const char*) dbuf_cdata (overflow) + 1; 666 | dbuf_addmem(buffer, dbuf_cdata (overflow), len); 667 | overflow->size -= len; 668 | memmove (dbuf_data(overflow), 669 | dbuf_data(overflow) + len, 670 | dbuf_size(overflow)); 671 | return dbuf_size(buffer); 672 | } 673 | 674 | if (overflow->alloc < (dbuf_size(overflow) + readbytes)) { 675 | /* 676 | * XXX Calling dbuf_resize() will *both* resize the buffer data 677 | * area and set overflow->alloc *and* overflow->size. But we 678 | * do not want the later to be changed we save and restore it. 679 | */ 680 | const size_t oldlen = dbuf_size(overflow); 681 | dbuf_resize(overflow, oldlen + readbytes); 682 | overflow->size = oldlen; 683 | } 684 | 685 | ssize_t r = safe_read(fd, dbuf_data(overflow) + dbuf_size(overflow), readbytes); 686 | if (r > 0) { 687 | overflow->size += r; 688 | } else { 689 | /* Handles both EOF and errors. */ 690 | return r; 691 | } 692 | } 693 | } 694 | 695 | NORETURN static inline void 696 | doexit(int code) 697 | { 698 | if (code == 111) 699 | _exit(111); 700 | else 701 | exit(code); 702 | } 703 | 704 | static inline void 705 | errmsgv(const char *format, va_list args) 706 | { 707 | if (format) 708 | vfprintf(stderr, format, args); 709 | fflush(stderr); 710 | } 711 | 712 | void 713 | errexit(int code, const char *format, ...) 714 | { 715 | va_list args; 716 | va_start(args, format); 717 | errmsgv(format, args); 718 | va_end(args); 719 | doexit(code); 720 | } 721 | 722 | void 723 | die(const char *format, ...) 724 | { 725 | va_list args; 726 | va_start(args, format); 727 | errmsgv(format, args); 728 | va_end(args); 729 | doexit(EXIT_FAILURE); 730 | } 731 | 732 | void 733 | fatal(const char *format, ...) 734 | { 735 | va_list args; 736 | va_start(args, format); 737 | errmsgv(format, args); 738 | va_end(args); 739 | doexit(111); 740 | } 741 | 742 | /* vim: expandtab shiftwidth=4 tabstop=4 743 | */ 744 | -------------------------------------------------------------------------------- /dmon.c: -------------------------------------------------------------------------------- 1 | /* 2 | * dmon.c 3 | * Copyright (C) 2010-2024 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #ifndef __linux 9 | #define _BSD_SOURCE 10 | #endif 11 | 12 | #define _GNU_SOURCE 13 | #define _POSIX_C_SOURCE 200809L 14 | 15 | #include "deps/cflag/cflag.h" 16 | #include "deps/clog/clog.h" 17 | #include "conf.h" 18 | #include "task.h" 19 | #include "util.h" 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | 33 | #if !(defined(MULTICALL) && MULTICALL) 34 | # define dmon_main main 35 | #endif /* !MULTICALL */ 36 | 37 | 38 | static int log_fds[2] = { -1, -1 }; 39 | static FILE *status_file = NULL; 40 | static task_t cmd_task = TASK; 41 | static task_t log_task = TASK; 42 | static float load_low = 0.0f; 43 | static float load_high = 0.0f; 44 | static bool success_exit = false; 45 | static int num_respawns = -1; 46 | static bool log_signals = false; 47 | static bool cmd_signals = false; 48 | static unsigned long cmd_timeout = 0; 49 | static unsigned long cmd_interval = 0; 50 | static int check_child = 0; 51 | static int running = 1; 52 | static int paused = 0; 53 | static bool nodaemon = false; 54 | static char *status_path = NULL; 55 | static char *pidfile_path = NULL; 56 | static char *workdir_path = NULL; 57 | 58 | 59 | static const struct { 60 | const char *name; 61 | int code; 62 | } forward_signals[] = { 63 | { "CONT", SIGCONT }, 64 | { "ALRM", SIGALRM }, 65 | { "QUIT", SIGQUIT }, 66 | { "USR1", SIGUSR1 }, 67 | { "USR2", SIGUSR2 }, 68 | { "HUP" , SIGHUP }, 69 | { "NONE", NO_SIGNAL }, 70 | { "STOP", SIGSTOP }, 71 | { "TERM", SIGTERM }, 72 | { "INT" , SIGINT }, 73 | { "KILL", SIGKILL }, 74 | { NULL , NO_SIGNAL }, 75 | }; 76 | 77 | 78 | #define almost_zerof(_v) ((_v) < 0.000000001f) 79 | 80 | #define log_enabled (log_fds[0] != -1) 81 | #define load_enabled (!almost_zerof (load_high)) 82 | 83 | 84 | __attribute__((format (printf, 1, 2))) 85 | static inline void 86 | _write_status (const char *fmt, ...) 87 | { 88 | assert (status_file); 89 | 90 | va_list arg; 91 | va_start (arg, fmt); 92 | ssize_t r = vfprintf (status_file, fmt, arg); 93 | va_end (arg); 94 | 95 | if (r < 0) 96 | clog_warning("Writing to status file: %s.", ERRSTR); 97 | } 98 | 99 | #define write_status(...) \ 100 | if (status_file != NULL) \ 101 | _write_status(__VA_ARGS__) 102 | 103 | 104 | #define task_action_dispatch_and_write_status(_what, _task) \ 105 | do { \ 106 | int __pidafter__ = 0; \ 107 | if (status_file != NULL) { \ 108 | switch ((_task)->action) { \ 109 | case A_NONE: \ 110 | break; \ 111 | case A_START: \ 112 | _write_status ("%s start ", (_what)); \ 113 | __pidafter__ = 1; \ 114 | break; \ 115 | case A_STOP: \ 116 | _write_status ("%s stop %li\n", (_what), \ 117 | (long) ((_task)->pid)); \ 118 | break; \ 119 | case A_SIGNAL: \ 120 | _write_status ("%s signal %li %i\n", (_what), \ 121 | (long) ((_task)->pid), \ 122 | (_task)->signal); \ 123 | break; \ 124 | } \ 125 | } \ 126 | task_action_dispatch (_task); \ 127 | if (__pidafter__ && status_file != NULL) \ 128 | _write_status ("%li\n", (long) ((_task)->pid)); \ 129 | } while (0) 130 | 131 | 132 | const char* 133 | signal_to_name (int signum) 134 | { 135 | static const char *unknown = "(unknown)"; 136 | int i = 0; 137 | 138 | while (forward_signals[i].name != NULL) { 139 | if (forward_signals[i].code == signum) 140 | return forward_signals[i].name; 141 | i++; 142 | } 143 | return unknown; 144 | } 145 | 146 | #if defined(__UCLIBC__) 147 | #include 148 | static int getloadavg(double *a, int n) 149 | { 150 | struct sysinfo si; 151 | if (n <= 0) return n ? -1 : 0; 152 | if (n > 3) n = 3; 153 | 154 | if (sysinfo(&si) != 0) return -1; 155 | 156 | int i = 0; 157 | for (i=0; i 0) { 192 | num_respawns -= 1; 193 | } 194 | task_action_queue (&cmd_task, A_START); 195 | } 196 | return status; 197 | } 198 | else if (log_enabled && pid == log_task.pid) { 199 | clog_debug("Reaped log process %i", pid); 200 | 201 | write_status ("log exit %li %i\n", (long) pid, status); 202 | 203 | log_task.pid = NO_PID; 204 | task_action_queue (&log_task, A_START); 205 | } 206 | else { 207 | clog_debug("Reaped unknown process %i", pid); 208 | } 209 | 210 | /* 211 | * For cases where a return status is not meaningful (PIDs other than 212 | * that of the command being run) just return some invalid return code 213 | * value. 214 | */ 215 | return -1; 216 | } 217 | 218 | 219 | static void 220 | handle_signal (int signum) 221 | { 222 | clog_debug("Got signal %i, %s", signum, signal_to_name(signum)); 223 | 224 | /* Receiving INT/TERM signal will stop gracefully */ 225 | if (signum == SIGINT || signum == SIGTERM) { 226 | running = 0; 227 | return; 228 | } 229 | 230 | /* Handle CHLD: check children */ 231 | if (signum == SIGCHLD) { 232 | check_child = 1; 233 | return; 234 | } 235 | 236 | /* 237 | * If we have a maximum time to run the process, and we receive SIGALRM, 238 | * then the timeout was reached. As per signal(7) it is safe to kill(2) 239 | * the process from a signal handler, so we do that and then mark it for 240 | * respawning. 241 | */ 242 | if (cmd_timeout && signum == SIGALRM) { 243 | write_status ("cmd timeout %li\n", (long) cmd_task.pid); 244 | task_action (&cmd_task, A_STOP); 245 | task_action_queue (&cmd_task, A_START); 246 | alarm (cmd_timeout); 247 | return; 248 | } 249 | 250 | unsigned i = 0; 251 | while (forward_signals[i].code != NO_SIGNAL) { 252 | if (signum == forward_signals[i++].code) 253 | break; 254 | } 255 | 256 | if (signum != NO_SIGNAL) { 257 | /* Try to forward signals */ 258 | if (cmd_signals) { 259 | clog_debug("Delayed signal %i for cmd process", signum); 260 | task_action_queue (&cmd_task, A_SIGNAL); 261 | task_signal_queue (&cmd_task, signum); 262 | } 263 | if (log_signals && log_enabled) { 264 | clog_debug("Delayed signal %i for log process", signum); 265 | task_action_queue (&log_task, A_SIGNAL); 266 | task_signal_queue (&log_task, signum); 267 | } 268 | } 269 | } 270 | 271 | 272 | 273 | static void 274 | setup_signals (void) 275 | { 276 | unsigned i = 0; 277 | struct sigaction sa; 278 | 279 | sa.sa_handler = handle_signal; 280 | sa.sa_flags = SA_NOCLDSTOP; 281 | sigfillset(&sa.sa_mask); 282 | 283 | while (forward_signals[i].code!= NO_SIGNAL) { 284 | safe_sigaction (forward_signals[i].name, 285 | forward_signals[i].code, &sa); 286 | i++; 287 | } 288 | 289 | safe_sigaction ("CHLD", SIGCHLD, &sa); 290 | safe_sigaction ("TERM", SIGTERM, &sa); 291 | safe_sigaction ("INT" , SIGINT , &sa); 292 | } 293 | 294 | 295 | static enum cflag_status 296 | _environ_option(const struct cflag *spec, const char *arg) 297 | { 298 | if (!spec) 299 | return CFLAG_NEEDS_ARG; 300 | 301 | const char *equalsign; 302 | if ((equalsign = strchr(arg, '=')) == NULL) { 303 | unsetenv(arg); 304 | } else { 305 | char *varname = strndup(arg, equalsign - arg); 306 | setenv(varname, equalsign + 1, 1); 307 | free(varname); 308 | } 309 | return CFLAG_OK; 310 | } 311 | 312 | 313 | static enum cflag_status 314 | _rlimit_option(const struct cflag *spec, const char *arg) 315 | { 316 | if (!spec) 317 | return CFLAG_NEEDS_ARG; 318 | 319 | long value; 320 | int limit; 321 | int status = parse_limit_arg(arg, &limit, &value); 322 | 323 | if (status < 0) 324 | return CFLAG_OK; 325 | if (status) 326 | return CFLAG_BAD_FORMAT; 327 | 328 | safe_setrlimit(limit, value); 329 | return CFLAG_OK; 330 | } 331 | 332 | 333 | static enum cflag_status 334 | _store_uidgids_option (const struct cflag *spec, const char *arg) 335 | { 336 | if (!spec) 337 | return CFLAG_NEEDS_ARG; 338 | 339 | char *arg_copy = strdup(arg); 340 | int status = parse_uidgids(arg_copy, spec->data); 341 | free(arg_copy); 342 | 343 | return status ? CFLAG_BAD_FORMAT : CFLAG_OK; 344 | } 345 | 346 | static enum cflag_status 347 | _config_option(const struct cflag *spec, const char *arg) 348 | { 349 | (void) arg; 350 | 351 | if (!spec) 352 | return CFLAG_NEEDS_ARG; 353 | 354 | assert(!"Unreachable"); 355 | clog_error("Option --config/-C must be the first one specified"); 356 | return CFLAG_BAD_FORMAT; 357 | } 358 | 359 | static const struct cflag dmon_options[] = { 360 | { 361 | .name = "config", .letter = 'C', 362 | .func = _config_option, 363 | .help = 364 | "Read options from the specified configuration file. If given, " 365 | "this option must be the first one in the command line.", 366 | }, 367 | CFLAG(bool, "no-daemon", 'n', &nodaemon, 368 | "Do not daemonize, stay in foreground."), 369 | CFLAG(bool, "stderr-redir", 'e', &cmd_task.redir_errfd, 370 | "Redirect command's standard error stream to its standard " 371 | "output stream."), 372 | CFLAG(bool, "cmd-sigs", 's', &cmd_signals, 373 | "Forward signals to command process."), 374 | CFLAG(bool, "log-sigs", 'S', &log_signals, 375 | "Forward signals to log process."), 376 | CFLAG(bool, "once", '1', &success_exit, 377 | "Exit if command exits with a zero return code. The process " 378 | "will be still respawned when it exits with a non-zero code."), 379 | CFLAG(int, "max-respawns", 'm', &num_respawns, 380 | "Exit after max number of respawns no matter the exit code."), 381 | CFLAG(string, "write-info", 'I', &status_path, 382 | "Write information on process status to the given file. " 383 | "Sockets and FIFOs may be used."), 384 | CFLAG(string, "pid-file", 'p', &pidfile_path, 385 | "Write PID to a file in the given path."), 386 | CFLAG(string, "work-dir", 'W', &workdir_path, 387 | "Specify a working directory. All other specified relative paths " 388 | "have to be specified in relation with this directory."), 389 | CFLAG(float, "load-high", 'L', &load_high, 390 | "Stop process when system load surpasses the given value."), 391 | CFLAG(float, "load-low", 'l', &load_low, 392 | "Resume process execution when system load drops below the " 393 | "given value. If not given, defaults to half the value passed " 394 | "to '-L'."), 395 | CFLAG(timei, "timeout", 't', &cmd_timeout, 396 | "If command execution takes longer than the time specified " 397 | "the process will be killed and started again."), 398 | CFLAG(timei, "interval", 'i', &cmd_interval, 399 | "Time to wait between successful command executions. When " 400 | "exit code is non-zero, the interval is ignored and the " 401 | "command is executed again as soon as possible."), 402 | { 403 | .name = "environ", .letter = 'E', 404 | .func = _environ_option, 405 | .help = 406 | "Define an environment variable, or if no value is given, " 407 | "delete it. This option can be specified multiple times.", 408 | }, 409 | { 410 | .name = "limit", .letter = 'r', 411 | .func = _rlimit_option, 412 | .help = 413 | "Sets a resource limit, given as 'name=value'. This option " 414 | "can be specified multiple times. Use '-r help' for a list.", 415 | }, 416 | { 417 | .name = "cmd-user", .letter = 'u', 418 | .func = _store_uidgids_option, 419 | .data = &cmd_task.user, 420 | .help = 421 | "User and (optionally) groups to run the command as. Format " 422 | "is 'user[:group1[:group2[:...groupN]]]'.", 423 | }, 424 | { 425 | .name = "log-user", .letter = 'U', 426 | .func = _store_uidgids_option, 427 | .data = &log_task.user, 428 | .help = 429 | "User and (optionally) groups to run the log process as. " 430 | "Format is 'user[:group1[:group2[:...groupN]]]'.", 431 | }, 432 | CFLAG_HELP, 433 | CFLAG_END 434 | }; 435 | 436 | 437 | int 438 | dmon_main (int argc, char **argv) 439 | { 440 | clog_init(NULL); 441 | 442 | FILE *pid_file = NULL; 443 | char *opts_env = NULL; 444 | 445 | /* Check for -C/--config given as first command line argument. */ 446 | if (argc > 2 && ((argv[1][0] == '-' && 447 | argv[1][1] == 'C' && 448 | argv[1][2] == '\0') || 449 | !strcmp("--config", argv[1]))) { 450 | FILE *input = fopen(argv[2], "r"); 451 | if (!input) 452 | die("%s: Cannot open file '%s', %s\n", argv[0], argv[2], ERRSTR); 453 | 454 | struct dbuf errmsg = DBUF_INIT; 455 | if (!conf_parse(input, dmon_options + 1, &errmsg)) 456 | die("%s: Error parsing %s:%s\n", argv[0], argv[2], dbuf_str(&errmsg)); 457 | 458 | replace_args_shift(2, &argc, &argv); 459 | } 460 | 461 | if ((opts_env = getenv ("DMON_OPTIONS")) != NULL) 462 | replace_args_string (opts_env, &argc, &argv); 463 | 464 | const char *argv0 = cflag_apply(dmon_options, 465 | "cmd [cmd-options] [ -- " 466 | "log-cmd [log-cmd-options]]", 467 | &argc, &argv); 468 | 469 | if (workdir_path) { 470 | if (chdir (workdir_path) != 0) 471 | die ("%s: Cannot use '%s' as work directory, %s\n", argv0, workdir_path, ERRSTR); 472 | } 473 | 474 | if (status_path) { 475 | int fd = safe_openatm(AT_FDCWD, status_path, O_WRONLY | O_CREAT | O_APPEND, 0666); 476 | if (fd < 0) 477 | die ("%s: Cannot open '%s' for writing, %s\n", argv0, status_path, ERRSTR); 478 | status_file = fdopen (fd, "w"); 479 | setvbuf (status_file, NULL, _IOLBF, 0); 480 | } 481 | 482 | if (cmd_interval && success_exit) 483 | die ("%s: Options '-i' and '-1' cannot be used together.\n", argv0); 484 | 485 | if (load_enabled && almost_zerof (load_low)) 486 | load_low = load_high / 2.0f; 487 | 488 | cmd_task.argv = argv; 489 | 490 | /* Skip over until "--" is found */ 491 | unsigned i = 0; 492 | while (i < (unsigned) argc && strcmp (argv[i], "--") != 0) { 493 | cmd_task.argc++; 494 | i++; 495 | } 496 | 497 | /* There is a log command */ 498 | if (i < (unsigned) argc && strcmp (argv[i], "--") == 0) { 499 | log_task.argc = argc - cmd_task.argc - 1; 500 | log_task.argv = argv + argc - log_task.argc; 501 | log_task.argv[log_task.argc] = NULL; 502 | } 503 | 504 | cmd_task.argv[cmd_task.argc] = NULL; 505 | 506 | if (log_task.argc > 0) { 507 | if (pipe (log_fds) != 0) { 508 | die ("%s: Cannot create pipe: %s\n", argv0, ERRSTR); 509 | } 510 | clog_debug("pipe_read = %i, pipe_write = %i\n", log_fds[0], log_fds[1]); 511 | fd_cloexec (log_fds[0]); 512 | fd_cloexec (log_fds[1]); 513 | } 514 | 515 | if (clog_debug_enabled) { 516 | char **xxargv = cmd_task.argv; 517 | fputs("cmd:", stderr); 518 | while (*xxargv) { 519 | fputc(' ', stderr); 520 | fputs(*xxargv++, stderr); 521 | } 522 | fputc('\n', stderr); 523 | if (log_enabled) { 524 | char **xxargv = log_task.argv; 525 | fputs("log:", stderr); 526 | while (*xxargv) { 527 | fputc(' ', stderr); 528 | fputs(*xxargv++, stderr); 529 | } 530 | fputc('\n', stderr); 531 | } 532 | } 533 | 534 | if (cmd_task.argc == 0) 535 | die ("%s: No command to run given.\n", argv0); 536 | 537 | if (pidfile_path) { 538 | int fd = safe_openatm(AT_FDCWD, pidfile_path, O_TRUNC | O_CREAT | O_WRONLY, 0666); 539 | if (fd < 0) { 540 | die ("%s: cannot open '%s' for writing, %s\n", 541 | argv0, pidfile_path, ERRSTR); 542 | } 543 | pid_file = fdopen (fd, "w"); 544 | } 545 | 546 | if (!nodaemon) 547 | become_daemon (); 548 | 549 | /* We have a valid file descriptor: write PID */ 550 | if (pid_file) { 551 | if (fprintf (pid_file, "%li\n", (long) getpid ()) < 0) 552 | clog_warning("Writing to PID file: %s", ERRSTR); 553 | fclose (pid_file); 554 | pid_file = NULL; 555 | } 556 | 557 | setup_signals (); 558 | alarm (cmd_timeout); 559 | 560 | cmd_task.write_fd = log_fds[1]; 561 | log_task.read_fd = log_fds[0]; 562 | 563 | int retcode = 0; 564 | while (running) { 565 | clog_debug(">>> loop iteration"); 566 | if (check_child) { 567 | retcode = reap_and_check (); 568 | clog_debug("retcode = %d", retcode); 569 | 570 | /* 571 | * Wait the specified timeout but DO NOT use safe_sleep(): here 572 | * we want an interruptible sleep-wait so reaction to signals is 573 | * quick, which we definitely want for SIGINT/SIGTERM. 574 | */ 575 | if (cmd_interval && !success_exit && retcode == 0 && num_respawns != 0) { 576 | int retval; 577 | struct timespec ts; 578 | ts.tv_sec = cmd_interval; 579 | ts.tv_nsec = 0; 580 | 581 | do { 582 | retval = nanosleep (&ts, &ts); 583 | clog_debug("nanosleep -> %i\n", retval); 584 | } while (retval == -1 && errno == EINTR && running); 585 | } 586 | 587 | /* 588 | * Either handling signals which interrupt the previous loop, 589 | * or reap_and_check() may request stopping on successful exit 590 | */ 591 | if (!running) { 592 | task_action_queue (&cmd_task, A_NONE); 593 | break; 594 | } 595 | } 596 | 597 | task_action_dispatch_and_write_status ("cmd", &cmd_task); 598 | if (log_enabled) 599 | task_action_dispatch_and_write_status ("log", &log_task); 600 | 601 | if (load_enabled) { 602 | double load_cur; 603 | 604 | clog_debug("Checking load after sleeping 1s"); 605 | interruptible_sleep (1); 606 | 607 | if (getloadavg (&load_cur, 1) == -1) 608 | clog_debug("getloadavg() failed: %s", ERRSTR); 609 | 610 | if (paused) { 611 | /* If the current load dropped below load_low -> resume */ 612 | if (load_cur <= load_low) { 613 | clog_debug("Resuming..."); 614 | task_signal (&cmd_task, SIGCONT); 615 | write_status ("cmd resume %li\n", (long) cmd_task.pid); 616 | paused = 0; 617 | } 618 | } 619 | else { 620 | /* If the load went above load_high -> pause */ 621 | if (load_cur > load_high) { 622 | clog_debug("Pausing..."); 623 | task_signal (&cmd_task, SIGSTOP); 624 | write_status ("cmd pause %li\n", (long) cmd_task.pid); 625 | paused = 1; 626 | } 627 | } 628 | } 629 | else { 630 | /* Wait for signals to arrive. */ 631 | clog_debug("Waiting for signals to come..."); 632 | pause (); 633 | } 634 | } 635 | 636 | clog_debug("Exiting gracefully..."); 637 | 638 | if (cmd_task.pid != NO_PID) { 639 | write_status ("cmd stop %li\n", (long) cmd_task.pid); 640 | task_action (&cmd_task, A_STOP); 641 | } 642 | if (log_enabled && log_task.pid != NO_PID) { 643 | write_status ("log stop %li\n", (long) log_task.pid); 644 | task_action (&log_task, A_STOP); 645 | } 646 | 647 | if (status_file) { 648 | fclose (status_file); 649 | status_file = NULL; 650 | } 651 | 652 | if (WIFEXITED (retcode)) 653 | exit (WEXITSTATUS (retcode)); 654 | 655 | exit (EXIT_FAILURE); 656 | } 657 | 658 | /* vim: expandtab shiftwidth=4 tabstop=4 659 | */ 660 | --------------------------------------------------------------------------------