source: MondoRescue/branches/3.3/mindi-busybox/findutils/xargs.c@ 3647

Last change on this file since 3647 was 3621, checked in by Bruno Cornec, 10 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

File size: 17.2 KB
Line 
1/* vi: set sw=4 ts=4: */
2/*
3 * Mini xargs implementation for busybox
4 *
5 * (C) 2002,2003 by Vladimir Oleynik <dzo@simtreas.ru>
6 *
7 * Special thanks
8 * - Mark Whitley and Glenn McGrath for stimulus to rewrite :)
9 * - Mike Rendell <michael@cs.mun.ca>
10 * and David MacKenzie <djm@gnu.ai.mit.edu>.
11 *
12 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
13 *
14 * xargs is described in the Single Unix Specification v3 at
15 * http://www.opengroup.org/onlinepubs/007904975/utilities/xargs.html
16 */
17
18//config:config XARGS
19//config: bool "xargs"
20//config: default y
21//config: help
22//config: xargs is used to execute a specified command for
23//config: every item from standard input.
24//config:
25//config:config FEATURE_XARGS_SUPPORT_CONFIRMATION
26//config: bool "Enable -p: prompt and confirmation"
27//config: default y
28//config: depends on XARGS
29//config: help
30//config: Support -p: prompt the user whether to run each command
31//config: line and read a line from the terminal.
32//config:
33//config:config FEATURE_XARGS_SUPPORT_QUOTES
34//config: bool "Enable single and double quotes and backslash"
35//config: default y
36//config: depends on XARGS
37//config: help
38//config: Support quoting in the input.
39//config:
40//config:config FEATURE_XARGS_SUPPORT_TERMOPT
41//config: bool "Enable -x: exit if -s or -n is exceeded"
42//config: default y
43//config: depends on XARGS
44//config: help
45//config: Support -x: exit if the command size (see the -s or -n option)
46//config: is exceeded.
47//config:
48//config:config FEATURE_XARGS_SUPPORT_ZERO_TERM
49//config: bool "Enable -0: NUL-terminated input"
50//config: default y
51//config: depends on XARGS
52//config: help
53//config: Support -0: input items are terminated by a NUL character
54//config: instead of whitespace, and the quotes and backslash
55//config: are not special.
56//config:
57//config:config FEATURE_XARGS_SUPPORT_REPL_STR
58//config: bool "Enable -I STR: string to replace"
59//config: default y
60//config: depends on XARGS
61//config: help
62//config: Support -I STR and -i[STR] options.
63
64//applet:IF_XARGS(APPLET_NOEXEC(xargs, xargs, BB_DIR_USR_BIN, BB_SUID_DROP, xargs))
65
66//kbuild:lib-$(CONFIG_XARGS) += xargs.o
67
68#include "libbb.h"
69#include "common_bufsiz.h"
70
71/* This is a NOEXEC applet. Be very careful! */
72
73
74//#define dbg_msg(...) bb_error_msg(__VA_ARGS__)
75#define dbg_msg(...) ((void)0)
76
77
78#ifdef TEST
79# ifndef ENABLE_FEATURE_XARGS_SUPPORT_CONFIRMATION
80# define ENABLE_FEATURE_XARGS_SUPPORT_CONFIRMATION 1
81# endif
82# ifndef ENABLE_FEATURE_XARGS_SUPPORT_QUOTES
83# define ENABLE_FEATURE_XARGS_SUPPORT_QUOTES 1
84# endif
85# ifndef ENABLE_FEATURE_XARGS_SUPPORT_TERMOPT
86# define ENABLE_FEATURE_XARGS_SUPPORT_TERMOPT 1
87# endif
88# ifndef ENABLE_FEATURE_XARGS_SUPPORT_ZERO_TERM
89# define ENABLE_FEATURE_XARGS_SUPPORT_ZERO_TERM 1
90# endif
91#endif
92
93
94struct globals {
95 char **args;
96#if ENABLE_FEATURE_XARGS_SUPPORT_REPL_STR
97 char **argv;
98 const char *repl_str;
99 char eol_ch;
100#endif
101 const char *eof_str;
102 int idx;
103} FIX_ALIASING;
104#define G (*(struct globals*)bb_common_bufsiz1)
105#define INIT_G() do { \
106 setup_common_bufsiz(); \
107 G.eof_str = NULL; /* need to clear by hand because we are NOEXEC applet */ \
108 IF_FEATURE_XARGS_SUPPORT_REPL_STR(G.repl_str = "{}";) \
109 IF_FEATURE_XARGS_SUPPORT_REPL_STR(G.eol_ch = '\n';) \
110} while (0)
111
112
113static int xargs_exec(void)
114{
115 int status;
116
117 status = spawn_and_wait(G.args);
118 if (status < 0) {
119 bb_simple_perror_msg(G.args[0]);
120 return errno == ENOENT ? 127 : 126;
121 }
122 if (status == 255) {
123 bb_error_msg("%s: exited with status 255; aborting", G.args[0]);
124 return 124;
125 }
126 if (status >= 0x180) {
127 bb_error_msg("'%s' terminated by signal %d",
128 G.args[0], status - 0x180);
129 return 125;
130 }
131 if (status)
132 return 123;
133 return 0;
134}
135
136/* In POSIX/C locale isspace is only these chars: "\t\n\v\f\r" and space.
137 * "\t\n\v\f\r" happen to have ASCII codes 9,10,11,12,13.
138 */
139#define ISSPACE(a) ({ unsigned char xargs__isspace = (a) - 9; xargs__isspace == (' ' - 9) || xargs__isspace <= (13 - 9); })
140
141static void store_param(char *s)
142{
143 /* Grow by 256 elements at once */
144 if (!(G.idx & 0xff)) { /* G.idx == N*256 */
145 /* Enlarge, make G.args[(N+1)*256 - 1] last valid idx */
146 G.args = xrealloc(G.args, sizeof(G.args[0]) * (G.idx + 0x100));
147 }
148 G.args[G.idx++] = s;
149}
150
151/* process[0]_stdin:
152 * Read characters into buf[n_max_chars+1], and when parameter delimiter
153 * is seen, store the address of a new parameter to args[].
154 * If reading discovers that last chars do not form the complete
155 * parameter, the pointer to the first such "tail character" is returned.
156 * (buf has extra byte at the end to accommodate terminating NUL
157 * of "tail characters" string).
158 * Otherwise, the returned pointer points to NUL byte.
159 * On entry, buf[] may contain some "seed chars" which are to become
160 * the beginning of the first parameter.
161 */
162
163#if ENABLE_FEATURE_XARGS_SUPPORT_QUOTES
164static char* FAST_FUNC process_stdin(int n_max_chars, int n_max_arg, char *buf)
165{
166#define NORM 0
167#define QUOTE 1
168#define BACKSLASH 2
169#define SPACE 4
170 char q = '\0'; /* quote char */
171 char state = NORM;
172 char *s = buf; /* start of the word */
173 char *p = s + strlen(buf); /* end of the word */
174
175 buf += n_max_chars; /* past buffer's end */
176
177 /* "goto ret" is used instead of "break" to make control flow
178 * more obvious: */
179
180 while (1) {
181 int c = getchar();
182 if (c == EOF) {
183 if (p != s)
184 goto close_word;
185 goto ret;
186 }
187 if (state == BACKSLASH) {
188 state = NORM;
189 goto set;
190 }
191 if (state == QUOTE) {
192 if (c != q)
193 goto set;
194 q = '\0';
195 state = NORM;
196 } else { /* if (state == NORM) */
197 if (ISSPACE(c)) {
198 if (p != s) {
199 close_word:
200 state = SPACE;
201 c = '\0';
202 goto set;
203 }
204 } else {
205 if (c == '\\') {
206 state = BACKSLASH;
207 } else if (c == '\'' || c == '"') {
208 q = c;
209 state = QUOTE;
210 } else {
211 set:
212 *p++ = c;
213 }
214 }
215 }
216 if (state == SPACE) { /* word's delimiter or EOF detected */
217 if (q) {
218 bb_error_msg_and_die("unmatched %s quote",
219 q == '\'' ? "single" : "double");
220 }
221 /* A full word is loaded */
222 if (G.eof_str) {
223 if (strcmp(s, G.eof_str) == 0) {
224 while (getchar() != EOF)
225 continue;
226 p = s;
227 goto ret;
228 }
229 }
230 store_param(s);
231 dbg_msg("args[]:'%s'", s);
232 s = p;
233 n_max_arg--;
234 if (n_max_arg == 0) {
235 goto ret;
236 }
237 state = NORM;
238 }
239 if (p == buf) {
240 goto ret;
241 }
242 }
243 ret:
244 *p = '\0';
245 /* store_param(NULL) - caller will do it */
246 dbg_msg("return:'%s'", s);
247 return s;
248}
249#else
250/* The variant does not support single quotes, double quotes or backslash */
251static char* FAST_FUNC process_stdin(int n_max_chars, int n_max_arg, char *buf)
252{
253 char *s = buf; /* start of the word */
254 char *p = s + strlen(buf); /* end of the word */
255
256 buf += n_max_chars; /* past buffer's end */
257
258 while (1) {
259 int c = getchar();
260 if (c == EOF) {
261 if (p == s)
262 goto ret;
263 }
264 if (c == EOF || ISSPACE(c)) {
265 if (p == s)
266 continue;
267 c = EOF;
268 }
269 *p++ = (c == EOF ? '\0' : c);
270 if (c == EOF) { /* word's delimiter or EOF detected */
271 /* A full word is loaded */
272 if (G.eof_str) {
273 if (strcmp(s, G.eof_str) == 0) {
274 while (getchar() != EOF)
275 continue;
276 p = s;
277 goto ret;
278 }
279 }
280 store_param(s);
281 dbg_msg("args[]:'%s'", s);
282 s = p;
283 n_max_arg--;
284 if (n_max_arg == 0) {
285 goto ret;
286 }
287 }
288 if (p == buf) {
289 goto ret;
290 }
291 }
292 ret:
293 *p = '\0';
294 /* store_param(NULL) - caller will do it */
295 dbg_msg("return:'%s'", s);
296 return s;
297}
298#endif /* FEATURE_XARGS_SUPPORT_QUOTES */
299
300#if ENABLE_FEATURE_XARGS_SUPPORT_ZERO_TERM
301static char* FAST_FUNC process0_stdin(int n_max_chars, int n_max_arg, char *buf)
302{
303 char *s = buf; /* start of the word */
304 char *p = s + strlen(buf); /* end of the word */
305
306 buf += n_max_chars; /* past buffer's end */
307
308 while (1) {
309 int c = getchar();
310 if (c == EOF) {
311 if (p == s)
312 goto ret;
313 c = '\0';
314 }
315 *p++ = c;
316 if (c == '\0') { /* NUL or EOF detected */
317 /* A full word is loaded */
318 store_param(s);
319 dbg_msg("args[]:'%s'", s);
320 s = p;
321 n_max_arg--;
322 if (n_max_arg == 0) {
323 goto ret;
324 }
325 }
326 if (p == buf) {
327 goto ret;
328 }
329 }
330 ret:
331 *p = '\0';
332 /* store_param(NULL) - caller will do it */
333 dbg_msg("return:'%s'", s);
334 return s;
335}
336#endif /* FEATURE_XARGS_SUPPORT_ZERO_TERM */
337
338#if ENABLE_FEATURE_XARGS_SUPPORT_REPL_STR
339/*
340 * Used if -I<repl> was specified.
341 * In this mode, words aren't appended to PROG ARGS.
342 * Instead, entire input line is read, then <repl> string
343 * in every PROG and ARG is replaced with the line:
344 * echo -e "ho ho\nhi" | xargs -I_ cmd __ _
345 * results in "cmd 'ho hoho ho' 'ho ho'"; "cmd 'hihi' 'hi'".
346 * -n MAX_ARGS seems to be ignored.
347 * Tested with GNU findutils 4.5.10.
348 */
349//FIXME: n_max_chars is not handled the same way as in GNU findutils.
350//FIXME: quoting is not implemented.
351static char* FAST_FUNC process_stdin_with_replace(int n_max_chars, int n_max_arg UNUSED_PARAM, char *buf)
352{
353 int i;
354 char *end, *p;
355
356 /* Free strings from last invocation, if any */
357 for (i = 0; G.args && G.args[i]; i++)
358 if (G.args[i] != G.argv[i])
359 free(G.args[i]);
360
361 end = buf + n_max_chars;
362 p = buf;
363
364 while (1) {
365 int c = getchar();
366 if (c == EOF || c == G.eol_ch) {
367 if (p == buf)
368 goto ret; /* empty line */
369 c = '\0';
370 }
371 *p++ = c;
372 if (c == '\0') { /* EOL or EOF detected */
373 i = 0;
374 while (G.argv[i]) {
375 char *arg = G.argv[i];
376 int count = count_strstr(arg, G.repl_str);
377 if (count != 0)
378 arg = xmalloc_substitute_string(arg, count, G.repl_str, buf);
379 store_param(arg);
380 dbg_msg("args[]:'%s'", arg);
381 i++;
382 }
383 p = buf;
384 goto ret;
385 }
386 if (p == end) {
387 goto ret;
388 }
389 }
390 ret:
391 *p = '\0';
392 /* store_param(NULL) - caller will do it */
393 dbg_msg("return:'%s'", buf);
394 return buf;
395}
396#endif
397
398#if ENABLE_FEATURE_XARGS_SUPPORT_CONFIRMATION
399/* Prompt the user for a response, and
400 * if user responds affirmatively, return true;
401 * otherwise, return false. Uses "/dev/tty", not stdin.
402 */
403static int xargs_ask_confirmation(void)
404{
405 FILE *tty_stream;
406 int c, savec;
407
408 tty_stream = xfopen_for_read(CURRENT_TTY);
409 fputs(" ?...", stderr);
410 fflush_all();
411 c = savec = getc(tty_stream);
412 while (c != EOF && c != '\n')
413 c = getc(tty_stream);
414 fclose(tty_stream);
415 return (savec == 'y' || savec == 'Y');
416}
417#else
418# define xargs_ask_confirmation() 1
419#endif
420
421//usage:#define xargs_trivial_usage
422//usage: "[OPTIONS] [PROG ARGS]"
423//usage:#define xargs_full_usage "\n\n"
424//usage: "Run PROG on every item given by stdin\n"
425//usage: IF_FEATURE_XARGS_SUPPORT_CONFIRMATION(
426//usage: "\n -p Ask user whether to run each command"
427//usage: )
428//usage: "\n -r Don't run command if input is empty"
429//usage: IF_FEATURE_XARGS_SUPPORT_ZERO_TERM(
430//usage: "\n -0 Input is separated by NUL characters"
431//usage: )
432//usage: "\n -t Print the command on stderr before execution"
433//usage: "\n -e[STR] STR stops input processing"
434//usage: "\n -n N Pass no more than N args to PROG"
435//usage: "\n -s N Pass command line of no more than N bytes"
436//usage: IF_FEATURE_XARGS_SUPPORT_REPL_STR(
437//usage: "\n -I STR Replace STR within PROG ARGS with input line"
438//usage: )
439//usage: IF_FEATURE_XARGS_SUPPORT_TERMOPT(
440//usage: "\n -x Exit if size is exceeded"
441//usage: )
442//usage:#define xargs_example_usage
443//usage: "$ ls | xargs gzip\n"
444//usage: "$ find . -name '*.c' -print | xargs rm\n"
445
446/* Correct regardless of combination of CONFIG_xxx */
447enum {
448 OPTBIT_VERBOSE = 0,
449 OPTBIT_NO_EMPTY,
450 OPTBIT_UPTO_NUMBER,
451 OPTBIT_UPTO_SIZE,
452 OPTBIT_EOF_STRING,
453 OPTBIT_EOF_STRING1,
454 IF_FEATURE_XARGS_SUPPORT_CONFIRMATION(OPTBIT_INTERACTIVE,)
455 IF_FEATURE_XARGS_SUPPORT_TERMOPT( OPTBIT_TERMINATE ,)
456 IF_FEATURE_XARGS_SUPPORT_ZERO_TERM( OPTBIT_ZEROTERM ,)
457 IF_FEATURE_XARGS_SUPPORT_REPL_STR( OPTBIT_REPLSTR ,)
458 IF_FEATURE_XARGS_SUPPORT_REPL_STR( OPTBIT_REPLSTR1 ,)
459
460 OPT_VERBOSE = 1 << OPTBIT_VERBOSE ,
461 OPT_NO_EMPTY = 1 << OPTBIT_NO_EMPTY ,
462 OPT_UPTO_NUMBER = 1 << OPTBIT_UPTO_NUMBER,
463 OPT_UPTO_SIZE = 1 << OPTBIT_UPTO_SIZE ,
464 OPT_EOF_STRING = 1 << OPTBIT_EOF_STRING , /* GNU: -e[<param>] */
465 OPT_EOF_STRING1 = 1 << OPTBIT_EOF_STRING1, /* SUS: -E<param> */
466 OPT_INTERACTIVE = IF_FEATURE_XARGS_SUPPORT_CONFIRMATION((1 << OPTBIT_INTERACTIVE)) + 0,
467 OPT_TERMINATE = IF_FEATURE_XARGS_SUPPORT_TERMOPT( (1 << OPTBIT_TERMINATE )) + 0,
468 OPT_ZEROTERM = IF_FEATURE_XARGS_SUPPORT_ZERO_TERM( (1 << OPTBIT_ZEROTERM )) + 0,
469 OPT_REPLSTR = IF_FEATURE_XARGS_SUPPORT_REPL_STR( (1 << OPTBIT_REPLSTR )) + 0,
470 OPT_REPLSTR1 = IF_FEATURE_XARGS_SUPPORT_REPL_STR( (1 << OPTBIT_REPLSTR1 )) + 0,
471};
472#define OPTION_STR "+trn:s:e::E:" \
473 IF_FEATURE_XARGS_SUPPORT_CONFIRMATION("p") \
474 IF_FEATURE_XARGS_SUPPORT_TERMOPT( "x") \
475 IF_FEATURE_XARGS_SUPPORT_ZERO_TERM( "0") \
476 IF_FEATURE_XARGS_SUPPORT_REPL_STR( "I:i::")
477
478int xargs_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
479int xargs_main(int argc, char **argv)
480{
481 int i;
482 int child_error = 0;
483 char *max_args;
484 char *max_chars;
485 char *buf;
486 unsigned opt;
487 int n_max_chars;
488 int n_max_arg;
489#if ENABLE_FEATURE_XARGS_SUPPORT_ZERO_TERM \
490 || ENABLE_FEATURE_XARGS_SUPPORT_REPL_STR
491 char* FAST_FUNC (*read_args)(int, int, char*) = process_stdin;
492#else
493#define read_args process_stdin
494#endif
495
496 INIT_G();
497
498#if ENABLE_DESKTOP && ENABLE_LONG_OPTS
499 /* For example, Fedora's build system uses --no-run-if-empty */
500 applet_long_options =
501 "no-run-if-empty\0" No_argument "r"
502 ;
503#endif
504 opt = getopt32(argv, OPTION_STR,
505 &max_args, &max_chars, &G.eof_str, &G.eof_str
506 IF_FEATURE_XARGS_SUPPORT_REPL_STR(, &G.repl_str, &G.repl_str)
507 );
508
509 /* -E ""? You may wonder why not just omit -E?
510 * This is used for portability:
511 * old xargs was using "_" as default for -E / -e */
512 if ((opt & OPT_EOF_STRING1) && G.eof_str[0] == '\0')
513 G.eof_str = NULL;
514
515 if (opt & OPT_ZEROTERM) {
516 IF_FEATURE_XARGS_SUPPORT_ZERO_TERM(read_args = process0_stdin;)
517 IF_FEATURE_XARGS_SUPPORT_REPL_STR(G.eol_ch = '\0';)
518 }
519
520 argv += optind;
521 argc -= optind;
522 if (!argv[0]) {
523 /* default behavior is to echo all the filenames */
524 *--argv = (char*)"echo";
525 argc++;
526 }
527
528 /*
529 * The Open Group Base Specifications Issue 6:
530 * "The xargs utility shall limit the command line length such that
531 * when the command line is invoked, the combined argument
532 * and environment lists (see the exec family of functions
533 * in the System Interfaces volume of IEEE Std 1003.1-2001)
534 * shall not exceed {ARG_MAX}-2048 bytes".
535 */
536 n_max_chars = bb_arg_max();
537 if (n_max_chars > 32 * 1024)
538 n_max_chars = 32 * 1024;
539 /*
540 * POSIX suggests substracting 2048 bytes from sysconf(_SC_ARG_MAX)
541 * so that the process may safely modify its environment.
542 */
543 n_max_chars -= 2048;
544
545 if (opt & OPT_UPTO_SIZE) {
546 n_max_chars = xatou_range(max_chars, 1, INT_MAX);
547 }
548 /* Account for prepended fixed arguments */
549 {
550 size_t n_chars = 0;
551 for (i = 0; argv[i]; i++) {
552 n_chars += strlen(argv[i]) + 1;
553 }
554 n_max_chars -= n_chars;
555 }
556 /* Sanity check */
557 if (n_max_chars <= 0) {
558 bb_error_msg_and_die("can't fit single argument within argument list size limit");
559 }
560
561 buf = xzalloc(n_max_chars + 1);
562
563 n_max_arg = n_max_chars;
564 if (opt & OPT_UPTO_NUMBER) {
565 n_max_arg = xatou_range(max_args, 1, INT_MAX);
566 /* Not necessary, we use growable args[]: */
567 /* if (n_max_arg > n_max_chars) n_max_arg = n_max_chars */
568 }
569
570#if ENABLE_FEATURE_XARGS_SUPPORT_REPL_STR
571 if (opt & (OPT_REPLSTR | OPT_REPLSTR1)) {
572 /*
573 * -I<str>:
574 * Unmodified args are kept in G.argv[i],
575 * G.args[i] receives malloced G.argv[i] with <str> replaced
576 * with input line. Setting this up:
577 */
578 G.args = NULL;
579 G.argv = argv;
580 argc = 0;
581 read_args = process_stdin_with_replace;
582 /* Make -I imply -r. GNU findutils seems to do the same: */
583 /* (otherwise "echo -n | xargs -I% echo %" would SEGV) */
584 opt |= OPT_NO_EMPTY;
585 } else
586#endif
587 {
588 /* Allocate pointers for execvp.
589 * We can statically allocate (argc + n_max_arg + 1) elements
590 * and do not bother with resizing args[], but on 64-bit machines
591 * this results in args[] vector which is ~8 times bigger
592 * than n_max_chars! That is, with n_max_chars == 20k,
593 * args[] will take 160k (!), which will most likely be
594 * almost entirely unused.
595 *
596 * See store_param() for matching 256-step growth logic
597 */
598 G.args = xmalloc(sizeof(G.args[0]) * ((argc + 0xff) & ~0xff));
599 /* Store the command to be executed, part 1 */
600 for (i = 0; argv[i]; i++)
601 G.args[i] = argv[i];
602 }
603
604 while (1) {
605 char *rem;
606
607 G.idx = argc;
608 rem = read_args(n_max_chars, n_max_arg, buf);
609 store_param(NULL);
610
611 if (!G.args[argc]) {
612 if (*rem != '\0')
613 bb_error_msg_and_die("argument line too long");
614 if (opt & OPT_NO_EMPTY)
615 break;
616 }
617 opt |= OPT_NO_EMPTY;
618
619 if (opt & (OPT_INTERACTIVE | OPT_VERBOSE)) {
620 const char *fmt = " %s" + 1;
621 char **args = G.args;
622 for (i = 0; args[i]; i++) {
623 fprintf(stderr, fmt, args[i]);
624 fmt = " %s";
625 }
626 if (!(opt & OPT_INTERACTIVE))
627 bb_putchar_stderr('\n');
628 }
629
630 if (!(opt & OPT_INTERACTIVE) || xargs_ask_confirmation()) {
631 child_error = xargs_exec();
632 }
633
634 if (child_error > 0 && child_error != 123) {
635 break;
636 }
637
638 overlapping_strcpy(buf, rem);
639 } /* while */
640
641 if (ENABLE_FEATURE_CLEAN_UP) {
642 free(G.args);
643 free(buf);
644 }
645
646 return child_error;
647}
648
649
650#ifdef TEST
651
652const char *applet_name = "debug stuff usage";
653
654void bb_show_usage(void)
655{
656 fprintf(stderr, "Usage: %s [-p] [-r] [-t] -[x] [-n max_arg] [-s max_chars]\n",
657 applet_name);
658 exit(EXIT_FAILURE);
659}
660
661int main(int argc, char **argv)
662{
663 return xargs_main(argc, argv);
664}
665#endif /* TEST */
Note: See TracBrowser for help on using the repository browser.