[comp.sources.atari.st] v02i088: yaff -- Yet Another File Finder

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