1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
|
#include "spawn.h"
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define LOG_MODULE "spawn"
#define LOG_ENABLE_DBG 0
#include "log.h"
#include "debug.h"
#include "xmalloc.h"
pid_t
spawn(struct reaper *reaper, const char *cwd, char *const argv[],
int stdin_fd, int stdout_fd, int stderr_fd, reaper_cb cb, void *cb_data,
const char *xdg_activation_token)
{
int pipe_fds[2] = {-1, -1};
if (pipe2(pipe_fds, O_CLOEXEC) < 0) {
LOG_ERRNO("failed to create pipe");
goto err;
}
pid_t pid = fork();
if (pid < 0) {
LOG_ERRNO("failed to fork");
goto err;
}
if (pid == 0) {
/* Child */
close(pipe_fds[0]);
if (setsid() < 0)
goto child_err;
/* Clear signal mask */
sigset_t mask;
sigemptyset(&mask);
if (sigprocmask(SIG_SETMASK, &mask, NULL) < 0)
goto child_err;
/* Restore ignored (SIG_IGN) signals */
struct sigaction dfl = {.sa_handler = SIG_DFL};
sigemptyset(&dfl.sa_mask);
if (sigaction(SIGHUP, &dfl, NULL) < 0 ||
sigaction(SIGPIPE, &dfl, NULL) < 0)
{
goto child_err;
}
if (cwd != NULL) {
setenv("PWD", cwd, 1);
if (chdir(cwd) < 0) {
LOG_WARN("failed to change working directory to %s: %s",
cwd, strerror(errno));
}
}
if (xdg_activation_token != NULL) {
setenv("XDG_ACTIVATION_TOKEN", xdg_activation_token, 1);
if (getenv("DISPLAY") != NULL)
setenv("DESKTOP_STARTUP_ID", xdg_activation_token, 1);
}
bool close_stderr = stderr_fd >= 0;
bool close_stdout = stdout_fd >= 0 && stdout_fd != stderr_fd;
bool close_stdin = stdin_fd >= 0 && stdin_fd != stdout_fd && stdin_fd != stderr_fd;
if ((stdin_fd >= 0 && (dup2(stdin_fd, STDIN_FILENO) < 0
|| (close_stdin && close(stdin_fd) < 0))) ||
(stdout_fd >= 0 && (dup2(stdout_fd, STDOUT_FILENO) < 0
|| (close_stdout && close(stdout_fd) < 0))) ||
(stderr_fd >= 0 && (dup2(stderr_fd, STDERR_FILENO) < 0
|| (close_stderr && close(stderr_fd) < 0))) ||
execvp(argv[0], argv) < 0)
{
goto child_err;
}
xassert(false);
_exit(errno);
child_err:
;
const int errno_copy = errno;
(void)!write(pipe_fds[1], &errno_copy, sizeof(errno_copy));
_exit(errno_copy);
}
/* Parent */
close(pipe_fds[1]);
int errno_copy;
static_assert(sizeof(errno_copy) == sizeof(errno), "errno size mismatch");
ssize_t ret = read(pipe_fds[0], &errno_copy, sizeof(errno_copy));
close(pipe_fds[0]);
if (ret == 0) {
reaper_add(reaper, pid, cb, cb_data);
return pid;
} else if (ret < 0) {
LOG_ERRNO("failed to read from pipe");
return -1;
} else {
LOG_ERRNO_P(errno_copy, "%s: failed to spawn", argv[0]);
errno = errno_copy;
waitpid(pid, NULL, 0);
return -1;
}
err:
if (pipe_fds[0] != -1)
close(pipe_fds[0]);
if (pipe_fds[1] != -1)
close(pipe_fds[1]);
return -1;
}
bool
spawn_expand_template(const struct config_spawn_template *template,
size_t key_count,
const char *key_names[static key_count],
const char *key_values[static key_count],
size_t *argc, char ***argv)
{
*argc = 0;
*argv = NULL;
for (; template->argv.args[*argc] != NULL; (*argc)++)
;
#define append(s, n) \
do { \
expanded = xrealloc(expanded, len + (n) + 1); \
memcpy(&expanded[len], s, n); \
len += n; \
expanded[len] = '\0'; \
} while (0)
*argv = xmalloc((*argc + 1) * sizeof((*argv)[0]));
/* Expand the provided keys */
for (size_t i = 0; i < *argc; i++) {
size_t len = 0;
char *expanded = NULL;
char *start = NULL;
char *last_end = template->argv.args[i];
while ((start = strstr(last_end, "${")) != NULL) {
/* Append everything from the last template's end to this
* one's beginning */
append(last_end, start - last_end);
/* Find end of template */
start += 2;
char *end = strstr(start, "}");
if (end == NULL) {
/* Ensure final append() copies the unclosed '${' */
last_end = start - 2;
LOG_WARN("notify: unclosed template: %s", last_end);
break;
}
/* Expand template */
bool valid_key = false;
for (size_t j = 0; j < key_count; j++) {
if (strncmp(start, key_names[j], end - start) != 0)
continue;
append(key_values[j], strlen(key_values[j]));
valid_key = true;
break;
}
if (!valid_key) {
/* Unrecognized template - append it as-is */
start -= 2;
append(start, end + 1 - start);
LOG_WARN("notify: unrecognized template: %.*s",
(int)(end + 1 - start), start);
}
last_end = end + 1;
}
append(
last_end,
template->argv.args[i] + strlen(template->argv.args[i]) - last_end);
(*argv)[i] = expanded;
}
(*argv)[*argc] = NULL;
#undef append
return true;
}
|