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 
22 const char defifsvar[] ALIGN1 = "IFS= \t\n";
23 const char defoptindvar[] ALIGN1 = "OPTIND=1";
24 
25 /* read builtin */
26 
27 /* Needs to be interruptible: shell must handle traps and shell-special signals
28  * while inside read. To implement this, be sure to not loop on EINTR
29  * and return errno == EINTR reliably.
30  */
31 //TODO: use more efficient setvar() which takes a pointer to malloced "VAR=VAL"
32 //string. hush naturally has it, and ash has setvareq().
33 //Here we can simply store "VAR=" at buffer start and store read data directly
34 //after "=", then pass buffer to setvar() to consume.
35 const char* FAST_FUNC
shell_builtin_read(struct builtin_read_params * params)36 shell_builtin_read(struct builtin_read_params *params)
37 {
38 	struct pollfd pfd[1];
39 #define fd (pfd[0].fd) /* -u FD */
40 	unsigned err;
41 	unsigned end_ms; /* -t TIMEOUT */
42 	int nchars; /* -n NUM */
43 	char **pp;
44 	char *buffer;
45 	char delim;
46 	struct termios tty, old_tty;
47 	const char *retval;
48 	int bufpos; /* need to be able to hold -1 */
49 	int startword;
50 	smallint backslash;
51 	char **argv;
52 	const char *ifs;
53 	int read_flags;
54 
55 	errno = err = 0;
56 
57 	argv = params->argv;
58 	pp = argv;
59 	while (*pp) {
60 		if (endofname(*pp)[0] != '\0') {
61 			/* Mimic bash message */
62 			bb_error_msg("read: '%s': bad variable name", *pp);
63 			return (const char *)(uintptr_t)1;
64 		}
65 		pp++;
66 	}
67 
68 	nchars = 0; /* if != 0, -n is in effect */
69 	if (params->opt_n) {
70 		nchars = bb_strtou(params->opt_n, NULL, 10);
71 		if (nchars < 0 || errno)
72 			return "invalid count";
73 		/* note: "-n 0": off (bash 3.2 does this too) */
74 	}
75 
76 	end_ms = 0;
77 	if (params->opt_t && !ENABLE_FEATURE_SH_READ_FRAC) {
78 		end_ms = bb_strtou(params->opt_t, NULL, 10);
79 		if (errno)
80 			return "invalid timeout";
81 		if (end_ms > UINT_MAX / 2048) /* be safely away from overflow */
82 			end_ms = UINT_MAX / 2048;
83 		end_ms *= 1000;
84 	}
85 	if (params->opt_t && ENABLE_FEATURE_SH_READ_FRAC) {
86 		/* bash 4.3 (maybe earlier) supports -t N.NNNNNN */
87 		char *p;
88 		/* Eat up to three fractional digits */
89 		int frac_digits = 3 + 1;
90 
91 		end_ms = bb_strtou(params->opt_t, &p, 10);
92 		if (end_ms > UINT_MAX / 2048) /* be safely away from overflow */
93 			end_ms = UINT_MAX / 2048;
94 
95 		if (errno) {
96 			/* EINVAL = number is ok, but not NUL terminated */
97 			if (errno != EINVAL || *p != '.')
98 				return "invalid timeout";
99 			/* Do not check the rest: bash allows "0.123456xyz" */
100 			while (*++p && --frac_digits) {
101 				end_ms *= 10;
102 				end_ms += (*p - '0');
103 				if ((unsigned char)(*p - '0') > 9)
104 					return "invalid timeout";
105 			}
106 		}
107 		while (--frac_digits > 0) {
108 			end_ms *= 10;
109 		}
110 	}
111 
112 	fd = STDIN_FILENO;
113 	if (params->opt_u) {
114 		fd = bb_strtou(params->opt_u, NULL, 10);
115 		if (fd < 0 || errno)
116 			return "invalid file descriptor";
117 	}
118 
119 	if (params->opt_t && end_ms == 0) {
120 		/* "If timeout is 0, read returns immediately, without trying
121 		 * to read any data. The exit status is 0 if input is available
122 		 * on the specified file descriptor, non-zero otherwise."
123 		 * bash seems to ignore -p PROMPT for this use case.
124 		 */
125 		int r;
126 		pfd[0].events = POLLIN;
127 		r = poll(pfd, 1, /*timeout:*/ 0);
128 		/* Return 0 only if poll returns 1 ("one fd ready"), else return 1: */
129 		return (const char *)(uintptr_t)(r <= 0);
130 	}
131 
132 	if (params->opt_p && isatty(fd)) {
133 		fputs(params->opt_p, stderr);
134 		fflush_all();
135 	}
136 
137 	ifs = params->ifs;
138 	if (ifs == NULL)
139 		ifs = defifs;
140 
141 	read_flags = params->read_flags;
142 	if (nchars || (read_flags & BUILTIN_READ_SILENT)) {
143 		tcgetattr(fd, &tty);
144 		old_tty = tty;
145 		if (nchars) {
146 			tty.c_lflag &= ~ICANON;
147 			// Setting it to more than 1 breaks poll():
148 			// it blocks even if there's data. !??
149 			//tty.c_cc[VMIN] = nchars < 256 ? nchars : 255;
150 			/* reads will block only if < 1 char is available */
151 			tty.c_cc[VMIN] = 1;
152 			/* no timeout (reads block forever) */
153 			tty.c_cc[VTIME] = 0;
154 		}
155 		if (read_flags & BUILTIN_READ_SILENT) {
156 			tty.c_lflag &= ~(ECHO | ECHOK | ECHONL);
157 		}
158 		/* This forces execution of "restoring" tcgetattr later */
159 		read_flags |= BUILTIN_READ_SILENT;
160 		/* if tcgetattr failed, tcsetattr will fail too.
161 		 * Ignoring, it's harmless. */
162 		tcsetattr(fd, TCSANOW, &tty);
163 	}
164 
165 	retval = (const char *)(uintptr_t)0;
166 	startword = 1;
167 	backslash = 0;
168 	if (params->opt_t)
169 		end_ms += (unsigned)monotonic_ms();
170 	buffer = NULL;
171 	bufpos = 0;
172 	delim = params->opt_d ? params->opt_d[0] : '\n';
173 	do {
174 		char c;
175 		int timeout;
176 
177 		if ((bufpos & 0xff) == 0)
178 			buffer = xrealloc(buffer, bufpos + 0x101);
179 
180 		timeout = -1;
181 		if (params->opt_t) {
182 			timeout = end_ms - (unsigned)monotonic_ms();
183 			/* ^^^^^^^^^^^^^ all values are unsigned,
184 			 * wrapping math is used here, good even if
185 			 * 32-bit unix time wrapped (year 2038+).
186 			 */
187 			if (timeout <= 0) { /* already late? */
188 				retval = (const char *)(uintptr_t)1;
189 				goto ret;
190 			}
191 		}
192 
193 		/* We must poll even if timeout is -1:
194 		 * we want to be interrupted if signal arrives,
195 		 * regardless of SA_RESTART-ness of that signal!
196 		 */
197 		errno = 0;
198 		pfd[0].events = POLLIN;
199 		if (poll(pfd, 1, timeout) <= 0) {
200 			/* timed out, or EINTR */
201 			err = errno;
202 			retval = (const char *)(uintptr_t)1;
203 			goto ret;
204 		}
205 		if (read(fd, &buffer[bufpos], 1) != 1) {
206 			err = errno;
207 			retval = (const char *)(uintptr_t)1;
208 			break;
209 		}
210 
211 		c = buffer[bufpos];
212 		if (!(read_flags & BUILTIN_READ_RAW)) {
213 			if (backslash) {
214 				backslash = 0;
215 				if (c != '\n')
216 					goto put;
217 				continue;
218 			}
219 			if (c == '\\') {
220 				backslash = 1;
221 				continue;
222 			}
223 		}
224 		if (c == delim) /* '\n' or -d CHAR */
225 			break;
226 		if (c == '\0')
227 			continue;
228 
229 		/* $IFS splitting. NOT done if we run "read"
230 		 * without variable names (bash compat).
231 		 * Thus, "read" and "read REPLY" are not the same.
232 		 */
233 		if (argv[0]) {
234 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_05 */
235 			const char *is_ifs = strchr(ifs, c);
236 			if (startword && is_ifs) {
237 				if (isspace(c))
238 					continue;
239 				/* it is a non-space ifs char */
240 				startword--;
241 				if (startword == 1) /* first one? */
242 					continue; /* yes, it is not next word yet */
243 			}
244 			startword = 0;
245 			if (argv[1] != NULL && is_ifs) {
246 				buffer[bufpos] = '\0';
247 				bufpos = 0;
248 				params->setvar(*argv, buffer);
249 				argv++;
250 				/* can we skip one non-space ifs char? (2: yes) */
251 				startword = isspace(c) ? 2 : 1;
252 				continue;
253 			}
254 		}
255  put:
256 		bufpos++;
257 	} while (--nchars);
258 
259 	if (argv[0]) {
260 		/* Remove trailing space $IFS chars */
261 		while (--bufpos >= 0
262 		 && isspace(buffer[bufpos])
263 		 && strchr(ifs, buffer[bufpos]) != NULL
264 		) {
265 			continue;
266 		}
267 		buffer[bufpos + 1] = '\0';
268 
269 		/* Last variable takes the entire remainder with delimiters
270 		 * (sans trailing whitespace $IFS),
271 		 * but ***only "if there are fewer vars than fields"(c)***!
272 		 * The "X:Y:" case below: there are two fields,
273 		 * and therefore last delimiter (:) is eaten:
274 		 * IFS=": "
275 		 * echo "X:Y:Z:"  | (read x y; echo "|$x|$y|") # |X|Y:Z:|
276 		 * echo "X:Y:Z"   | (read x y; echo "|$x|$y|") # |X|Y:Z|
277 		 * echo "X:Y:"    | (read x y; echo "|$x|$y|") # |X|Y|, not |X|Y:|
278 		 * echo "X:Y  : " | (read x y; echo "|$x|$y|") # |X|Y|
279 		 */
280 		if (bufpos >= 0
281 		 && strchr(ifs, buffer[bufpos]) != NULL
282 		) {
283 			/* There _is_ a non-whitespace IFS char */
284 			/* Skip whitespace IFS char before it */
285 			while (--bufpos >= 0
286 			 && isspace(buffer[bufpos])
287 			 && strchr(ifs, buffer[bufpos]) != NULL
288 			) {
289 				continue;
290 			}
291 			/* Are there $IFS chars? */
292 			if (strcspn(buffer, ifs) >= ++bufpos) {
293 				/* No: last var takes one field, not more */
294 				/* So, drop trailing IFS delims */
295 				buffer[bufpos] = '\0';
296 			}
297 		}
298 
299 		/* Use the remainder as a value for the next variable */
300 		params->setvar(*argv, buffer);
301 		/* Set the rest to "" */
302 		while (*++argv)
303 			params->setvar(*argv, "");
304 	} else {
305 		/* Note: no $IFS removal */
306 		buffer[bufpos] = '\0';
307 		params->setvar("REPLY", buffer);
308 	}
309 
310  ret:
311 	free(buffer);
312 	if (read_flags & BUILTIN_READ_SILENT)
313 		tcsetattr(fd, TCSANOW, &old_tty);
314 
315 	errno = err;
316 	return retval;
317 #undef fd
318 }
319 
320 /* ulimit builtin */
321 
322 struct limits {
323 	uint8_t cmd;            /* RLIMIT_xxx fit into it */
324 	uint8_t factor_shift;   /* shift by to get rlim_{cur,max} values */
325 };
326 
327 /* Order of entries matches order in which bash prints "ulimit -a" */
328 static const struct limits limits_tbl[] ALIGN2 = {
329 	{ RLIMIT_CORE,		9,	}, // -c
330 	{ RLIMIT_DATA,		10,	}, // -d
331 #ifdef RLIMIT_NICE
332 	{ RLIMIT_NICE,		0,	}, // -e
333 #define LIMIT_F_IDX     3
334 #else
335 /* for example, Hurd */
336 #define LIMIT_F_IDX     2
337 #endif
338 	{ RLIMIT_FSIZE,		9,	}, // -f
339 #ifdef RLIMIT_SIGPENDING
340 	{ RLIMIT_SIGPENDING,	0,	}, // -i
341 #endif
342 #ifdef RLIMIT_MEMLOCK
343 	{ RLIMIT_MEMLOCK,	10,	}, // -l
344 #endif
345 #ifdef RLIMIT_RSS
346 	{ RLIMIT_RSS,		10,	}, // -m
347 #endif
348 #ifdef RLIMIT_NOFILE
349 	{ RLIMIT_NOFILE,	0,	}, // -n
350 #endif
351 #ifdef RLIMIT_MSGQUEUE
352 	{ RLIMIT_MSGQUEUE,	0,	}, // -q
353 #endif
354 #ifdef RLIMIT_RTPRIO
355 	{ RLIMIT_RTPRIO,	0,	}, // -r
356 #endif
357 #ifdef RLIMIT_STACK
358 	{ RLIMIT_STACK,		10,	}, // -s
359 #endif
360 #ifdef RLIMIT_CPU
361 	{ RLIMIT_CPU,		0,	}, // -t
362 #endif
363 #ifdef RLIMIT_NPROC
364 	{ RLIMIT_NPROC,		0,	}, // -u
365 #endif
366 #ifdef RLIMIT_AS
367 	{ RLIMIT_AS,		10,	}, // -v
368 #endif
369 #ifdef RLIMIT_LOCKS
370 	{ RLIMIT_LOCKS,		0,	}, // -x
371 #endif
372 };
373 // 1) bash also shows:
374 //pipe size            (512 bytes, -p) 8
375 // 2) RLIMIT_RTTIME ("timeout for RT tasks in us") is not in the table
376 
377 static const char limits_help[] ALIGN1 =
378 	"core file size (blocks)"          // -c
379 	"\0""data seg size (kb)"           // -d
380 #ifdef RLIMIT_NICE
381 	"\0""scheduling priority"          // -e
382 #endif
383 	"\0""file size (blocks)"           // -f
384 #ifdef RLIMIT_SIGPENDING
385 	"\0""pending signals"              // -i
386 #endif
387 #ifdef RLIMIT_MEMLOCK
388 	"\0""max locked memory (kb)"       // -l
389 #endif
390 #ifdef RLIMIT_RSS
391 	"\0""max memory size (kb)"         // -m
392 #endif
393 #ifdef RLIMIT_NOFILE
394 	"\0""open files"                   // -n
395 #endif
396 #ifdef RLIMIT_MSGQUEUE
397 	"\0""POSIX message queues (bytes)" // -q
398 #endif
399 #ifdef RLIMIT_RTPRIO
400 	"\0""real-time priority"           // -r
401 #endif
402 #ifdef RLIMIT_STACK
403 	"\0""stack size (kb)"              // -s
404 #endif
405 #ifdef RLIMIT_CPU
406 	"\0""cpu time (seconds)"           // -t
407 #endif
408 #ifdef RLIMIT_NPROC
409 	"\0""max user processes"           // -u
410 #endif
411 #ifdef RLIMIT_AS
412 	"\0""virtual memory (kb)"          // -v
413 #endif
414 #ifdef RLIMIT_LOCKS
415 	"\0""file locks"                   // -x
416 #endif
417 ;
418 
419 static const char limit_chars[] ALIGN1 =
420 			"c"
421 			"d"
422 #ifdef RLIMIT_NICE
423 			"e"
424 #endif
425 			"f"
426 #ifdef RLIMIT_SIGPENDING
427 			"i"
428 #endif
429 #ifdef RLIMIT_MEMLOCK
430 			"l"
431 #endif
432 #ifdef RLIMIT_RSS
433 			"m"
434 #endif
435 #ifdef RLIMIT_NOFILE
436 			"n"
437 #endif
438 #ifdef RLIMIT_MSGQUEUE
439 			"q"
440 #endif
441 #ifdef RLIMIT_RTPRIO
442 			"r"
443 #endif
444 #ifdef RLIMIT_STACK
445 			"s"
446 #endif
447 #ifdef RLIMIT_CPU
448 			"t"
449 #endif
450 #ifdef RLIMIT_NPROC
451 			"u"
452 #endif
453 #ifdef RLIMIT_AS
454 			"v"
455 #endif
456 #ifdef RLIMIT_LOCKS
457 			"x"
458 #endif
459 ;
460 
461 /* "-": treat args as parameters of option with ASCII code 1 */
462 static const char ulimit_opt_string[] ALIGN1 = "-HSa"
463 			"c::"
464 			"d::"
465 #ifdef RLIMIT_NICE
466 			"e::"
467 #endif
468 			"f::"
469 #ifdef RLIMIT_SIGPENDING
470 			"i::"
471 #endif
472 #ifdef RLIMIT_MEMLOCK
473 			"l::"
474 #endif
475 #ifdef RLIMIT_RSS
476 			"m::"
477 #endif
478 #ifdef RLIMIT_NOFILE
479 			"n::"
480 #endif
481 #ifdef RLIMIT_MSGQUEUE
482 			"q::"
483 #endif
484 #ifdef RLIMIT_RTPRIO
485 			"r::"
486 #endif
487 #ifdef RLIMIT_STACK
488 			"s::"
489 #endif
490 #ifdef RLIMIT_CPU
491 			"t::"
492 #endif
493 #ifdef RLIMIT_NPROC
494 			"u::"
495 #endif
496 #ifdef RLIMIT_AS
497 			"v::"
498 #endif
499 #ifdef RLIMIT_LOCKS
500 			"x::"
501 #endif
502 ;
503 
504 enum {
505 	OPT_hard = (1 << 0),
506 	OPT_soft = (1 << 1),
507 	OPT_all  = (1 << 2),
508 };
509 
printlim(unsigned opts,const struct rlimit * limit,const struct limits * l)510 static void printlim(unsigned opts, const struct rlimit *limit,
511 			const struct limits *l)
512 {
513 	rlim_t val;
514 
515 	val = limit->rlim_max;
516 	if (opts & OPT_soft)
517 		val = limit->rlim_cur;
518 
519 	if (val == RLIM_INFINITY)
520 		puts("unlimited");
521 	else {
522 		val >>= l->factor_shift;
523 		printf("%llu\n", (long long) val);
524 	}
525 }
526 
527 int FAST_FUNC
shell_builtin_ulimit(char ** argv)528 shell_builtin_ulimit(char **argv)
529 {
530 	struct rlimit limit;
531 	unsigned opt_cnt;
532 	unsigned opts;
533 	unsigned argc;
534 	unsigned i;
535 
536 	/* We can't use getopt32: need to handle commands like
537 	 * ulimit 123 -c2 -l 456
538 	 */
539 
540 	/* In case getopt() was already called:
541 	 * reset libc getopt() internal state.
542 	 */
543 	GETOPT_RESET();
544 
545 // bash 4.4.23:
546 //
547 // -H and/or -S change meaning even of options *before* them: ulimit -f 2000 -H
548 // sets hard limit, ulimit -a -H prints hard limits.
549 //
550 // -a is equivalent for requesting all limits to be shown.
551 //
552 // If -a is specified, attempts to set limits are ignored:
553 //  ulimit -m 1000; ulimit -m 2000 -a
554 // shows 1000, not 2000. HOWEVER, *implicit* -f form "ulimit 2000 -a"
555 // DOES set -f limit [we don't implement this quirk], "ulimit -a 2000" does not.
556 // Options are still parsed: ulimit -az complains about unknown -z opt.
557 //
558 // -a is not cumulative: "ulimit -a -a" = "ulimit -a -f -m" = "ulimit -a"
559 //
560 // -HSa can be combined in one argument and with one other option (example: -Sm),
561 // but other options can't: limit value is an optional argument,
562 // thus "-mf" means "-m f", f is the parameter of -m.
563 //
564 // Limit can be set and then printed: ulimit -m 2000 -m
565 // If set more than once, they are set and printed in order:
566 // try ulimit -m -m 1000 -m -m 2000 -m -m 3000 -m
567 //
568 // Limits are shown in the order of options given:
569 // ulimit -m -f is not the same as ulimit -f -m.
570 //
571 // If both -S and -H are given, show soft limit.
572 //
573 // Short printout (limit value only) is printed only if just one option
574 // is given: ulimit -m. ulimit -f -m prints verbose lines.
575 // ulimit -f -f prints same verbose line twice.
576 // ulimit -m 10000 -f prints verbose line for -f.
577 
578 	argc = string_array_len(argv);
579 
580 	/* First pass over options: detect -H/-S/-a status,
581 	 * and "bare ulimit" and "only one option" cases
582 	 * by counting other opts.
583 	 */
584 	opt_cnt = 0;
585 	opts = 0;
586 	while (1) {
587 		int opt_char = getopt(argc, argv, ulimit_opt_string);
588 
589 		if (opt_char == -1)
590 			break;
591 		if (opt_char == 'H') {
592 			opts |= OPT_hard;
593 			continue;
594 		}
595 		if (opt_char == 'S') {
596 			opts |= OPT_soft;
597 			continue;
598 		}
599 		if (opt_char == 'a') {
600 			opts |= OPT_all;
601 			continue;
602 		}
603 		if (opt_char == '?') {
604 			/* bad option. getopt already complained. */
605 			return EXIT_FAILURE;
606 		}
607 		opt_cnt++;
608 	} /* while (there are options) */
609 
610 	if (!(opts & (OPT_hard | OPT_soft)))
611 		opts |= (OPT_hard | OPT_soft);
612 	if (opts & OPT_all) {
613 		const char *help = limits_help;
614 		for (i = 0; i < ARRAY_SIZE(limits_tbl); i++) {
615 			getrlimit(limits_tbl[i].cmd, &limit);
616 			printf("%-32s(-%c) ", help, limit_chars[i]);
617 			printlim(opts, &limit, &limits_tbl[i]);
618 			help += strlen(help) + 1;
619 		}
620 		return EXIT_SUCCESS;
621 	}
622 
623 	/* Second pass: set or print limits, in order */
624 	GETOPT_RESET();
625 	while (1) {
626 		char *val_str;
627 		int opt_char = getopt(argc, argv, ulimit_opt_string);
628 
629 		if (opt_char == -1)
630 			break;
631 		if (opt_char == 'H')
632 			continue;
633 		if (opt_char == 'S')
634 			continue;
635 		//if (opt_char == 'a') - impossible
636 
637 		if (opt_char == 1) /* if "ulimit NNN", -f is assumed */
638 			opt_char = 'f';
639 		i = strchrnul(limit_chars, opt_char) - limit_chars;
640 		//if (i >= ARRAY_SIZE(limits_tbl)) - bad option, impossible
641 
642 		val_str = optarg;
643 		if (!val_str && argv[optind] && argv[optind][0] != '-')
644 			val_str = argv[optind++]; /* ++ skips NN in "-c NN" case */
645 
646 		getrlimit(limits_tbl[i].cmd, &limit);
647 		if (!val_str) {
648 			if (opt_cnt > 1)
649 				printf("%-32s(-%c) ", nth_string(limits_help, i), limit_chars[i]);
650 			printlim(opts, &limit, &limits_tbl[i]);
651 		} else {
652 			rlim_t val = RLIM_INFINITY;
653 			if (strcmp(val_str, "unlimited") != 0) {
654 				if (sizeof(val) == sizeof(int))
655 					val = bb_strtou(val_str, NULL, 10);
656 				else if (sizeof(val) == sizeof(long))
657 					val = bb_strtoul(val_str, NULL, 10);
658 				else
659 					val = bb_strtoull(val_str, NULL, 10);
660 				if (errno) {
661 					bb_error_msg("invalid number '%s'", val_str);
662 					return EXIT_FAILURE;
663 				}
664 				val <<= limits_tbl[i].factor_shift;
665 			}
666 //bb_error_msg("opt %c val_str:'%s' val:%lld", opt_char, val_str, (long long)val);
667 			/* from man bash: "If neither -H nor -S
668 			 * is specified, both the soft and hard
669 			 * limits are set. */
670 			if (opts & OPT_hard)
671 				limit.rlim_max = val;
672 			if (opts & OPT_soft)
673 				limit.rlim_cur = val;
674 //bb_error_msg("setrlimit(%d, %lld, %lld)", limits_tbl[i].cmd, (long long)limit.rlim_cur, (long long)limit.rlim_max);
675 			if (setrlimit(limits_tbl[i].cmd, &limit) < 0) {
676 				bb_simple_perror_msg("error setting limit");
677 				return EXIT_FAILURE;
678 			}
679 		}
680 	} /* while (there are options) */
681 
682 	if (opt_cnt == 0) {
683 		/* "bare ulimit": treat it as if it was -f */
684 		getrlimit(RLIMIT_FSIZE, &limit);
685 		printlim(opts, &limit, &limits_tbl[LIMIT_F_IDX]);
686 	}
687 
688 	return EXIT_SUCCESS;
689 }
690