koreth@panarthea.ebay.sun.com (Steven Grimm) (10/11/89)
Submitted-by: uunet!unido!infbs!tubsibr!hafer (Udo Hafermann)
Posting-number: Volume 2, Issue 88
Archive-name: yaff
Here is yet another version of an Atari ST file finder. This one is
a no-frills version meant to be used from a shell (gulam, msh etc.);
it will run from the desktop though. As the basic machinery was
there, I went a little option-crazy and included stuff like filesize
counting etc.
#!/bin/sh
# shar: Shell Archiver (v1.22)
#
# Run the following text with /bin/sh to create:
# FF.C
# FF.MAN
# MAKEFILE
#
sed 's/^X//' << 'SHAR_EOF' > FF.C &&
X
X
X/*
X * Searches recursively for all directory entries matching a given
X * specification beginning at the directory specified.
X * See ff.man for details.
X *
X * For ST-TOS, MWC 3.05.
X * History: 17-SEP-87
X * 06-JUL-89 regexp as filespec; -x option
X * 24-JUL-89 file sizes
X */
X
X#include <stdio.h>
X#include <osbind.h>
X#include <bios.h>
X#include <ctype.h>
X
X/* requirements: */
X
X#define PATHLEN 256 /* max. length of paths we can manage */
X /* (more than GEMDOS can, hopefully) */
X#define BUFLEN 1000 /* string buffer size */
X#define CLUSTERSIZE 1024 /* cluster size assumed for -k */
X#define STACKSIZE 10000 /* recursive search might need it */
X
X/* file types: */
X
X#define FWPROT (0x01) /* write protected */
X#define FHIDE (0x02) /* hidden */
X#define FSYS (0x04) /* system file */
X#define FVOL (0x08) /* volume label */
X#define FSUB (0x10) /* subdirectory */
X#define FARCH (0x20) /* archive bit */
X
X/* return codes: */
X
X#define E_OK (0L) /* no error */
X#define E_FILNF (-33L) /* file not found */
X#define E_PTHNF (-34L) /* path not found */
X#define E_NMFIL (-49L) /* no more files */
X
X/* check macro for "." and ".." file entries: */
X#define NOTDOT(fname) (fname[0]!='.' || (fname[1]!='\0' && fname[1]!='.'))
X
X/* stack request: */
Xlong _stksize = STACKSIZE;
X
X/* variables: */
X
Xstatic char *whoami = "ff"; /* if non-empty, will point to argv[0] */
Xstatic char *olddta; /* dta backup (do we really need that?) */
X
Xstatic char filespec[PATHLEN];
X /* wildcard-spec of filenames */
X /* to be searched for */
Xstatic char path[PATHLEN], /* path of directory */
X /* currently being searched */
X drivespec[BUFSIZ],
X /* drive specification part of filespec */
X buf1[BUFSIZ],
X *xflag; /* pointer to "-x cmd", if not null */
X
Xstatic int cflag, /* if true, -c flag */
X qflag, /* if true, -q flag */
X vflag, /* ... and so on ... */
X hflag,
X sflag,
X dflag,
X tflag,
X kflag,
X count, /* # matches so far */
X ftypes; /* attribute bits */
X
Xstatic long total; /* accumulated size */
X
Xunsigned char fattset, /* if non-zero, these atts must be set */
X fattunset = FSUB;
X /* these atts must be unset */
X
Xstatic terminate(status)
Xint status;
X{
X /* terminate program returning status */
X Fsetdta(olddta); /* is this necessary? */
X if (hflag) {
X fprintf(stderr, "Type <Return>: ");
X getchar();
X }
X exit(status);
X}
X
Xstatic info(full)
Xint full;
X{
X /* usage info, short or full */
X fprintf((full ? stdout : stderr),
X "Usage: %s [-?] [-hcqvsktd] [-aatts] [-iatts] [-x command] 'filespec'\n",
X whoami);
X if (full) {
X printf("\tPrints all filenames matching 'filespec', recursively searching\n");
X printf("\tsubdirectories from the one specified (default: current).\n");
X printf("\tWildcards: * ? []\n");
X printf("Flags:\n");
X printf("\t-h\tWait before terminating\n");
X printf("\t-c\tPrint match count\n");
X printf("\t-q\tQuiet: print no filenames\n");
X printf("\t-v\tVerbose: list directories searched (on stderr)\n");
X printf("\t-s\tList file sizes\n");
X printf("\t-d\tList subtotals for subdirectories\n");
X printf("\t-t\tSuppress total size\n");
X printf("\t-k\tList file sizes, rounded to multiples of %d\n",
X CLUSTERSIZE);
X printf("\t-aatts\tOnly list files with these attributes\n");
X printf("\t-iatts\tIgnore files with these attributes (default: d)\n");
X printf("\t-x cmd\tExecute command, where %%%% is replaced by filename\n");
X printf("Exit status: The match count\n");
X }
X terminate (-1);
X}
X
Xstatic unsigned char parseatts (cp)
Xchar *cp;
X{
X /* cp points to string of letters r,h,s,d, or m.
X Returns bitmap of attributes. */
X unsigned char atts = 0;
X char c;
X
X while (c = *cp++) {
X if (c=='r')
X atts |= FWPROT;
X else if (c=='h')
X atts |= FHIDE;
X else if (c=='s')
X atts |= FSYS;
X else if (c=='v')
X atts |= FVOL;
X else if (c=='d')
X atts |= FSUB;
X else if (c=='m')
X atts |= FARCH;
X }
X return atts;
X}
X
Xstatic void str_tolower(s)
Xchar *s;
X{
X /* convert s to lower case */
X while (*s) {
X (*s) = tolower(*s);
X s++;
X }
X}
X
Xstatic void str_repl (target, source, s1, s2)
Xchar *target, *source, *s1, *s2;
X{
X /* copies source into target, replacing each occurrence
X of s1 with s2 */
X unsigned int s1len = strlen(s1), s2len = strlen(s2);
X
X while (*source) {
X if (0 == strncmp(source, s1, s1len)) {
X /* substitute s2 for s1: */
X strcpy (target, s2);
X target += s2len;
X source += s1len;
X } else {
X *target++ = *source++;
X }
X }
X *target = '\0';
X}
X
Xmain(argc, argv)
Xint argc;
Xchar *argv[];
X{
X olddta = (char *)Fgetdta(); /* restored by terminate() */
X
X /* First, save program name for messages: */
X if (argv[0][0])
X whoami = argv[0];
X
X /* Process options: */
X while (--argc) {
X char c,
X *cp;
X
X cp = *++argv;
X if (cp[0] != '-') {
X /* not an option: must be first filespec */
X break;
X }
X if (strlen(cp) < 2) {
X /* invalid option */
X info(0);
X }
X /* process these options: */
X str_tolower(cp);
X while (c = *++cp) {
X if (c == 'c')
X cflag = -1;
X else if (c=='h')
X hflag = -1;
X else if (c=='q')
X qflag = -1;
X else if (c=='v')
X vflag = -1;
X else if (c=='s')
X sflag = -1;
X else if (c=='t')
X tflag = -1;
X else if (c=='k')
X kflag = sflag = -1;
X else if (c=='d')
X dflag = -1;
X else if (c=='x') {
X xflag = *++argv;
X argc--;
X } else if (c=='a') {
X /* set attributes */
X fattset = parseatts (++cp);
X break;
X } else if (c=='i') {
X /* unset atts */
X fattunset = 0;
X fattunset = parseatts (++cp);
X break;
X } else {
X /* unknown option (possibly '?') */
X info(c=='?');
X }
X }
X }
X
X /* set up file types for fsnext: */
X ftypes = FSUB | FHIDE | FWPROT | FSYS | FARCH;
X
X /* apply principle of least surprise (we ignore subdirs by
X default, remember): */
X if (fattset & FSUB)
X fattunset &= ~FSUB;
X
X /* now process the filespecs: */
X if (!argc) {
X info(0); /* we must have at least one... */
X }
X
X while (argc--) {
X char *cp, *dp, *ep; /* quite a lot, eh? */
X /* but wait and see... */
X int have_drive;
X
X /* parse the argument into:
X filespec - argument for pnmatch
X drivespec - drive portion (possibly empty)
X path - absolute part of path (first subdir to search)
X */
X
X drivespec[0] = 0;
X cp = *argv; /* argument pointer */
X dp = path; /* path construction pointer */
X ep = path; /* current end of absolute path */
X have_drive = 0; /* true when drive spec is passed */
X do {
X *dp++ = tolower(*cp);
X if (*cp == '\\') {
X /* drivespec is complete: */
X have_drive = 1;
X /* mark position: */
X ep = dp;
X } else if (*cp == ':' && !have_drive) {
X /* end of real drive spec! */
X /* copy it: */
X *(dp-1) = 0;
X strcpy (drivespec, path);
X have_drive = 1;
X /* intialize path appropriately: */
X dp = path;
X *dp++ = 'x'; *dp++ = ':';
X ep = dp;
X }
X } while (*cp++);
X /* copy end of path to filespec: */
X strcpy (filespec, ep);
X /* and terminate path after last backslash: */
X *ep = 0;
X
X if (drivespec[0]) {
X /* drive specified; we must try all: */
X char drive;
X long drivemap;
X char *hugo = "x";
X
X drivemap = Drvmap();
X for (drive='a'; drive<='p'; drive++) {
X if (drivemap & 0x1) {
X path[0] = hugo[0] = drive;
X if (pnmatch(hugo, drivespec, 0))
X recursive(*argv);
X }
X drivemap >>= 1;
X }
X } else {
X recursive(*argv);
X }
X argv++;
X }
X
X /* Report results: */
X if (cflag) {
X if (vflag)
X printf ("Entries: ");
X printf ("%d\n", count);
X } else {
X if (vflag && !count)
X fprintf (stderr, "%s: no match.\n", whoami);
X }
X if (sflag && !(tflag || dflag))
X if (vflag)
X printf ("Total size: %ld\n", total);
X else
X printf ("%10ld\n", total);
X
X terminate(count);
X}
X
Xstatic recursive (arg)
Xchar *arg; /* the original search argument (for verbose mode) */
X{
X /* search for files matching filespec in current path and subdirs */
X struct Buff {
X unsigned char reserved[21];
X unsigned char type;
X unsigned short time;
X unsigned short date;
X unsigned long size;
X unsigned char name[14];
X } buffer; /* dta buffer for this path */
X int pp, /* path marker for backtracking */
X status; /* GEMDOS return code */
X long oldtotal = total;
X /* so that we can work out subtotal */
X
X /* first, search current dir: */
X if (vflag)
X fprintf (stderr,
X "%s: Searching for '%s' in '%s'\n", whoami, arg, path);
X Fsetdta(&buffer);
X pp = strlen(path);
X strcat (path, "*.*");
X status = Fsfirst(path, ftypes);
X while (status == E_OK) {
X /* does this entry match? */
X str_tolower(buffer.name);
X if (pnmatch(buffer.name, filespec, 0)
X && (!fattset || fattset == (fattset & buffer.type))
X && (! (fattunset & buffer.type))
X && NOTDOT (buffer.name)) {
X path[pp] = '\0';
X strcat(path, buffer.name);
X if (sflag || dflag) {
X long s = buffer.size;
X if (kflag)
X s = CLUSTERSIZE *
X ((s+CLUSTERSIZE-1)/CLUSTERSIZE);
X if (!qflag)
X printf("%10ld ", s);
X total += s;
X }
X if (!qflag) {
X printf("%s\n", path);
X }
X count++;
X if (xflag) {
X str_repl (buf1, xflag, "%%", path);
X system (buf1);
X }
X path[pp] = '\0';
X strcat(path, "*.*");
X }
X status = Fsnext();
X }
X path[pp] = '\0';
X /* check return code for unusual stuff: */
X if (status != E_FILNF && status != E_NMFIL) {
X /* unexpected return code! */
X fprintf (stderr,
X "%s '%s': Oops - unexpected error code: %d\n",
X whoami, path, status);
X terminate(status);
X }
X /* Now search all subdirs: */
X strcat(path, "*.*");
X status = Fsfirst(path, FSUB|FHIDE|FSYS|FWPROT|FARCH);
X path[pp] = '\0';
X while (status==E_OK) {
X if (buffer.type&FSUB && NOTDOT(buffer.name)) {
X str_tolower(buffer.name);
X strcat (path, buffer.name);
X strcat (path, "\\");
X recursive(arg);
X path[pp] = '\0';
X Fsetdta(&buffer);
X }
X status = Fsnext();
X }
X if (dflag)
X printf ("%10ld %s\n", total-oldtotal, path);
X}
SHAR_EOF
chmod 0600 FF.C || echo "restore of FF.C fails"
sed 's/^X//' << 'SHAR_EOF' > FF.MAN &&
X
XFF (1) ATARI ST UTILITIES FF (1)
X
X
XNAME
X ff - _f_i_l_e_f_i_n_d: recursive search for files
X
XSYNOPSIS
X
X ff [-cqvsktd] [-aatts] [-iatts] [-x command] 'filespec'
X
XDESCRIPTION
X
X ff takes everything up to the last backslash as the directory to
X start searching (default is the current directory). The rest of
X filespec is the pattern. ff will list all files (with their complete
X paths) whose last names match this pattern, recursively scanning all
X subdirectories. filespec may begin with a drive specification
X (ending with a colon; a backslash should follow), which is matched
X against all drives present.
X
X ff uses the MWC routine pnmatch() for the pattern match; thus,
X permissible wildcards are * (matches any sequence of characters),
X ? (matches any one character), and character lists enclosed in
X brackets ([]).
X
X Flags:
X -c _C_o_u_n_t: print number of files found.
X -q _Q_u_i_e_t: print no filenames or extra messages.
X -v _V_e_r_b_o_s_e: list directories searched.
X -aatts Include only files with these _a_t_t_r_i_b_u_t_e_s
X (r,h,s,d,m for readonly, hidden, system, subdir,
X modified flag, respectively).
X -iatts _I_g_n_o_r_e files with these attributes (default: d).
X -s _S_i_z_e: list file sizes and total.
X -t Suppress _t_o_t_a_l.
X -k _k_B_y_t_e: round sizes to multiples of 1024 (implies -s).
X On systems with a cluster size of 1 kByte, this should
X report the space actually used by the files.
X -d List subtotal for each _s_u_b_d_i_r_e_c_t_o_r_y (implies -s).
X -x cmd For each match, _e_x_e_c_u_t_e command cmd
X using msh, where %% is replaced with the filename.
X
X Exit status: The number of matching filenames found, or a negative
X error number.
X
XEXAMPLES
X
X ff 'hugo' find files named hugo, beginning at the current
X directory.
X ff -c '*' find all files, beginning at the current directory,
X reporting the number of files found.
X ff '?:\*.c' find all C source files on all existing drives.
X ff -am '[cd]:\hugo\*' find all files, beginning at the
X subdirectories "c:\hugo" and "d:\hugo", which have
X the "modified" attribute set.
X
XRESTRICTIONS
X
X - For the -x option, msh.prg must be in the search path.
X - The files are listed in the order in which they appear in the
X directory (i.e., not sorted in any way).
X - As the backslash is the directory seperator, there is no escape
X character available for the wildcard characters.
X
XNOTES
X
X - The filespec must be enclosed in quotes if the shell used
X would otherwise expand the wildcards. Contemplate the
X difference between "ff *.c" and "ff '*.c'" under gulam (or msh).
X - Arguments are forced to lower case (for use from the desktop under
X old TOS versions).
X - "ff -qkd 'dir\*'" is roughly equivalent to "du dir".
X - Very convenient under msh using backquotes:
X rm `ff '*'` is equivalent to UNIX rm -r, with the
X exception that subdirectories are not deleted, and that the msh
X cannot process an unlimited number of arguments that way.
X
XWARNINGS
X
X As ff may traverse quite a few directories, you might run into
X the _4_0_-_f_o_l_d_e_r_-_b_u_g (discussed at length elsewhere).
X
XTODO
X - There should be support for matching against the file timestamps.
X - ff should, instead of just calling system(), use the gulam shell
X interface if present.
X - There should be a replacement for the pnmatch() escape character.
X
XFILES
X ff.ttp
X
XAUTHOR
X
X hafer@tubsibr july 1989
X
SHAR_EOF
chmod 0600 FF.MAN || echo "restore of FF.MAN fails"
sed 's/^X//' << 'SHAR_EOF' > MAKEFILE &&
X
Xff.ttp: ff.c
X cc -s -o ff.ttp ff.c
X
X
SHAR_EOF
chmod 0600 MAKEFILE || echo "restore of MAKEFILE fails"
exit 0