platform: strchrnul is missing if __APPLE__
[platform/upstream/busybox.git] / runit / chpst.c
1 /*
2 Copyright (c) 2001-2006, Gerrit Pape
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
7
8    1. Redistributions of source code must retain the above copyright notice,
9       this list of conditions and the following disclaimer.
10    2. Redistributions in binary form must reproduce the above copyright
11       notice, this list of conditions and the following disclaimer in the
12       documentation and/or other materials provided with the distribution.
13    3. The name of the author may not be used to endorse or promote products
14       derived from this software without specific prior written permission.
15
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 /* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
29 /* Dependencies on runit_lib.c removed */
30
31 //usage:#define chpst_trivial_usage
32 //usage:       "[-vP012] [-u USER[:GRP]] [-U USER[:GRP]] [-e DIR]\n"
33 //usage:       "        [-/ DIR] [-n NICE] [-m BYTES] [-d BYTES] [-o N]\n"
34 //usage:       "        [-p N] [-f BYTES] [-c BYTES] PROG ARGS"
35 //usage:#define chpst_full_usage "\n\n"
36 //usage:       "Change the process state, run PROG\n"
37 //usage:     "\n        -u USER[:GRP]   Set uid and gid"
38 //usage:     "\n        -U USER[:GRP]   Set $UID and $GID in environment"
39 //usage:     "\n        -e DIR          Set environment variables as specified by files"
40 //usage:     "\n                        in DIR: file=1st_line_of_file"
41 //usage:     "\n        -/ DIR          Chroot to DIR"
42 //usage:     "\n        -n NICE         Add NICE to nice value"
43 //usage:     "\n        -m BYTES        Same as -d BYTES -s BYTES -l BYTES"
44 //usage:     "\n        -d BYTES        Limit data segment"
45 //usage:     "\n        -o N            Limit number of open files per process"
46 //usage:     "\n        -p N            Limit number of processes per uid"
47 //usage:     "\n        -f BYTES        Limit output file sizes"
48 //usage:     "\n        -c BYTES        Limit core file size"
49 //usage:     "\n        -v              Verbose"
50 //usage:     "\n        -P              Create new process group"
51 //usage:     "\n        -0              Close stdin"
52 //usage:     "\n        -1              Close stdout"
53 //usage:     "\n        -2              Close stderr"
54 //usage:
55 //usage:#define envdir_trivial_usage
56 //usage:       "DIR PROG ARGS"
57 //usage:#define envdir_full_usage "\n\n"
58 //usage:       "Set various environment variables as specified by files\n"
59 //usage:       "in the directory DIR, run PROG"
60 //usage:
61 //usage:#define envuidgid_trivial_usage
62 //usage:       "USER PROG ARGS"
63 //usage:#define envuidgid_full_usage "\n\n"
64 //usage:       "Set $UID to USER's uid and $GID to USER's gid, run PROG"
65 //usage:
66 //usage:#define setuidgid_trivial_usage
67 //usage:       "USER PROG ARGS"
68 //usage:#define setuidgid_full_usage "\n\n"
69 //usage:       "Set uid and gid to USER's uid and gid, drop supplementary group ids,\n"
70 //usage:       "run PROG"
71 //usage:
72 //usage:#define softlimit_trivial_usage
73 //usage:       "[-a BYTES] [-m BYTES] [-d BYTES] [-s BYTES] [-l BYTES]\n"
74 //usage:       "        [-f BYTES] [-c BYTES] [-r BYTES] [-o N] [-p N] [-t N]\n"
75 //usage:       "        PROG ARGS"
76 //usage:#define softlimit_full_usage "\n\n"
77 //usage:       "Set soft resource limits, then run PROG\n"
78 //usage:     "\n        -a BYTES        Limit total size of all segments"
79 //usage:     "\n        -m BYTES        Same as -d BYTES -s BYTES -l BYTES -a BYTES"
80 //usage:     "\n        -d BYTES        Limit data segment"
81 //usage:     "\n        -s BYTES        Limit stack segment"
82 //usage:     "\n        -l BYTES        Limit locked memory size"
83 //usage:     "\n        -o N            Limit number of open files per process"
84 //usage:     "\n        -p N            Limit number of processes per uid"
85 //usage:     "\nOptions controlling file sizes:"
86 //usage:     "\n        -f BYTES        Limit output file sizes"
87 //usage:     "\n        -c BYTES        Limit core file size"
88 //usage:     "\nEfficiency opts:"
89 //usage:     "\n        -r BYTES        Limit resident set size"
90 //usage:     "\n        -t N            Limit CPU time, process receives"
91 //usage:     "\n                        a SIGXCPU after N seconds"
92
93 #include "libbb.h"
94 #include <sys/resource.h> /* getrlimit */
95
96 /*
97 Five applets here: chpst, envdir, envuidgid, setuidgid, softlimit.
98
99 Only softlimit and chpst are taking options:
100
101 # common
102 -o N            Limit number of open files per process
103 -p N            Limit number of processes per uid
104 -m BYTES        Same as -d BYTES -s BYTES -l BYTES [-a BYTES]
105 -d BYTES        Limit data segment
106 -f BYTES        Limit output file sizes
107 -c BYTES        Limit core file size
108 # softlimit
109 -a BYTES        Limit total size of all segments
110 -s BYTES        Limit stack segment
111 -l BYTES        Limit locked memory size
112 -r BYTES        Limit resident set size
113 -t N            Limit CPU time
114 # chpst
115 -u USER[:GRP]   Set uid and gid
116 -U USER[:GRP]   Set $UID and $GID in environment
117 -e DIR          Set environment variables as specified by files in DIR
118 -/ DIR          Chroot to DIR
119 -n NICE         Add NICE to nice value
120 -v              Verbose
121 -P              Create new process group
122 -0 -1 -2        Close fd 0,1,2
123
124 Even though we accept all these options for both softlimit and chpst,
125 they are not to be advertised on their help texts.
126 We have enough problems with feature creep in other people's
127 software, don't want to add our own.
128
129 envdir, envuidgid, setuidgid take no options, but they reuse code which
130 handles -e, -U and -u.
131 */
132
133 enum {
134         OPT_a = (1 << 0) * ENABLE_SOFTLIMIT,
135         OPT_c = (1 << 1) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
136         OPT_d = (1 << 2) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
137         OPT_f = (1 << 3) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
138         OPT_l = (1 << 4) * ENABLE_SOFTLIMIT,
139         OPT_m = (1 << 5) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
140         OPT_o = (1 << 6) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
141         OPT_p = (1 << 7) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
142         OPT_r = (1 << 8) * ENABLE_SOFTLIMIT,
143         OPT_s = (1 << 9) * ENABLE_SOFTLIMIT,
144         OPT_t = (1 << 10) * ENABLE_SOFTLIMIT,
145         OPT_u = (1 << 11) * (ENABLE_CHPST || ENABLE_SETUIDGID),
146         OPT_U = (1 << 12) * (ENABLE_CHPST || ENABLE_ENVUIDGID),
147         OPT_e = (1 << 13) * (ENABLE_CHPST || ENABLE_ENVDIR),
148         OPT_root = (1 << 14) * ENABLE_CHPST,
149         OPT_n = (1 << 15) * ENABLE_CHPST,
150         OPT_v = (1 << 16) * ENABLE_CHPST,
151         OPT_P = (1 << 17) * ENABLE_CHPST,
152         OPT_0 = (1 << 18) * ENABLE_CHPST,
153         OPT_1 = (1 << 19) * ENABLE_CHPST,
154         OPT_2 = (1 << 20) * ENABLE_CHPST,
155 };
156
157 /* TODO: use recursive_action? */
158 static NOINLINE void edir(const char *directory_name)
159 {
160         int wdir;
161         DIR *dir;
162         struct dirent *d;
163         int fd;
164
165         wdir = xopen(".", O_RDONLY | O_NDELAY);
166         xchdir(directory_name);
167         dir = xopendir(".");
168         for (;;) {
169                 char buf[256];
170                 char *tail;
171                 int size;
172
173                 errno = 0;
174                 d = readdir(dir);
175                 if (!d) {
176                         if (errno)
177                                 bb_perror_msg_and_die("readdir %s",
178                                                 directory_name);
179                         break;
180                 }
181                 if (d->d_name[0] == '.')
182                         continue;
183                 fd = open(d->d_name, O_RDONLY | O_NDELAY);
184                 if (fd < 0) {
185                         if ((errno == EISDIR) && directory_name) {
186                                 if (option_mask32 & OPT_v)
187                                         bb_perror_msg("warning: %s/%s is a directory",
188                                                 directory_name, d->d_name);
189                                 continue;
190                         }
191                         bb_perror_msg_and_die("open %s/%s",
192                                                 directory_name, d->d_name);
193                 }
194                 size = full_read(fd, buf, sizeof(buf)-1);
195                 close(fd);
196                 if (size < 0)
197                         bb_perror_msg_and_die("read %s/%s",
198                                         directory_name, d->d_name);
199                 if (size == 0) {
200                         unsetenv(d->d_name);
201                         continue;
202                 }
203                 buf[size] = '\n';
204                 tail = strchr(buf, '\n');
205                 /* skip trailing whitespace */
206                 while (1) {
207                         *tail = '\0';
208                         tail--;
209                         if (tail < buf || !isspace(*tail))
210                                 break;
211                 }
212                 xsetenv(d->d_name, buf);
213         }
214         closedir(dir);
215         if (fchdir(wdir) == -1)
216                 bb_perror_msg_and_die("fchdir");
217         close(wdir);
218 }
219
220 static void limit(int what, long l)
221 {
222         struct rlimit r;
223
224         /* Never fails under Linux (except if you pass it bad arguments) */
225         getrlimit(what, &r);
226         if ((l < 0) || (l > r.rlim_max))
227                 r.rlim_cur = r.rlim_max;
228         else
229                 r.rlim_cur = l;
230         if (setrlimit(what, &r) == -1)
231                 bb_perror_msg_and_die("setrlimit");
232 }
233
234 int chpst_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
235 int chpst_main(int argc UNUSED_PARAM, char **argv)
236 {
237         struct bb_uidgid_t ugid;
238         char *set_user = set_user; /* for compiler */
239         char *env_user = env_user;
240         char *env_dir = env_dir;
241         char *root;
242         char *nicestr;
243         unsigned limita;
244         unsigned limitc;
245         unsigned limitd;
246         unsigned limitf;
247         unsigned limitl;
248         unsigned limitm;
249         unsigned limito;
250         unsigned limitp;
251         unsigned limitr;
252         unsigned limits;
253         unsigned limitt;
254         unsigned opt;
255
256         if ((ENABLE_CHPST && applet_name[0] == 'c')
257          || (ENABLE_SOFTLIMIT && applet_name[1] == 'o')
258         ) {
259                 // FIXME: can we live with int-sized limits?
260                 // can we live with 40000 days?
261                 // if yes -> getopt converts strings to numbers for us
262                 opt_complementary = "-1:a+:c+:d+:f+:l+:m+:o+:p+:r+:s+:t+";
263                 opt = getopt32(argv, "+a:c:d:f:l:m:o:p:r:s:t:u:U:e:"
264                         IF_CHPST("/:n:vP012"),
265                         &limita, &limitc, &limitd, &limitf, &limitl,
266                         &limitm, &limito, &limitp, &limitr, &limits, &limitt,
267                         &set_user, &env_user, &env_dir
268                         IF_CHPST(, &root, &nicestr));
269                 argv += optind;
270                 if (opt & OPT_m) { // -m means -asld
271                         limita = limits = limitl = limitd = limitm;
272                         opt |= (OPT_s | OPT_l | OPT_a | OPT_d);
273                 }
274         } else {
275                 option_mask32 = opt = 0;
276                 argv++;
277                 if (!*argv)
278                         bb_show_usage();
279         }
280
281         // envdir?
282         if (ENABLE_ENVDIR && applet_name[3] == 'd') {
283                 env_dir = *argv++;
284                 opt |= OPT_e;
285         }
286
287         // setuidgid?
288         if (ENABLE_SETUIDGID && applet_name[1] == 'e') {
289                 set_user = *argv++;
290                 opt |= OPT_u;
291         }
292
293         // envuidgid?
294         if (ENABLE_ENVUIDGID && applet_name[0] == 'e' && applet_name[3] == 'u') {
295                 env_user = *argv++;
296                 opt |= OPT_U;
297         }
298
299         // we must have PROG [ARGS]
300         if (!*argv)
301                 bb_show_usage();
302
303         // set limits
304         if (opt & OPT_d) {
305 #ifdef RLIMIT_DATA
306                 limit(RLIMIT_DATA, limitd);
307 #else
308                 if (opt & OPT_v)
309                         bb_error_msg("system does not support RLIMIT_%s",
310                                 "DATA");
311 #endif
312         }
313         if (opt & OPT_s) {
314 #ifdef RLIMIT_STACK
315                 limit(RLIMIT_STACK, limits);
316 #else
317                 if (opt & OPT_v)
318                         bb_error_msg("system does not support RLIMIT_%s",
319                                 "STACK");
320 #endif
321         }
322         if (opt & OPT_l) {
323 #ifdef RLIMIT_MEMLOCK
324                 limit(RLIMIT_MEMLOCK, limitl);
325 #else
326                 if (opt & OPT_v)
327                         bb_error_msg("system does not support RLIMIT_%s",
328                                 "MEMLOCK");
329 #endif
330         }
331         if (opt & OPT_a) {
332 #ifdef RLIMIT_VMEM
333                 limit(RLIMIT_VMEM, limita);
334 #else
335 #ifdef RLIMIT_AS
336                 limit(RLIMIT_AS, limita);
337 #else
338                 if (opt & OPT_v)
339                         bb_error_msg("system does not support RLIMIT_%s",
340                                 "VMEM");
341 #endif
342 #endif
343         }
344         if (opt & OPT_o) {
345 #ifdef RLIMIT_NOFILE
346                 limit(RLIMIT_NOFILE, limito);
347 #else
348 #ifdef RLIMIT_OFILE
349                 limit(RLIMIT_OFILE, limito);
350 #else
351                 if (opt & OPT_v)
352                         bb_error_msg("system does not support RLIMIT_%s",
353                                 "NOFILE");
354 #endif
355 #endif
356         }
357         if (opt & OPT_p) {
358 #ifdef RLIMIT_NPROC
359                 limit(RLIMIT_NPROC, limitp);
360 #else
361                 if (opt & OPT_v)
362                         bb_error_msg("system does not support RLIMIT_%s",
363                                 "NPROC");
364 #endif
365         }
366         if (opt & OPT_f) {
367 #ifdef RLIMIT_FSIZE
368                 limit(RLIMIT_FSIZE, limitf);
369 #else
370                 if (opt & OPT_v)
371                         bb_error_msg("system does not support RLIMIT_%s",
372                                 "FSIZE");
373 #endif
374         }
375         if (opt & OPT_c) {
376 #ifdef RLIMIT_CORE
377                 limit(RLIMIT_CORE, limitc);
378 #else
379                 if (opt & OPT_v)
380                         bb_error_msg("system does not support RLIMIT_%s",
381                                 "CORE");
382 #endif
383         }
384         if (opt & OPT_r) {
385 #ifdef RLIMIT_RSS
386                 limit(RLIMIT_RSS, limitr);
387 #else
388                 if (opt & OPT_v)
389                         bb_error_msg("system does not support RLIMIT_%s",
390                                 "RSS");
391 #endif
392         }
393         if (opt & OPT_t) {
394 #ifdef RLIMIT_CPU
395                 limit(RLIMIT_CPU, limitt);
396 #else
397                 if (opt & OPT_v)
398                         bb_error_msg("system does not support RLIMIT_%s",
399                                 "CPU");
400 #endif
401         }
402
403         if (opt & OPT_P)
404                 setsid();
405
406         if (opt & OPT_e)
407                 edir(env_dir);
408
409         if (opt & (OPT_u|OPT_U))
410                 xget_uidgid(&ugid, set_user);
411
412         // chrooted jail must have /etc/passwd if we move this after chroot.
413         // OTOH chroot fails for non-roots.
414         // Solution: cache uid/gid before chroot, apply uid/gid after.
415         if (opt & OPT_U) {
416                 xsetenv("GID", utoa(ugid.gid));
417                 xsetenv("UID", utoa(ugid.uid));
418         }
419
420         if (opt & OPT_root) {
421                 xchroot(root);
422         }
423
424         if (opt & OPT_u) {
425                 if (setgroups(1, &ugid.gid) == -1)
426                         bb_perror_msg_and_die("setgroups");
427                 xsetgid(ugid.gid);
428                 xsetuid(ugid.uid);
429         }
430
431         if (opt & OPT_n) {
432                 errno = 0;
433                 if (nice(xatoi(nicestr)) == -1)
434                         bb_perror_msg_and_die("nice");
435         }
436
437         if (opt & OPT_0)
438                 close(STDIN_FILENO);
439         if (opt & OPT_1)
440                 close(STDOUT_FILENO);
441         if (opt & OPT_2)
442                 close(STDERR_FILENO);
443
444         BB_EXECVP_or_die(argv);
445 }