home *** CD-ROM | disk | FTP | other *** search
/ ftp.update.uu.se / ftp.update.uu.se.2014.03.zip / ftp.update.uu.se / pub / rainbow / msdos / decus / RB120 / treedupl.pas < prev    next >
Pascal/Delphi Source File  |  1995-05-28  |  57KB  |  1,776 lines

  1. PROGRAM TreeDuplicate;                                                  {008}
  2.  
  3. CONST
  4.  
  5.     VersionIdentification = '2.0A';
  6.  
  7. (***********************************************************************
  8.  
  9.  
  10.     This software has been placed into the public domain by Digital
  11.                          Equipment Corporation.
  12.  
  13. DISCLAIMER:
  14.  
  15. The information herein is subject to change without  notice  and  should
  16. not be construed as a commitment by Digital Equipment Corporation.
  17.  
  18. Digital Equipment Corporation assumes no responsibility for the  use  or
  19. reliability  of  this  software.   This  software  is  provided "as is,"
  20. without any warranty of any kind, express or implied.  Digital Equipment
  21. Corporation  will  not  be liable in any event for any damages including
  22. any loss of data, profit, or savings, claims against  the  user  by  any
  23. other  party,  or  any other incidental or consequential damages arising
  24. out of the use of, or inability to use, this software, even  if  Digital
  25. Equipment Corporation is advised of the possibility of such damage.
  26.  
  27. DEFECT REPORTING AND SUGGESTIONS:
  28.  
  29. Please send reports of defects or suggestions for  improvement  directly
  30. to the author:
  31.  
  32.         Brian Hetrick
  33.         Digital Equipment Corporation
  34.         110 Spit Brook Road  ZKO1-3/J10
  35.         Nashua NH  03062-2698
  36.  
  37. Do NOT file a Software Performance Report on  this  software,  call  the
  38. Telephone  Support  Center regarding this software, contact your Digital
  39. Field Office  regarding  this  software,  or  use  any  other  mechanism
  40. provided for Digital's supported and warranted software.
  41.  
  42.  
  43. FACILITY:
  44.  
  45.     General user utilities
  46.  
  47. ABSTRACT:
  48.  
  49.     Duplicates one directory tree into another, attempting not  to  copy{008}
  50.     data if possible.  Intended for use as a backup utility using a DEC-
  51.     net-DOS virtual disk as the backup medium.
  52.  
  53. ENVIRONMENT:
  54.  
  55.     MS-DOS compiled with Borland International's TURBO Pascal
  56.  
  57. AUTHOR: Brian Hetrick, CREATION DATE: 27 May 1986.
  58.  
  59. MODIFICATION HISTORY:
  60.  
  61.         Brian Hetrick, 27-May-86: Version 1.0
  62.   000 - Original creation of module.
  63.         Released to Easynet 28-May-86.
  64.  
  65.         Brian Hetrick, 30-May-86: Version 1.1
  66.   001 - Attributes on directories were not updated.  Cause was that dir-
  67.         ectory modification date cannot be set, and IDAttrMatch  routine
  68.         was  testing  modification  date  for directories.  Main program
  69.         then attempted to replace the target directory, but  ReplaceFile
  70.         simply  returned.   Fix  is to have IDAttrMatch not look at mod-
  71.         ification dates for directories;  main program now  uses  Match-
  72.         File to update the attributes.
  73.   002 - Included program name and version in banner.
  74.         Released to Easynet 30-May-86
  75.  
  76.         Brian Hetrick, 31-May-86: Version 1.2
  77.   003 - Introduce hook for having files accumulate on target volume,  to
  78.         match hook for event logging.
  79.   004 - Introduce procedure to check for MS-DOS error, instead of always
  80.         explicitly checking low bit of returned Flags register.
  81.   005 - Introduce function to form name from  root  directory,  relative
  82.         directory,  and  file  in relative directory, rather than always
  83.         building directly from volume letter,  absolute  directory,  and
  84.         file  in absolute directory, as a hook for later permitting root
  85.         to be any directory.
  86.   006 - Avoid exteraneous copy in ExpandDirectory.
  87.   007 - Use only ASCII in message  text--replace  MCS  copyright  symbol
  88.         with (c) as program may run on IBM PCs without MCS.
  89.         Not released to Easynet as no user-visible improvements.
  90.  
  91.         Brian Hetrick, 03-Jun-86: Version 2.0
  92.   008 - Change name from VOLCOPY to TREEDUPL, as  now  will  copy  trees
  93.         rooted at other than the volume root directory.
  94.   009 - Use Bela Lubkin's public domain CommandLineArgument  routine  to
  95.         parse the command line.
  96.   010 - Deleted copyright notice as program will be submitted  to  DECUS
  97.         program library.
  98.         Released to Easynet on 3 June 1986.
  99.         Submitted to DECUS Program Library in September 1986.
  100.  
  101.         Brian Hetrick, 03-Dec-86: Version 2.0A
  102.   011 - Discovered error day before verification  master  received  from
  103.         DECUS Program Library.  Error was command qualifiers were parsed
  104.         incorrectly if abbreviated.
  105.  
  106. ***********************************************************************)
  107. {.PA}
  108. (*
  109.  *  INCLUDE FILES:
  110.  *)
  111.  
  112. {$I CLA.PAS}                                                            {009}
  113.  
  114. (*
  115.  *  LABEL DECLARATIONS:
  116.  *)
  117.  
  118. (*
  119.  *  CONSTANT DECLARATIONS:
  120.  *)
  121.  
  122. CONST
  123.  
  124.     DOSFunctionChangeAttributes     = $43;
  125.     DOSFunctionCloseFile            = $3E;
  126.     DOSFunctionCreateFile           = $3C;
  127.     DOSFunctionCreateSubDirectory   = $39;
  128.     DOSFunctionDeleteDirectoryEntry = $41;
  129.     DOSFunctionFindMatchFile        = $4E;
  130.     DOSFunctionGetDTA               = $2F;
  131.     DOSFunctionOpenFile             = $3D;
  132.     DOSFunctionReadFromFile         = $3F;
  133.     DOSFunctionRemoveDirectoryEntry = $3A;
  134.     DOSFunctionSetDTA               = $1A;
  135.     DOSFunctionSetFileDateTime      = $57;
  136.     DOSFunctionStepThroughDirectory = $4F;
  137.     DOSFunctionWriteToFile          = $40;
  138.  
  139. CONST
  140.  
  141.     DirectoryAttrMask  = $10;   { Attribute bit for directory          }
  142.     DirectoryEntrySize = 5;     { Base length of DirectoryEntry        }
  143.     FileEntrySize      = 20;    { Base length of FileEntry             }
  144.     FileSpecLength     = 12;    { Length of MS-DOS base name           }
  145.     PathSpecLength     = 127;   { Length of MS-DOS path specification  }
  146.     ReadOnlyAttrMask   = $01;   { Attribute bit for read-only          }
  147.  
  148. (*
  149.  *  TYPE DECLARATIONS:
  150.  *)
  151.  
  152. TYPE
  153.  
  154.     FileSpec = STRING [FileSpecLength];
  155.  
  156.     PathSpec = STRING [PathSpecLength];
  157.  
  158.     DirectoryEntryPtr = ^ DirectoryEntry;
  159.  
  160.     DirectoryEntry = RECORD
  161.         Next : DirectoryEntryPtr;
  162.         Name : PathSpec
  163.         END;
  164.  
  165.     FileEntryPtr = ^ FileEntry;
  166.  
  167.     FileEntry = RECORD
  168.         Next : FileEntryPtr;
  169.         Prev : FileEntryPtr;
  170.         Size : REAL;
  171.         Time : INTEGER;
  172.         Date : INTEGER;
  173.         Attr : BYTE;
  174.         Name : FileSpec
  175.         END;
  176.  
  177.     FileEntryQueue = RECORD
  178.         Head : FileEntryPtr;
  179.         Tail : FileEntryPtr
  180.         END;
  181.  
  182.     RegPack = RECORD
  183.         CASE INTEGER OF
  184.          0: (AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags : INTEGER);
  185.          1: (AL, AH, BL, BH, CL, CH, DL, DH            : BYTE)
  186.         END;
  187.  
  188. (*
  189.  *  OWN STORAGE:
  190.  *)
  191.  
  192. VAR
  193.  
  194.     Accumulating : BOOLEAN;
  195.     Logging      : BOOLEAN;
  196.     SourceRoot   : PathSpec;
  197.     TargetRoot   : PathSpec;
  198.  
  199. (*
  200.  *  TABLE OF CONTENTS:
  201.  *)
  202. {.PA}
  203. PROCEDURE ParseCommandLine;                                             {009}
  204.  
  205. (***********************************************************************{009}
  206.  
  207. FUNCTIONAL DESCRIPTION:                                                 {009}
  208.  
  209.     Parses the program command line.                                    {009}
  210.  
  211. FORMAL PARAMETERS:                                                      {009}
  212.  
  213.     None.                                                               {009}
  214.  
  215. RETURN VALUE:                                                           {009}
  216.  
  217.     None.                                                               {009}
  218.  
  219. IMPLICIT INPUTS:                                                        {009}
  220.  
  221.     None.                                                               {009}
  222.  
  223. IMPLICIT OUTPUTS:                                                       {009}
  224.  
  225.     Accumulating - The BOOLEAN telling whether files on the  target  are{009}
  226.         to be retained if they are not on the source.                   {009}
  227.     Logging - The BOOLEAN telling whether messages informing the user of{009}
  228.         actions taken are to be written to the standard output.         {009}
  229.     SourceRoot - The root directory of the source directory tree.       {009}
  230.     TargetRoot - The root directory of the target directory tree.       {009}
  231.  
  232. SIDE EFFECTS:
  233.  
  234.     Will Halt the program if an error in the command line is discovered.{009}
  235.  
  236. ***********************************************************************){009}
  237.  
  238.     VAR                                                                 {009}
  239.  
  240.         CharIndex    : INTEGER;                                         {009}
  241.         CommandValid : BOOLEAN;                                         {009}
  242.         SwitchSense  : BOOLEAN;                                         {009}
  243.         SwitchText   : BigString;                                       {009}
  244.  
  245.     FUNCTION IsPrefix (Str1 : BigString; Str2 : BigString) : BOOLEAN;   {009}
  246.  
  247.         VAR                                                             {009}
  248.  
  249.             CharIndex : INTEGER;                                        {009}
  250.  
  251.         BEGIN                                                           {009}
  252.  
  253.         IF Length (Str1) > Length (Str2)                                {009}
  254.         THEN                                                            {009}
  255.             IsPrefix := FALSE                                           {009}
  256.         ELSE                                                            {009}
  257.             IsPrefix := Str1 = Copy (Str2, 1, Length (Str1))            {011}
  258.  
  259.         END;                                                            {009}
  260.  
  261.     PROCEDURE UpCaseString (VAR Str : PathSpec);                        {009}
  262.  
  263.         VAR                                                             {009}
  264.  
  265.             CharIndex : INTEGER;                                        {009}
  266.  
  267.         BEGIN                                                           {009}
  268.  
  269.         FOR CharIndex := 1 TO Length (Str)                              {009}
  270.         DO                                                              {009}
  271.             Str [CharIndex] := UpCase (Str [CharIndex])                 {009}
  272.  
  273.         END;                                                            {009}
  274.  
  275.     BEGIN                                                               {009}
  276.  
  277.     (*                                                                  {009}
  278.      *  Get source and destination roots                                {009}
  279.      *)                                                                 {009}
  280.  
  281.     SourceRoot := CommandLineArgument                                   {009}
  282.         ('Source directory: ', '/', FALSE);                             {009}
  283.     UpCaseString (SourceRoot);                                          {009}
  284.     TargetRoot := CommandLineArgument                                   {009}
  285.         ('Destination directory: ', '/', FALSE);                        {009}
  286.     UpCaseString (TargetRoot);                                          {009}
  287.  
  288.     (*                                                                  {009}
  289.      *  Set defaults                                                    {009}
  290.      *)                                                                 {009}
  291.  
  292.     Accumulating := TRUE;                                               {009}
  293.     Logging      := TRUE;                                               {009}
  294.  
  295.     (*                                                                  {009}
  296.      *  Process switches                                                {009}
  297.      *)                                                                 {009}
  298.  
  299.     CommandValid := TRUE;                                               {009}
  300.     SwitchText := CommandLineArgument ('', '', TRUE);                   {009}
  301.     WHILE CommandValid AND (Length (SwitchText) > 0)                    {009}
  302.     DO                                                                  {009}
  303.         BEGIN                                                           {009}
  304.  
  305.         UpCaseString (SwitchText);                                      {009}
  306.  
  307.         (*                                                              {009}
  308.          *  Get rid of the leading slash                                {009}
  309.          *)                                                             {009}
  310.  
  311.         Delete (SwitchText, 1, 1);                                      {009}
  312.         IF Length (SwitchText) = 0                                      {009}
  313.         THEN                                                            {009}
  314.             BEGIN                                                       {009}
  315.             WriteLn ('Invalid switch: "/"');                            {009}
  316.             CommandValid := FALSE;                                      {009}
  317.             END;                                                        {009}
  318.  
  319.         IF CommandValid                                                 {009}
  320.         THEN                                                            {009}
  321.  
  322.             (*                                                          {009}
  323.              *  Check for "NO" prefix                                   {009}
  324.              *)                                                         {009}
  325.  
  326.             IF Copy (SwitchText, 1, 2) = 'NO'                           {009}
  327.             THEN                                                        {009}
  328.                 BEGIN                                                   {009}
  329.                 SwitchSense := FALSE;                                   {009}
  330.                 Delete (SwitchText, 1, 2);                              {009}
  331.                 IF Length (SwitchText) = 0                              {009}
  332.                 THEN                                                    {009}
  333.                     BEGIN                                               {009}
  334.                     WriteLn ('Invalid switch: "/NO"');                  {009}
  335.                     CommandValid := FALSE                               {009}
  336.                     END                                                 {009}
  337.                 END                                                     {009}
  338.             ELSE                                                        {009}
  339.                 SwitchSense := TRUE;                                    {009}
  340.  
  341.         IF CommandValid                                                 {009}
  342.         THEN                                                            {009}
  343.             BEGIN                                                       {009}
  344.  
  345.             (*                                                          {009}
  346.              *  Check for switch names                                  {009}
  347.              *)                                                         {009}
  348.  
  349.             IF IsPrefix (SwitchText, 'LOG')                             {009}
  350.             THEN                                                        {009}
  351.  
  352.                 Logging := SwitchSense                                  {009}
  353.  
  354.             ELSE IF IsPrefix (SwitchText, 'ACCUMULATE')                 {009}
  355.             THEN                                                        {009}
  356.  
  357.                 Accumulating := SwitchSense                             {009}
  358.  
  359.             ELSE                                                        {009}
  360.                 BEGIN                                                   {009}
  361.  
  362.                 Write ('Invalid switch: "/');                           {009}
  363.                 IF SwitchSense = FALSE                                  {009}
  364.                 THEN                                                    {009}
  365.                     Write ('NO');                                       {009}
  366.                 WriteLn (SwitchText, '"');                              {009}
  367.                 CommandValid := FALSE                                   {009}
  368.  
  369.                 END                                                     {009}
  370.  
  371.             END;                                                        {009}
  372.  
  373.         IF CommandValid                                                 {009}
  374.         THEN                                                            {009}
  375.             SwitchText := CommandLineArgument ('', '', TRUE)            {009}
  376.  
  377.         END;                                                            {009}
  378.  
  379.     IF NOT CommandValid                                                 {009}
  380.         THEN                                                            {009}
  381.         Halt                                                            {009}
  382.  
  383.     END;                                                                {009}
  384. {.PA}
  385. FUNCTION ErrorReturn                                                    {004}
  386.    (    Registers : RegPack) : BOOLEAN;                                 {004}
  387.  
  388. (***********************************************************************{004}
  389.  
  390. FUNCTIONAL DESCRIPTION:                                                 {004}
  391.  
  392.     Checks a set of registers returned from the MsDos procedure and  de-{004}
  393.     termines whether the function completed successfully.               {004}
  394.  
  395. FORMAL PARAMETERS:                                                      {004}
  396.  
  397.     Registers - A RegPack expression giving the register values returned{004}
  398.         by the MsDos procedure.                                         {004}
  399.  
  400. RETURN VALUE:                                                           {004}
  401.  
  402.     TRUE - The MsDos function failed.                                   {004}
  403.     FALSE - The MsDos function succeeded.                               {004}
  404.  
  405. IMPLICIT INPUTS:                                                        {004}
  406.  
  407.     None.                                                               {004}
  408.  
  409. IMPLICIT OUTPUTS:                                                       {004}
  410.  
  411.     None.                                                               {004}
  412.  
  413. SIDE EFFECTS:                                                           {004}
  414.  
  415.     None.                                                               {004}
  416.  
  417. ***********************************************************************){004}
  418.  
  419.     BEGIN                                                               {004}
  420.  
  421.     ErrorReturn := (Registers . Flags AND 1) <> 0                       {004}
  422.  
  423.     END;                                                                {004}
  424. {.PA}
  425. FUNCTION ConstructFileName                                              {005}
  426.    (    RootDirectory     : PathSpec;                                   {005}
  427.         RelativeDirectory : PathSpec;                                   {005}
  428.         FileName          : FileSpec) : PathSpec;                       {005}
  429.  
  430. (***********************************************************************{005}
  431.  
  432. FUNCTIONAL DESCRIPTION:                                                 {005}
  433.  
  434.     Constructs a path specification from a root  directory,  a  relative{005}
  435.     directory, and file name by concatenating these elements, separating{005}
  436.     them by backslash if there is not already a separator.              {005}
  437.  
  438. FORMAL PARAMETERS:                                                      {005}
  439.  
  440.     RootDirectory - A PathSpec expression giving the root  directory  of{005}
  441.         the eventual path specification.                                {005}
  442.     RelativeDirectory -  A  PathSpec  expression  giving  the  directory{005}
  443.         relative to RootDirectory of the eventual path specification.   {005}
  444.     FileName - A FileSpec expression giving the file name of  the  even-{005}
  445.         tual path specification.                                        {005}
  446.  
  447. RETURN VALUE:                                                           {005}
  448.  
  449.     The resultant path specification.                                   {005}
  450.  
  451. IMPLICIT INPUTS:                                                        {005}
  452.  
  453.     None.                                                               {005}
  454.  
  455. IMPLICIT OUTPUTS:                                                       {005}
  456.  
  457.     None.                                                               {005}
  458.  
  459. SIDE EFFECTS:                                                           {005}
  460.  
  461.     None.                                                               {005}
  462.  
  463. ***********************************************************************){005}
  464.  
  465.     CONST                                                               {005}
  466.         Separator : SET OF CHAR = [':', '\', '/'];                      {005}
  467.  
  468.     VAR                                                                 {005}
  469.         TempName : PathSpec;                                            {005}
  470.  
  471.     BEGIN                                                               {005}
  472.  
  473.     TempName := RootDirectory;                                          {005}
  474.  
  475.     IF (Length (TempName) > 0) AND (Length (RelativeDirectory) > 0)     {005}
  476.     THEN                                                                {005}
  477.         IF NOT (TempName [Length (TempName)] IN Separator)              {005}
  478.         THEN                                                            {005}
  479.             Insert ('\', TempName, Length (TempName) + 1);              {005}
  480.  
  481.     Insert (RelativeDirectory, TempName, Length (TempName) + 1);        {005}
  482.  
  483.     IF (Length (TempName) > 0) AND (Length (FileName) > 0)              {005}
  484.     THEN                                                                {005}
  485.         IF NOT (TempName [Length (TempName)] IN Separator)              {005}
  486.         THEN                                                            {005}
  487.             Insert ('\', TempName, Length (TempName) + 1);              {005}
  488.  
  489.     Insert (FileName, TempName, Length (TempName) + 1);                 {005}
  490.  
  491.     ConstructFileName := TempName                                       {005}
  492.  
  493.     END;                                                                {005}
  494. {.PA}
  495. PROCEDURE ExpandDirectory
  496.    (    RootDirectory     : PathSpec;                                   {005}
  497.         DirectoryToExpand : DirectoryEntryPtr;
  498.     VAR FileQueue         : FileEntryQueue);
  499.  
  500. (***********************************************************************
  501.  
  502. FUNCTIONAL DESCRIPTION:
  503.  
  504.     Finds and lexicographically sorts the names of all files  in  a  di-
  505.     rectory
  506.  
  507. FORMAL PARAMETERS:
  508.  
  509.     RootDirectory - A PathSpec expression giving the root  directory  to{005}
  510.         which DirectoryName is a relative directory.                    {005}
  511.     DirectoryName - A DirectoryEntryPtr expression pointing to  the  Di-
  512.         recoryEntry describing the directory to be examined
  513.     FileQueue - A FileEntryQueue object which is modified to point to  a
  514.         newly created queue of the names of files in the directory
  515.  
  516. RETURN VALUE:
  517.  
  518.     None.
  519.  
  520. IMPLICIT INPUTS:
  521.  
  522.     None.
  523.  
  524. IMPLICIT OUTPUTS:
  525.  
  526.     None.
  527.  
  528. SIDE EFFECTS:
  529.  
  530.     Modifies and resets the DTA.  This should be observable only by  in-
  531.     terrupt routines.
  532.  
  533.     Dynamically allocates storage with GetMem.
  534.  
  535. ***********************************************************************)
  536.  
  537.     VAR
  538.  
  539.         FoundPos       : BOOLEAN;
  540.         FileNameLength : INTEGER;
  541.         FileName       : FileSpec;
  542.         MSDOSBlock     : RECORD
  543.             Reserved   : ARRAY [1..21] OF BYTE;
  544.             Attribute  : BYTE;
  545.             Time       : INTEGER;
  546.             Date       : INTEGER;
  547.             SizeLow    : INTEGER;
  548.             SizeHigh   : INTEGER;
  549.             Name       : ARRAY [1..13] OF CHAR
  550.             END;
  551.         NextFile       : FileEntryPtr;
  552.         OldDTA         : ^ CHAR;
  553.         PrevFile       : FileEntryPtr;
  554.         Registers      : RegPack;
  555.         SearchSpec     : PathSpec;
  556.         ThisFile       : FileEntryPtr;
  557.  
  558.     BEGIN
  559.  
  560.     (*
  561.      *  Initialize the file queue
  562.      *)
  563.  
  564.     FileQueue . Head := NIL;
  565.     FileQueue . Tail := NIL;
  566.  
  567.     (*
  568.      *  Save the old DTA
  569.      *)
  570.  
  571.     Registers.AH := DOSFunctionGetDTA;
  572.     MsDos (Registers);
  573.     OldDTA := Ptr (Registers.ES, Registers.BX);
  574.  
  575.     (*
  576.      *  Set the DTA to be the MS-DOS information block
  577.      *)
  578.  
  579.     Registers.AH := DOSFunctionSetDTA;
  580.     Registers.DS := Seg (MSDOSBlock);
  581.     Registers.DX := Ofs (MSDOSBlock);
  582.     MsDos (Registers);
  583.  
  584.     (*
  585.      *  Find the contents of the directory
  586.      *)
  587.  
  588.     SearchSpec := ConstructFileName (RootDirectory,                     {005}
  589.         DirectoryToExpand ^. Name, '*.*');                              {005}
  590.     SearchSpec [Length (SearchSpec) + 1] := #$00;                       {005}
  591.  
  592.     Registers.AH := DOSFunctionFindMatchFile;
  593.     Registers.DS := Seg (SearchSpec [1]);
  594.     Registers.DX := Ofs (SearchSpec [1]);
  595.     Registers.CX := $37;
  596.     MsDos (Registers);
  597.  
  598.     WHILE NOT ErrorReturn (Registers)                                   {004}
  599.     DO
  600.         BEGIN
  601.  
  602.         (*
  603.          *  Extract the file name
  604.          *)
  605.  
  606.         FileNameLength := 1;
  607.         WHILE MSDOSBlock . Name [FileNameLength] <> #$00
  608.         DO
  609.             FileNameLength := FileNameLength + 1;
  610.         FileNameLength := FileNameLength - 1;
  611.         FileName := Copy (MSDOSBlock . Name, 1, FileNameLength);
  612.  
  613.         (*
  614.          *  Ignore relative directories
  615.          *)
  616.  
  617.         IF (FileName <> '.') AND (FileName <> '..')
  618.         THEN
  619.             BEGIN
  620.  
  621.             (*
  622.              *  Create a file entry for this file
  623.              *)
  624.  
  625.             GetMem (ThisFile, FileEntrySize + FileNameLength);
  626.  
  627.             ThisFile ^. Attr := MSDOSBlock . Attribute;
  628.             ThisFile ^. Time := MSDOSBlock . Time;
  629.             ThisFile ^. Date := MSDOSBlock . Date;
  630.             IF MSDOSBlock . SizeHigh < 0
  631.             THEN
  632.                 ThisFile ^. Size := MSDOSBlock . SizeHigh + 65536.0
  633.             ELSE
  634.                 ThisFile ^. Size := MSDOSBlock . SizeHigh;
  635.             ThisFile ^. Size := ThisFile ^. Size * 65536.0;
  636.             IF MSDOSBlock . SizeLow < 0
  637.             THEN
  638.                 ThisFile ^. Size := ThisFile ^. Size +
  639.                     MSDOSBlock . SizeLow + 65536.0
  640.             ELSE
  641.                 ThisFile ^. Size := ThisFile ^. Size +
  642.                     MSDOSBlock . SizeLow;
  643.  
  644.             ThisFile ^. Name := FileName;                               {006}
  645.  
  646.             (*
  647.              *  Insert the newly allocated entry into the sorted queue
  648.              *)
  649.  
  650.             NextFile := FileQueue . Head;
  651.             PrevFile := NIL;
  652.             FoundPos := FALSE;
  653.             WHILE NOT FoundPos
  654.             DO
  655.                 BEGIN
  656.                 IF NextFile = NIL
  657.                 THEN
  658.                     FoundPos := TRUE
  659.                 ELSE
  660.                     IF NextFile ^. Name > ThisFile ^. Name
  661.                     THEN
  662.                         FoundPos := TRUE
  663.                     ELSE
  664.                         BEGIN
  665.                         PrevFile := NextFile;
  666.                         NextFile := NextFile ^. Next
  667.                         END
  668.                 END;
  669.  
  670.             ThisFile ^. Prev := PrevFile;
  671.             IF PrevFile = NIL
  672.             THEN
  673.                 FileQueue . Head := ThisFile
  674.             ELSE
  675.                 PrevFile ^. Next := ThisFile;
  676.             ThisFile ^. Next := NextFile;
  677.             IF NextFile = NIL
  678.             THEN
  679.                 FileQueue . Tail := ThisFile
  680.             ELSE
  681.                 NextFile ^. Prev := ThisFile
  682.  
  683.             END;
  684.  
  685.         (*
  686.          *  Get the next file in the directory
  687.          *)
  688.  
  689.         Registers.AH := DOSFunctionStepThroughDirectory;
  690.         MsDos (Registers)
  691.  
  692.         END;
  693.  
  694.     (*
  695.      *  The directory has been expanded.  Reset the DTA
  696.      *)
  697.  
  698.     Registers.AH := DOSFunctionSetDTA;
  699.     Registers.DS := Seg (OldDTA ^);
  700.     Registers.DX := Ofs (OldDTA ^);
  701.     MsDos (Registers)
  702.  
  703.     END;
  704. {.PA}
  705. PROCEDURE ExtractDirectories
  706.    (    CurrentDirectory : DirectoryEntryPtr;
  707.         FileQueue        : FileEntryQueue;
  708.     VAR DirectoryList    : DirectoryEntryPtr);
  709.  
  710. (***********************************************************************
  711.  
  712. FUNCTIONAL DESCRIPTION:
  713.  
  714.     Examines the contents of the current directory,  extracts  the  full
  715.     path  names  of  all  subdirectories,  and places these subdirectory
  716.     names on a queue of pending directories.
  717.  
  718. FORMAL PARAMETERS:
  719.  
  720.     CurrentDirectory - A DirectoryEntryPtr pointing to a  DirectoryEntry
  721.         describing the directory whose contents are given by FileQueue.
  722.     FileQueue - A FileEntryQueue pointing to a list of FileEntry objects
  723.         describing  the  files  in  the  directory described by Current-
  724.         Directory.
  725.     DirectoryList - A DirectoryEntryPtr pointing to a list of Directory-
  726.         Entry objects.  New DirectoryEntry objects are created  for  the
  727.         subdirectories found on the list of FileEntry objects pointed to
  728.         by FileQueue, and are placed onto this list.
  729.  
  730. RETURN VALUE:
  731.  
  732.     None.
  733.  
  734. IMPLICIT INPUTS:
  735.  
  736.     None.
  737.  
  738. IMPLICIT OUTPUTS:
  739.  
  740.     None.
  741.  
  742. SIDE EFFECTS:
  743.  
  744.     Dynamically allocates storage with GetMem.
  745.  
  746. ***********************************************************************)
  747.  
  748.     VAR
  749.  
  750.         DirectoryText : PathSpec;
  751.         ThisDirectory : DirectoryEntryPtr;
  752.         ThisEntry     : FileEntryPtr;
  753.  
  754.     BEGIN
  755.  
  756.     (*
  757.      *  Scan list backwards, looking for directories
  758.      *)
  759.  
  760.     ThisEntry := FileQueue . Tail;
  761.  
  762.     WHILE ThisEntry <> NIL
  763.     DO
  764.         BEGIN
  765.  
  766.         IF (ThisEntry ^. Attr AND DirectoryAttrMask) <> 0
  767.         THEN
  768.             BEGIN
  769.  
  770.             (*
  771.              *  This entry is a directory.
  772.              *)
  773.  
  774.             DirectoryText :=                                            {005}
  775.                 ConstructFileName (CurrentDirectory ^. Name,            {005}
  776.                     ThisEntry ^. Name, '');                             {005}
  777.             GetMem (ThisDirectory, DirectoryEntrySize +
  778.                 Length (DirectoryText));
  779.             ThisDirectory ^. Next := DirectoryList;
  780.             ThisDirectory ^. Name := DirectoryText;
  781.             DirectoryList := ThisDirectory
  782.  
  783.             END;
  784.  
  785.         ThisEntry := ThisEntry ^. Prev
  786.  
  787.         END
  788.  
  789.     END;
  790. {.PA}
  791. PROCEDURE AdvanceFile
  792.    (VAR FileQueue : FileEntryQueue);
  793.  
  794. (***********************************************************************
  795.  
  796. FUNCTIONAL DESCRIPTION:
  797.  
  798.     Deletes the first item on a file entry queue.
  799.  
  800. FORMAL PARAMETERS:
  801.  
  802.     FileQueue - A FileEntryQueue object pointing to a queue of FileEntry
  803.         objects.  The item pointed at by the Head  pointer  is  deleted,
  804.         and the queue is adjusted for this deletion.
  805.  
  806. RETURN VALUE:
  807.  
  808.     None.
  809.  
  810. IMPLICIT INPUTS:
  811.  
  812.     None.
  813.  
  814. IMPLICIT OUTPUTS:
  815.  
  816.     None.
  817.  
  818. SIDE EFFECTS:
  819.  
  820.     Dynamically frees storage with FreeMem.
  821.  
  822. ***********************************************************************)
  823.  
  824.     VAR
  825.         ThisEntry : FileEntryPtr;
  826.  
  827.     BEGIN
  828.  
  829.     (*
  830.      *  Ensure that there is an item to delete
  831.      *)
  832.  
  833.     ThisEntry := FileQueue . Head;
  834.     IF ThisEntry <> NIL
  835.     THEN
  836.         BEGIN
  837.  
  838.         (*
  839.          *  There is.  First, relink the queue around the item
  840.          *)
  841.  
  842.         FileQueue . Head := ThisEntry ^. Next;
  843.         IF FileQueue . Head = NIL
  844.         THEN
  845.             FileQueue . Tail := NIL
  846.         ELSE
  847.             FileQueue . Head ^. Prev := NIL;
  848.  
  849.         (*
  850.          *  Now free the item's storage
  851.          *)
  852.  
  853.         FreeMem (ThisEntry, FileEntrySize + Length (ThisEntry ^. Name))
  854.  
  855.         END
  856.  
  857.     END;
  858. {.PA}
  859. FUNCTION IDAttrMatch
  860.    (    FileEntry1 : FileEntryPtr;
  861.         FileEntry2 : FileEntryPtr) : BOOLEAN;
  862.  
  863. (***********************************************************************
  864.  
  865. FUNCTIONAL DESCRIPTION:
  866.  
  867.     Determine whether two files are putatively identical.
  868.  
  869.     Two files are considered to be identical if they have the same name,
  870.     same directory attribute, and, in the case of  non-directory  files,{001}
  871.     the  same  creation/modification  date  and  time and size.  NO COM-{001}
  872.     PARISON OF THE FILE CONTENTS IS MADE.
  873.  
  874. FORMAL PARAMETERS:
  875.  
  876.     File1Desc - A FileEntryPtr pointing to a FileEntry object describing
  877.         the first of the two files.
  878.     File2Desc - A FileEntryPtr pointing to a FileEntry object describing
  879.         the second of the two files.
  880.  
  881. RETURN VALUE:
  882.  
  883.     TRUE - The files are considered to be identical.
  884.     FALSE - The files are not considered to be identical.
  885.  
  886. IMPLICIT INPUTS:
  887.  
  888.     None.
  889.  
  890. IMPLICIT OUTPUTS:
  891.  
  892.     None.
  893.  
  894. SIDE EFFECTS:
  895.  
  896.     None.
  897.  
  898. ***********************************************************************)
  899.  
  900.     VAR
  901.  
  902.         Difference : BOOLEAN;
  903.  
  904.     BEGIN
  905.  
  906.     Difference := FALSE;
  907.  
  908.     IF FileEntry1 ^. Name <> FileEntry2 ^. Name
  909.     THEN
  910.         Difference := TRUE;
  911.  
  912.     IF (FileEntry1 ^. Attr AND DirectoryAttrMask) <>
  913.        (FileEntry2 ^. Attr AND DirectoryAttrMask)
  914.     THEN
  915.         Difference := TRUE;
  916.  
  917.     IF (FileEntry1 ^. Attr AND DirectoryAttrMask) = 0                   {001}
  918.     THEN                                                                {001}
  919.         BEGIN                                                           {001}
  920.  
  921.         IF FileEntry1 ^. Time <> FileEntry2 ^. Time
  922.         THEN
  923.             Difference := TRUE;
  924.  
  925.         IF FileEntry1 ^. Date <> FileEntry2 ^. Date
  926.         THEN
  927.             Difference := TRUE;
  928.  
  929.         IF FileEntry1 ^. Size <> FileEntry2 ^. Size
  930.         THEN
  931.             Difference := TRUE                                          {001}
  932.  
  933.         END;                                                            {001}
  934.  
  935.     IDAttrMatch := NOT Difference
  936.  
  937.     END;
  938. {.PA}
  939. PROCEDURE DeleteFile
  940.    (    RootDirectory    : PathSpec;                                    {005}
  941.         CurrentDirectory : DirectoryEntryPtr;
  942.         FileInfo         : FileEntryPtr);
  943.  
  944. (***********************************************************************
  945.  
  946. FUNCTIONAL DESCRIPTION:
  947.  
  948.     Deletes a single file or an entire subdirectory tree.  When deleting
  949.     an entire subdirectory tree, recurses to the depth of the subdirect-
  950.     ory tree.
  951.  
  952. FORMAL PARAMETERS:
  953.  
  954.     RootDirectory - A PathSpec expression giving the root  directory  to{005}
  955.         which DirectoryName is a relative directory.                    {005}
  956.     CurrentDirectory - A DirectoryEntryPtr expression pointing to a  Di-
  957.         rectoryEntry  object  describing the directory in which the file
  958.         resides.
  959.     FileInformation - A FileEntryPtr expression pointing to a  FileEntry
  960.         object describing the file to be deleted.
  961.  
  962. RETURN VALUE:
  963.  
  964.     None.
  965.  
  966. IMPLICIT INPUTS:
  967.  
  968.     Logging - The BOOLEAN telling whether event logging is currently on.
  969.  
  970. IMPLICIT OUTPUTS:
  971.  
  972.     None.
  973.  
  974. SIDE EFFECTS:
  975.  
  976.     None.
  977.  
  978. ***********************************************************************)
  979.  
  980.     VAR
  981.  
  982.         NewDirEntry : DirectoryEntry;
  983.         Registers   : RegPack;
  984.         SubDirQueue : FileEntryQueue;
  985.  
  986.     (*
  987.      *  A DirectoryEntry is used in place of a PathSpec for the name  of
  988.      *  the  single file to be deleted, in order to minimize local stor-
  989.      *  age requirements.  This is important only  as  this  routine  is
  990.      *  recursive.
  991.      *)
  992.  
  993.     BEGIN
  994.  
  995.     (*
  996.      *  If the "file" to be deleted is a directory,  delete  the  entire
  997.      *  tree rooted there
  998.      *)
  999.  
  1000.     IF (FileInfo ^. Attr AND DirectoryAttrMask) <> 0
  1001.     THEN
  1002.         BEGIN
  1003.  
  1004.         (*
  1005.          *  Construct a directory entry for the directory
  1006.          *)
  1007.  
  1008.         NewDirEntry . Name :=                                           {005}
  1009.             ConstructFileName (CurrentDirectory ^. Name,                {005}
  1010.                 FileInfo ^. Name, '');                                  {005}
  1011.  
  1012.         (*
  1013.          *  Get contents of directory
  1014.          *)
  1015.  
  1016.         ExpandDirectory (RootDirectory, Addr (NewDirEntry),             {005}
  1017.             SubDirQueue);
  1018.  
  1019.         (*
  1020.          *  Recursively delete the contents of the directory
  1021.          *)
  1022.  
  1023.         WHILE SubDirQueue . Head <> NIL
  1024.         DO
  1025.             BEGIN
  1026.  
  1027.             DeleteFile (RootDirectory, Addr (NewDirEntry),              {005}
  1028.                 SubDirQueue . Head);
  1029.             AdvanceFile (SubDirQueue)
  1030.  
  1031.             END
  1032.  
  1033.         END;
  1034.  
  1035.     (*
  1036.      *  Generate the file specification
  1037.      *)
  1038.  
  1039.     NewDirEntry . Name := ConstructFileName (RootDirectory,             {005}
  1040.         CurrentDirectory ^. Name, FileInfo ^. Name);                    {005}
  1041.  
  1042.     (*
  1043.      *  Put on the trailing NUL for MS-DOS calls
  1044.      *)
  1045.  
  1046.     NewDirEntry . Name [Length (NewDirEntry . Name) + 1] := #$00;
  1047.  
  1048.     (*
  1049.      *  The Read-Only attribute implies  that  the  file  cannot  be
  1050.      *  deleted.  If the Read-Only attribute is on, turn it off.
  1051.      *)
  1052.  
  1053.     IF (FileInfo ^. Attr AND ReadOnlyAttrMask) <> 0
  1054.     THEN
  1055.         BEGIN
  1056.  
  1057.         Registers . AH := DOSFunctionChangeAttributes;
  1058.         Registers . DS := Seg (NewDirEntry . Name [1]);
  1059.         Registers . DX := Ofs (NewDirEntry . Name [1]);
  1060.         Registers . CX := FileInfo ^. Attr AND NOT                      {001}
  1061.             (ReadOnlyAttrMask OR DirectoryAttrMask);                    {001}
  1062.         Registers . AL := 1;
  1063.         MsDos (Registers);
  1064.         IF ErrorReturn (Registers)                                      {004}
  1065.         THEN
  1066.             BEGIN
  1067.  
  1068.             WriteLn ('Cannot change attributes on ', NewDirEntry . Name);
  1069.             Halt
  1070.  
  1071.             END
  1072.  
  1073.         END;
  1074.  
  1075.     (*
  1076.      *  Actually delete the file
  1077.      *)
  1078.  
  1079.     IF (FileInfo ^. Attr AND DirectoryAttrMask) <> 0
  1080.     THEN
  1081.         Registers . AH := DOSFunctionRemoveDirectoryEntry
  1082.     ELSE
  1083.         Registers . AH := DOSFunctionDeleteDirectoryEntry;
  1084.  
  1085.     Registers . DS := Seg (NewDirEntry . Name [1]);
  1086.     Registers . DX := Ofs (NewDirEntry . Name [1]);
  1087.     MsDos (Registers);
  1088.     IF ErrorReturn (Registers)                                          {004}
  1089.     THEN
  1090.         BEGIN
  1091.  
  1092.         Write ('Cannot delete ');
  1093.         IF (FileInfo ^. Attr AND DirectoryAttrMask) <> 0
  1094.         THEN
  1095.             Write ('directory ');
  1096.         WriteLn (NewDirEntry . Name);
  1097.         Halt
  1098.  
  1099.         END;
  1100.  
  1101.     (*
  1102.      *  If logging is on, note the deletion
  1103.      *)
  1104.  
  1105.     IF Logging
  1106.     THEN
  1107.         WriteLn ('Deleted ', NewDirEntry . Name)
  1108.  
  1109.     END;
  1110. {.PA}
  1111. PROCEDURE CopyFile
  1112.    (    SourceRootDir    : PathSpec;                                    {005}
  1113.         CurrentDirectory : DirectoryEntryPtr;
  1114.         FileInfo         : FileEntryPtr;
  1115.         DestinRootDir    : PathSpec);                                   {005}
  1116.  
  1117. (***********************************************************************
  1118.  
  1119. FUNCTIONAL DESCRIPTION:
  1120.  
  1121.     Duplicates the source file on the destination.  This duplication al-{005}
  1122.     ways includes relative directory and file name, and file attributes.{005}
  1123.     In the case of non-directory files, this also includes  modification{005}
  1124.     date and time, and contents.                                        {005}
  1125.  
  1126. FORMAL PARAMETERS:
  1127.  
  1128.     SourceRootDirectory - A PathSpec expression giving the root  direct-{005}
  1129.         ory  to  which  DirectoryName  is  a  relative directory for the{005}
  1130.         source file.                                                    {005}
  1131.     CurrentDirectory - A DirectoryEntryPtr pointing to a  DirectoryEntry
  1132.         object describing the directory in which the source file resides
  1133.         and in which the target file is to reside.
  1134.     FileInfo - A FileEntryPtr pointing to a FileEntry object  describing
  1135.         the source file, and which is to describe the target file.
  1136.     TargetRootDirectory - A PathSpec expression giving the root  direct-{005}
  1137.         ory  to which DirectoryName is a relative directory for the tar-{005}
  1138.         get file.                                                       {005}
  1139.  
  1140. RETURN VALUE:
  1141.  
  1142.     None.
  1143.  
  1144. IMPLICIT INPUTS:
  1145.  
  1146.     Logging - The BOOLEAN telling whether event logging is currently on.
  1147.  
  1148. IMPLICIT OUTPUTS:
  1149.  
  1150.     None.
  1151.  
  1152. SIDE EFFECTS:
  1153.  
  1154.     None.
  1155.  
  1156. ***********************************************************************)
  1157.  
  1158.     CONST
  1159.  
  1160.         BufferSize = 1024;
  1161.  
  1162.     VAR
  1163.  
  1164.         CopyBuffer     : ARRAY [1..BufferSize] OF CHAR;
  1165.         DestinHandle   : INTEGER;
  1166.         DestinName     : PathSpec;
  1167.         Registers      : RegPack;
  1168.         SourceHandle   : INTEGER;
  1169.         SourceName     : PathSpec;
  1170.         TransferSize   : INTEGER;
  1171.  
  1172.     BEGIN
  1173.  
  1174.     (*
  1175.      *  Construct the source and destination file names
  1176.      *)
  1177.  
  1178.     SourceName := ConstructFileName (SourceRootDir,                     {005}
  1179.         CurrentDirectory ^. Name, FileInfo ^. Name);                    {005}
  1180.     DestinName := ConstructFileName (DestinRootDir,                     {005}
  1181.         CurrentDirectory ^. Name, FileInfo ^. Name);                    {005}
  1182.     SourceName [Length (SourceName) + 1] := #$00;
  1183.     DestinName [Length (DestinName) + 1] := #$00;
  1184.  
  1185.     (*
  1186.      *  Now copy the files
  1187.      *)
  1188.  
  1189.     IF (FileInfo ^. Attr AND DirectoryAttrMask) <> 0
  1190.     THEN
  1191.         BEGIN
  1192.  
  1193.         (*
  1194.          *  For a directory, simply create the target directory
  1195.          *)
  1196.  
  1197.         Registers . AH := DOSFunctionCreateSubDirectory;
  1198.         Registers . DS := Seg (DestinName [1]);
  1199.         Registers . DX := Ofs (DestinName [1]);
  1200.         MsDos (Registers);
  1201.         IF ErrorReturn (Registers)                                      {004}
  1202.         THEN
  1203.             BEGIN
  1204.  
  1205.             WriteLn ('Cannot create directory ', DestinName);
  1206.             Halt
  1207.  
  1208.             END
  1209.  
  1210.         END
  1211.     ELSE
  1212.         BEGIN
  1213.  
  1214.         (*
  1215.          *  For a file, copy the data and set the creation date and time
  1216.          *)
  1217.  
  1218.         Registers . AH := DOSFunctionOpenFile;
  1219.         Registers . AL := 0;
  1220.         Registers . DS := Seg (SourceName [1]);
  1221.         Registers . DX := Ofs (SourceName [1]);
  1222.         MsDos (Registers);
  1223.         IF ErrorReturn (Registers)                                      {004}
  1224.         THEN
  1225.             BEGIN
  1226.  
  1227.             WriteLn ('Cannot open ', SourceName);
  1228.             Halt
  1229.  
  1230.             END;
  1231.  
  1232.         SourceHandle := Registers . AX;
  1233.  
  1234.         Registers . AH := DOSFunctionCreateFile;
  1235.         Registers . CX := 0;
  1236.         Registers . DS := Seg (DestinName [1]);
  1237.         Registers . DX := Ofs (DestinName [1]);
  1238.         MsDos (Registers);
  1239.         IF ErrorReturn (Registers)                                      {004}
  1240.         THEN
  1241.             BEGIN
  1242.  
  1243.             WriteLn ('Cannot create ', DestinName);
  1244.             Halt
  1245.  
  1246.             END;
  1247.  
  1248.         DestinHandle := Registers . AX;
  1249.  
  1250.         Registers . AH := DOSFunctionReadFromFile;
  1251.         Registers . BX := SourceHandle;
  1252.         Registers . CX := BufferSize;
  1253.         Registers . DS := Seg (CopyBuffer);
  1254.         Registers . DX := Ofs (CopyBuffer);
  1255.         MsDos (Registers);
  1256.         IF ErrorReturn (Registers)                                      {004}
  1257.         THEN
  1258.             BEGIN
  1259.  
  1260.             WriteLn ('Cannot read ', SourceName);
  1261.             Halt
  1262.  
  1263.             END;
  1264.  
  1265.         TransferSize := Registers . AX;
  1266.  
  1267.         WHILE TransferSize > 0
  1268.         DO
  1269.             BEGIN
  1270.  
  1271.             Registers . AH := DOSFunctionWriteToFile;
  1272.             Registers . BX := DestinHandle;
  1273.             Registers . CX := TransferSize;
  1274.             Registers . DS := Seg (CopyBuffer);
  1275.             Registers . DX := Ofs (CopyBuffer);
  1276.             MsDos (Registers);
  1277.             IF ErrorReturn (Registers) OR                               {004}
  1278.                (TransferSize <> Registers . AX)
  1279.             THEN
  1280.                 BEGIN
  1281.  
  1282.                 WriteLn ('Cannot write ', DestinName);
  1283.                 Halt
  1284.  
  1285.                 END;
  1286.  
  1287.             Registers . AH := DOSFunctionReadFromFile;
  1288.             Registers . BX := SourceHandle;
  1289.             Registers . CX := BufferSize;
  1290.             Registers . DS := Seg (CopyBuffer);
  1291.             Registers . DX := Ofs (CopyBuffer);
  1292.             MsDos (Registers);
  1293.             IF ErrorReturn (Registers)                                  {004}
  1294.             THEN
  1295.                 BEGIN
  1296.  
  1297.                 WriteLn ('Cannot read ', SourceName);
  1298.                 Halt
  1299.  
  1300.                 END;
  1301.  
  1302.             TransferSize := Registers . AX
  1303.  
  1304.             END;
  1305.  
  1306.         (*
  1307.          *  The data have been copied.  Set the creation date  and  time
  1308.          *  to be that of the source file.
  1309.          *)
  1310.  
  1311.         Registers . AH := DOSFunctionSetFileDateTime;
  1312.         Registers . AL := 1;
  1313.         Registers . BX := DestinHandle;
  1314.         Registers . CX := FileInfo ^. Time;
  1315.         Registers . DX := FileInfo ^. Date;
  1316.         MsDos (Registers);
  1317.         IF ErrorReturn (Registers)                                      {004}
  1318.         THEN
  1319.             BEGIN
  1320.  
  1321.             WriteLn ('Cannot set date and time on ', DestinName);
  1322.             Halt
  1323.  
  1324.             END;
  1325.  
  1326.         (*
  1327.          *  Close the source and destination files
  1328.          *)
  1329.  
  1330.         Registers . AH := DOSFunctionCloseFile;
  1331.         Registers . BX := SourceHandle;
  1332.         MsDos (Registers);
  1333.         IF ErrorReturn (Registers)                                      {004}
  1334.         THEN
  1335.             BEGIN
  1336.  
  1337.             WriteLn ('Cannot close ', SourceName);
  1338.             Halt
  1339.  
  1340.             END;
  1341.  
  1342.         Registers . AH := DOSFunctionCloseFile;
  1343.         Registers . BX := DestinHandle;
  1344.         MsDos (Registers);
  1345.         IF ErrorReturn (Registers)                                      {004}
  1346.         THEN
  1347.             BEGIN
  1348.  
  1349.             WriteLn ('Cannot close ', DestinName);
  1350.             Halt
  1351.  
  1352.             END
  1353.  
  1354.         END;
  1355.  
  1356.     (*
  1357.      *  Ensure that the source and target attributes match
  1358.      *)
  1359.  
  1360.     IF (FileInfo ^. Attr AND NOT DirectoryAttrMask) <> 0
  1361.     THEN
  1362.         BEGIN
  1363.  
  1364.         Registers . AH := DOSFunctionChangeAttributes;
  1365.         Registers . AL := 1;
  1366.         Registers . DS := Seg (DestinName [1]);
  1367.         Registers . DX := Ofs (DestinName [1]);
  1368.         Registers . CX := FileInfo ^. Attr;
  1369.         MsDos (Registers);
  1370.         IF ErrorReturn (Registers)                                      {004}
  1371.         THEN
  1372.             BEGIN
  1373.  
  1374.             WriteLn ('Cannot set attributes for ', DestinName);
  1375.             Halt
  1376.  
  1377.             END
  1378.  
  1379.         END;
  1380.  
  1381.     (*
  1382.      *  If necessary, log the copying
  1383.      *)
  1384.  
  1385.     IF Logging
  1386.     THEN
  1387.         IF (FileInfo ^. Attr AND DirectoryAttrMask) <> 0
  1388.         THEN
  1389.             WriteLn ('Created directory ', DestinName)
  1390.         ELSE
  1391.             WriteLn ('Copied ', SourceName, ' to ', DestinName)
  1392.  
  1393.     END;
  1394. {.PA}
  1395. PROCEDURE ReplaceFile
  1396.    (    SourceRootDir    : PathSpec;                                    {005}
  1397.         CurrentDirectory : DirectoryEntryPtr;
  1398.         SourceFile       : FileEntryPtr;
  1399.         DestinRootDir    : PathSpec;                                    {005}
  1400.         DestinFile       : FileEntryPtr);
  1401.  
  1402. (***********************************************************************
  1403.  
  1404. FUNCTIONAL DESCRIPTION:
  1405.  
  1406.     Replaces a file on the destination drive with one of the  same  path
  1407.     specification from the source drive.
  1408.  
  1409. FORMAL PARAMETERS:
  1410.  
  1411.     SourceRootDirectory - A PathSpec expression giving the root  direct-{005}
  1412.         ory  to  which  DirectoryName  is  a  relative directory for the{005}
  1413.         source file.                                                    {005}
  1414.     CurrentDirectory - A  DirectoryEntryPtr  expression  pointing  to  a
  1415.         DirectoryEntry  object  describing  the  directory  in which the
  1416.         source and destination files are found.
  1417.     SourceFile - A  FileEntryPtr  expression  pointing  to  a  FileEntry
  1418.         object describing the source file.
  1419.     TargetRootDirectory - A PathSpec expression giving the root  direct-{005}
  1420.         ory  to which DirectoryName is a relative directory for the tar-{005}
  1421.         get file.                                                       {005}
  1422.     DestinationFile - A FileEntryPtr expression pointing to a  FileEntry
  1423.         object describing the destination file.
  1424.  
  1425. RETURN VALUE:
  1426.  
  1427.     None.
  1428.  
  1429. IMPLICIT INPUTS:
  1430.  
  1431.     None.
  1432.  
  1433. IMPLICIT OUTPUTS:
  1434.  
  1435.     None.
  1436.  
  1437. SIDE EFFECTS:
  1438.  
  1439.     None.
  1440.  
  1441. ***********************************************************************)
  1442.  
  1443.     BEGIN
  1444.  
  1445.     (*
  1446.      *  For directories, nothing need be done
  1447.      *)
  1448.  
  1449.     IF ((SourceFile ^. Attr AND DirectoryAttrMask) = 0) OR
  1450.        ((DestinFile ^. Attr AND DirectoryAttrMask) = 0)
  1451.     THEN
  1452.         BEGIN
  1453.  
  1454.         (*
  1455.          *  At least one is a file.  Delete the existing thing, and copy
  1456.          *  the new thing
  1457.          *)
  1458.  
  1459.         DeleteFile (DestinRootDir, CurrentDirectory, DestinFile);       {005}
  1460.  
  1461.         CopyFile (SourceRootDir, CurrentDirectory, SourceFile,          {005}
  1462.             DestinRootDir)                                              {005}
  1463.  
  1464.         END
  1465.  
  1466.     END;
  1467. {.PA}
  1468. PROCEDURE MatchFile
  1469.    (    SourceRootDir    : PathSpec;                                    {005}
  1470.         CurrentDirectory : DirectoryEntryPtr;
  1471.         SourceFile       : FileEntryPtr;
  1472.         DestinRootDir    : PathSpec;                                    {005}
  1473.         DestinFile       : FileEntryPtr);
  1474.  
  1475. (***********************************************************************
  1476.  
  1477. FUNCTIONAL DESCRIPTION:
  1478.  
  1479.     Modifies the non-directory attributes of a destination file to  dup-
  1480.     licate those of a source file.
  1481.  
  1482. FORMAL PARAMETERS:
  1483.  
  1484.     SourceRootDirectory - A PathSpec expression giving the root  direct-{005}
  1485.         ory  to  which  DirectoryName  is  a  relative directory for the{005}
  1486.         source file.                                                    {005}
  1487.     CurrentDirectory - A DirectoryEntryPtr expression pointing to a Dir-
  1488.         ectoryEntry object describing the directory in which the destin-
  1489.         ation file is to be found.
  1490.     SourceFile - A FileEntryPtr expression pointing to a  FileEntry  ob-
  1491.         ject describing the source file.
  1492.     TargetRootDirectory - A PathSpec expression giving the root  direct-{005}
  1493.         ory  to which DirectoryName is a relative directory for the tar-{005}
  1494.         get file.                                                       {005}
  1495.     DestinationFile - A FileEntryPtr expression pointing to a  FileEntry
  1496.         object describing the destination file.
  1497.  
  1498. RETURN VALUE:
  1499.  
  1500.     None.
  1501.  
  1502. IMPLICIT INPUTS:
  1503.  
  1504.     Logging - The BOOLEAN telling whether event logging is currently on.
  1505.  
  1506. IMPLICIT OUTPUTS:
  1507.  
  1508.     None.
  1509.  
  1510. SIDE EFFECTS:
  1511.  
  1512.     None.
  1513.  
  1514. ***********************************************************************)
  1515.  
  1516.     VAR
  1517.  
  1518.         DestinName : PathSpec;
  1519.         Registers  : RegPack;
  1520.  
  1521.     BEGIN
  1522.  
  1523.     (*
  1524.      *  Ensure the attributes match
  1525.      *)
  1526.  
  1527.     IF SourceFile ^. Attr <> DestinFile ^. Attr
  1528.     THEN
  1529.         BEGIN
  1530.  
  1531.         (*
  1532.          *  Copy attributes from the source to the destination
  1533.          *)
  1534.  
  1535.         DestinName := ConstructFileName (TargetRoot,                    {005}
  1536.             CurrentDirectory ^. Name, DestinFile ^. Name);              {005}
  1537.  
  1538.         DestinName [Length (DestinName) + 1] := #$00;
  1539.  
  1540.         Registers . AH := DOSFunctionChangeAttributes;
  1541.         Registers . AL := 1;
  1542.         Registers . DS := Seg (DestinName [1]);
  1543.         Registers . DX := Ofs (DestinName [1]);
  1544.         Registers . CX := SourceFile ^. Attr AND NOT DirectoryAttrMask; {001}
  1545.         MsDos (Registers);
  1546.         IF ErrorReturn (Registers)                                      {004}
  1547.         THEN
  1548.             BEGIN
  1549.  
  1550.             WriteLn ('Cannot change attributes on ', DestinName);
  1551.             Halt
  1552.  
  1553.             END;
  1554.  
  1555.         (*
  1556.          *  If logging, note the change
  1557.          *)
  1558.  
  1559.         IF Logging
  1560.         THEN
  1561.             WriteLn ('Modified attributes of ', DestinName)
  1562.  
  1563.         END
  1564.  
  1565.     END;
  1566. {.PA}
  1567. (***********************************************************************
  1568.  
  1569. FUNCTIONAL DESCRIPTION:
  1570.  
  1571.     Modifies a target volume to  duplicate  as  closely  as  possible  a
  1572.     source volume.
  1573.  
  1574. COMMAND LINE:
  1575.  
  1576.     <SourceRoot> <TargetRoot> [/[NO]LOG] [/[NO]ACCUMULATE]              {009}
  1577.  
  1578. RETURN VALUE:
  1579.  
  1580.     None.
  1581.  
  1582. IMPLICIT INPUTS:
  1583.  
  1584.     SourceRoot - The root directory of the source directory tree.       {009}
  1585.     TargetRoot - The root directory of the target directory tree.       {009}
  1586.     Accumulating - The BOOLEAN telling whether files on the  target  are{009}
  1587.         to be retained if they are not on the source.                   {009}
  1588.  
  1589. IMPLICIT OUTPUTS:
  1590.  
  1591.     None.
  1592.  
  1593. SIDE EFFECTS:
  1594.  
  1595.     None.
  1596.  
  1597. ***********************************************************************)
  1598.  
  1599. VAR
  1600.  
  1601.     CurrentDirectory   : DirectoryEntryPtr;
  1602.     DestinDirectory    : FileEntryQueue;
  1603.     PendingDirectories : DirectoryEntryPtr;
  1604.     SourceDirectory    : FileEntryQueue;
  1605.  
  1606. BEGIN
  1607.  
  1608. (*
  1609.  *  Print the copyright notice
  1610.  *)
  1611.  
  1612. WriteLn ('TREEDUPL version ', VersionIdentification);                   {008,002}
  1613. WriteLn;
  1614.  
  1615. (*                                                                      {009}
  1616.  *  Parse the command line                                              {009}
  1617.  *)                                                                     {009}
  1618.  
  1619. ParseCommandLine;                                                       {009}
  1620.  
  1621. (*
  1622.  *  Initialize the directory needing duplication to be the root
  1623.  *)
  1624.  
  1625. GetMem (PendingDirectories, DirectoryEntrySize);                        {005}
  1626. PendingDirectories ^. Next := NIL;
  1627. PendingDirectories ^. Name := '';                                       {005}
  1628.  
  1629. (*
  1630.  *  Copy the directories on the pending directory list
  1631.  *)
  1632.  
  1633. WHILE PendingDirectories <> NIL
  1634. DO
  1635.     BEGIN
  1636.  
  1637.     CurrentDirectory := PendingDirectories;
  1638.     PendingDirectories := PendingDirectories ^. Next;
  1639.  
  1640.     (*
  1641.      *  Expand directories on the two volumes
  1642.      *)
  1643.  
  1644.     ExpandDirectory (SourceRoot, CurrentDirectory, SourceDirectory);    {005}
  1645.     ExpandDirectory (TargetRoot, CurrentDirectory, DestinDirectory);    {005}
  1646.  
  1647.     (*
  1648.      *  Extract the directories from the source listing
  1649.      *)
  1650.  
  1651.     ExtractDirectories (CurrentDirectory, SourceDirectory,
  1652.         PendingDirectories);
  1653.  
  1654.     (*
  1655.      *  Ensure that the contents of the source and  destination  direct-
  1656.      *  ories match
  1657.      *)
  1658.  
  1659.     WHILE (SourceDirectory . Head <> NIL) OR
  1660.           (DestinDirectory . Head <> NIL)
  1661.     DO
  1662.         BEGIN
  1663.  
  1664.         IF SourceDirectory . Head = NIL
  1665.         THEN
  1666.             BEGIN
  1667.  
  1668.             (*
  1669.              *  The  source  directory  has  been  exhausted  before the
  1670.              *  destination directory.  Delete the destination directory
  1671.              *  file if not accumulating files.                         {003}
  1672.              *)
  1673.  
  1674.             IF NOT Accumulating                                         {003}
  1675.             THEN                                                        {003}
  1676.                 DeleteFile (TargetRoot, CurrentDirectory,               {005}
  1677.                     DestinDirectory . Head);
  1678.             AdvanceFile (DestinDirectory)
  1679.  
  1680.             END
  1681.         ELSE IF DestinDirectory . Head = NIL
  1682.         THEN
  1683.             BEGIN
  1684.  
  1685.             (*
  1686.              *  The destination directory has been exhausted before  the
  1687.              *  source directory.  Copy the file.
  1688.              *)
  1689.  
  1690.             CopyFile (SourceRoot, CurrentDirectory,                     {005}
  1691.                 SourceDirectory . Head, TargetRoot);                    {005}
  1692.             AdvanceFile (SourceDirectory)
  1693.  
  1694.             END
  1695.         ELSE IF SourceDirectory . Head ^. Name <
  1696.                 DestinDirectory . Head ^. Name
  1697.         THEN
  1698.             BEGIN
  1699.  
  1700.             (*
  1701.              *  The destination directory does not have a  file  of  the
  1702.              *  same name as the file in the source directory.  Copy the
  1703.              *  file.
  1704.              *)
  1705.  
  1706.             CopyFile (SourceRoot, CurrentDirectory,                     {005}
  1707.                 SourceDirectory . Head, TargetRoot);                    {005}
  1708.             AdvanceFile (SourceDirectory)
  1709.  
  1710.             END
  1711.         ELSE IF SourceDirectory . Head ^. Name >
  1712.                 DestinDirectory . Head ^. Name
  1713.         THEN
  1714.             BEGIN
  1715.  
  1716.             (*
  1717.              *  The destination directory has a file whose name  is  not
  1718.              *  in  the source directory.  Delete the destinatin file if{003}
  1719.              *  not accumulating files.                                 {003}
  1720.              *)
  1721.  
  1722.             IF NOT Accumulating
  1723.             THEN
  1724.                 DeleteFile (TargetRoot, CurrentDirectory,               {005}
  1725.                     DestinDirectory . Head);
  1726.             AdvanceFile (DestinDirectory)
  1727.  
  1728.             END
  1729.         ELSE IF NOT IDAttrMatch (SourceDirectory . Head,
  1730.                                  DestinDirectory . Head)
  1731.         THEN
  1732.             BEGIN
  1733.  
  1734.             (*
  1735.              *  The source and destination directories have files of the
  1736.              *  same  name,  but  the  identity attributes do not match.
  1737.              *  Delete the file in the destination directory,  and  copy
  1738.              *  the file from the source directory.
  1739.              *)
  1740.  
  1741.             ReplaceFile (SourceRoot, CurrentDirectory,                  {005}
  1742.                 SourceDirectory . Head, TargetRoot,                     {005}
  1743.                 DestinDirectory . Head);
  1744.             AdvanceFile (SourceDirectory);
  1745.             AdvanceFile (DestinDirectory)
  1746.             END
  1747.         ELSE
  1748.             BEGIN
  1749.  
  1750.             (*
  1751.              *  The source and destination directories have files of the
  1752.              *  same name and the identity attributes match.   Make  the
  1753.              *  MS-DOS file attributes match.
  1754.              *)
  1755.  
  1756.             MatchFile (SourceRoot, CurrentDirectory,                    {005}
  1757.                 SourceDirectory . Head, TargetRoot,                     {005}
  1758.                 DestinDirectory . Head);
  1759.             AdvanceFile (SourceDirectory);
  1760.             AdvanceFile (DestinDirectory)
  1761.  
  1762.             END
  1763.  
  1764.         END;
  1765.  
  1766.     (*
  1767.      *  The current directory has been handled.
  1768.      *)
  1769.  
  1770.     FreeMem (CurrentDirectory, DirectoryEntrySize +
  1771.         Length (CurrentDirectory ^. Name));
  1772.  
  1773.     END
  1774.  
  1775.  END.
  1776.