2 * Copyright 1998,2001-2003 Sun Microsystems, Inc. All rights reserved.
3 * Use is subject to license terms.
6 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
7 /* All Rights Reserved */
10 * Copyright (c) 1985 Regents of the University of California.
11 * All rights reserved. The Berkeley software License Agreement
12 * specifies the terms and conditions for redistribution.
15 #pragma ident "%Z%%M% %I% %E% SMI"
27 #define round(a, b) ((((a) + (b) - 1) / (b)) * (b))
30 * Things to handle interruptions.
34 static char *nextarg
= NULL
;
36 static int dontexpand
; /* co-routine state set in getnext, used in expandarg */
39 static void getcmd(char *, char *, size_t, char *, size_t, struct arglist
*);
40 static void expandarg(char *, struct arglist
*);
41 static void printlist(char *, ino_t
, char *, int);
42 static void formatf(struct arglist
*);
43 static char *copynext(char *, char *, size_t);
44 static int fcmp(struct afile
*, struct afile
*);
45 static char *fmtentry(struct afile
*);
46 static void setpagercmd(void);
47 static uint_t
setpagerargs(char **);
50 static void expandarg();
51 static void printlist();
52 static void formatf();
53 static char *copynext();
55 static char *fmtentry();
56 static void setpagercmd();
57 static uint_t
setpagerargs();
61 * Read and execute commands from the terminal.
72 static struct arglist alist
= { 0, 0, 0, 0, 0 };
73 char curdir
[MAXCOMPLEXLEN
];
74 char name
[MAXCOMPLEXLEN
];
81 canon("/", curdir
, sizeof (curdir
));
83 if (setjmp(reset
) != 0) {
84 for (; alist
.head
< alist
.last
; alist
.head
++)
85 freename(alist
.head
->fname
);
88 goto loop
; /* make sure jmpbuf is up-to-date */
91 getcmd(curdir
, cmd
, sizeof (cmd
), name
, sizeof (name
), &alist
);
94 * Using strncmp() to catch unique prefixes.
98 * Add elements to the extraction list.
101 if (strncmp(cmd
, "add", strlen(cmd
)) != 0)
105 ino
= dirlookup(name
);
110 treescan(name
, ino
, addfile
);
113 * Change working directory.
116 if (strncmp(cmd
, "cd", strlen(cmd
)) != 0)
120 ino
= dirlookup(name
);
123 if (inodetype(ino
) == LEAF
) {
124 (void) fprintf(stderr
,
125 gettext("%s: not a directory\n"), name
);
129 /* No need to canon(name), getcmd() did it for us */
130 (void) strncpy(curdir
, name
, sizeof (curdir
));
131 curdir
[sizeof (curdir
) - 1] = '\0';
134 * Delete elements from the extraction list.
137 if (strncmp(cmd
, "delete", strlen(cmd
)) != 0)
141 np
= lookupname(name
);
142 if (np
== NIL
|| (np
->e_flags
& NEW
) == 0) {
143 (void) fprintf(stderr
,
144 gettext("%s: not on extraction list\n"), name
);
147 treescan(name
, np
->e_ino
, deletefile
);
150 * Extract the requested list.
153 if (strncmp(cmd
, "extract", strlen(cmd
)) != 0)
155 attrscan(0, addfile
);
164 * List available commands.
167 if (strncmp(cmd
, "help", strlen(cmd
)) != 0)
171 /* ANSI string catenation, to shut cstyle up */
172 (void) fprintf(stderr
, "%s",
173 gettext("Available commands are:\n"
174 "\tls [arg] - list directory\n"
175 "\tmarked [arg] - list items marked for extraction from directory\n"
176 "\tcd arg - change directory\n"
177 "\tpwd - print current directory\n"
178 "\tadd [arg] - add `arg' to list of files to be extracted\n"
179 "\tdelete [arg] - delete `arg' from list of files to be extracted\n"
180 "\textract - extract requested files\n"
181 "\tsetmodes - set modes of requested directories\n"
182 "\tquit - immediately exit program\n"
183 "\twhat - list dump header information\n"
184 "\tverbose - toggle verbose flag (useful with ``ls'')\n"
185 "\tpaginate - toggle pagination flag (affects ``ls'' and ``marked'')\n"
186 "\tsetpager - set pagination command and arguments\n"
187 "\thelp or `?' - print this list\n"
188 "If no `arg' is supplied, the current directory is used\n"));
195 if ((strncmp(cmd
, "ls", strlen(cmd
)) != 0) &&
196 (strncmp(cmd
, "marked", strlen(cmd
)) != 0))
200 ino
= dirlookup(name
);
203 printlist(name
, ino
, curdir
, *cmd
== 'm');
206 * Print current directory or enable pagination.
211 if (strncmp(cmd
, "pwd", strlen(cmd
)) == 0) {
212 if (curdir
[1] == '\0') {
213 (void) fprintf(stderr
, "/\n");
215 (void) fprintf(stderr
, "%s\n", &curdir
[1]);
217 } else if (strncmp(cmd
, "paginate", strlen(cmd
)) == 0) {
219 (void) fprintf(stderr
,
220 gettext("paging disabled\n"));
225 (void) fprintf(stderr
,
226 gettext("paging enabled (%s)\n"),
229 (void) fprintf(stderr
,
230 gettext("paging enabled\n"));
235 while (index
< pager_len
) {
236 (void) fprintf(stderr
,
237 ">>>pager_vector[%d] = `%s'\n",
239 pager_vector
[index
] ?
240 pager_vector
[index
] : "(null)");
253 if (strncmp(cmd
, "quit", strlen(cmd
)) != 0)
258 if (strncmp(cmd
, "xit", strlen(cmd
)) != 0)
263 * Toggle verbose mode.
266 if (strncmp(cmd
, "verbose", strlen(cmd
)) != 0)
269 (void) fprintf(stderr
, gettext("verbose mode off\n"));
273 (void) fprintf(stderr
, gettext("verbose mode on\n"));
277 * Just restore requested directory modes, or set pagination command.
282 if (strncmp(cmd
, "setmodes", strlen(cmd
)) == 0) {
284 } else if (strncmp(cmd
, "setpager", strlen(cmd
)) == 0) {
291 * Print out dump header information.
294 if (strncmp(cmd
, "what", strlen(cmd
)) != 0)
302 if (strncmp(cmd
, "Debug", strlen(cmd
)) != 0)
305 (void) fprintf(stderr
, gettext("debugging mode off\n"));
309 (void) fprintf(stderr
, gettext("debugging mode on\n"));
317 (void) fprintf(stderr
,
318 gettext("%s: unknown command; type ? for help\n"), cmd
);
321 (void) fprintf(stderr
,
322 gettext("%s: ambiguous command; type ? for help\n"), cmd
);
328 static char input
[MAXCOMPLEXLEN
]; /* shared by getcmd() and setpagercmd() */
329 #define rawname input /* save space by reusing input buffer */
332 * Read and parse an interactive command.
333 * The first word on the line is assigned to "cmd". If
334 * there are no arguments on the command line, then "curdir"
335 * is returned as the argument. If there are arguments
336 * on the line they are returned one at a time on each
337 * successive call to getcmd. Each argument is first assigned
338 * to "name". If it does not start with "/" the pathname in
339 * "curdir" is prepended to it. Finally "canon" is called to
340 * eliminate any embedded ".." components.
344 getcmd(curdir
, cmd
, cmdsiz
, name
, namesiz
, ap
)
345 char *curdir
, *cmd
, *name
;
346 size_t cmdsiz
, namesiz
;
350 char output
[MAXCOMPLEXLEN
];
353 * Check to see if still processing arguments.
355 if (ap
->head
!= ap
->last
) {
356 (void) strncpy(name
, ap
->head
->fname
, namesiz
);
357 name
[namesiz
- 1] = '\0';
358 /* double null terminate string */
359 if ((strlen(name
) + 2) > namesiz
) {
360 fprintf(stderr
, gettext("name is too long, ignoring"));
361 memset(name
, 0, namesiz
);
363 name
[strlen(name
) + 1] = '\0';
365 freename(ap
->head
->fname
);
372 * Read a command line and trim off trailing white space.
376 (void) fprintf(stderr
, "%s > ", progname
);
377 (void) fflush(stderr
);
378 (void) fgets(input
, sizeof (input
), terminal
);
379 } while (!feof(terminal
) && input
[0] == '\n');
380 if (feof(terminal
)) {
381 (void) strncpy(cmd
, "quit", cmdsiz
);
384 /* trim off trailing white space and newline */
385 for (cp
= &input
[strlen(input
) - 2];
386 cp
>= &input
[0] && isspace((uchar_t
)*cp
);
389 /*LINTED [empty loop body]*/
392 if ((strlen(input
) + 2) > MAXCOMPLEXLEN
) {
393 fprintf(stderr
, gettext("command is too long\n"));
396 /* double null terminate string */
404 * Copy the command into "cmd".
406 cp
= copynext(input
, cmd
, cmdsiz
);
409 * If no argument, use curdir as the default.
412 (void) strncpy(name
, curdir
, namesiz
);
413 name
[namesiz
- 1] = '\0';
414 /* double null terminate string */
415 if ((strlen(name
) + 2) > namesiz
) {
416 fprintf(stderr
, gettext("name is too long, ignoring"));
417 memset(name
, 0, namesiz
);
419 name
[strlen(name
) + 1] = '\0';
425 * Find the next argument.
428 cp
= copynext(nextarg
, rawname
, sizeof (rawname
));
434 * If it an absolute pathname, canonicalize it and return it.
436 if (rawname
[0] == '/') {
437 canon(rawname
, name
, namesiz
);
440 * For relative pathnames, prepend the current directory to
441 * it then canonicalize and return it.
443 (void) snprintf(output
, sizeof (output
), "%s/%s",
445 canon(output
, name
, namesiz
);
449 * ap->head->fname guaranteed to be double null-terminated and
450 * no more than MAXCOMPLEXLEN characters long.
452 assert(namesiz
>= (MAXCOMPLEXLEN
));
453 (void) strcpy(name
, ap
->head
->fname
);
454 /* double null terminate string */
455 name
[strlen(name
) + 1] = '\0';
456 freename(ap
->head
->fname
);
462 * Strip off the next token of the input.
465 copynext(input
, output
, outsize
)
466 char *input
, *output
;
469 char *cp
, *bp
, *limit
;
473 /* skip to argument */
474 for (cp
= input
; *cp
!= '\0' && isspace((uchar_t
)*cp
); cp
++) {
476 /*LINTED [empty loop body]*/
479 limit
= output
+ outsize
- 1; /* -1 for the trailing \0 */
480 while (!isspace((uchar_t
)*cp
) && *cp
!= '\0' && bp
< limit
) {
482 * Handle back slashes.
486 (void) fprintf(stderr
, gettext(
487 "command lines cannot be continued\n"));
494 * The usual unquoted case.
496 if (*cp
!= '\'' && *cp
!= '"') {
501 * Handle single and double quotes.
505 while (*cp
!= quote
&& *cp
!= '\0' && bp
< limit
)
508 (void) fprintf(stderr
,
509 gettext("missing %c\n"), (uchar_t
)quote
);
515 if ((strlen(output
) + 2) > outsize
) {
516 fprintf(stderr
, gettext(
517 "name is too long, ignoring"));
518 memset(output
, 0, outsize
);
520 /* double null terminate string */
527 * Canonicalize file names to always start with ``./'' and
528 * remove any imbedded "." and ".." components.
530 * The pathname "canonname" is returned double null terminated.
533 canon(rawname
, canonname
, limit
)
534 char *rawname
, *canonname
;
537 char *cp
, *np
, *prefix
;
541 if (strcmp(rawname
, ".") == 0 || strncmp(rawname
, "./", 2) == 0)
543 else if (rawname
[0] == '/')
547 (void) snprintf(canonname
, limit
, "%s%s", prefix
, rawname
);
549 * Eliminate multiple and trailing '/'s
551 for (cp
= np
= canonname
; *np
!= '\0'; cp
++) {
553 while (*cp
== '/' && *np
== '/')
557 if ((strlen(canonname
) + 2) > limit
) {
559 gettext("canonical name is too long, ignoring name\n"));
560 memset(canonname
, 0, limit
);
562 /* double null terminate string */
569 * Eliminate extraneous "." and ".." from pathnames. Uses
570 * memmove(), as strcpy() might do the wrong thing for these
574 while (*np
!= '\0') {
577 while (*np
!= '/' && *np
!= '\0')
579 if (np
- cp
== 1 && *cp
== '.') {
582 (void) memmove(cp
, np
, len
);
584 /* double null terminate string */
585 *(cp
+ len
+ 1) = '\0';
588 if (np
- cp
== 2 && strncmp(cp
, "..", 2) == 0) {
590 /* find beginning of name */
591 while (cp
> &canonname
[1] && *--cp
!= '/') {
593 /*LINTED [empty loop body]*/
596 (void) memmove(cp
, np
, len
);
598 /* double null terminate string */
599 *(cp
+ len
+ 1) = '\0';
606 * globals (file name generation)
608 * "*" in params matches r.e ".*"
609 * "?" in params matches r.e. "."
610 * "[...]" in params matches character class
611 * "[...a-z...]" in params matches a through z.
618 static struct afile single
;
621 ap
->head
= ap
->last
= (struct afile
*)0;
625 size
= expand(arg
, 0, ap
);
629 ep
= lookupname(arg
);
630 single
.fnum
= ep
? ep
->e_ino
: 0;
631 single
.fname
= savename(arg
);
633 ap
->last
= ap
->head
+ 1;
636 if ((ap
->last
- ap
->head
) > ULONG_MAX
) {
637 (void) fprintf(stderr
,
638 gettext("Argument expansion too large to sort\n"));
640 /* LINTED pointer arith just range-checked */
641 qsort((char *)ap
->head
, (size_t)(ap
->last
- ap
->head
),
643 (int (*)(const void *, const void *)) fcmp
);
648 * Do an "ls" style listing of a directory
651 printlist(name
, ino
, basename
, marked_only
)
659 static struct arglist alist
= { 0, 0, 0, 0, "ls" };
665 if ((dirp
= rst_opendir(name
)) == NULL
) {
667 if (strncmp(name
, basename
, strlen(basename
)) == 0)
668 single
.fname
= savename(name
+ strlen(basename
) + 1);
670 single
.fname
= savename(name
);
671 alist
.head
= &single
;
672 alist
.last
= alist
.head
+ 1;
673 if (alist
.base
!= NULL
) {
678 alist
.head
= (struct afile
*)0;
679 (void) fprintf(stderr
, "%s:\n", name
);
680 while (dp
= rst_readdir(dirp
)) {
681 if (dp
== NULL
|| dp
->d_ino
== 0) {
686 if (!dflag
&& BIT(dp
->d_ino
, dumpmap
) == 0)
689 (strcmp(dp
->d_name
, ".") == 0 ||
690 strcmp(dp
->d_name
, "..") == 0))
694 np
= lookupino(dp
->d_ino
);
695 if ((np
== NIL
) || ((np
->e_flags
& NEW
) == 0))
699 if (!mkentry(dp
->d_name
, dp
->d_ino
, &alist
)) {
706 if (alist
.head
!= 0) {
707 if ((alist
.last
- alist
.head
) > ULONG_MAX
) {
708 (void) fprintf(stderr
,
709 gettext("Directory too large to sort\n"));
711 qsort((char *)alist
.head
,
712 /* LINTED range-checked */
713 (size_t)(alist
.last
- alist
.head
),
714 sizeof (*alist
.head
),
715 (int (*)(const void *, const void *)) fcmp
);
718 for (fp
= alist
.head
; fp
< alist
.last
; fp
++)
722 * Don't free alist.base, as we'll probably be called
723 * again, and might as well re-use what we've got.
727 (void) fprintf(stderr
, "\n");
733 * Print out a pretty listing of a directory
741 /* LINTED: result fits into an int */
742 int nentry
= (int)(ap
->last
- ap
->head
);
744 uint_t len
, w
, width
= 0, columns
, lines
;
746 FILE *output
= stderr
;
748 if (ap
->head
== ap
->last
)
755 perror(gettext("could not create pipe"));
761 perror(gettext("could not fork"));
765 * Make sure final output still ends up in
768 (void) dup2(fileno(stderr
), fileno(stdout
));
769 (void) close(fds
[0]);
770 (void) dup2(fds
[1], fileno(stdin
));
771 execvp(pager_vector
[0], pager_vector
);
772 perror(gettext("execvp of pager failed"));
776 (void) close(fds
[1]);
777 output
= fdopen(fds
[0], "w");
778 if (output
!= (FILE *)NULL
) {
781 perror(gettext("could not open pipe to pager"));
784 (void) fprintf(stderr
,
785 gettext("pagination disabled\n"));
790 for (fp
= ap
->head
; fp
< ap
->last
; fp
++) {
791 fp
->ftype
= inodetype(fp
->fnum
);
792 np
= lookupino(fp
->fnum
);
794 fp
->fflags
= np
->e_flags
;
797 len
= strlen(fmtentry(fp
));
802 columns
= 80 / width
;
805 lines
= (nentry
+ columns
- 1) / columns
;
806 for (i
= 0; i
< lines
&& !ferror(output
); i
++) {
807 for (j
= 0; j
< columns
&& !ferror(output
); j
++) {
808 fp
= ap
->head
+ j
* lines
+ i
;
810 (void) fprintf(output
, "%s", cp
);
811 if (fp
+ lines
>= ap
->last
) {
812 (void) fprintf(output
, "\n");
818 if (fprintf(output
, " ") < 0)
825 (void) fclose(output
);
826 (void) wait((int *)NULL
);
831 * Comparison routine for qsort.
835 struct afile
*f1
, *f2
;
838 return (strcoll(f1
->fname
, f2
->fname
));
842 * Format a directory entry.
848 static char fmtres
[MAXCOMPLEXLEN
];
849 static int precision
= 0;
851 char *cp
, *dp
, *limit
;
854 /* MAXCOMPLEXLEN assumed to be >= 1 */
857 if (precision
== 0) {
858 for (i
= maxino
; i
!= 0; i
/= 10)
860 if (sizeof (fmtres
) < (unsigned)(precision
+ 2)) {
861 (void) fprintf(stderr
, gettext(
862 "\nInternal check failed, minimum width %d exceeds available size %d\n"),
863 (precision
+ 2), sizeof (fmtres
));
867 (void) snprintf(fmtres
, sizeof (fmtres
), "%*ld ",
868 precision
, fp
->fnum
);
870 dp
= &fmtres
[strlen(fmtres
)];
871 limit
= fmtres
+ sizeof (fmtres
) - 1;
872 if (dflag
&& BIT(fp
->fnum
, dumpmap
) == 0)
874 else if ((fp
->fflags
& NEW
) != 0)
878 for (cp
= fp
->fname
; *cp
&& dp
< limit
; cp
++)
879 /* LINTED: precedence ok, can't fix system macro */
880 if (!vflag
&& (!ISPRINT(*cp
, wp
)))
884 if (fp
->ftype
== NODE
&& dp
< limit
)
891 * respond to interrupts
900 if (command
== 'i' && reset_OK
)
903 (void) snprintf(buf
, sizeof (buf
),
904 gettext("%s interrupted, continue"), progname
);
905 if (reply(buf
) == FAIL
)
909 * Set up pager_catenated and pager_vector.
920 cp
= getenv("PAGER");
922 pager_catenated
= strdup(cp
);
923 if ((pager_catenated
== NULL
) || (*pager_catenated
== '\0')) {
924 if (pager_catenated
!= NULL
)
925 free(pager_catenated
);
926 pager_catenated
= strdup(DEF_PAGER
);
928 if (pager_catenated
== NULL
) {
929 (void) fprintf(stderr
, gettext("out of memory\n"));
933 pager_vector
= (char **)malloc(sizeof (char *));
934 if (pager_vector
== NULL
) {
935 (void) fprintf(stderr
, gettext("out of memory\n"));
940 cp
= pager_catenated
;
941 (void) setpagerargs(&cp
);
946 * Resets pager_catenated and pager_vector from user input.
955 uint_t catenate_length
;
959 * We'll get called immediately after setting a pager, due to
960 * our interaction with getcmd()'s internal state. Don't do
961 * anything when that happens.
967 for (index
= 0; pager_vector
[index
] != (char *)NULL
; index
+= 1)
968 free(pager_vector
[index
]);
970 free(pager_catenated
);
973 pager_vector
= (char **)malloc(2 * sizeof (char *));
974 if (pager_vector
== NULL
) {
975 (void) fprintf(stderr
, gettext("out of memory\n"));
980 pager_vector
[0] = strdup(input
);
981 if (pager_vector
[0] == NULL
) {
982 (void) fprintf(stderr
, gettext("out of memory\n"));
986 (void) fprintf(stderr
, gettext("got command `%s'\n"), input
);
987 catenate_length
= setpagerargs(&nextarg
) + strlen(pager_vector
[0]) + 1;
988 pager_catenated
= (char *)malloc(catenate_length
*
989 (size_t)sizeof (char));
990 if (pager_catenated
== (char *)NULL
) {
991 (void) fprintf(stderr
, gettext("out of memory\n"));
994 for (index
= 0; pager_vector
[index
] != (char *)NULL
; index
+= 1) {
996 (void) strcat(pager_catenated
, " ");
997 (void) strcat(pager_catenated
, pager_vector
[index
]);
1003 * Extract arguments for the pager command from getcmd()'s input buffer.
1006 setpagerargs(source
)
1009 char word
[MAXCOMPLEXLEN
];
1013 while ((cp
!= (char *)NULL
) && (*cp
!= '\0')) {
1014 cp
= copynext(cp
, word
, sizeof (word
));
1016 fprintf(stderr
, gettext("got word `%s'\n"), word
);
1017 pager_vector
= (char **)realloc(pager_vector
,
1018 (size_t)sizeof (char *) * (pager_len
+ 1));
1019 if (pager_vector
== (char **)NULL
) {
1020 (void) fprintf(stderr
, gettext("out of memory\n"));
1023 pager_vector
[pager_len
- 1] = strdup(word
);
1024 if (pager_vector
[pager_len
- 1] == (char *)NULL
) {
1025 (void) fprintf(stderr
, gettext("out of memory\n"));
1028 length
+= strlen(word
) + 1;
1031 pager_vector
[pager_len
- 1] = (char *)NULL
;