Coverage Report

Created: 2025-07-11 06:26

/src/h2o/lib/common/serverutil.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2014-2016 DeNA Co., Ltd., Kazuho Oku, Nick Desaulniers
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining a copy
5
 * of this software and associated documentation files (the "Software"), to
6
 * deal in the Software without restriction, including without limitation the
7
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8
 * sell copies of the Software, and to permit persons to whom the Software is
9
 * furnished to do so, subject to the following conditions:
10
 *
11
 * The above copyright notice and this permission notice shall be included in
12
 * all copies or substantial portions of the Software.
13
 *
14
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20
 * IN THE SOFTWARE.
21
 */
22
#include <errno.h>
23
#include <fcntl.h>
24
#include <grp.h>
25
#include <pthread.h>
26
#include <pwd.h>
27
#include <signal.h>
28
#ifndef __linux__
29
#include <spawn.h>
30
#endif
31
#include <stdint.h>
32
#include <stdlib.h>
33
#include <string.h>
34
#include <sys/types.h>
35
#include <sys/wait.h>
36
#include <unistd.h>
37
#if !defined(_SC_NPROCESSORS_ONLN)
38
#include <sys/sysctl.h>
39
#endif
40
#include "cloexec.h"
41
#include "h2o/memory.h"
42
#include "h2o/serverutil.h"
43
#include "h2o/socket.h"
44
#include "h2o/string_.h"
45
46
void h2o_set_signal_handler(int signo, void (*cb)(int signo))
47
0
{
48
0
    struct sigaction action;
49
50
0
    memset(&action, 0, sizeof(action));
51
0
    sigemptyset(&action.sa_mask);
52
0
    action.sa_handler = cb;
53
0
    sigaction(signo, &action, NULL);
54
0
}
55
56
int h2o_setuidgid(const char *user)
57
0
{
58
0
    struct passwd pwbuf, *pw;
59
0
    char buf[65536]; /* should be large enough */
60
61
0
    errno = 0;
62
0
    if (getpwnam_r(user, &pwbuf, buf, sizeof(buf), &pw) != 0) {
63
0
        h2o_perror("getpwnam_r");
64
0
        return -1;
65
0
    }
66
0
    if (pw == NULL) {
67
0
        h2o_error_printf("unknown user:%s\n", user);
68
0
        return -1;
69
0
    }
70
0
    if (setgid(pw->pw_gid) != 0) {
71
0
        h2o_error_printf("setgid(%d) failed:%s\n", (int)pw->pw_gid, strerror(errno));
72
0
        return -1;
73
0
    }
74
0
    if (initgroups(pw->pw_name, pw->pw_gid) != 0) {
75
0
        h2o_error_printf("initgroups(%s, %d) failed:%s\n", pw->pw_name, (int)pw->pw_gid, strerror(errno));
76
0
        return -1;
77
0
    }
78
0
    if (setuid(pw->pw_uid) != 0) {
79
0
        h2o_error_printf("setuid(%d) failed:%s\n", (int)pw->pw_uid, strerror(errno));
80
0
        return -1;
81
0
    }
82
83
0
    return 0;
84
0
}
85
86
size_t h2o_server_starter_get_fds(int **_fds)
87
0
{
88
0
    const char *ports_env, *start, *end, *eq;
89
0
    size_t t;
90
0
    H2O_VECTOR(int) fds = {NULL};
91
92
0
    if ((ports_env = getenv(SERVER_STARTER_PORT)) == NULL)
93
0
        return 0;
94
0
    if (ports_env[0] == '\0') {
95
0
        h2o_error_printf("$" SERVER_STARTER_PORT " is empty\n");
96
0
        return SIZE_MAX;
97
0
    }
98
99
    /* ports_env example: 127.0.0.1:80=3;/tmp/sock=4 */
100
0
    for (start = ports_env; *start != '\0'; start = *end == ';' ? end + 1 : end) {
101
0
        if ((end = strchr(start, ';')) == NULL)
102
0
            end = start + strlen(start);
103
0
        if ((eq = memchr(start, '=', end - start)) == NULL) {
104
0
            h2o_error_printf("invalid $" SERVER_STARTER_PORT ", an element without `=` in: %s\n", ports_env);
105
0
            goto Error;
106
0
        }
107
0
        if ((t = h2o_strtosize(eq + 1, end - eq - 1)) == SIZE_MAX) {
108
0
            h2o_error_printf("invalid file descriptor number in $" SERVER_STARTER_PORT ": %s\n", ports_env);
109
0
            goto Error;
110
0
        }
111
0
        h2o_vector_reserve(NULL, &fds, fds.size + 1);
112
0
        fds.entries[fds.size++] = (int)t;
113
0
    }
114
115
0
    *_fds = fds.entries;
116
0
    return fds.size;
117
0
Error:
118
0
    free(fds.entries);
119
0
    return SIZE_MAX;
120
0
}
121
122
static char **build_spawn_env(void)
123
0
{
124
0
    extern char **environ;
125
0
    size_t num;
126
127
    /* calculate number of envvars, as well as looking for H2O_ROOT= */
128
0
    for (num = 0; environ[num] != NULL; ++num)
129
0
        if (strncmp(environ[num], "H2O_ROOT=", sizeof("H2O_ROOT=") - 1) == 0)
130
0
            return NULL;
131
132
    /* not found */
133
0
    char **newenv = h2o_mem_alloc(sizeof(*newenv) * (num + 2) + sizeof("H2O_ROOT=" H2O_TO_STR(H2O_ROOT)));
134
0
    memcpy(newenv, environ, sizeof(*newenv) * num);
135
0
    newenv[num] = (char *)(newenv + num + 2);
136
0
    newenv[num + 1] = NULL;
137
0
    strcpy(newenv[num], "H2O_ROOT=" H2O_TO_STR(H2O_ROOT));
138
139
0
    return newenv;
140
0
}
141
142
pid_t h2o_spawnp(const char *cmd, char *const *argv, const int *mapped_fds, int cloexec_mutex_is_locked)
143
0
{
144
0
#if defined(__linux__)
145
#ifndef _GNU_SOURCE
146
    extern int pipe2(int pipefd[2], int flags);
147
#endif
148
149
    /* Before glibc 2.24, posix_spawnp of Linux does not return error if the executable does not exist, see
150
     * https://gist.github.com/kazuho/0c233e6f86d27d6e4f09
151
     */
152
0
    extern char **environ;
153
0
    int pipefds[2] = {-1, -1}, errnum;
154
0
    pid_t pid;
155
156
    /* create pipe, used for sending error codes */
157
0
    if (pipe2(pipefds, O_CLOEXEC) != 0)
158
0
        goto Error;
159
160
    /* fork */
161
0
    if (!cloexec_mutex_is_locked)
162
0
        pthread_mutex_lock(&cloexec_mutex);
163
0
    if ((pid = fork()) == 0) {
164
        /* in child process, map the file descriptors and execute; return the errnum through pipe if exec failed */
165
0
        if (mapped_fds != NULL) {
166
0
            for (; *mapped_fds != -1; mapped_fds += 2) {
167
0
                if (mapped_fds[0] != mapped_fds[1]) {
168
0
                    if (mapped_fds[1] != -1)
169
0
                        dup2(mapped_fds[0], mapped_fds[1]);
170
0
                    close(mapped_fds[0]);
171
0
                }
172
0
            }
173
0
        }
174
0
        char **env = build_spawn_env();
175
0
        if (env != NULL)
176
0
            environ = env;
177
0
        execvp(cmd, argv);
178
0
        errnum = errno;
179
0
        write(pipefds[1], &errnum, sizeof(errnum));
180
0
        _exit(EX_SOFTWARE);
181
0
    }
182
0
    if (!cloexec_mutex_is_locked)
183
0
        pthread_mutex_unlock(&cloexec_mutex);
184
0
    if (pid == -1)
185
0
        goto Error;
186
187
    /* parent process */
188
0
    close(pipefds[1]);
189
0
    pipefds[1] = -1;
190
0
    ssize_t rret;
191
0
    errnum = 0;
192
0
    while ((rret = read(pipefds[0], &errnum, sizeof(errnum))) == -1 && errno == EINTR)
193
0
        ;
194
0
    if (rret != 0) {
195
        /* spawn failed */
196
0
        while (waitpid(pid, NULL, 0) != pid)
197
0
            ;
198
0
        pid = -1;
199
0
        errno = errnum;
200
0
        goto Error;
201
0
    }
202
203
    /* spawn succeeded */
204
0
    close(pipefds[0]);
205
0
    return pid;
206
207
0
Error:
208
0
    errnum = errno;
209
0
    if (pipefds[0] != -1)
210
0
        close(pipefds[0]);
211
0
    if (pipefds[1] != -1)
212
0
        close(pipefds[1]);
213
0
    errno = errnum;
214
0
    return -1;
215
216
#else
217
218
    posix_spawn_file_actions_t file_actions;
219
    pid_t pid;
220
    extern char **environ;
221
    char **env = build_spawn_env();
222
    posix_spawn_file_actions_init(&file_actions);
223
    if (mapped_fds != NULL) {
224
        for (; *mapped_fds != -1; mapped_fds += 2) {
225
            if (mapped_fds[1] != -1)
226
                posix_spawn_file_actions_adddup2(&file_actions, mapped_fds[0], mapped_fds[1]);
227
            posix_spawn_file_actions_addclose(&file_actions, mapped_fds[0]);
228
        }
229
    }
230
    if (!cloexec_mutex_is_locked)
231
        pthread_mutex_lock(&cloexec_mutex);
232
    errno = posix_spawnp(&pid, cmd, &file_actions, NULL, argv, env != NULL ? env : environ);
233
    if (!cloexec_mutex_is_locked)
234
        pthread_mutex_unlock(&cloexec_mutex);
235
    free(env);
236
    posix_spawn_file_actions_destroy(&file_actions);
237
    if (errno != 0)
238
        return -1;
239
240
    return pid;
241
242
#endif
243
0
}
244
245
int h2o_read_command(const char *cmd, char **argv, h2o_iovec_t std_in, h2o_buffer_t **resp, int *child_status)
246
0
{
247
0
    int respfds[2] = {-1, -1}, inputfds[2] = {-1, -1};
248
0
    pid_t pid = -1;
249
0
    int mutex_locked = 0, ret = -1;
250
251
0
    h2o_buffer_init(resp, &h2o_socket_buffer_prototype);
252
253
0
    pthread_mutex_lock(&cloexec_mutex);
254
0
    mutex_locked = 1;
255
256
    /* create pipes for reading the result and for supplying input */
257
0
    if (pipe(respfds) != 0)
258
0
        goto Exit;
259
0
    if (fcntl(respfds[0], F_SETFD, FD_CLOEXEC) < 0)
260
0
        goto Exit;
261
0
    if (pipe(inputfds) != 0)
262
0
        goto Exit;
263
0
    if (fcntl(inputfds[1], F_SETFD, FD_CLOEXEC) < 0)
264
0
        goto Exit;
265
266
    /* spawn */
267
0
    int mapped_fds[] = {inputfds[0], 0, /* stdin of the child process is what is being provide as input */
268
0
                        respfds[1], 1,  /* stdout of the child process is read from the pipe */
269
0
                        -1};
270
0
    if ((pid = h2o_spawnp(cmd, argv, mapped_fds, 1)) == -1)
271
0
        goto Exit;
272
0
    close(respfds[1]);
273
0
    respfds[1] = -1;
274
0
    close(inputfds[0]);
275
0
    inputfds[0] = -1;
276
277
0
    pthread_mutex_unlock(&cloexec_mutex);
278
0
    mutex_locked = 0;
279
280
    /* supply input */
281
0
    for (size_t off = 0; off < std_in.len;) {
282
0
        ssize_t r;
283
0
        while ((r = write(inputfds[1], std_in.base + off, std_in.len - off)) == -1 && errno == EINTR)
284
0
            ;
285
0
        if (r < 0)
286
0
            break;
287
0
        off += r;
288
0
    }
289
0
    close(inputfds[1]);
290
0
    inputfds[1] = -1;
291
292
    /* read the response from pipe */
293
0
    while (1) {
294
0
        h2o_iovec_t buf = h2o_buffer_reserve(resp, 8192);
295
0
        ssize_t r;
296
0
        while ((r = read(respfds[0], buf.base, buf.len)) == -1 && errno == EINTR)
297
0
            ;
298
0
        if (r <= 0)
299
0
            break;
300
0
        (*resp)->size += r;
301
0
    }
302
303
0
Exit:
304
0
    if (mutex_locked)
305
0
        pthread_mutex_unlock(&cloexec_mutex);
306
0
    if (pid != -1) {
307
        /* wait for the child to complete */
308
0
        pid_t r;
309
0
        while ((r = waitpid(pid, child_status, 0)) == -1 && errno == EINTR)
310
0
            ;
311
0
        if (r == pid) {
312
            /* success */
313
0
            ret = 0;
314
0
        }
315
0
    }
316
0
#define CLOSE_FD(x)                                                                                                                \
317
0
    do {                                                                                                                           \
318
0
        if ((x) != -1)                                                                                                             \
319
0
            close(x);                                                                                                              \
320
0
    } while (0)
321
0
    CLOSE_FD(respfds[0]);
322
0
    CLOSE_FD(respfds[1]);
323
0
    CLOSE_FD(inputfds[0]);
324
0
    CLOSE_FD(inputfds[1]);
325
0
#undef CLOSE_FD
326
0
    if (ret != 0)
327
0
        h2o_buffer_dispose(resp);
328
329
0
    return ret;
330
0
}
331
332
size_t h2o_numproc(void)
333
0
{
334
0
#if defined(_SC_NPROCESSORS_ONLN)
335
0
    return (size_t)sysconf(_SC_NPROCESSORS_ONLN);
336
#elif defined(CTL_HW) && defined(HW_AVAILCPU)
337
    int name[] = {CTL_HW, HW_AVAILCPU};
338
    int ncpu;
339
    size_t ncpu_sz = sizeof(ncpu);
340
    if (sysctl(name, sizeof(name) / sizeof(name[0]), &ncpu, &ncpu_sz, NULL, 0) != 0 || sizeof(ncpu) != ncpu_sz) {
341
        h2o_error_printf("[ERROR] failed to obtain number of CPU cores, assuming as one\n");
342
        ncpu = 1;
343
    }
344
    return ncpu;
345
#else
346
    return 1;
347
#endif
348
0
}