xref: /unit/src/nxt_process.c (revision 0:a63ceefd6ab0)
1 
2 /*
3  * Copyright (C) Igor Sysoev
4  * Copyright (C) NGINX, Inc.
5  */
6 
7 #include <nxt_main.h>
8 
9 
10 static nxt_int_t nxt_user_groups_get(nxt_user_cred_t *uc);
11 
12 
13 /* A cached process pid. */
14 nxt_pid_t  nxt_pid;
15 
16 /* An original parent process pid. */
17 nxt_pid_t  nxt_ppid;
18 
19 
20 nxt_pid_t
21 nxt_process_create(nxt_process_start_t start, void *data, const char *name)
22 {
23     nxt_pid_t     pid;
24     nxt_thread_t  *thr;
25 
26     thr = nxt_thread();
27 
28     pid = fork();
29 
30     switch (pid) {
31 
32     case -1:
33         nxt_log_alert(thr->log, "fork() failed while creating \"%s\" %E",
34                       name, nxt_errno);
35         break;
36 
37     case 0:
38         /* A child. */
39         nxt_pid = getpid();
40 
41         /* Clean inherited cached thread tid. */
42         thr->tid = 0;
43 
44         start(data);
45         break;
46 
47     default:
48         /* A parent. */
49         nxt_log_debug(thr->log, "fork(): %PI", pid);
50         break;
51     }
52 
53     return pid;
54 }
55 
56 
57 #if (NXT_HAVE_POSIX_SPAWN)
58 
59 /*
60  * Linux glibc 2.2 posix_spawn() is implemented via fork()/execve().
61  * Linux glibc 2.4 posix_spawn() without file actions and spawn
62  * attributes uses vfork()/execve().
63  *
64  * On FreeBSD 8.0 posix_spawn() is implemented via vfork()/execve().
65  *
66  * Solaris 10:
67  *   In the Solaris 10 OS, posix_spawn() is currently implemented using
68  *   private-to-libc vfork(), execve(), and exit() functions.  They are
69  *   identical to regular vfork(), execve(), and exit() in functionality,
70  *   but they are not exported from libc and therefore don't cause the
71  *   deadlock-in-the-dynamic-linker problem that any multithreaded code
72  *   outside of libc that calls vfork() can cause.
73  *
74  * On MacOSX 10.5 (Leoprad) and NetBSD 6.0 posix_spawn() is implemented
75  * as syscall.
76  */
77 
78 nxt_pid_t
79 nxt_process_execute(char *name, char **argv, char **envp)
80 {
81     nxt_pid_t  pid;
82 
83     nxt_thread_log_debug("posix_spawn(\"%s\")", name);
84 
85     if (posix_spawn(&pid, name, NULL, NULL, argv, envp) != 0) {
86         nxt_thread_log_alert("posix_spawn(\"%s\") failed %E", name, nxt_errno);
87         return -1;
88     }
89 
90     return pid;
91 }
92 
93 #else
94 
95 nxt_pid_t
96 nxt_process_execute(char *name, char **argv, char **envp)
97 {
98     nxt_pid_t  pid;
99 
100     /*
101      * vfork() is better than fork() because:
102      *   it is faster several times;
103      *   its execution time does not depend on private memory mapping size;
104      *   it has lesser chances to fail due to the ENOMEM error.
105      */
106 
107     pid = vfork();
108 
109     switch (pid) {
110 
111     case -1:
112         nxt_thread_log_alert("vfork() failed while executing \"%s\" %E",
113                              name, nxt_errno);
114         break;
115 
116     case 0:
117         /* A child. */
118         nxt_thread_log_debug("execve(\"%s\")", name);
119 
120         (void) execve(name, argv, envp);
121 
122         nxt_thread_log_alert("execve(\"%s\") failed %E", name, nxt_errno);
123 
124         exit(1);
125         break;
126 
127     default:
128         /* A parent. */
129         nxt_thread_log_debug("vfork(): %PI", pid);
130         break;
131     }
132 
133     return pid;
134 }
135 
136 #endif
137 
138 
139 nxt_int_t
140 nxt_process_daemon(void)
141 {
142     nxt_fd_t      fd;
143     nxt_pid_t     pid;
144     const char    *msg;
145     nxt_thread_t  *thr;
146 
147     thr = nxt_thread();
148 
149     /*
150      * fork() followed by a parent process's exit() detaches a child process
151      * from an init script or terminal shell process which has started the
152      * parent process and allows the child process to run in background.
153      */
154 
155     pid = fork();
156 
157     switch (pid) {
158 
159     case -1:
160         msg = "fork() failed %E";
161         goto fail;
162 
163     case 0:
164         /* A child. */
165         break;
166 
167     default:
168         /* A parent. */
169         nxt_log_debug(thr->log, "fork(): %PI", pid);
170         exit(0);
171         nxt_unreachable();
172     }
173 
174     nxt_pid = getpid();
175 
176     /* Clean inherited cached thread tid. */
177     thr->tid = 0;
178 
179     nxt_log_debug(thr->log, "daemon");
180 
181     /* Detach from controlling terminal. */
182 
183     if (setsid() == -1) {
184         nxt_log_emerg(thr->log, "setsid() failed %E", nxt_errno);
185         return NXT_ERROR;
186     }
187 
188     /*
189      * Reset file mode creation mask: any access
190      * rights can be set on file creation.
191      */
192     umask(0);
193 
194     /* Redirect STDIN and STDOUT to the "/dev/null". */
195 
196     fd = open("/dev/null", O_RDWR);
197     if (fd == -1) {
198         msg = "open(\"/dev/null\") failed %E";
199         goto fail;
200     }
201 
202     if (dup2(fd, STDIN_FILENO) == -1) {
203         msg = "dup2(\"/dev/null\", STDIN) failed %E";
204         goto fail;
205     }
206 
207     if (dup2(fd, STDOUT_FILENO) == -1) {
208         msg = "dup2(\"/dev/null\", STDOUT) failed %E";
209         goto fail;
210     }
211 
212     if (fd > STDERR_FILENO) {
213         nxt_fd_close(fd);
214     }
215 
216     return NXT_OK;
217 
218 fail:
219 
220     nxt_log_emerg(thr->log, msg, nxt_errno);
221 
222     return NXT_ERROR;
223 }
224 
225 
226 void
227 nxt_nanosleep(nxt_nsec_t ns)
228 {
229     struct timespec  ts;
230 
231     ts.tv_sec = ns / 1000000000;
232     ts.tv_nsec = ns % 1000000000;
233 
234     (void) nanosleep(&ts, NULL);
235 }
236 
237 
238 nxt_int_t
239 nxt_user_cred_get(nxt_user_cred_t *uc, const char *group)
240 {
241     struct group   *grp;
242     struct passwd  *pwd;
243 
244     pwd = getpwnam(uc->user);
245 
246     if (nxt_slow_path(pwd == NULL)) {
247         nxt_thread_log_emerg("getpwnam(%s) failed %E", uc->user, nxt_errno);
248         return NXT_ERROR;
249     }
250 
251     uc->uid = pwd->pw_uid;
252     uc->base_gid = pwd->pw_gid;
253 
254     if (group != NULL) {
255         grp = getgrnam(group);
256 
257         if (nxt_slow_path(grp == NULL)) {
258             nxt_thread_log_emerg("getgrnam(%s) failed %E", group, nxt_errno);
259             return NXT_ERROR;
260         }
261 
262         uc->base_gid = grp->gr_gid;
263     }
264 
265     if (getuid() == 0) {
266         return nxt_user_groups_get(uc);
267     }
268 
269     return NXT_OK;
270 }
271 
272 
273 /*
274  * nxt_user_groups_get() stores an array of groups IDs which should be
275  * set by the initgroups() function for a given user.  The initgroups()
276  * may block a just forked worker process for some time if LDAP or NDIS+
277  * is used, so nxt_user_groups_get() allows to get worker user groups in
278  * master process.  In a nutshell the initgroups() calls getgrouplist()
279  * followed by setgroups().  However Solaris lacks the getgrouplist().
280  * Besides getgrouplist() does not allow to query the exact number of
281  * groups while NGROUPS_MAX can be quite large (e.g. 65536 on Linux).
282  * So nxt_user_groups_get() emulates getgrouplist(): at first the function
283  * saves the super-user groups IDs, then calls initgroups() and saves the
284  * specified user groups IDs, and then restores the super-user groups IDs.
285  * This works at least on Linux, FreeBSD, and Solaris, but does not work
286  * on MacOSX, getgroups(2):
287  *
288  *   To provide compatibility with applications that use getgroups() in
289  *   environments where users may be in more than {NGROUPS_MAX} groups,
290  *   a variant of getgroups(), obtained when compiling with either the
291  *   macros _DARWIN_UNLIMITED_GETGROUPS or _DARWIN_C_SOURCE defined, can
292  *   be used that is not limited to {NGROUPS_MAX} groups.  However, this
293  *   variant only returns the user's default group access list and not
294  *   the group list modified by a call to setgroups(2).
295  *
296  * For such cases initgroups() is used in worker process as fallback.
297  */
298 
299 static nxt_int_t
300 nxt_user_groups_get(nxt_user_cred_t *uc)
301 {
302     int        nsaved, ngroups;
303     nxt_int_t  ret;
304     nxt_gid_t  *saved;
305 
306     nsaved = getgroups(0, NULL);
307 
308     if (nsaved == -1) {
309         nxt_thread_log_emerg("getgroups(0, NULL) failed %E", nxt_errno);
310         return NXT_ERROR;
311     }
312 
313     nxt_thread_log_debug("getgroups(0, NULL): %d", nsaved);
314 
315     if (nsaved > NGROUPS_MAX) {
316         /* MacOSX case. */
317         return NXT_OK;
318     }
319 
320     saved = nxt_malloc(nsaved * sizeof(nxt_gid_t));
321 
322     if (saved == NULL) {
323         return NXT_ERROR;
324     }
325 
326     ret = NXT_ERROR;
327 
328     nsaved = getgroups(nsaved, saved);
329 
330     if (nsaved == -1) {
331         nxt_thread_log_emerg("getgroups(%d) failed %E", nsaved, nxt_errno);
332         goto fail;
333     }
334 
335     nxt_thread_log_debug("getgroups(): %d", nsaved);
336 
337     if (initgroups(uc->user, uc->base_gid) != 0) {
338         nxt_thread_log_emerg("initgroups(%s, %d) failed",
339                              uc->user, uc->base_gid);
340         goto restore;
341     }
342 
343     ngroups = getgroups(0, NULL);
344 
345     if (ngroups == -1) {
346         nxt_thread_log_emerg("getgroups(0, NULL) failed %E", nxt_errno);
347         goto restore;
348     }
349 
350     nxt_thread_log_debug("getgroups(0, NULL): %d", ngroups);
351 
352     uc->gids = nxt_malloc(ngroups * sizeof(nxt_gid_t));
353 
354     if (uc->gids == NULL) {
355         goto restore;
356     }
357 
358     ngroups = getgroups(ngroups, uc->gids);
359 
360     if (ngroups == -1) {
361         nxt_thread_log_emerg("getgroups(%d) failed %E", ngroups, nxt_errno);
362         goto restore;
363     }
364 
365     uc->ngroups = ngroups;
366 
367 #if (NXT_DEBUG)
368     {
369         u_char      *p, *end;
370         nxt_uint_t  i;
371         u_char      msg[NXT_MAX_ERROR_STR];
372 
373         p = msg;
374         end = msg + NXT_MAX_ERROR_STR;
375 
376         for (i = 0; i < uc->ngroups; i++) {
377             p = nxt_sprintf(p, end, "%uL:", (uint64_t) uc->gids[i]);
378         }
379 
380         nxt_thread_log_debug("user \"%s\" cred: uid:%uL base gid:%uL, gids:%*s",
381                              uc->user, (uint64_t) uc->uid,
382                              (uint64_t) uc->base_gid, p - msg, msg);
383     }
384 #endif
385 
386     ret = NXT_OK;
387 
388 restore:
389 
390     if (setgroups(nsaved, saved) != 0) {
391         nxt_thread_log_emerg("setgroups(%d) failed %E", nsaved, nxt_errno);
392         ret = NXT_ERROR;
393     }
394 
395 fail:
396 
397     nxt_free(saved);
398 
399     return ret;
400 }
401 
402 
403 nxt_int_t
404 nxt_user_cred_set(nxt_user_cred_t *uc)
405 {
406     nxt_thread_log_debug("user cred set: \"%s\" uid:%uL base gid:%uL",
407                          uc->user, (uint64_t) uc->uid, uc->base_gid);
408 
409     if (setgid(uc->base_gid) != 0) {
410         nxt_thread_log_emerg("setgid(%d) failed %E", uc->base_gid, nxt_errno);
411         return NXT_ERROR;
412     }
413 
414     if (uc->gids != NULL) {
415         if (setgroups(uc->ngroups, uc->gids) != 0) {
416             nxt_thread_log_emerg("setgroups(%i) failed %E",
417                                  uc->ngroups, nxt_errno);
418             return NXT_ERROR;
419         }
420 
421     } else {
422         /* MacOSX fallback. */
423         if (initgroups(uc->user, uc->base_gid) != 0) {
424             nxt_thread_log_emerg("initgroups(%s, %d) failed",
425                                  uc->user, uc->base_gid);
426             return NXT_ERROR;
427         }
428     }
429 
430     if (setuid(uc->uid) != 0) {
431         nxt_thread_log_emerg("setuid(%d) failed %E", uc->uid, nxt_errno);
432         return NXT_ERROR;
433     }
434 
435     return NXT_OK;
436 }
437