/* * DTREE - Print the tree structure of a directory * 4/7/83 name was changed from TREE to DTREE * 9/7/83 mods for 4.1c and 4.2 dirctory structure added * * Dave Borman, Digital Unix Engineering Group * decvax!borman * Originally written at St. Olaf College, Northfield MN. * Copyright (c) 1983 by Dave Borman * All rights reserved * This program may not be sold, but may be distributed * provided this header is included. * * Usage: dtree [-adfgHlnpsvxR] [-c line-length] [directory...] * Flags: -a) include non-directory entries in listing * -d) sort tree with directories at the top * -f) sort tree with files at the top * -g) same as l, but use group name instead of user name * -H) display a header at top * -l) print stats with each listing * if both g & l flags are given, both owner and * group will be printed * -n) do not sort the tree * -p) include files starting with a '.' (except "." & "..") * -s) use shorter stats. Implies -l if -g isn't given. * -v) variable length columns off * -x) do not cross mounted file systems. * -c length) set max column length to "length" * -R) ignore RCS and SCCS directories */ /* Modified by Ed Arnold CSU-CS (csu-cs!arnold) 3-5-84 * * Allows symbolic links to both directories and individual files. * With a '-l' or '-al' option, links are denoted with a 'l' in front of * file or directory permissions. In all other instances both links to * directories and files are represented just as files are. Contents of * linked directories are not printed due to the possibility of * recursively linked directories. * * Big directory name option added by: * Mike Vevea CSU-CS (csu-cs!vevea) 3-22-84 * * Toggle sense of -v (running 4.2), and eliminate some extraneous * print info Mike Meyer Energy Analysts (mwm@ea) 4/17/84 * * Fix the exit status to correctly indicate what happened. * Mike Meyer Energy Analysts (mwm@ea) 4/23/84 * * Change MAX to INITIAL_ELEM to fix conflict on Suns, * fix incorrect default value for Clength when -v not given, * add -H option to print header with date and description of tree, * remove -b option (useless) and simplify array access, * use getopt to parse options, fix usage message, * use getwd instead of opening a pipe to pwd, * make error messages more informative, * use strncpy instead of sprintf for speed, * move function declarations to top of file, * comment out junk after #else and #endif, * make symbolic link configuring automatic, * check all error returns from malloc and realloc, * add System V/Xenix/Unos compatibility, * remove definition of rindex. * David MacKenzie 12/20/89 */ /* Compile-time options: * * STATS leave undefined to remove stats, giving more core space * and thus the ability to tree larger tree structures on PDP 11/70s. * * NEWDIR directory structure a la Berkeley 4.1c or 4.2 * * NDIR use instead of * NEWDIR must be defined as well. * * DIRENT use Posix directory library. * NEWDIR must be defined as well. * * SYSV use getcwd instead of getwd, strrchr instead of rindex. */ static char Sccsid[]="@(#)dtree.c 2.3 2/14/84"; static char Rcsid[] ="$Id: dtree.c,v 1.3 1994/06/15 22:21:02 tytso Exp $"; #include #include #include #include # include # include # if DIRENT # include # define direct dirent # else # if NDIR # include # else # include # endif # endif /* DIRENT */ /* default column length when -v is given */ #ifdef unos #define DEFCOLWID 30 #else #define DEFCOLWID 14 #endif #include /* for MAXPATHLEN */ #ifndef MAXPATHLEN # ifdef LPNMAX /* Xenix from stdio.h */ # define MAXPATHLEN (LPNMAX - 1) # else # include /* try somewhere else */ # define MAXPATHLEN PATH_MAX # endif #endif #ifndef MAXNAMLEN /* defined with NEWDIR routines */ # ifdef LFNMAX /* Xenix again */ # define MAXNAMLEN (LFNMAX - 1) # else # define MAXNAMLEN DEFCOLWID # endif #endif #define DEPTH 10 /* maximum depth that dtree will go */ #define INITIAL_ELEM 100 /* initial # of elements for list of files */ #define FFIRST 2 /* sort files first */ #define DFIRST 1 /* sort directories first */ #define FAIL -1 /* failure return status of sys calls */ #define GREATER 1 /* return value of strcmp if arg1 > arg2 */ #define LESSTHAN -1 /* return value of strcmp if arg1 < arg2 */ #define SAME 0 /* return value of strcmp if arg1 == arg2 */ char *getmode(); char *guid(); char *ggid(); struct passwd *getpwuid(); struct group *getgrgid(); #ifdef SYSV #define rindex strrchr #endif /* SYSV */ char *getwd(); char *malloc(); char *realloc(); char *rindex(); int qsort(); long time(); int compar(); /* comparison routine for qsort */ char *xmalloc(); char *xrealloc(); #ifdef SYSV #define getwd(buf) getcwd((buf), MAXPATHLEN + 1) #endif /* SYSV */ int Index = 0; /* current element of list[] */ int CLength = 0; /* max length of a column */ int All = 0; /* all != 0; list non-directory entries */ int File_dir = 0; /* flag for how to sort */ int Sort = 1; /* flag to cause sorting of entries */ int Point = 1; /* skip point files if set */ int Header = 0; /* print verbose header */ int Maxes[DEPTH]; /* array keeps track of max length in columns */ int Level = 0; /* counter for how deep we are */ int Device; /* device that we are starting tree on */ int Xdev = 1; /* set to allow crossing of devices */ int NoRCS = 0; /* unset to allow inclusion of RCS/SCCS dirs */ int Varspaces = 1; /* set to allow compaction of column width */ int Gflag = 0; /* set for group stats instead of owner */ int Longflg = 0; /* set for long listing */ int Compact = 0; /* set for shortened long listing */ struct stat Status; #ifdef S_IFLNK struct stat Lstat; /* stat of link, if there is one */ #endif /* S_IFLNK */ struct entry { int next; /* index to next element in list */ /* could be a ptr, but realloc() */ /* might screw us then */ off_t e_size; /* size in blocks */ unsigned short e_mode; /* file mode */ short e_uid; /* uid of owner */ short e_gid; /* gid of owner */ unsigned short dir : 1; /* entry is a directory */ unsigned short last : 1; /* last entry in the dir. */ unsigned short dev : 1; /* set if same device as top */ unsigned short end : 13; /* index of last subdir entry*/ char e_name[MAXNAMLEN + 1]; /* name from directory entry */ } *List, *SaveList; unsigned Size; /* how big of space we've malloced */ char *Spaces; /* used for output */ char Buf1[BUFSIZ]; /* buffers for stdio stuff. We don't want */ main(argc, argv) char **argv; int argc; { extern int optind; extern char *optarg; register int i; char top[MAXPATHLEN + 1]; /* array for treetop name */ char home[MAXPATHLEN + 1]; /* starting dir for multiple trees */ char *ptr; setbuf(stdout, Buf1); while ((i = getopt (argc, argv, "adfgHRlnpsvxc:" )) != EOF) { switch (i) { case 'a': All = 1; break; case 'c': CLength = atoi(optarg); if (CLength > MAXNAMLEN) CLength = MAXNAMLEN; else if (CLength < 1) CLength = DEFCOLWID; break; case 'd': File_dir = DFIRST; break; case 'f': File_dir = FFIRST; break; case 'H': Header = 1; break; case 'n': Sort = 0; break; case 'p': Point = 0; break; case 'v': Varspaces = 0; break; case 'x': Xdev = 0; break; case 'R': NoRCS = 1; break; case 'g': Gflag = 1; break; case 'l': Longflg = 1; break; case 's': Compact = 1; break; default: fprintf(stderr, "Usage: dtree [-adfgHRlnpsvx] [-c linelength] [directory ... ]\n" ); exit(FAIL); } } if (Compact && !Gflag) Longflg = 1; if (CLength == 0) CLength = Varspaces ? MAXNAMLEN : DEFCOLWID; /* Establish where we are (our home base...) */ if (getwd(home) == 0) { fprintf(stderr, "dtree: Cannot get initial directory: %s\n", home); exit(1); } Spaces = xmalloc(MAXNAMLEN+2); for(i = 0; i <= MAXNAMLEN; i++) Spaces[i] = ' '; Spaces[i] = '\0'; /* Get initial Storage space */ Size = sizeof(struct entry) * INITIAL_ELEM; SaveList = (struct entry *)xmalloc(Size); /* adjust for no specified directory */ if (optind == argc) argv[--optind] = "."; if (Header) print_date(); /* walk down the rest of the args, treeing them one at at time */ for (; optind < argc; optind++) { if (chdir(home) == -1) { fprintf(stderr, "dtree: Cannot change to initial directory "); perror(home); exit(1); } strncpy (top, argv[optind], MAXPATHLEN); if (chdir(top) == FAIL) { fprintf(stderr, "dtree: Cannot change to top directory "); perror(top); continue; } else if (getwd(top) == 0) { fprintf(stderr,"dtree: Cannot get current directory: %s\n", top); continue; } List = SaveList; Index = 0; ptr = rindex(top, '/'); if (!ptr || *++ptr == '\0') strncpy(List[Index].e_name, top, MAXNAMLEN); else strncpy(List[Index].e_name, ptr, MAXNAMLEN); if(stat(top, &Status) == FAIL) { fprintf(stderr, "dtree: Cannot stat directory "); perror(top); continue; } Device = Status.st_dev; List[0].dir = 1; List[0].last = 1; List[0].next = 1; List[0].e_mode = Status.st_mode; List[0].e_uid = Status.st_uid; List[0].e_gid = Status.st_gid; List[0].e_size = Status.st_size; Index = 1; for (i = 1; i < DEPTH; i++) Maxes[i] = 0; Maxes[0] = stln(List[0].e_name); Level = 1; /* search the tree */ List[0].end = t_search(top, &List[0]); if (Index == 1) /* empty tree */ List[0].next = 0; if (Header) { if (All) printf("\nDirectory structure and contents of %s\n", top); else printf("\nDirectory structure of %s\n", top); if (Point) printf("(excluding entries that begin with '.')\n"); } pt_tree(); /* print the tree */ } exit(0) ; } t_search(dir, addrs) char *dir; struct entry *addrs; { int bsort; /* index to begin sort */ int stmp; /* save temporary index value */ struct entry *sstep; /* saved step in list */ int nitems; /* # of items in this directory */ DIR *dirp; /* pointer to directory */ char sub[MAXNAMLEN+1]; /* used for subdirectory names */ int i; struct direct *dp; int n_subs = 0; int tmp = 0; dirp = opendir("."); if (dirp == NULL) { fprintf(stderr, "dtree: Cannot open directory "); perror(dir); return(0); } bsort = Index; sstep = &List[bsort]; /* initialize sstep for for loop later on */ nitems = Index; /* get the entries of the directory that we are interested in */ while ((dp = readdir(dirp)) != NULL) { if (dp->d_ino #ifdef unos == -1 #else == 0 #endif /* unos */ || (strcmp(dp->d_name, ".") == SAME) || (strcmp(dp->d_name, "..") == SAME) || (NoRCS && ((strcmp(dp->d_name, "RCS") == SAME) || (strcmp(dp->d_name, "CVS") == SAME) || (strcmp(dp->d_name, "SCCS") == SAME))) || (Point && dp->d_name[0] == '.')) continue; strncpy(sub, dp->d_name, MAXNAMLEN); #ifdef S_IFLNK if (lstat(sub,&Lstat) == FAIL) { fprintf(stderr, "dtree: In directory %s, cannot lstat entry ", dir); perror(sub); continue; } #endif /* S_IFLNK */ if (stat(sub, &Status) == FAIL) { fprintf(stderr, "dtree: In directory %s, cannot stat entry ", dir); perror(sub); continue; } #ifdef S_IFLNK if (((Lstat.st_mode & S_IFMT) == S_IFLNK) && ((Status.st_mode & S_IFMT) == S_IFDIR)) List[Index].dir = 0; else if ((((Lstat.st_mode & S_IFMT) == S_IFLNK) && ((Status.st_mode & S_IFMT) != S_IFDIR)) && (All)) List[Index].dir = 0; #endif /* S_IFLNK */ else if ((Status.st_mode & S_IFMT) == S_IFDIR) List[Index].dir = 1; else if (All) List[Index].dir = 0; else continue; strncpy(List[Index].e_name, dp->d_name, MAXNAMLEN); List[Index].last = 0; List[Index].end = 0; #ifdef S_IFLNK if ((Lstat.st_mode & S_IFMT) == S_IFLNK) { List[Index].dev = (Device == Lstat.st_dev); List[Index].e_mode = Lstat.st_mode; List[Index].e_uid = Lstat.st_uid; List[Index].e_gid = Lstat.st_gid; List[Index].e_size = Lstat.st_size; } else { #endif /* S_IFLNK */ List[Index].dev = (Device == Status.st_dev); List[Index].e_mode = Status.st_mode; List[Index].e_uid = Status.st_uid; List[Index].e_gid = Status.st_gid; List[Index].e_size = Status.st_size; #ifdef S_IFLNK } #endif /* S_IFLNK */ if (stln(List[Index].e_name) > Maxes[Level]) Maxes[Level] = stln(List[Index].e_name); ++Index; if (Index*sizeof(struct entry) >= Size) { Size += 20*sizeof(struct entry); List = (struct entry *)xrealloc((char *)List, Size); } } closedir(dirp); nitems = Index - nitems; /* nitems now contains the # of */ /* items in this dir, rather than */ /* # total items before this dir */ if (Sort) qsort(&List[bsort], nitems, sizeof(struct entry), compar); List[Index-1].last = 1; /* mark last item for this dir */ n_subs = nitems; stmp = Index; /* now walk through, and recurse on directory entries */ /* sstep was initialized above */ for (i = 0; i < nitems; sstep = &List[stmp - nitems+(++i)]) { if (sstep->dir && (Xdev || sstep->dev)) { sstep->next = Index; strncpy(sub, sstep->e_name, MAXNAMLEN); tmp = n_subs; Level++; if (chdir(sub) == FAIL) { fprintf(stderr, "dtree: Cannot change to directory %s/", dir); perror(sub); } else { n_subs += t_search(sub, sstep); if (chdir("..") == FAIL) { fprintf(stderr, "dtree: %s/%s lacks '..' entry\n",dir, sub); exit(1); } } --Level; if (n_subs - tmp <= 0) sstep->next = 0; else --n_subs; } else sstep->next = 0; } addrs->end = (unsigned)n_subs; return(n_subs); } /* * comparison routine for qsort */ compar(a, b) struct entry *a, *b; { if (!File_dir) /* straight alphabetical */ return(strncmp(a->e_name, b->e_name, MAXNAMLEN)); /* sort alphabetically if both dirs or both not dirs */ if ((a->dir && b->dir) || (!a->dir && !b->dir)) return(strncmp(a->e_name, b->e_name, MAXNAMLEN)); if (File_dir == FFIRST) { /* sort by files first */ if (a->dir) return(GREATER); else return(LESSTHAN); } if (a->dir) /* sort by dir first */ return(LESSTHAN); else return(GREATER); } pt_tree() { register int i,j; struct entry *l; struct entry *hdr[DEPTH]; int posit[DEPTH]; /* array of positions to print dirs */ int con[DEPTH]; /* flags for connecting up tree */ char flag = 0; /* flag to leave blank line after dir */ struct entry *stack[DEPTH]; /* save positions for changing levels */ int top = 0; /* index to top of stack */ int count = 1; /* count of line of output */ Level = 0; /* initialize Level */ /* this loop appends each entry with dashes or spaces, for */ /* directories or files respectively */ for (i = 0; i < Index; i++) { for (j = 0; j < MAXNAMLEN; j++) { if (!List[i].e_name[j]) break; } if (List[i].dir) { for (; j < MAXNAMLEN; j++) List[i].e_name[j] = '-'; } else { for (; j < MAXNAMLEN; j++) List[i].e_name[j] = ' '; } } /* adjust the Maxes array according to the flags */ for (i = 0; i < DEPTH; i++) { if (Varspaces) { if (Maxes[i] > CLength ) Maxes[i] = CLength; } else Maxes[i] = CLength; } /* clear the connective and position flags */ for (i = 0; i < DEPTH; i++) con[i] = posit[i] = 0; /* this is the main loop to print the tree structure. */ l = &List[0]; j = 0; for (;;) { /* directory entry, save it for later printing */ if (l->dir != 0 && l->next != 0) { hdr[Level] = l; posit[Level] = count + (l->end + 1)/2 - 1; flag = 1; stack[top++] = l; l = &List[l->next]; ++Level; continue; } do_it_again: /* print columns up to our entry */ for (j = 0; j < (flag ? Level-1 : Level); j++) { if (!flag && posit[j] && posit[j] <= count) { /* time to print it */ if (hdr[j]->e_name[CLength-1] != '-') hdr[j]->e_name[CLength-1] = '*'; printf("|-%.*s",Maxes[j],hdr[j]->e_name); posit[j] = 0; if (hdr[j]->last != 0) con[j] = 0; else con[j] = 1; if (Gflag || Longflg) { if ((i = j+1) <= Level) printf("| %.*s", Maxes[i], Spaces); for (i++; i <= Level; i++) { printf("%c %.*s", (con[i] ? '|' : ' '), Maxes[i], Spaces); } if (!Compact) { printf("%s ", getmode(hdr[j]->e_mode)); if (Longflg) printf("%8.8s ",guid(hdr[j]->e_uid)); if (Gflag) printf("%8.8s ",ggid(hdr[j]->e_gid)); printf("%7ld\n", (hdr[j]->e_size+511L)/512L); } else { printf(" %04o ",hdr[j]->e_mode & 07777); if (Longflg) printf("%5u ", hdr[j]->e_uid); if (Gflag) printf("%5u ", hdr[j]->e_gid); printf("%7ld\n", (hdr[j]->e_size+511L)/512L); } goto do_it_again; } } else printf("%c %.*s", (con[j] ? '|' : ' '), Maxes[j], Spaces); } if (flag) { /* start of directory, so leave a blank line */ printf(con[j] ? "|\n" : "\n"); flag = 0; continue; } else { /* normal file name, print it out */ if (l->e_name[CLength-1] != '-' && l->e_name[CLength-1] != ' ') l->e_name[CLength-1] = '*'; printf("|-%.*s",Maxes[Level],l->e_name); if (l->last) { con[j] = 0; } else { con[j] = 1; } if (Gflag || Longflg) { if (Compact) { printf(" %04o ", l->e_mode & 07777); if (Longflg) printf("%5u ", l->e_uid); if (Gflag) printf("%5u ", l->e_gid); printf("%7ld", (l->e_size+511L)/512L); } else { printf("%s ", getmode(l->e_mode)); if (Longflg) printf("%8.8s ",guid(l->e_uid)); if (Gflag) printf("%8.8s ",ggid(l->e_gid)); printf("%7ld", (l->e_size+511L)/512L); } } } printf("\n"); if (l->last) { /* walk back up */ while (l->last) { --Level; if (--top <= 0) return; l = stack[top]; } } l = &l[1]; ++count; } } char * guid(uid) short uid; { static char tb[10]; struct passwd *pswd; pswd = getpwuid(uid); if (pswd == NULL) sprintf(tb,"%u", uid); else sprintf(tb, "%8s", pswd->pw_name); return(tb); } char * ggid(gid) short gid; { static char tb[10]; struct group *grp; grp = getgrgid(gid); if (grp == NULL) sprintf(tb,"%u", gid); else sprintf(tb, "%8s", grp->gr_name); return(tb); } /* take the mode and make it into a nice character string */ char * getmode(p_mode) unsigned short p_mode; { static char a_mode[16]; register int i = 0, j = 0; a_mode[j++] = ' '; switch (p_mode & S_IFMT) { #ifdef S_IFLNK case S_IFLNK: a_mode[j++] = 'l'; break; #endif /* S_IFLNK */ case S_IFDIR: a_mode[j++] = 'd'; break; #ifdef S_IFMPC /* defined in stat.h if you have MPX files */ case S_IFMPC: a_mode[j-1] = 'm'; /* FALL THROUGH */ #endif /* S_IFMPC */ case S_IFCHR: a_mode[j++] = 'c'; break; #ifdef S_IFMPB /* defined in stat.h if you have MPX files */ case S_IFMPB: a_mode[j-1] = 'm'; /* FALL THROUGH */ #endif /* S_IFMPB */ case S_IFBLK: a_mode[j++] = 'b'; break; case S_IFREG: default: a_mode[j++] = (p_mode & S_ISVTX) ? 't' : ' '; break; } a_mode[j++] = ' '; for( i = 0;i<3;i++ ) { a_mode[j++] = (p_mode<<(3*i) & S_IREAD) ? 'r' : '-'; a_mode[j++] = (p_mode<<(3*i) & S_IWRITE) ? 'w' : '-'; a_mode[j++] = (i<2 && (p_mode<