Bump to version 1.22.1
[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_dir = env_dir;
240         char *root;
241         char *nicestr;
242         unsigned limita;
243         unsigned limitc;
244         unsigned limitd;
245         unsigned limitf;
246         unsigned limitl;
247         unsigned limitm;
248         unsigned limito;
249         unsigned limitp;
250         unsigned limitr;
251         unsigned limits;
252         unsigned limitt;
253         unsigned opt;
254
255         if ((ENABLE_CHPST && applet_name[0] == 'c')
256          || (ENABLE_SOFTLIMIT && applet_name[1] == 'o')
257         ) {
258                 // FIXME: can we live with int-sized limits?
259                 // can we live with 40000 days?
260                 // if yes -> getopt converts strings to numbers for us
261                 opt_complementary = "-1:a+:c+:d+:f+:l+:m+:o+:p+:r+:s+:t+";
262                 opt = getopt32(argv, "+a:c:d:f:l:m:o:p:r:s:t:u:U:e:"
263                         IF_CHPST("/:n:vP012"),
264                         &limita, &limitc, &limitd, &limitf, &limitl,
265                         &limitm, &limito, &limitp, &limitr, &limits, &limitt,
266                         &set_user, &set_user, &env_dir
267                         IF_CHPST(, &root, &nicestr));
268                 argv += optind;
269                 if (opt & OPT_m) { // -m means -asld
270                         limita = limits = limitl = limitd = limitm;
271                         opt |= (OPT_s | OPT_l | OPT_a | OPT_d);
272                 }
273         } else {
274                 option_mask32 = opt = 0;
275                 argv++;
276                 if (!*argv)
277                         bb_show_usage();
278         }
279
280         // envdir?
281         if (ENABLE_ENVDIR && applet_name[3] == 'd') {
282                 env_dir = *argv++;
283                 opt |= OPT_e;
284         }
285
286         // setuidgid?
287         if (ENABLE_SETUIDGID && applet_name[1] == 'e') {
288                 set_user = *argv++;
289                 opt |= OPT_u;
290         }
291
292         // envuidgid?
293         if (ENABLE_ENVUIDGID && applet_name[0] == 'e' && applet_name[3] == 'u') {
294                 set_user = *argv++;
295                 opt |= OPT_U;
296         }
297
298         // we must have PROG [ARGS]
299         if (!*argv)
300                 bb_show_usage();
301
302         // set limits
303         if (opt & OPT_d) {
304 #ifdef RLIMIT_DATA
305                 limit(RLIMIT_DATA, limitd);
306 #else
307                 if (opt & OPT_v)
308                         bb_error_msg("system does not support RLIMIT_%s",
309                                 "DATA");
310 #endif
311         }
312         if (opt & OPT_s) {
313 #ifdef RLIMIT_STACK
314                 limit(RLIMIT_STACK, limits);
315 #else
316                 if (opt & OPT_v)
317                         bb_error_msg("system does not support RLIMIT_%s",
318                                 "STACK");
319 #endif
320         }
321         if (opt & OPT_l) {
322 #ifdef RLIMIT_MEMLOCK
323                 limit(RLIMIT_MEMLOCK, limitl);
324 #else
325                 if (opt & OPT_v)
326                         bb_error_msg("system does not support RLIMIT_%s",
327                                 "MEMLOCK");
328 #endif
329         }
330         if (opt & OPT_a) {
331 #ifdef RLIMIT_VMEM
332                 limit(RLIMIT_VMEM, limita);
333 #else
334 #ifdef RLIMIT_AS
335                 limit(RLIMIT_AS, limita);
336 #else
337                 if (opt & OPT_v)
338                         bb_error_msg("system does not support RLIMIT_%s",
339                                 "VMEM");
340 #endif
341 #endif
342         }
343         if (opt & OPT_o) {
344 #ifdef RLIMIT_NOFILE
345                 limit(RLIMIT_NOFILE, limito);
346 #else
347 #ifdef RLIMIT_OFILE
348                 limit(RLIMIT_OFILE, limito);
349 #else
350                 if (opt & OPT_v)
351                         bb_error_msg("system does not support RLIMIT_%s",
352                                 "NOFILE");
353 #endif
354 #endif
355         }
356         if (opt & OPT_p) {
357 #ifdef RLIMIT_NPROC
358                 limit(RLIMIT_NPROC, limitp);
359 #else
360                 if (opt & OPT_v)
361                         bb_error_msg("system does not support RLIMIT_%s",
362                                 "NPROC");
363 #endif
364         }
365         if (opt & OPT_f) {
366 #ifdef RLIMIT_FSIZE
367                 limit(RLIMIT_FSIZE, limitf);
368 #else
369                 if (opt & OPT_v)
370                         bb_error_msg("system does not support RLIMIT_%s",
371                                 "FSIZE");
372 #endif
373         }
374         if (opt & OPT_c) {
375 #ifdef RLIMIT_CORE
376                 limit(RLIMIT_CORE, limitc);
377 #else
378                 if (opt & OPT_v)
379                         bb_error_msg("system does not support RLIMIT_%s",
380                                 "CORE");
381 #endif
382         }
383         if (opt & OPT_r) {
384 #ifdef RLIMIT_RSS
385                 limit(RLIMIT_RSS, limitr);
386 #else
387                 if (opt & OPT_v)
388                         bb_error_msg("system does not support RLIMIT_%s",
389                                 "RSS");
390 #endif
391         }
392         if (opt & OPT_t) {
393 #ifdef RLIMIT_CPU
394                 limit(RLIMIT_CPU, limitt);
395 #else
396                 if (opt & OPT_v)
397                         bb_error_msg("system does not support RLIMIT_%s",
398                                 "CPU");
399 #endif
400         }
401
402         if (opt & OPT_P)
403                 setsid();
404
405         if (opt & OPT_e)
406                 edir(env_dir);
407
408         if (opt & (OPT_u|OPT_U))
409                 xget_uidgid(&ugid, set_user);
410
411         // chrooted jail must have /etc/passwd if we move this after chroot.
412         // OTOH chroot fails for non-roots.
413         // Solution: cache uid/gid before chroot, apply uid/gid after.
414         if (opt & OPT_U) {
415                 xsetenv("GID", utoa(ugid.gid));
416                 xsetenv("UID", utoa(ugid.uid));
417         }
418
419         if (opt & OPT_root) {
420                 xchroot(root);
421         }
422
423         if (opt & OPT_u) {
424                 if (setgroups(1, &ugid.gid) == -1)
425                         bb_perror_msg_and_die("setgroups");
426                 xsetgid(ugid.gid);
427                 xsetuid(ugid.uid);
428         }
429
430         if (opt & OPT_n) {
431                 errno = 0;
432                 if (nice(xatoi(nicestr)) == -1)
433                         bb_perror_msg_and_die("nice");
434         }
435
436         if (opt & OPT_0)
437                 close(STDIN_FILENO);
438         if (opt & OPT_1)
439                 close(STDOUT_FILENO);
440         if (opt & OPT_2)
441                 close(STDERR_FILENO);
442
443         BB_EXECVP_or_die(argv);
444 }