 Program WHERE - is a program to locate files on the IBM PC/XT or PC/AT     
    hard disks. The search begins at the starting directory (root directory 
    by default (\)) and traverses the tree for all files that match the     
    search string. This search string may contain wildcards * and ?.	     

    Output: For each file found:                                            
									     
	nnnnn mm-dd-yy hh:mm pm pathname                                     
									     
    	giving the file size, creation date and time, and complete pathname. 

    The program is written in Pascal to be built with IBM Pascal 2.0.
    If anyone ports it to other Pascal compilers, please let me know.

    Mike Johnson
    MIT Project Athena
    mjohnson@athena.mit.edu

--------------
...and here is the source file:
--------------
 
{$include:'c:\usr\src\pascal\ibmintrp.int'}

{****************************************************************************}
{									     }
{ Program WHERE - is a program to locate files on the IBM PC/XT or PC/AT     }
{    hard disks. The search begins at the starting directory (root directory }
{    by default (\)) and traverses the tree for all files that match the     }
{    search string. This search string may contain wildcards * and ?.	     }
{									     }
{    Output: For each file found:                                            }
{									     }
{	nnnnn mm-dd-yy hh:mm pm pathname                                     }
{									     }
{    	giving the file size, creation date and time, and complete pathname. }
{									     }
{    Syntax:  where [starting_directory]filename.ext			     }
{									     }
{	      starting_directory - the sub-tree of the directory heirarchy   }
{				   for the program to search		     }
{	      filename.ext	 - standard DOS file description that may    }
{				   include wildcards * and ?.		     }
{									     }
{    To build:   using IBM Pascal Compiler, release 2.0:                     }
{									     }
{	      PAS1 where.pas,where.obj;                                      }
{	      PAS2                                                           }
{	      LINK where,where,NUL.MAP,\lib\ibmpas.lib+\lib\pascal.lib       }
{									     }
{									     }
{    Copyright:  1985  by the Massachusetts Institute of Technology	     }
{									     }
{		 Permission to use, copy, modify, and distribute this	     }
{		 program for any purpose and without fee is hereby granted,  }
{		 provided that this copyright and permission notice appear on}
{		 all copies and supporting documentation, the name of M.I.T. }
{		 not be used in advertising or publicity pertaining to	     }
{		 distribution of the program without specific prior	     }
{		 permission, and notice be given in supporting documentation }
{		 that copying and distribution is by permission of M.I.T.    }
{		 M.I.T. makes no representations about the suitability of    }
{		 this software for any purpose. It is provided "as is"       }
{		 without express or implied warranty.			     }
{									     }
{		 1984, 1985  by Mark S. Ackerman.			     }
{		 Permission is granted for unlimited copies if not sold      }
{		 or otherwise exchanged for gain.			     }
{									     }
{    Status   :  Version 1.00						     }
{									     }
{    Author   :  Michael G. Johnson, MIT Project Athena 		     }
{									     }
{		 This code is a port of Mark S. Ackerman's WHERE.C program   }
{		 written for the Mark Williams C Compiler to the IBM Pascal  }
{		 Compiler Version 2.00 . The C code and algorithm appear in  }
{		 the October 1985 issue of the PC Tech Journal vol. 3 no. 10 }
{		 page 85.						     }
{									     }
{    Creation Date :   10/30/85 					     }
{									     }
{    Revisions	   :   none						     }
{									     }
{    Parameters passed	 :						     }
{									     }
{	incoming_string - the arguement line containing the starting	     }
{			  directory and the search string		     }
{									     }
{    Parameters returned : none 					     }
{									     }
{    Entry Conditions	 : none 					     }
{									     }
{    Exit Conditions	 :						     }
{									     }
{	  Normal  - Normal Pascal return sequence			     }
{	  Special - None.						     }
{									     }
{    External Calls	 :						     }
{									     }
{	 intrp(intno, inregs, outregs) - performs a software interrupte,     }
{					 found in library IBMPAS.LIB	     }
{		  intno   : byte	! interrupt number		     }
{	     Vars inregs  : reglist	! register settings before interrupt }
{	     Vars outregs : reglist	! register settings after interrupt  }
{									     }
{    External Data Areas ( Global Constants, Types, & Variables ) :	     }
{									     }
{	  adsmem   - pointer structure to access data in memory 	     }
{		     ADSMEM : ADS OF ARRAY [0..32766] OF BYTE		     }
{									     }
{	  reglist  - record structure used to set and save system registers  }
{		     REGLIST : RECORD					     }
{			       AX, BX, CX, DX, SI, DI, DS, ES, FLAGS : WORD  }
{			       END;					     }
{									     }
{    Compiler  : IBM Personal Computer Pascal Compiler Version 2.00	     }
{									     }
{****************************************************************************}

Program WHERE (input, output, incoming_string);

   Uses ibmintrp;

   Const
	 size_where_string = 255;		       ! maximum string size
	 backslash	   = chr(#5C);		       ! '\' character
	 end_of_string	   = chr(#00);		       ! DOS ASCIIZ end of string char

   Type
	 where_string  = string(size_where_string);    ! fixed length string
	 where_lstring = lstring(size_where_string);   ! variable length string

   Var
	 incoming_string  : where_string;	       ! input command line
	 directory_string : where_lstring;	       ! starting directory
	 check_string	  : where_lstring;	       ! search string
	 time_ampm	  : array [0..1] of string(2); ! AM/PM time indicator

   Value
	 time_ampm[0] := 'am';
	 time_ampm[1] := 'pm';

   {------------------------------------------------------------------------}
   {  Function INIT_STRINGS						    }
   {------------------------------------------------------------------------}
   Function init_strings : boolean;
     { Initialze the starting directory string and the search string,	    }
     { DIRECTORY_STRING and CHECK_STRING respectively. This is done by	    }
     { by parsing the command line (INCOMING_STRING) at the last backslash. }
     { If a backslask does not exist, the strating directory is by default  }
     { the root (\) and the search string in the command line		    }

      Const
	    space     = chr(#20);

      Var
	    inc_size, dir_size, chk_size : integer;
	    i : integer;

      Begin { function init_strings   }
	 inc_size := ord(sizeof(incoming_string)) - 1;
	 inc_size := inc_size +
		     scanne(-inc_size, space, incoming_string, inc_size);
	 dir_size := inc_size +
		     scaneq(-inc_size, backslash, incoming_string, inc_size);
	 chk_size := inc_size - dir_size;
	 directory_string.len := wrd(dir_size);
	 check_string.len     := wrd(chk_size);

	 If dir_size = 0
	    Then directory_string := '\'
	    Else For i := 1 to dir_size
	       Do directory_string[i] := incoming_string[i];
	 If chk_size > 0
	    Then For i := 1 to chk_size
	      Do check_string[i] := incoming_string[i+dir_size];
	 If inc_size > 0
	    Then init_strings := true
	    Else init_strings := false;
      End;  { function init_strings   }


   {------------------------------------------------------------------------}
   {  Procedure LOOKUP - is a recursive call that traverses the DOS tree    }
   {	 heirarchy for files matching the the search string. Lookup is	    }
   {	 called once for each subdirectory and uses a post-order or suffix  }
   {	 tree search.							    }
   {------------------------------------------------------------------------}
   Procedure lookup (Const directory_string : string);

      Const
	    carry_flag_mask = #01;     ! carry flag mask for error checking
	    no_type	    = #00;     ! file attrib.- normal file, no archive
	    directory_type  = #10;     ! file attrib.- directory
	    no_more_files   = #12;     ! error code indicating no more files

      Type
	    { DOS Disk Tranfer Area, see page 5-132 of DOS Tech Ref 3.0     }
	    dta = record
	       dta_data       : string(43);    ! DOS DTA, first 21 used by DOS
	       dta_attr       : byte;	       ! file attribute
	       dta_time       : word;	       ! file creation time
	       dta_date       : word;	       ! file creation date
	       dta_size       : integer4;      ! file size
	       dta_filename   : lstring(13);   ! ASCIIZ filename
	    end;

      Var
	    regs	: reglist;
	    current_dta : dta;
	    current_string	: where_lstring;
	    newdirectory_string : where_lstring;

      {---------------------------------------------------------------------}
      {  Procedure SET_DTA - define the memory area where the DOS disk	    }
      {     transfer area will be stored. This routine sets the 	    }
      {     DTA to be current_dta.dta_data. It does so by using DOS	    }
      {     interrupt 21h, function 1Ah Set Disk Transfer Address.	    }
      {---------------------------------------------------------------------}
      Procedure set_dta (Var current_dta : dta);

	 Begin { procedure set_dta   }
	    regs.ax := byword(#1A, #00);
	    regs.ds := (ADS current_dta.dta_data[1]).s;
	    regs.dx := (ADS current_dta.dta_data[1]).r;
	    intrp(#21, regs, regs);
	 End;  { procedure set_dta   }


      {---------------------------------------------------------------------}
      {  Procedure SET_DTA_VALUES - takes values in the Disk Transfer Area, }
      {     CURRENT_DTA.DTA_DATA and sets the remainder of the the data     }
      {     structure CURRENT_DTA. When the DTA gets set by DOS, it loads   }
      {     the memory in 43 consecutive bytes. Pascal 2.00 forces elements }
      {     in a record structure to predetermined alignments. This is the  }
      {     why the disk transfer area was not define as a RECORD with	    }
      {     separate elments for time, data, size, etc. but as a string of  }
      {     43 bytes, then after being set transfered to record elments of  }
      {     the correct size.
      {---------------------------------------------------------------------}
      Procedure set_dta_values (Var current_dta : dta);

	 Var
	       i       : word;
	       ads_mem : adsmem;

	 Begin { procedure set_dta_values   }

	    current_dta.dta_attr := wrd(current_dta.dta_data[22]);
	    ads_mem := ads current_dta.dta_time;
	    For i := 0 to 1 Do
	      ads_mem^[i] := wrd(current_dta.dta_data[i+23]);
	    ads_mem := ads current_dta.dta_date;
	    For i := 0 to 1 Do
	      ads_mem^[i] := wrd(current_dta.dta_data[i+25]);
	    ads_mem := ads current_dta.dta_size;
	    For i := 0 to 3 Do
	      ads_mem^[i] := wrd(current_dta.dta_data[i+27]);
	    i := 0;
	    Repeat
	      i := i + 1;
	      current_dta.dta_filename[i] := current_dta.dta_data[i+31-1];
	    Until (i >= 13) Or (current_dta.dta_filename[i] = end_of_string);
	    current_dta.dta_filename[0] := chr(i-1);

	 End;  { procedure set_dta_values   }

      {---------------------------------------------------------------------}
      {  Procedure GET_FIRST - find the first file having the file	    }
      {     attribute FILETYPE, and matching the SEARCH_STRING. If a	    }
      {     match is found, the DTA record CURRENT_DTA is updated.	    }
      {     The DOS interrupt 21h, function 4Eh Find First Matching file    }
      {     is used achieve this.					    }
      {---------------------------------------------------------------------}
      Procedure get_first (Var search_string : string;
			   Const filetype : integer;
			   Var current_dta : dta);
	 Begin { procedure get_first   }
	    regs.ax := byword(#4E, #00);
	    regs.cx := wrd(filetype);
	    regs.ds := (ADS search_string).s;
	    regs.dx := (ADS search_string).r;
	    intrp(#21, regs, regs);
	    set_dta_values(current_dta);
	 End;  { procedure get_first   }

      {---------------------------------------------------------------------}
      {  Procedure GET_NEXT  - find the next file having the file attribute }
      {     and matches the search string as set in the GET_FIRST procedure.}
      {     The criteria for the search was saved in the DTA record element }
      {     CURRENT_DTA.DTA_DATA. The DOS interrupt 21h, function 4Eh	    }
      {     Find Next Matching file is used to achieve this.		    }
      {---------------------------------------------------------------------}
      Procedure get_next (Var current_dta : dta);
	 Begin { procedure get_next   }
	    regs.ax := byword(#4F, #00);
	    intrp(#21, regs, regs);
	    set_dta_values(current_dta);
	 End;  { procedure get_next   }

      {---------------------------------------------------------------------}
      {  Procedure GET_FILES - is called once per subdirectory to look	    }
      {     for all files matching the search string, and having a file     }
      {     attribute byte indicating a normal file with the archive bit    }
      {     not set.							    }
      {---------------------------------------------------------------------}
      Procedure get_files (Const directory_string : string;
			   Var current_dta : dta);

	 Const
	       hrs_mask = 2#1111100000000000;	! hour mask for time
	       min_mask = 2#0000011111100000;	! minute mask for time
	       yrs_mask = 2#1111111000000000;	! year mask for date
	       mon_mask = 2#0000000111100000;	! month mask for date
	       day_mask = 2#0000000000011111;	! day mask for date

	 Var
	       current_string : where_lstring;

	 {------------------------------------------------------------------}
	 {  Function HOUR - convert hour miltary time to AM/PM time	    }
	 {------------------------------------------------------------------}
	 Function hour(Const military_hour : word) : word;
	 Begin	{ function hour }
	    If (military_hour = 12) OR (military_hour = 0)
	       then hour := 12
	       else hour := military_hour MOD 12;
	 End;	{ function hour }

	 Begin { procedure get_files   }
	    copylst(directory_string, current_string);
	    concat(current_string, check_string);
	    concat(current_string, end_of_string);
	    get_first(current_string, no_type, current_dta);
	    While (regs.flags And carry_flag_mask) <> carry_flag_mask
	       Do Begin   { write the file information out to OUTPUT }
		  Writeln(current_dta.dta_size:10, ' ',
			((current_dta.dta_date And mon_mask) DIV 32):2, '-',
		    chr(((current_dta.dta_date And day_mask)) DIV 10 + #30),
		    chr(((current_dta.dta_date And day_mask)) MOD 10 + #30), '-',
		       (((current_dta.dta_date And yrs_mask) DIV 512) + 80):2, ' ',
		   (hour((current_dta.dta_time And hrs_mask) DIV 2048)):2, ':',
		    chr(((current_dta.dta_time And min_mask) DIV 32) DIV 10 + #30),
		    chr(((current_dta.dta_time And min_mask) DIV 32) MOD 10 + #30), ' ',
			  time_ampm[ord(((current_dta.dta_time And hrs_mask) DIV 2048) DIV 12)], ' ',
			  directory_string,
			  current_dta.dta_filename);
		  get_next(current_dta);
	       End;	 { write the file information out to OUTPUT }
	    If (regs.ax <> no_more_files)
	       Then Writeln('problem with looking for ', current_string);
	 End;  { procedure get_files   }

   Begin { procedure lookup   }
      copylst(directory_string, current_string);
      concat(current_string, '*.*');
      concat(current_string, end_of_string);
      set_dta(current_dta);
      get_first(current_string, directory_type, current_dta);
      While (regs.flags And carry_flag_mask) <> carry_flag_mask
	 Do Begin
	    If (current_dta.dta_attr = directory_type) And
	       (current_dta.dta_filename[1] <> '.')
	       Then Begin
		  copylst(directory_string, newdirectory_string);
		  concat(newdirectory_string, current_dta.dta_filename);
		  concat(newdirectory_string, backslash);
		  lookup(newdirectory_string);
		  set_dta(current_dta);
	       End;
	    get_next(current_dta);
	 End;
      If (regs.ax = no_more_files)
	 Then get_files(directory_string, current_dta)
	 Else Writeln('problem with looking thru ', directory_string);
   End;  { procedure lookup   }


{---------------------------------------------------------------------------}
{  Main Program WHERE							    }
{---------------------------------------------------------------------------}
Begin { main program where   }
   If init_strings
      Then lookup(directory_string)
      Else Begin
	 writeln('Syntax:  where [starting_directory]filename.ext');
	 writeln;
	 writeln(' ':9,  'starting_directory - the sub-tree of the directory heirarchy');
	 writeln(' ':30, 'for the program to search');
	 writeln(' ':9,  'filename.ext       - standard DOS file description that may');
	 writeln(' ':30, 'include wildcards * and ?.');
	 writeln;
      End;
End.  { main program where   }
