/*-------------------------------------------------------------* | File: TREE.c - After Tomas Rokicki, Fred Fish Disk nr. 306 | | Revised: Maurizio LORETI (MLO) - First version 1.00, 911123 | | Last revision: version 1.04, MLO 920405 | +-------------------------------------------------------------+ | For the description, refer to the README file, or to the | | Usage() procedure at the end of this program; a short help | | will be printed giving the command TREE ? , TREE -H or TREE | | followed by an invalid option. | *-------------------------------------------------------------*/ #include #include #include #include #include #include #include #include #include "pattern.h" #define SYS_NORMAL_CODE 0 /* Exit codes for the operating system */ #define SYS_ABORT_CODE 1 #define MATCH_STRING -1 /* Flag for the NamEntry tree */ #define FILENAME_MAX 128 /* Dimension of the file name buffer */ #define OUTLINE_DIM 128 /* Dimension of the printout buffer*/ /** | Structures to implement: a linked list of character strings | (StringSet); a linked list of directory entries (DirEntry); | and a linked list of name patterns (NamEntry). **/ typedef struct sStringSet { struct sStringSet *next; char what[1]; } StringSet; typedef struct sDirEntry { struct sDirEntry *next; char name[1]; } DirEntry; typedef struct sNamEntry { struct sNamEntry *next; LONG type; void *pArg; } NamEntry; /** | Global variables **/ struct Library *PatternBase = NULL; /* pattern.library pointer */ struct FileInfoBlock *pFIB = NULL; /* FileInfoBlock buffer */ int aborted = FALSE; /* CTRL-C flag */ int dirWanted = FALSE; /* "-x" or "+x" flag */ int namWanted = FALSE; /* "-n|N" or "+n|N" flag */ int caseSignificant; /* "case is significant" flag */ int fullDirPath; /* "-d" or "-D" flag */ StringSet *dirList = NULL; /* Subdirectories tree root */ StringSet *extList = NULL; /* Extensions tree root */ NamEntry *namList = NULL; /* Name patterns tree root */ char *mFormat = "%p%s"; /* Default output format */ char outLine[OUTLINE_DIM]; /* Output print buffer */ /** | Procedure prototypes **/ StringSet *Add(char *s, StringSet *pSS); NamEntry *AddNam(char *s, NamEntry *pNE); void CheckControlC(void); int CheckExt(char *s); void Cleanup(int code); int CXBRK(void); int FileDate(char *pc, struct DateStamp *pDS); int FileTime(char *pc, struct DateStamp *pDS); int Member(char *s, StringSet *pSS); int NamMatch(char *name); void NoMem(void); void PrintFile(char *s); char *StrLast(char *s, char c); void Tree(char *s); void Usage(void); void main( int argc, char *argv[] ){ char path[FILENAME_MAX] = ""; register char option = 'F'; /* Default opt for first arg: format */ /** | Main program: allocates the internal buffer for the directory | chain (pFIB); then scans the input line arguments; then calls | Tree() that does the work. **/ if ((pFIB = (struct FileInfoBlock *) AllocMem(sizeof(struct FileInfoBlock), MEMF_CLEAR)) == NULL) { NoMem(); } while (--argc > 0) { switch ((*++argv)[0]) { case '-': switch (option = (*argv)[1]) { case 'X': case 'x': dirWanted = FALSE; break; case 'N': caseSignificant = TRUE; namWanted = FALSE; break; case 'n': caseSignificant = FALSE; namWanted = FALSE; break; case 'D': case 'd': case 'F': case 'f': case 'R': case 'r': break; default: Usage(); break; } break; case '+': switch (option = (*argv)[1]) { case 'X': case 'x': dirWanted = TRUE; break; case 'N': caseSignificant = TRUE; namWanted = TRUE; break; case 'n': caseSignificant = FALSE; namWanted = TRUE; break; default: Usage(); break; } break; case '?': Usage(); break; default: switch (option) { case 'd': fullDirPath = FALSE; dirList = Add(*argv, dirList); break; case 'D': fullDirPath = TRUE; dirList = Add(*argv, dirList); break; case 'x': case 'X': extList = Add(*argv, extList); break; case 'f': case 'F': mFormat = *argv; break; case 'r': case 'R': strcpy(path, *argv); option = '\0'; break; case 'N': case 'n': namList = AddNam(*argv, namList); break; default: /* Cannot happen */ break; } break; } } Tree(path); Cleanup(SYS_NORMAL_CODE); } StringSet *Add( char *s, StringSet *pSS ){ register StringSet *t; /** | Adds the string "s" in the linked list with root "pSS". **/ if ((t = malloc(sizeof(StringSet) + strlen(s))) == NULL) NoMem(); t->next = pSS; strcpy(t->what, s); return t; } NamEntry *AddNam( char *s, NamEntry *pNE ){ register NamEntry *t; /** | Adds the name pattern "s" in the linked list with root "pNE". | The pattern.library is opened at the first entry; and some memory | for the new structure is obtained from the heap. If "s" is a real | pattern, its "pArg" member is NULL and its "type" member is the | PatternHandle (must be positive); if "s" is a simple string, | "pArg" points to a cleaned copy of "s" and "type" is negative. **/ if (PatternBase == NULL && (PatternBase = OpenLibrary(PATLIB_NAME, PATLIB_MIN_VERSION)) == NULL) { fprintf(stderr, "TREE: couldn't open %s rev. %d\n", PATLIB_NAME, PATLIB_MIN_VERSION); Cleanup(SYS_ABORT_CODE); } if ((t = malloc(sizeof(NamEntry))) == NULL) NoMem(); if (IsPattern(s, outLine, 0)) { t->pArg = NULL; if ( (t->type = caseSignificant ? AllocPattern(s, 0) : AllocPatternNoCase(s, 0)) < 0) { fprintf(stderr, "TREE: %s\n", PatternErrorString(t->type, "default", outLine, OUTLINE_DIM)); Cleanup(SYS_ABORT_CODE); } } else { t->type = MATCH_STRING; if ((t->pArg = malloc(strlen(outLine) + 1)) == NULL) { free(t); NoMem(); } strcpy(t->pArg, outLine); } t->next = pNE; return t; } void CheckControlC(void) { if ((SetSignal(0L, SIGBREAKF_CTRL_C)) & SIGBREAKF_CTRL_C) { aborted = TRUE; } } int CheckExt( char *s ){ register char *pc; /** | Looks for the extension of the filename "s" (the characters after | the last '.') and compares the extension with the linked list | starting at "extList"). **/ return ((pc = StrLast(s, '.')) == NULL || !Member(++pc, extList)); } void Cleanup( int code ){ register StringSet *pS; register NamEntry *pNE; /** | Frees allocated memory, gives back all obtained resources. **/ if (pFIB != NULL) FreeMem(pFIB, sizeof(struct FileInfoBlock)); while ((pS = extList) != NULL) { extList = pS->next; free(pS); } while ((pS = dirList) != NULL) { dirList = pS->next; free(pS); } while ((pNE = namList) != NULL) { namList = pNE->next; if (pNE->pArg != NULL) free(pNE->pArg); if (pNE->type > 0) FreePattern(pNE->type); free(pNE); } if (PatternBase != NULL) CloseLibrary(PatternBase); exit(code); } int CXBRK(void) { aborted = TRUE; return 0; } int FileDate( char *pc, struct DateStamp *pDS ){ register int day, month, year; static char *mName[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; day = pDS->ds_Days - 2251; year = (4 * day + 3) / 1461; day -= 1461 * year / 4; year += 1984; month = (5 * day + 2) / 153; day = day - (153 * month + 2) / 5 + 1; month += 3; if (month > 12) { year++; month -= 12; } return sprintf(pc, "%02d-%s-%d", day, mName[--month], year); } int FileTime( char *pc, struct DateStamp *pDS ){ register int hour, mins, sec; sec = pDS->ds_Tick / TICKS_PER_SECOND; hour = pDS->ds_Minute / 60; mins = pDS->ds_Minute % 60; return sprintf(pc, "%02d:%02d:%02d", hour, mins, sec); } int Member( char *s, StringSet *pSS ){ /** | Looks for the string "s" in the linked list with root "pSS". | The comparison is case insensitive (stricmp). **/ if (*s) { while (pSS != NULL) { if (!stricmp(s, pSS->what)) return TRUE; pSS = pSS->next; } } return FALSE; } int NamMatch( char *name ){ register NamEntry *pNE = namList; /** | Compares the file name ("name") with the known name patterns. **/ while (pNE != NULL) { if (pNE->type == MATCH_STRING) { if (caseSignificant) { if (!strcmp(name, pNE->pArg)) return TRUE; } else { if (!stricmp(name, pNE->pArg)) return TRUE; } } else { register LONG r = MatchThePattern(pNE->type, name); if (r < 0) { fprintf(stderr, "TREE: %s\n", PatternErrorString(r, "default", outLine, OUTLINE_DIM)); } if (r) return TRUE; } pNE = pNE->next; } return FALSE; } void NoMem(void) { fputs("TREE: couldn't obtain memory\n", stderr); Cleanup(SYS_ABORT_CODE); } void PrintFile( char *s ){ register char *p = outLine; register char *q = mFormat; /** | Prints the file name ("s") in the needed format. **/ while (*q) { if (*q == '%') { switch (*++q) { case 's': { register char *pc; if ((pc = StrLast(s, '/')) != NULL || (pc = StrLast(s, ':')) != NULL) { strcpy(p, ++pc); } else { strcpy(p, s); } p += strlen(p); ++q; } break; case 'p': { register char *pc; register char *ps=s; if ((pc = StrLast(s, '/')) != NULL || (pc = StrLast(s, ':')) != NULL) { while (ps <= pc) *p++ = *ps++; } } ++q; break; case 'b': { register int i = sprintf(p, "%ld", pFIB->fib_Size); p += i; } ++q; break; case 'd': p += FileDate(p, &pFIB->fib_Date); ++q; break; case 't': p += FileTime(p, &pFIB->fib_Date); ++q; break; case 'q': *p++ = '\"'; ++q; break; default: *p++ = *q++; break; } } else { *p++ = *q++; } } *p = '\0'; puts(outLine); } char *StrLast( char *s, char c ){ register char *pc = NULL; /** | Returns a pointer to the last occurrence of "c" in the | string "s", or NULL. **/ while (*s) { if (*s == c) pc = s; s++; } return pc; } void Tree( char *path ){ BPTR dlock; char fileName[FILENAME_MAX]; register char *pc, *pDir; register DirEntry *rdE = NULL; register DirEntry *pdE; /** | This procedure checks (recursively) a directory. | "path" contains the full directory name; Tree() scans the wanted | directory, processing immediately the 'true' files; the local | subdirectories (if any) are checked recursively at the end. | If an error is detected from Tree(), the directory/file test is | stopped and the global flag "aborted" is set; this makes possible, in | the further steps, to recursively free() all the memory that has been | allocated in order to store subdirectory names. | | First: obtain a lock on the wanted directory, and Examine() the lock; | since only one directory is being scanned at a time, we can use a | single FileInfoBlock buffer. **/ if ((dlock = Lock(path, ACCESS_READ)) == NULL) return; if (Examine(dlock, pFIB)) { /** | Prepare in "fileName" the full directory name - to which local | filenames will be appended; and pDir, the pointer to the directory | name according to the fullDirPath global flag. **/ pc = strcpy(fileName, path); pc += strlen(fileName); if (pc != fileName && *(pc-1) != ':') *pc++ = '/'; pDir = fullDirPath ? fileName : pc; /** | Now, loop over all directory entries. As already said, all the | 'real' files are immediately checked; the subdirectory names are | stored in a linked list to be examined at the end. This list is | implemented as a LIFO tree (the simplest type). **/ while (!aborted && ExNext(dlock, pFIB)) { (void) strcpy(pc, pFIB->fib_FileName); if (pFIB->fib_DirEntryType < 0) { /** | A file. If a name pattern has been given, check this first; | then check the extension, if any. **/ if (namWanted ^ NamMatch(pc)) continue; if (dirWanted ^ CheckExt(pc)) PrintFile(fileName); } else { /** | If a memory allocation error is detected when asking space for | our linked list, we exit the "while" loop; setting the "aborted" | flag before exiting, ensures that all the memory we had from | these malloc()'s will later be free()-ed recursively. **/ if (!Member(pDir, dirList)) { if ((pdE = malloc(sizeof(DirEntry) + strlen(fileName))) == NULL) { aborted = TRUE; break; } (void) strcpy(pdE->name, fileName); pdE->next = rdE; rdE = pdE; } } CheckControlC(); } } UnLock(dlock); /** | Now, loop over all detected subdirectories (if any); | freeing in the same time the memory used to store their names. **/ while (rdE != NULL) { if (!aborted) Tree(rdE->name); free(rdE->name); pdE = rdE->next; free(rdE); rdE = pdE; } } void Usage(void) { char *desc[] = { "\nSyntax:\tTREE [opt arg [arg [ ... ]] [opt arg [arg [ ... ]] ...", "Lists on stdout the file names in the directory tree. The options are:\n", " -r name : starting directory (default: current working directory).", " -d name [name [ ... ]] : name(s) (case not significant) of the", " directories to be excluded (directory name only, e.g. \"pk\").", " -D name [name [ ... ]] : name(s) (case not significant) of the", " directories to be excluded (full path, e.g. \"work:tex/pk\").", " -n pat [pat [ ... ]] : patterns matching files to be excluded; *OR*,", " +n pat [pat [ ... ]] : patterns matching the only files to be", " listed. If given uppercase (-N,+N) the case is significant.", " -x ext [ext [ ... ]] : extensions of files to be excluded; *OR*,", " +x ext [ext [ ... ]] : extensions of the only files to be listed.", " -f \"fmt\" : output format, between double quotes (\"). Can contain:", " %s - that will be substituted from the file name;", " %p - the file path (e.g. \"ram:\" or \"tex:pk/\");", " %b - the file size (in bytes);", " %d - the file date (e.g. \"08-May-1988\");", " %t - the file time (e.g. \"09:10:11\");", " %q - a double quote character (\");", " %% - the escape character % itself;", " all other characters will copied to stdout.\n", NULL }; register char **ppc = desc; while (*ppc) { fputs(*ppc++, stderr); putc('\n', stderr); } Cleanup(SYS_NORMAL_CODE); }