/* TO DO: Make -o intermix dirs and files instead of separating? Not when -r though! Some way to make -x info follow -r info when both are specified? Option to choose between absolute dates and "Yesterday" etc. Lformat type option. Option to execute that output instead of printing it. Upto/Since date options. -K option to show disk keys. Make -P able to handle more complex bit tests. Organize .infos some way so that .info search when using chron sort is faster? Disk key sort when -o? No, unsorted. Option to sort alpha/cron? Have an option that means apply pattern to all depths of -r. Like, foo/???.c also acts like foo/#?/???.c and foo/#?/#?/???.c, etc. Make it use :: after the pattern (foo/#?.c::) to mean this? Make ~ in front of pattern accept all files that DO NOT fit. Handle wildcards anywhere in the path (low priority). Handling :: may be tough. Allow :: only at very end. Allow ~ in middle. Avoid repeating "unknown option" for same letter? -A means show age of file? #ifdef'd out? Later: use two processes on same seglist: background scans disk, sends flist(s) to foreground which does output. Wow, in testing this ... I found that as of July 18, 1990, my hard disk contains 1944 files in 152 directories. A mere 20 megger! */ /* ========================================================================== The idea here is to make Yet Another Cli Directory Command. What's special about this one? It's really really fast, and it doesn't show .info files! Instead it just shows all files that have .info's associated with them by writing the name in orange instead of white when output is to the screen. And it puts file names (and directory names also) in as many columns (up to five) as will fit comfortably in the window. And everything is alphabetized in columns. It will also do Amiga patterns. It tries a pattern first as an exact literal before expanding it, in case you have a drawer named "Doesn't work?" or something. And it does recursive descents, faster than the Fast File System. It is intended to replace Dir, List, rls, and du. Future versions might even replace things like foreach and SPAT/DPAT. Dr is written for Aztec C for Amiga, by Paul Kienitz. Public Domain. See the files Dr.doc and FastExNext.doc for useful information. Documentation for pureio.c is in the source. ========================================================================== */ /* some #defines which you can make different verisons with: if SMALLSLOW is defined it uses regular ExNext instead of FastExNext. if WEEEEK is defined it gives recent dates as "Yesterday" or "Tuesday" instead of an absolute date. if LEAKAGE is defined it reports all memory allocations and freeings. if C_NOT_ASM it does not use inline assembly language instead of C for some sorting functions (works only with Aztec). In fastex.c, if QUEST is defined it has the ability to put up "Please replace volume ..." and "... read/write error" system requesters when an error occurs; otherwise it reports errors silently. */ #define FLACK ':' /* NOT USED */ #define WIDEFAULT 77 #define HYPH 0xAD #define CSI "\233" #define LWID 25 #define MAXCOLS 5 #define PREPENGTH 300 #define PUREBUFSIZE 128L #define STACKNEEDED (1500 + 300) /* WIDEFAULT is the assumed output width when we can't measure the window. FLACK is char used to mark icon'd files in output. Not used these days. HYPH is used to mark option arguments for mane (8-bit ascii soft hyphen). CSI is the character that starts "escape sequences"; same as esc [. LWID is how many spaces (ideally) to use for name and size in -L output. MAXCOLS is the max number of text columns to stack listed names in. PREPENGTH is the maximum length of pathnames labelling recursive levels. PUREBUFSIZE is the size of the buffer for pureio. STACKNEEDED is the amount of stack space needed to scan a directory. */ #include #include #include #include #include #undef put /* Here we have a simplified version of which does not pull in stuff like struct Window and struct TextFont: */ struct ConUnit { short pad[21]; short cu_XMax, cu_YMax; }; /* pretty simplified, wasn't it */ typedef struct _fly *flip; typedef struct _fly { flip next; /* list link */ long length, blox, tection; struct DateStamp when; str comment; ubyte /* bool */ jected, infoed, wanted, dirred; char name[31]; ubyte size; /* either 70 or 80 */ short ordination; /* consumption rounds up to 72 anyway... */ } fly; typedef struct { struct FileInfoBlock f; long p, s; } fib; struct cuont { long blok, byt, fil, dir, jb; }; /* Here is the stuff that would be global variables if we weren't reentrant: */ typedef struct { adr stacklimit; str argline; stray argv; int arglen, argc, hyphc; short cwid, song, cols, wid, abort; bool color, curse, cize, complete, cron, cons, colsort, cutdirs, cutfils; bool /* cage, canydepth, */ csternal, completenames, consumption; bool zize, zomplete, /* zons, FLACK */ zurse, zutdirs, zutfils, zmptnames; long protlook, protwant; struct cuont tot, gran; struct ConUnit *cuca; struct Process *me; long hair; short purestuff[16]; /* pad in case changes */ int mesh[128]; str pat; bool patty, didaninny; char prepath[PREPENGTH]; char pat0, nullpat; } glob; #define GG register glob *g /* the only global variables visible here: */ import int Enable_Abort; /* has to stay always 1 */ long goofset; /* works like boofset in pureio */ /* there are actually others, like library bases and pureio's boofset, but they are all constants, not variables. */ str helpslab[] = { " -C means sort oldest to newest, not alphabetically\n", " -D means show subdirectory names only\n", " -F means show file names only\n", " -H means sort in rows instead of columns\n", " -I means show .info files like normal files\n", /* " -K means show disk addresses (keys) of files and directories\n", */ " -L means show size, protection, datestamp, and filenote\n", " -O means list each file on a separate line as a complete pathname\n", " -R means recursively show subdirectory contents\n", " -S means show length of each file (-L overrides)\n", " -U means list no names, just show total disk space consumption\n", " -X means show directory's info -L style instead of its contents\n", " -Pb or -P~b where b is one of H S P A R W E D means show files with\n", " the named protection bit clear (if ~ present) or set (if no ~)\n", null }; /* ================== functions: ================== */ #ifdef SMALLSLOW #define FastExamine(L, F) Examine(L, (struct FileInfoBlock *) F) #define FastExNext(L, F) ExNext(L, (struct FileInfoBlock *) F) #define FastExCleanup(F) #define Get80(F) Alloc(80) #else import long FastExamine(BPTR l, fib *f), FastExNext(BPTR l, fib *f); import void FastExCleanup(fib *f); import adr Get80(fib *f); #endif #ifdef LEAKAGE import adr AllocYell(long a, long b, str c, long d); import void FreeYell(adr a, long b, str c, long d); #define AllocMem(a, b) AllocYell((long) a, (long) b, __FUNC__, (long) __LINE__) #define FreeMem(a, b) FreeYell(a, (long) b, __FUNC__, (long) __LINE__) #define _AllocMem(a, b) AllocYell((long) a, (long) b, __FUNC__, (long) __LINE__) #define _FreeMem(a, b) FreeYell(a, (long) b, __FUNC__, (long) __LINE__) #endif import bool CmplPat(str pat, int *aux), /* PatMatch by Jeff Lydiat */ Match(str pat, int *aux, str S); import bool OpenPureIO(short *b, ulong s); import void ClosePureIO(void), puch(short c), put(str s), putfmt(str f, ...), pflush(void); long StackLeft(GG) { int i; return (long) &i + 14 - (long) g->stacklimit; } short digits(register ulong l) { register short d = 0; while (l) { d++; l /= 10; } if (d) return d; else return 1; } void pad(short w) { while (--w >= 0) puch(' '); } void padong(ulong n, short w) { pad(w - digits(n)); /* putfmt doesn't have %*ld */ putfmt("%ld", n); } /* putfmt doesn't have %lu either, so let's just hope n is always positive */ void putection(register ulong bits) { char bee[10]; register short b; bits ^= 15; strcpy(bee, " hsparwed"); for (b = 0; b <= 7; b++) if (!(bits & bit(b))) bee[8 - b] = '-'; put(bee); } void putdate(struct DateStamp *when) { long day = when->ds_Days; register short yell, mday, month, year; short hour = (short) when->ds_Minute / 60, minute = (short) when->ds_Minute % 60, second = (short) when->ds_Tick / (short) TICKS_PER_SECOND; static short smods[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; short mods[12]; static char mane[] = "Jan\0Feb\0Mar\0Apr\0May\0Jun\0Jul\0Aug\0" "Sep\0Oct\0Nov\0Dec"; #ifdef WEEEEK /* betcha the reason they started the calendar from the beginning of 1978 in particular is because that way day zero is a Sunday */ static str weak[7] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; struct DateStamp today; short daydif; DateStamp(&today); daydif = today.ds_Days - day; if (!daydif) put(" Today "); else if (daydif == 1) put(" Yesterday"); else if (daydif > 1 && daydif < 7) putfmt(" %-9s", weak[day % 7]); else #endif { if (day <= 0 || day >= 44618) putfmt(" ?(%ld)", day); else { year = 78 + ((day / 1461) << 2); mday = day % 1461; while (mday >= (yell = (year & 3 ? 365 : 366))) { mday -= yell; year++; } /* 2000 is a leap year?, 1900 and 2100 are */ for (month = 0; month < 12; month++) mods[month] = smods[month]; if (!(year & 3)) mods[1] = 29; month = 0; while (mday >= mods[month]) mday -= mods[month++]; putfmt(" %2d-%s-%02d", mday + 1, mane + (month << 2), year % 100); } } if (hour < 0 || hour >= 24) putfmt(" ?(%ld,%ld)", when->ds_Minute, when->ds_Tick); else { putfmt(" %2d:%02d:", hour, minute); if (second < 0 || second >= 60) put("??"); else putfmt("%02d", second); } } /* Another personal ad found in EXPRESS "The East Bay's Free Weekly": HI. Yup, that was the whole ad. Right after it: RALPH, a 1967 Cadillac, now accepting devotees. */ void Lose(register flip f) { if (f) { if (f->comment) FreeMem(f->comment, 80L); FreeMem(f, (long) f->size); } } void _abort(void) /* called by ^C checker */ { /* is this a hack, or is this a hack? */ GG = (adr) (((str) ThisProcess()->pr_ReturnAddr) + goofset); if (g->abort) return; g->abort = 5; put(" *** BREAK\n"); } /* This is more efficient that a series of &&'s: */ #define bask(C) bit(C - 33) #define PASK (bask('?') | bask('\'') | bask('#') | bask('%') | \ bask('(') | bask(')')) bool patchar(register ubyte cc) { register ubyte c = cc; return c > ' ' && (c == '|' || (c < 'A' && bit(c - 33) & PASK)); } /* Takes dir/pat string in from and translates it into a compiled pattern in mesh (and uncompiled in pat) and a lock on the directory in deer to be scanned using the pattern */ bool SplitTailPat(str from, BPTR *deer, GG) { register str p; register bool f = true; g->pat = from; for (p = from; *p; p++) if (*p == ':' || *p == '/') g->pat = p + 1; /* pat will point to path tail */ for (p = g->pat; *p; p++) if (patchar(*p)) {f = false; break;} /* f is for fucked */ if (f) { putfmt("Couldn't find \"%s\".\n", from); g->hair = ERROR_OBJECT_NOT_FOUND; return false; } g->pat0 = *g->pat; /* truncate from by temporarily damaging pat */ /* should change CmplPat to be more forgiving of unmatched parens and empty vertical-bar halves, but I don't understand how it works yet ****/ if (!CmplPat(g->pat, g->mesh)) { putfmt("Bogus pattern \"%s\".\n", g->pat); return false; } *g->pat = 0; #ifdef UNNECESSARY for (p = from; *p; p++) /* truncate final slash in from */ if (*p != '/') f = true; /* if it contains non-slashes */ if (f && *(--p) == '/') *p = 0; #endif if (!(*deer = RLock(from))) { putfmt("Couldn't find directory \"%s\".\n", from); g->hair = ERROR_OBJECT_NOT_FOUND; return false; } return true; } void HairSpray(register long hair) { if (hair == ERROR_DEVICE_NOT_MOUNTED) put("disk removed from drive.\n"); else if (hair == ERROR_NO_FREE_STORE) put("not enough memory.\n"); else if (hair == ERROR_NOT_A_DOS_DISK) put("disk unreadable.\n"); else if (hair == ERROR_TOO_MANY_LEVELS) put("not enough stack space.\n"); else putfmt("DOS error code %ld.\n", hair); } flip Fly(register fib *b) { register flip z = New(fly); if (!z) { z = Get80(b); if (!z) return null; z->size = 80; } else z->size = sizeof(fly); if (*b->f.fib_Comment) z->comment = Get80(b); /* if alloc fails, fuck it */ else z->comment = null; if (z->comment) strcpy(z->comment, b->f.fib_Comment); z->next = null; z->length = b->f.fib_Size; z->blox = b->f.fib_NumBlocks; z->tection = b->f.fib_Protection; z->when = b->f.fib_Date; strcpy(z->name, b->f.fib_FileName); z->dirred = b->f.fib_DirEntryType > 0; z->wanted = z->jected = z->infoed = false; return z; } flip Scan1(short *ficou, fib *deef, BPTR deer, GG) { flip result = null; BPTR dp, ocd, fo; char fone[31]; if (!(result = Fly(deef))) return null; if (!g->curse) g->zomplete = true; *ficou = 1; /* we're seeing if it has a .info file */ if (!g->cons && strlen(deef->f.fib_FileName) < 26 && (dp = ParentDir(deer))) { ocd = CurrentDir(dp); strcpy(fone, result->name); strcat(fone, ".info"); Chk_Abort(); if (!g->abort && (fo = RLock(fone))) { result->infoed = true; UnLock(fo); } CurrentDir(ocd); UnLock(dp); } /* WE SEEM to be running into an undocumented feature here... the ding bling ParentDir function sometimes sets IoErr = 212 after a perfectly normal and SUCCESSFUL call. When it does this, the following RLock also does so. So we just band-aid it: */ if (g->me->pr_Result2 == ERROR_OBJECT_NOT_FOUND || g->me->pr_Result2 == ERROR_OBJECT_WRONG_TYPE) g->me->pr_Result2 = 0; return result; } flip ScanInside(short *ficou, fib *deef, BPTR deer, GG) { flip result = null, more; long air = 0; bool pokey; void DoInner(str n, BPTR l, fib *f, GG); /* vvvv prevent multiple "please replace" in -R */ while (g->hair != ERROR_DEVICE_NOT_MOUNTED && FastExNext(deer, deef)) { Chk_Abort(); if (g->abort || !(more = Fly(deef))) break; *g->pat = g->pat0; if (g->zutdirs & more->dirred || g->zutfils & ~more->dirred || (more->tection & g->protlook) != g->protwant || !(pokey = !g->patty || Match(g->pat, g->mesh, more->name))) more->jected = true; if (pokey && g->curse && more->dirred) { BPTR ocd = CurrentDir(deer), innerdeer; if (innerdeer = RLock(more->name)) { register bool p = g->patty; g->patty = false; *g->pat = 0; DoInner(more->name, innerdeer, deef, g); g->patty = p; UnLock(innerdeer); } else { air = g->me->pr_Result2; putfmt( "Can't lock inner directory \"%s\"! Probably no memory.\n", more->name); } CurrentDir(ocd); } more->next = result; result = more; (*ficou)++; } if (air) g->me->pr_Result2 = air; if (g->me->pr_Result2 == ERROR_NO_MORE_ENTRIES) g->me->pr_Result2 = 0; return result; } /* scans a Dos directory and returns the findings in a linked list of fly's. *ficou tells the number of elements in the list. */ flip ScanDeer(BPTR deer, short *ficou, fib *parent, GG) { flip result = null, more; register fib *deef; *ficou = 0; if (!(deef = New(fib))) { g->me->pr_Result2 = ERROR_NO_FREE_STORE; return null; } if (parent) deef->s = parent->s; else deef->s = g->curse ? -1 : 0; if (FastExamine(deer, deef)) { Chk_Abort(); if (!g->abort) if (deef->f.fib_DirEntryType < 0 || (g->csternal && !(g->patty | g->curse))) { result = Scan1(ficou, deef, deer, g); /* don't look inside */ if (!parent) g->zurse = g->zutfils = g->zutdirs = false; } else result = ScanInside(ficou, deef, deer, g); } if (g->me->pr_Result2) { g->hair = g->me->pr_Result2; put("DR COULDN'T FINISH SCAN; "); HairSpray(g->hair); } if (!parent) FastExCleanup(deef); Free(fib, deef); if (g->abort) while (result) { more = result; result = result->next; Lose(more); } return result; } #ifdef C_NOT_ASM /* redo these in assembly for speed */ short alpha(register ubyte *a, register ubyte *b) { register char ac, bc; do { ac = toupper(*(a++)); bc = toupper(*(b++)); } while (ac && ac == bc); return ac - bc; } short alpo(flip a, flip b) { return alpha(a->name, b->name); } #else /* these optimized versions make a noticeable difference when sorting a directory with 75 files or more */ #asm public _alpha public _alpo _alpo: move.l 4(sp),a0 ; first arg move.l 8(sp),a1 ; second arg lea 36(a0),a0 ; first->name lea 36(a1),a1 ; second->name bra alfa ; call-and-return alpha _alpha: move.l 4(sp),a0 ; first arg move.l 8(sp),a1 ; second arg alfa: moveq #0,d0 moveq #0,d1 nxt: move.b (a0)+,d0 move.b (a1)+,d1 beq out ; end of string cmp.b d0,d1 beq nxt ; exactly equal chars cmp.b #'Z',d0 ; tolower(*a) bgt noupa cmp.b #'A',d0 blt noupa add.b #'a'-'A',d0 noupa: cmp.b #'Z',d1 ; tolower(*b) bgt noupb cmp.b #'A',d1 blt noupb add.b #'a'-'A',d1 noupb: cmp.b d0,d1 beq nxt ; equal after tolower out: sub.w d1,d0 ; compare as unsigned bytes rts #endasm import short alpha(ubyte *a, ubyte *b), alpo(flip a, flip b); #endif short olda(register flip a, register flip b) { register long t; if (t = a->when.ds_Days - b->when.ds_Days) return t; if (t = a->when.ds_Minute - b->when.ds_Minute) return t; return a->when.ds_Tick - b->when.ds_Tick; } /* the C code generation does perfectly well with olda */ #ifdef C_NOT_ASM short infoo(ubyte *a, ubyte *b) /* returns 0 if filename b is the .info of filename a, positive if b is alphabetically after a's .info name, negative if before. */ { char acat[37]; register short f = (*a | 0x20) - (*b | 0x20); if (f) return f; strcpy(acat, a); strcat(acat, ".info"); return alpha(b, acat); } #else #asm public _infoo _infoo: move.l 4(sp),a1 ; first arg move.l 8(sp),a0 ; second arg infu: move.b (a1),d0 move.b (a0),d1 bset #5,d0 ; primitive tolower() bset #5,d1 sub.b d1,d0 ; quick pre-test beq check ext.w d0 rts check: move.l a2,-(sp) ; the real test link a5,#-40 move.l sp,a2 ; temporary copy area cpy: move.b (a1)+,(a2)+ bne cpy move.b #'.',-1(a2) ; overwrite final nul move.b #'i',(a2)+ move.b #'n',(a2)+ move.b #'f',(a2)+ move.b #'o',(a2)+ clr.b (a2) move.l sp,a1 bsr alfa unlk a5 move.l (sp)+,a2 rts #endasm import short infoo(ubyte *a, ubyte *b); #endif flip Soart(flip flist, GG) { short (*sortie)(flip a, flip b) = (g->cron ? &olda : &alpo); bool crudecons = g->cron | g->zmptnames; if (!flist) return null; if (!g->zmptnames) { register flip *head, *dot, *foist, t; /* last 2 get D regs */ for (head = &flist; (*head)->next; head = &(*head)->next) { foist = head; for (dot = &(*head)->next; *dot; dot = &(*dot)->next) if (sortie(*foist, *dot) > 0) foist = dot; t = *foist; *foist = (*foist)->next; t->next = *head; *head = t; } } if (!g->cons) { register flip t, tt; register short k; for (t = flist; t; t = t->next) for (tt = (crudecons ? flist : t->next); tt; tt = tt->next) if (!(k = infoo((ubyte *) t->name, (ubyte *) tt->name))) { if (!tt->dirred) { t->infoed = true; if (!t->jected) tt->jected = true; } break; } else if (!crudecons && k > 0) break; } return flist; } short CheckWindowWidth(GG) { import long dos_packet(struct MsgPort *p, long c, ...); struct InfoData *ind; /* ^^^ a Manx convenience */ adr cont = g->me->pr_ConsoleTask; if (!g->cuca) if (g->color && cont && (ind = NewP(struct InfoData))) { if (dos_packet(cont, ACTION_DISK_INFO, (long) ind >> 2)) g->cuca = (adr) ((struct IOStdReq *) ind->id_InUse)->io_Unit; else g->color = false; Free(struct InfoData, ind); } else g->color = false; return g->color ? g->cuca->cu_XMax + 1 : WIDEFAULT; } /* I know, there's an escape sequence. But it don't work without you does set_raw, which does dos_packet, and it comes out smaller this way. */ void Columnate(flip flist, short ficou, GG) { short n; register flip f; /* bool oneinfo = false; FLACK */ g->song = 1; for (f = flist; f; f = f->next) if (!f->jected) { if ((n = digits(f->length)) > g->song) g->song = n; } else /* Assumes 512-byte blocks! vvvv ****/ g->tot.jb += (f->dirred ? 1 : f->blox + f->blox / 72 + 1); if (g->consumption) return; g->cwid = 1; if (!g->wid) g->wid = CheckWindowWidth(g); for (f = flist; f; f = f->next) if (!f->jected) { n = strlen(f->name) + 1 /* 2 - g->cons */ ; /* FLACK */ if (f->dirred) n++; else if (g->zize) n += g->song + 1; if (n > g->cwid) g->cwid = n; /* if (f->infoed) oneinfo = true; FLACK */ } /* g->zons = g->cons; if (!(g->cons | oneinfo)) { g->zons = true; if (!--g->cwid) g->cwid = 1; } FLACK */ if (g->zomplete) { n = g->wid - 28; if (g->cwid > n) g->cwid = n; /* not enough space */ if (n > LWID) n = LWID; /* use at least LWID if possible */ if (n > g->cwid) g->cwid = n; } else { g->cols = g->wid / g->cwid; if (g->cols > MAXCOLS) g->cols = MAXCOLS; /* if (g->cols > ficou) g->cols = ficou; */ /* on second thought, nah */ if (!g->cols) g->cols = 1; /* window too narrow */ g->cwid = g->wid / g->cols; /* share extra space evenly */ } } void Cough1(register flip y, bool dirs, short *col, GG) { register short h, lused = strlen(y->name); bool icon = g->color & y->infoed; char p; if (dirs) { g->tot.blok++; g->tot.dir++; } else { g->tot.byt += y->length; g->tot.fil++; g->tot.blok += y->blox + y->blox / 72 + 1; } /* this assumes ^^^^ 512-byte block size! ****/ if (g->consumption) return; if (g->zize && !g->zomplete && !g->completenames) { if (dirs) pad(g->song); else padong(y->length, g->song); puch(' '); lused += g->song + 1; } /* if (!g->zons) { puch(y->infoed ? FLACK : ' '); lused++; } */ if (icon) put(CSI "33m"); if (g->completenames) { put(g->prepath); if (*g->prepath) { register short l = strlen(g->prepath); /* HEY, I just discovered another Aztec 3.6a compiler bug. If you leave */ /* out the line above and go "p = g->prepath[strlen(g->prepath) - 1];" */ /* where strlen is #defined as _BUILTIN_strlen, it generates code that */ /* uses the special non-existent 68000 instruction "ext.l a0". */ p = g->prepath[l - 1]; if (p != '/' && p != ':') puch('/'); } } put(y->name); if (icon) put(CSI "31m"); if (y->dirred) { puch('/'); lused++; } h = g->cwid - lused; if (g->completenames) puch('\n'); else if (!g->zomplete) { if (++*col >= g->cols) { puch('\n'); *col = 0; } else pad(h); } else { if (h > digits(y->length)) if (dirs) pad(h); else padong(y->length, h); else { puch('\n'); if (dirs) pad(g->cwid); else padong(y->length, g->cwid); } putection(y->tection); putdate(&y->when); puch('\n'); if (y->comment) putfmt(": %s\n", y->comment); } } bool CoughHalf(flip flist, bool dirs, GG) { short h, col = 0, n = 0; bool anyleft = false; register flip y; if (!flist) return false; for (y = flist; y; y = y->next) if (!y->jected) if (!(dirs ^ y->dirred)) { y->wanted = true; n++; } else { y->wanted = false; anyleft = true; } /* else wanted is always false */ if (!n) return false; h = 0; for (y = flist; y; y = y->next) if (y->wanted) { y->ordination = h; if (g->colsort) { if ((h += g->cols) >= n) h = h % g->cols + 1; } else h++; } else y->ordination = -1; y = flist; for (h = 0; h < n; h++) { while (y->ordination != h) if (!(y = y->next)) y = flist; /* a bit clumsy... */ Chk_Abort(); if (g->abort) break; Cough1(y, dirs, &col, g); } if (col > 0) puch('\n'); return anyleft && !g->abort && !g->zutfils; } void plural(str s, long n) { putfmt(s, n, (n == 1 ? 0 : 's')); } void Tote(register struct cuont *c) { plural("%ld dir%c ", c->dir); plural("and %ld file%c ", c->fil); plural("with %ld byte%c ", c->byt); plural("use %ld block%c ", c->blok); putfmt("(of %ld).\n", c->blok + c->jb); } void CoughUp(flip flist, short ficou, GG) { flip p, t; if (g->zurse && !g->abort && !g->zmptnames) { if (g->didaninny && !g->consumption) puch('\n'); if (flist) putfmt(" ------- \"%s\":\n", g->prepath); else putfmt(" ------- \"%s\" is empty.\n", g->prepath); g->didaninny = true; } if (!flist) return; g->tot.blok = g->tot.byt = g->tot.fil = g->tot.dir = g->tot.jb = 0; g->cols = 1; /* default */ if (!g->zmptnames) Columnate(flist, ficou, g); #ifdef STUPID_DASHES if (!g->zutdirs && CoughHalf(flist, true, g) && !g->consumption && !g->completenames) { short i; put(" "); for (i = 0; i < g->wid - 8; i++) puch('-'); puch('\n'); } #else if (!g->zutdirs) { #ifdef BLACKGROUND if (g->color) put(CSI "42m" CSI "J"); #endif #ifdef BOLDDIRS if (g->color) put(CSI "1m"); #endif CoughHalf(flist, true, g); #ifdef BLACKGROUND if (g->color) put(CSI "0m" CSI "J"); #endif #ifdef BOLDDIRS if (g->color) put(CSI "0m"); #endif } #endif Chk_Abort(); if (!g->zutfils && !g->abort) CoughHalf(flist, false, g); for (p = flist; p; p = t) { t = p->next; Lose(p); } if (g->abort || g->zmptnames) return; if (((g->complete | g->cize) && g->tot.fil > 1) || g->consumption) Tote(&g->tot); else if ((g->zomplete | g->cize) && g->tot.fil) plural("%ld block%c used.\n", g->tot.blok); g->gran.blok += g->tot.blok; g->gran.byt += g->tot.byt; g->gran.fil += g->tot.fil; g->gran.dir += g->tot.dir; g->gran.jb += g->tot.jb; } void DoInner(str what, BPTR deer, fib *parent, GG) { flip filist; short ficou, prength = strlen(g->prepath), preng1 = prength; char pe = g->prepath[prength - 1]; if (!g->abort && StackLeft(g) < STACKNEEDED) { putfmt("Cannot scan \"%s\" -- insufficient stack space!\n", what); g->hair = ERROR_TOO_MANY_LEVELS; /* dammit, my books don't define what TOO_MANY_LEVELS means, or */ /* when it is appropriate to set it! */ return; } if (prength && pe != ':' && pe != '/') { g->prepath[prength++] = '/'; g->prepath[prength] = 0; } if (prength < PREPENGTH - 30) strcpy(g->prepath + prength, what); g->zize = g->complete || (g->cize && !g->cutfils); g->zomplete = g->complete; g->zurse = g->curse; g->zutfils = g->cutfils; g->zutdirs = g->cutdirs; filist = ScanDeer(deer, &ficou, parent, g); if (!filist && !g->abort && (g->me->pr_Result2 == ERROR_NO_FREE_STORE)) { putfmt("Not enough memory to scan \"%s\"!\n", what); g->hair = ERROR_NO_FREE_STORE; } g->zize |= g->zomplete; if (!g->consumption) filist = Soart(filist, g); if (!g->abort) CoughUp(filist, ficou, g); g->prepath[preng1] = 0; /* remove tail just added */ } void Do(str what, GG) { BPTR deer; g->pat = &g->nullpat; if (g->patty = !(deer = RLock(what))) if (!SplitTailPat(what, &deer, g)) return; if (!deer) { putfmt("Couldn't find \"%s\".\n", what); g->hair = ERROR_OBJECT_NOT_FOUND; return; } g->gran.blok = g->gran.byt = g->gran.fil = g->gran.dir = g->gran.jb = 0; *g->prepath = 0; g->didaninny = false; g->zmptnames = g->completenames && !g->consumption; DoInner(what, deer, null, g); UnLock(deer); if (!g->abort && (g->complete || g->consumption || g->cize) && g->gran.blok > g->tot.blok) { put("\nTotal: "); Tote(&g->gran); } } #define lower(C) ((C) | 0x20) void Opt(register str p, GG) { register char c; for (c = lower(*++p) ; c > ' '; c = lower(*++p)) { switch (c) { case 'r': { g->curse ^= true; continue; } case 'i': { g->cons ^= true; continue; } case 's': { g->cize ^= true; continue; } case 'c': { g->cron ^= true; continue; } case 'l': { g->complete ^= true; continue; } case 'h': { g->colsort ^= true; continue; } case 'f': { g->cutdirs ^= true; g->cutfils = false; continue; } case 'd': { g->cutfils ^= true; g->cutdirs = false; continue; } case 'x': { g->csternal ^= true; continue; } case 'o': { g->completenames ^= true; continue; } case 'u': { g->consumption ^= true; continue; } /* case 'a': { g->cage ^= true; continue; } */ /* case 'e': { g->canydepth ^= true; continue; } */ case 'p': { register bool tilt = false; register short b = -1; g->protlook = g->protwant = 0; c = lower(p[1]); if (c == '~') { tilt = true; c = lower((++p)[1]); } if (c <= ' ') continue; else p++; switch (c) { case 'h': b = 7; break; case 's': b = 6; break; case 'p': b = 5; break; case 'a': b = 4; break; case 'r': b = 3; break; case 'w': b = 2; break; case 'e': b = 1; break; case 'd': b = 0; } if (b < 0) put(" *** letter after -P must be one of H S P A R W E D.\n"); else { g->protlook = bit(b); g->protwant = tilt ^ (b >= 4) ? bit(b) : 0; } continue; } } putfmt(" *** Unknown option -%c.\n", *p); } } void mane(GG) { struct FileHandle *out = gbip(g->me->pr_COS); short a, aa; bool help = g->argc == 2 && g->argv[1][0] == '?' && !g->argv[1][1]; bool laybull = g->argc - g->hyphc > 2, didone = false; str *hp; if (help) { /* [-cdfhilorsux | -pB | -p~B] */ putfmt("\nUsage: %s { [-options] [directory|pattern] } ...\n\n" "where options are:\n", *g->argv); for (hp = helpslab; *hp; hp++) put(*hp); g->abort = 1; return; } g->color = IsInteractive(g->me->pr_COS) & true; /* -1 => 1 */ g->wid = CheckWindowWidth(g); /* may reset g->color */ for (aa = g->argc - 1; aa > 0 && (ubyte) *g->argv[aa] == HYPH; aa--) Opt(g->argv[aa], g); if (aa) { for (a = 1; a < g->argc && !g->abort; a++) if ((ubyte) *g->argv[a] == HYPH) { Opt(g->argv[a], g); } else { if (laybull && (!g->completenames || g->consumption)) { if (didone) puch('\n'); didone = true; pad((g->wid - (short) strlen(g->argv[a]) - 10) >> 1); putfmt("-- %s --\n", g->argv[a]); } Do(g->argv[a], g); } } else Do("", g); if (g->hair && g->hair != ERROR_OBJECT_NOT_FOUND && !g->abort) { put("*** LISTING NOT COMPLETE; "); HairSpray(g->hair); } } /* This version of cliparse features handling of internal quotes BCPL-style with *" instead of Manx "". It marks -args with a special character (HYPH), accepting a -arg in quotes as a filename. *N and *E are not supported, since filenames can't contain newline or escape. */ void cliparse(GG, long alen, register str ap) { register short coml; bool quoted, star; register ubyte c; str tempargv[200]; register str poik = bip(char, bip(struct CommandLineInterface, g->me->pr_CLI)->cli_CommandName); g->hyphc = 0; coml = *poik; g->arglen = coml + (short) alen + 2; if (!(g->argline = Alloc(g->arglen))) return; strncpy(g->argline, poik + 1, (size_t) coml); g->argline[coml] = '\0'; *tempargv = g->argline; g->argc = 1; if (alen) { poik = g->argline + coml + 1; ap[--alen] = '\0'; /* probably just the newline */ for (;;) { while (*ap && *ap <= ' ') ap++; if (!*ap) break; if (*ap == '-') { g->hyphc++; (ubyte) *ap = HYPH; } quoted = *ap == '"'; star = false; ap += quoted; tempargv[g->argc++] = poik; while ((c = (ubyte) *ap) && (quoted ? star || c != '"' : c > ' ')) { if (!(star = quoted && c == '*')) *(poik++) = c; ap++; } if (*ap == '"') ap++; *(poik++) = '\0'; } } tempargv[g->argc] = null; if (!(g->argv = Alloc((g->argc + 1) << 2))) { g->argc = 0; FreeMem(g->argline, (long) g->arglen); } for (coml = 0; coml <= g->argc; coml++) g->argv[coml] = tempargv[coml]; } /* simplified reentrant startup code */ long _main(long alen, str aptr) { glob g; /* the single instance for everyone */ adr reta; Enable_Abort = 1; memset(&g, 0, sizeof(glob)); g.me = ThisProcess(); reta = g.me->pr_ReturnAddr; goofset = (str) &g - (str) reta; if (!g.me->pr_CLI) { /**** maybe reply wbstartup msg here? naah */ return 999999L; /* never gets unloaded by workbench */ } g.stacklimit = (adr) ((str) reta - *(long *) reta + 4); if (OpenPureIO(&g.purestuff[0], PUREBUFSIZE)) { /* move this to mane? */ g.me->pr_Result2 = ERROR_NO_FREE_STORE; return 20L; } g.colsort = true; cliparse(&g, alen, aptr); if (g.argc) mane(&g); else { put("Dr: Not enough memory to do a durn thing!\n"); g.abort = 20; } if (g.argline) { FreeMem(g.argline, (long) g.arglen); FreeMem(g.argv, (long) (g.argc + 1) << 2); } ClosePureIO(); if (g.hair) { g.me->pr_Result2 = g.hair; if (!g.abort) g.abort = 10; } return (long) g.abort; }