chpst: move misplaced comment
[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
95 /*
96 Five applets here: chpst, envdir, envuidgid, setuidgid, softlimit.
97
98 Only softlimit and chpst are taking options:
99
100 # common
101 -o N            Limit number of open files per process
102 -p N            Limit number of processes per uid
103 -m BYTES        Same as -d BYTES -s BYTES -l BYTES [-a BYTES]
104 -d BYTES        Limit data segment
105 -f BYTES        Limit output file sizes
106 -c BYTES        Limit core file size
107 # softlimit
108 -a BYTES        Limit total size of all segments
109 -s BYTES        Limit stack segment
110 -l BYTES        Limit locked memory size
111 -r BYTES        Limit resident set size
112 -t N            Limit CPU time
113 # chpst
114 -u USER[:GRP]   Set uid and gid
115 -U USER[:GRP]   Set $UID and $GID in environment
116 -e DIR          Set environment variables as specified by files in DIR
117 -/ DIR          Chroot to DIR
118 -n NICE         Add NICE to nice value
119 -v              Verbose
120 -P              Create new process group
121 -0 -1 -2        Close fd 0,1,2
122
123 Even though we accept all these options for both softlimit and chpst,
124 they are not to be advertised on their help texts.
125 We have enough problems with feature creep in other people's
126 software, don't want to add our own.
127
128 envdir, envuidgid, setuidgid take no options, but they reuse code which
129 handles -e, -U and -u.
130 */
131
132 enum {
133         OPT_a = (1 << 0) * ENABLE_SOFTLIMIT,
134         OPT_c = (1 << 1) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
135         OPT_d = (1 << 2) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
136         OPT_f = (1 << 3) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
137         OPT_l = (1 << 4) * ENABLE_SOFTLIMIT,
138         OPT_m = (1 << 5) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
139         OPT_o = (1 << 6) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
140         OPT_p = (1 << 7) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
141         OPT_r = (1 << 8) * ENABLE_SOFTLIMIT,
142         OPT_s = (1 << 9) * ENABLE_SOFTLIMIT,
143         OPT_t = (1 << 10) * ENABLE_SOFTLIMIT,
144         OPT_u = (1 << 11) * (ENABLE_CHPST || ENABLE_SETUIDGID),
145         OPT_U = (1 << 12) * (ENABLE_CHPST || ENABLE_ENVUIDGID),
146         OPT_e = (1 << 13) * (ENABLE_CHPST || ENABLE_ENVDIR),
147         OPT_root = (1 << 14) * ENABLE_CHPST,
148         OPT_n = (1 << 15) * ENABLE_CHPST,
149         OPT_v = (1 << 16) * ENABLE_CHPST,
150         OPT_P = (1 << 17) * ENABLE_CHPST,
151         OPT_0 = (1 << 18) * ENABLE_CHPST,
152         OPT_1 = (1 << 19) * ENABLE_CHPST,
153         OPT_2 = (1 << 20) * ENABLE_CHPST,
154 };
155
156 /* TODO: use recursive_action? */
157 static NOINLINE void edir(const char *directory_name)
158 {
159         int wdir;
160         DIR *dir;
161         struct dirent *d;
162         int fd;
163
164         wdir = xopen(".", O_RDONLY | O_NDELAY);
165         xchdir(directory_name);
166         dir = xopendir(".");
167         for (;;) {
168                 char buf[256];
169                 char *tail;
170                 int size;
171
172                 errno = 0;
173                 d = readdir(dir);
174                 if (!d) {
175                         if (errno)
176                                 bb_perror_msg_and_die("readdir %s",
177                                                 directory_name);
178                         break;
179                 }
180                 if (d->d_name[0] == '.')
181                         continue;
182                 fd = open(d->d_name, O_RDONLY | O_NDELAY);
183                 if (fd < 0) {
184                         if ((errno == EISDIR) && directory_name) {
185                                 if (option_mask32 & OPT_v)
186                                         bb_perror_msg("warning: %s/%s is a directory",
187                                                 directory_name, d->d_name);
188                                 continue;
189                         }
190                         bb_perror_msg_and_die("open %s/%s",
191                                                 directory_name, d->d_name);
192                 }
193                 size = full_read(fd, buf, sizeof(buf)-1);
194                 close(fd);
195                 if (size < 0)
196                         bb_perror_msg_and_die("read %s/%s",
197                                         directory_name, d->d_name);
198                 if (size == 0) {
199                         unsetenv(d->d_name);
200                         continue;
201                 }
202                 buf[size] = '\n';
203                 tail = strchr(buf, '\n');
204                 /* skip trailing whitespace */
205                 while (1) {
206                         *tail = '\0';
207                         tail--;
208                         if (tail < buf || !isspace(*tail))
209                                 break;
210                 }
211                 xsetenv(d->d_name, buf);
212         }
213         closedir(dir);
214         if (fchdir(wdir) == -1)
215                 bb_perror_msg_and_die("fchdir");
216         close(wdir);
217 }
218
219 static void limit(int what, long l)
220 {
221         struct rlimit r;
222
223         /* Never fails under Linux (except if you pass it bad arguments) */
224         getrlimit(what, &r);
225         if ((l < 0) || (l > r.rlim_max))
226                 r.rlim_cur = r.rlim_max;
227         else
228                 r.rlim_cur = l;
229         if (setrlimit(what, &r) == -1)
230                 bb_perror_msg_and_die("setrlimit");
231 }
232
233 int chpst_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
234 int chpst_main(int argc UNUSED_PARAM, char **argv)
235 {
236         struct bb_uidgid_t ugid;
237         char *set_user = set_user; /* for compiler */
238         char *env_user = env_user;
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, &env_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                 env_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                 xchdir(root);
421                 xchroot(".");
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 }