blob: 57297155e96286d55608e34e62de759117e4e05e
1 | /* vi: set sw=4 ts=4: */ |
2 | /* |
3 | * Adapted from ash applet code |
4 | * |
5 | * This code is derived from software contributed to Berkeley by |
6 | * Kenneth Almquist. |
7 | * |
8 | * Copyright (c) 1989, 1991, 1993, 1994 |
9 | * The Regents of the University of California. All rights reserved. |
10 | * |
11 | * Copyright (c) 1997-2005 Herbert Xu <herbert@gondor.apana.org.au> |
12 | * was re-ported from NetBSD and debianized. |
13 | * |
14 | * Copyright (c) 2010 Denys Vlasenko |
15 | * Split from ash.c |
16 | * |
17 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. |
18 | */ |
19 | #include "libbb.h" |
20 | #include "shell_common.h" |
21 | #include <sys/resource.h> /* getrlimit */ |
22 | |
23 | const char defifsvar[] ALIGN1 = "IFS= \t\n"; |
24 | |
25 | |
26 | int FAST_FUNC is_well_formed_var_name(const char *s, char terminator) |
27 | { |
28 | if (!s || !(isalpha(*s) || *s == '_')) |
29 | return 0; |
30 | |
31 | do |
32 | s++; |
33 | while (isalnum(*s) || *s == '_'); |
34 | |
35 | return *s == terminator; |
36 | } |
37 | |
38 | /* read builtin */ |
39 | |
40 | /* Needs to be interruptible: shell must handle traps and shell-special signals |
41 | * while inside read. To implement this, be sure to not loop on EINTR |
42 | * and return errno == EINTR reliably. |
43 | */ |
44 | //TODO: use more efficient setvar() which takes a pointer to malloced "VAR=VAL" |
45 | //string. hush naturally has it, and ash has setvareq(). |
46 | //Here we can simply store "VAR=" at buffer start and store read data directly |
47 | //after "=", then pass buffer to setvar() to consume. |
48 | const char* FAST_FUNC |
49 | shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), |
50 | char **argv, |
51 | const char *ifs, |
52 | int read_flags, |
53 | const char *opt_n, |
54 | const char *opt_p, |
55 | const char *opt_t, |
56 | const char *opt_u |
57 | ) |
58 | { |
59 | unsigned err; |
60 | unsigned end_ms; /* -t TIMEOUT */ |
61 | int fd; /* -u FD */ |
62 | int nchars; /* -n NUM */ |
63 | char **pp; |
64 | char *buffer; |
65 | struct termios tty, old_tty; |
66 | const char *retval; |
67 | int bufpos; /* need to be able to hold -1 */ |
68 | int startword; |
69 | smallint backslash; |
70 | |
71 | errno = err = 0; |
72 | |
73 | pp = argv; |
74 | while (*pp) { |
75 | if (!is_well_formed_var_name(*pp, '\0')) { |
76 | /* Mimic bash message */ |
77 | bb_error_msg("read: '%s': not a valid identifier", *pp); |
78 | return (const char *)(uintptr_t)1; |
79 | } |
80 | pp++; |
81 | } |
82 | |
83 | nchars = 0; /* if != 0, -n is in effect */ |
84 | if (opt_n) { |
85 | nchars = bb_strtou(opt_n, NULL, 10); |
86 | if (nchars < 0 || errno) |
87 | return "invalid count"; |
88 | /* note: "-n 0": off (bash 3.2 does this too) */ |
89 | } |
90 | end_ms = 0; |
91 | if (opt_t) { |
92 | end_ms = bb_strtou(opt_t, NULL, 10); |
93 | if (errno || end_ms > UINT_MAX / 2048) |
94 | return "invalid timeout"; |
95 | end_ms *= 1000; |
96 | #if 0 /* even bash has no -t N.NNN support */ |
97 | ts.tv_sec = bb_strtou(opt_t, &p, 10); |
98 | ts.tv_usec = 0; |
99 | /* EINVAL means number is ok, but not terminated by NUL */ |
100 | if (*p == '.' && errno == EINVAL) { |
101 | char *p2; |
102 | if (*++p) { |
103 | int scale; |
104 | ts.tv_usec = bb_strtou(p, &p2, 10); |
105 | if (errno) |
106 | return "invalid timeout"; |
107 | scale = p2 - p; |
108 | /* normalize to usec */ |
109 | if (scale > 6) |
110 | return "invalid timeout"; |
111 | while (scale++ < 6) |
112 | ts.tv_usec *= 10; |
113 | } |
114 | } else if (ts.tv_sec < 0 || errno) { |
115 | return "invalid timeout"; |
116 | } |
117 | if (!(ts.tv_sec | ts.tv_usec)) { /* both are 0? */ |
118 | return "invalid timeout"; |
119 | } |
120 | #endif /* if 0 */ |
121 | } |
122 | fd = STDIN_FILENO; |
123 | if (opt_u) { |
124 | fd = bb_strtou(opt_u, NULL, 10); |
125 | if (fd < 0 || errno) |
126 | return "invalid file descriptor"; |
127 | } |
128 | |
129 | if (opt_p && isatty(fd)) { |
130 | fputs(opt_p, stderr); |
131 | fflush_all(); |
132 | } |
133 | |
134 | if (ifs == NULL) |
135 | ifs = defifs; |
136 | |
137 | if (nchars || (read_flags & BUILTIN_READ_SILENT)) { |
138 | tcgetattr(fd, &tty); |
139 | old_tty = tty; |
140 | if (nchars) { |
141 | tty.c_lflag &= ~ICANON; |
142 | // Setting it to more than 1 breaks poll(): |
143 | // it blocks even if there's data. !?? |
144 | //tty.c_cc[VMIN] = nchars < 256 ? nchars : 255; |
145 | /* reads would block only if < 1 char is available */ |
146 | tty.c_cc[VMIN] = 1; |
147 | /* no timeout (reads block forever) */ |
148 | tty.c_cc[VTIME] = 0; |
149 | } |
150 | if (read_flags & BUILTIN_READ_SILENT) { |
151 | tty.c_lflag &= ~(ECHO | ECHOK | ECHONL); |
152 | } |
153 | /* This forces execution of "restoring" tcgetattr later */ |
154 | read_flags |= BUILTIN_READ_SILENT; |
155 | /* if tcgetattr failed, tcsetattr will fail too. |
156 | * Ignoring, it's harmless. */ |
157 | tcsetattr(fd, TCSANOW, &tty); |
158 | } |
159 | |
160 | retval = (const char *)(uintptr_t)0; |
161 | startword = 1; |
162 | backslash = 0; |
163 | if (end_ms) /* NB: end_ms stays nonzero: */ |
164 | end_ms = ((unsigned)monotonic_ms() + end_ms) | 1; |
165 | buffer = NULL; |
166 | bufpos = 0; |
167 | do { |
168 | char c; |
169 | struct pollfd pfd[1]; |
170 | int timeout; |
171 | |
172 | if ((bufpos & 0xff) == 0) |
173 | buffer = xrealloc(buffer, bufpos + 0x101); |
174 | |
175 | timeout = -1; |
176 | if (end_ms) { |
177 | timeout = end_ms - (unsigned)monotonic_ms(); |
178 | if (timeout <= 0) { /* already late? */ |
179 | retval = (const char *)(uintptr_t)1; |
180 | goto ret; |
181 | } |
182 | } |
183 | |
184 | /* We must poll even if timeout is -1: |
185 | * we want to be interrupted if signal arrives, |
186 | * regardless of SA_RESTART-ness of that signal! |
187 | */ |
188 | errno = 0; |
189 | pfd[0].fd = fd; |
190 | pfd[0].events = POLLIN; |
191 | if (poll(pfd, 1, timeout) != 1) { |
192 | /* timed out, or EINTR */ |
193 | err = errno; |
194 | retval = (const char *)(uintptr_t)1; |
195 | goto ret; |
196 | } |
197 | if (read(fd, &buffer[bufpos], 1) != 1) { |
198 | err = errno; |
199 | retval = (const char *)(uintptr_t)1; |
200 | break; |
201 | } |
202 | |
203 | c = buffer[bufpos]; |
204 | if (c == '\0') |
205 | continue; |
206 | if (backslash) { |
207 | backslash = 0; |
208 | if (c != '\n') |
209 | goto put; |
210 | continue; |
211 | } |
212 | if (!(read_flags & BUILTIN_READ_RAW) && c == '\\') { |
213 | backslash = 1; |
214 | continue; |
215 | } |
216 | if (c == '\n') |
217 | break; |
218 | |
219 | /* $IFS splitting. NOT done if we run "read" |
220 | * without variable names (bash compat). |
221 | * Thus, "read" and "read REPLY" are not the same. |
222 | */ |
223 | if (argv[0]) { |
224 | /* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_05 */ |
225 | const char *is_ifs = strchr(ifs, c); |
226 | if (startword && is_ifs) { |
227 | if (isspace(c)) |
228 | continue; |
229 | /* it is a non-space ifs char */ |
230 | startword--; |
231 | if (startword == 1) /* first one? */ |
232 | continue; /* yes, it is not next word yet */ |
233 | } |
234 | startword = 0; |
235 | if (argv[1] != NULL && is_ifs) { |
236 | buffer[bufpos] = '\0'; |
237 | bufpos = 0; |
238 | setvar(*argv, buffer); |
239 | argv++; |
240 | /* can we skip one non-space ifs char? (2: yes) */ |
241 | startword = isspace(c) ? 2 : 1; |
242 | continue; |
243 | } |
244 | } |
245 | put: |
246 | bufpos++; |
247 | } while (--nchars); |
248 | |
249 | if (argv[0]) { |
250 | /* Remove trailing space $IFS chars */ |
251 | while (--bufpos >= 0 && isspace(buffer[bufpos]) && strchr(ifs, buffer[bufpos]) != NULL) |
252 | continue; |
253 | buffer[bufpos + 1] = '\0'; |
254 | /* Use the remainder as a value for the next variable */ |
255 | setvar(*argv, buffer); |
256 | /* Set the rest to "" */ |
257 | while (*++argv) |
258 | setvar(*argv, ""); |
259 | } else { |
260 | /* Note: no $IFS removal */ |
261 | buffer[bufpos] = '\0'; |
262 | setvar("REPLY", buffer); |
263 | } |
264 | |
265 | ret: |
266 | free(buffer); |
267 | if (read_flags & BUILTIN_READ_SILENT) |
268 | tcsetattr(fd, TCSANOW, &old_tty); |
269 | |
270 | errno = err; |
271 | return retval; |
272 | } |
273 | |
274 | /* ulimit builtin */ |
275 | |
276 | struct limits { |
277 | uint8_t cmd; /* RLIMIT_xxx fit into it */ |
278 | uint8_t factor_shift; /* shift by to get rlim_{cur,max} values */ |
279 | char option; |
280 | const char *name; |
281 | }; |
282 | |
283 | static const struct limits limits_tbl[] = { |
284 | #ifdef RLIMIT_FSIZE |
285 | { RLIMIT_FSIZE, 9, 'f', "file size (blocks)" }, |
286 | #endif |
287 | #ifdef RLIMIT_CPU |
288 | { RLIMIT_CPU, 0, 't', "cpu time (seconds)" }, |
289 | #endif |
290 | #ifdef RLIMIT_DATA |
291 | { RLIMIT_DATA, 10, 'd', "data seg size (kb)" }, |
292 | #endif |
293 | #ifdef RLIMIT_STACK |
294 | { RLIMIT_STACK, 10, 's', "stack size (kb)" }, |
295 | #endif |
296 | #ifdef RLIMIT_CORE |
297 | { RLIMIT_CORE, 9, 'c', "core file size (blocks)" }, |
298 | #endif |
299 | #ifdef RLIMIT_RSS |
300 | { RLIMIT_RSS, 10, 'm', "resident set size (kb)" }, |
301 | #endif |
302 | #ifdef RLIMIT_MEMLOCK |
303 | { RLIMIT_MEMLOCK, 10, 'l', "locked memory (kb)" }, |
304 | #endif |
305 | #ifdef RLIMIT_NPROC |
306 | { RLIMIT_NPROC, 0, 'p', "processes" }, |
307 | #endif |
308 | #ifdef RLIMIT_NOFILE |
309 | { RLIMIT_NOFILE, 0, 'n', "file descriptors" }, |
310 | #endif |
311 | #ifdef RLIMIT_AS |
312 | { RLIMIT_AS, 10, 'v', "address space (kb)" }, |
313 | #endif |
314 | #ifdef RLIMIT_LOCKS |
315 | { RLIMIT_LOCKS, 0, 'w', "locks" }, |
316 | #endif |
317 | #ifdef RLIMIT_NICE |
318 | { RLIMIT_NICE, 0, 'e', "scheduling priority" }, |
319 | #endif |
320 | #ifdef RLIMIT_RTPRIO |
321 | { RLIMIT_RTPRIO, 0, 'r', "real-time priority" }, |
322 | #endif |
323 | }; |
324 | |
325 | enum { |
326 | OPT_hard = (1 << 0), |
327 | OPT_soft = (1 << 1), |
328 | }; |
329 | |
330 | /* "-": treat args as parameters of option with ASCII code 1 */ |
331 | static const char ulimit_opt_string[] = "-HSa" |
332 | #ifdef RLIMIT_FSIZE |
333 | "f::" |
334 | #endif |
335 | #ifdef RLIMIT_CPU |
336 | "t::" |
337 | #endif |
338 | #ifdef RLIMIT_DATA |
339 | "d::" |
340 | #endif |
341 | #ifdef RLIMIT_STACK |
342 | "s::" |
343 | #endif |
344 | #ifdef RLIMIT_CORE |
345 | "c::" |
346 | #endif |
347 | #ifdef RLIMIT_RSS |
348 | "m::" |
349 | #endif |
350 | #ifdef RLIMIT_MEMLOCK |
351 | "l::" |
352 | #endif |
353 | #ifdef RLIMIT_NPROC |
354 | "p::" |
355 | #endif |
356 | #ifdef RLIMIT_NOFILE |
357 | "n::" |
358 | #endif |
359 | #ifdef RLIMIT_AS |
360 | "v::" |
361 | #endif |
362 | #ifdef RLIMIT_LOCKS |
363 | "w::" |
364 | #endif |
365 | #ifdef RLIMIT_NICE |
366 | "e::" |
367 | #endif |
368 | #ifdef RLIMIT_RTPRIO |
369 | "r::" |
370 | #endif |
371 | ; |
372 | |
373 | static void printlim(unsigned opts, const struct rlimit *limit, |
374 | const struct limits *l) |
375 | { |
376 | rlim_t val; |
377 | |
378 | val = limit->rlim_max; |
379 | if (!(opts & OPT_hard)) |
380 | val = limit->rlim_cur; |
381 | |
382 | if (val == RLIM_INFINITY) |
383 | printf("unlimited\n"); |
384 | else { |
385 | val >>= l->factor_shift; |
386 | printf("%llu\n", (long long) val); |
387 | } |
388 | } |
389 | |
390 | int FAST_FUNC |
391 | shell_builtin_ulimit(char **argv) |
392 | { |
393 | unsigned opts; |
394 | unsigned argc; |
395 | |
396 | /* We can't use getopt32: need to handle commands like |
397 | * ulimit 123 -c2 -l 456 |
398 | */ |
399 | |
400 | /* In case getopt was already called: |
401 | * reset the libc getopt() function, which keeps internal state. |
402 | */ |
403 | #ifdef __GLIBC__ |
404 | optind = 0; |
405 | #else /* BSD style */ |
406 | optind = 1; |
407 | /* optreset = 1; */ |
408 | #endif |
409 | /* optarg = NULL; opterr = 0; optopt = 0; - do we need this?? */ |
410 | |
411 | argc = 1; |
412 | while (argv[argc]) |
413 | argc++; |
414 | |
415 | opts = 0; |
416 | while (1) { |
417 | struct rlimit limit; |
418 | const struct limits *l; |
419 | int opt_char = getopt(argc, argv, ulimit_opt_string); |
420 | |
421 | if (opt_char == -1) |
422 | break; |
423 | if (opt_char == 'H') { |
424 | opts |= OPT_hard; |
425 | continue; |
426 | } |
427 | if (opt_char == 'S') { |
428 | opts |= OPT_soft; |
429 | continue; |
430 | } |
431 | |
432 | if (opt_char == 'a') { |
433 | for (l = limits_tbl; l != &limits_tbl[ARRAY_SIZE(limits_tbl)]; l++) { |
434 | getrlimit(l->cmd, &limit); |
435 | printf("-%c: %-30s ", l->option, l->name); |
436 | printlim(opts, &limit, l); |
437 | } |
438 | continue; |
439 | } |
440 | |
441 | if (opt_char == 1) |
442 | opt_char = 'f'; |
443 | for (l = limits_tbl; l != &limits_tbl[ARRAY_SIZE(limits_tbl)]; l++) { |
444 | if (opt_char == l->option) { |
445 | char *val_str; |
446 | |
447 | getrlimit(l->cmd, &limit); |
448 | |
449 | val_str = optarg; |
450 | if (!val_str && argv[optind] && argv[optind][0] != '-') |
451 | val_str = argv[optind++]; /* ++ skips NN in "-c NN" case */ |
452 | if (val_str) { |
453 | rlim_t val; |
454 | |
455 | if (strcmp(val_str, "unlimited") == 0) |
456 | val = RLIM_INFINITY; |
457 | else { |
458 | if (sizeof(val) == sizeof(int)) |
459 | val = bb_strtou(val_str, NULL, 10); |
460 | else if (sizeof(val) == sizeof(long)) |
461 | val = bb_strtoul(val_str, NULL, 10); |
462 | else |
463 | val = bb_strtoull(val_str, NULL, 10); |
464 | if (errno) { |
465 | bb_error_msg("invalid number '%s'", val_str); |
466 | return EXIT_FAILURE; |
467 | } |
468 | val <<= l->factor_shift; |
469 | } |
470 | //bb_error_msg("opt %c val_str:'%s' val:%lld", opt_char, val_str, (long long)val); |
471 | /* from man bash: "If neither -H nor -S |
472 | * is specified, both the soft and hard |
473 | * limits are set. */ |
474 | if (!opts) |
475 | opts = OPT_hard + OPT_soft; |
476 | if (opts & OPT_hard) |
477 | limit.rlim_max = val; |
478 | if (opts & OPT_soft) |
479 | limit.rlim_cur = val; |
480 | //bb_error_msg("setrlimit(%d, %lld, %lld)", l->cmd, (long long)limit.rlim_cur, (long long)limit.rlim_max); |
481 | if (setrlimit(l->cmd, &limit) < 0) { |
482 | bb_perror_msg("error setting limit"); |
483 | return EXIT_FAILURE; |
484 | } |
485 | } else { |
486 | printlim(opts, &limit, l); |
487 | } |
488 | break; |
489 | } |
490 | } /* for (every possible opt) */ |
491 | |
492 | if (l == &limits_tbl[ARRAY_SIZE(limits_tbl)]) { |
493 | /* bad option. getopt already complained. */ |
494 | break; |
495 | } |
496 | |
497 | } /* while (there are options) */ |
498 | |
499 | return 0; |
500 | } |
501 |