home *** CD-ROM | disk | FTP | other *** search
/ The C Users' Group Library 1994 August / wc-cdrom-cusersgrouplibrary-1994-08.iso / vol_100 / 138_01 / bu.c < prev    next >
Text File  |  1985-08-21  |  32KB  |  1,028 lines

  1. /* 
  2. HEADER:     CUG
  3. TITLE:        BU.C - Archival File Backup Utility for CP/M
  4. VERSION:    1.1
  5. DATE:        01/18/85
  6. DESCRIPTION:    "Published under the title "Archiving Files with
  7.         CP/M-80 and CP/M-86" in the January 1985 issue of
  8.         DR. DOBB'S JOURNAL, BU is an archival file backup
  9.         utility for hard disks as well as floppies.
  10.         Functions include file copy and verification
  11.         using all of free RAM memory and full runtime
  12.         validation of CP/M file references entered on
  13.         command line."
  14. KEYWORDS:    archival, backup, CP/M, CP/M-80, CP/M-86, DDJ
  15. SYSTEM:        Any CP/M-80 or CP/M-86 system
  16. FILENAME:    BU.C
  17. WARNINGS:    "BU cannot detect files that have been updated
  18.         through random access disk writes or have had
  19.         data appended through sequential disk writes. It
  20.         will detect files that have been rewritten in
  21.         their entirety or have been renamed. In practise,
  22.         this has been found to be a minor inconvenience."
  23. CRC:        xxxx
  24. SEE-ALSO:    BU.DOC
  25. AUTHORS:    Ian Ashdown - byHeart Software
  26. COMPILERS:    Any C compiler for CP/M-80 or CP/M-86
  27. REFERENCES:    AUTHORS: Ian Ashdown;
  28.         TITLE:     'Archiving Files with CP/M-80 and
  29.              CP/M-86'
  30.              Dr. Dobb's Journal
  31.              January, 1985;
  32. ENDREF
  33. */
  34.  
  35. /*-------------------------------------------------------------*/
  36.  
  37. /* BU.C - A File Backup Utility for CP/M-80 & CP/M-86
  38.  *
  39.  * Copyright:    Ian Ashdown
  40.  *        byHeart Software
  41.  *        2 - 2016 West First Avenue
  42.  *        Vancouver, B.C. V6J 1G8
  43.  *        Canada
  44.  *
  45.  * This program may be copied for personal, non-commercial use
  46.  * only, provided that the above copyright notice is included in
  47.  * all copies of the source code. Copying for any other use
  48.  * without previously obtaining the written permission of the
  49.  * author is prohibited.
  50.  *
  51.  * pHILANTHROPICAL nOTES:
  52.  *
  53.  * Considerable time and effort went into the development of this
  54.  * software, which was expressly written for the public domain.
  55.  * The author will gladly accept any and all monetary
  56.  * contributions for the purpose of continuing such work!
  57.  *
  58.  * Acknowledgment: DeSmet C code and suggestions for program
  59.  *            improvement courtesy of Dr. Dobb's Journal
  60.  *           Contributing Editor Anthony Skjellum. This
  61.  *           program appeared in the January 1985 issue
  62.  *           of Dr. Dobb's Journal under the title of
  63.  *           "Archiving Files with CP/M-80 and CP/M-86".
  64.  *            
  65.  * Version:    1.1    Written for Aztec CII v1.06b (CP/M-80)
  66.  *            and DeSmet C88 v2.2 (CP/M-86)
  67.  *
  68.  * Date:    December 31st, 1983 (Version 1.0)
  69.  *        September 7th, 1984 (Version 1.1)
  70.  *        January 18th, 1985  (Public Domain Release)
  71.  *
  72.  * Version Modifications:
  73.  *
  74.  * 24/09/84 - "read()" and "write()" accept maximum of 32767
  75.  *          bytes, not 32768. Functions "copy_file()" and
  76.  *          "verify_file()" modified accordingly.
  77.  *
  78.  * BU utilizes the undocumented "archive" file attribute feature
  79.  * of CP/M-80 Versions 2.x and CP/M-86 to automatically detect
  80.  * files that have been changed since the disk was last "backed
  81.  * up" and copy them (with verification) to a backup disk. This
  82.  * program performs the same action as the "A" option of PIP 
  83.  * under Digital Research's MP/M 2, for which the Archive
  84.  * attribute is documented.
  85.  *
  86.  * Usage: BU x[:afn] y [-AFHQSn]
  87.  *
  88.  *      where x = drive name of disk to be backed up
  89.  *        y = drive name of backup disk
  90.  *
  91.  *      and the optional arguments are:
  92.  *
  93.  *        -A      All files, regardless of status
  94.  *        -F      Fast copy (without verification)
  95.  *        -H      Hard disk (files may be split)
  96.  *        -Q        Query each file before backup
  97.  *        -S      System attribute copied to backup
  98.  *        -n      Backup USER 'n' files only (0-31)
  99.  *        afn      Any legal CP/M ambiguous fileref
  100.  *              (can only be used with -n option)
  101.  */
  102.  
  103. #include "stdio.h"
  104. #include "ctype.h"    /* Contains macro for "isdigit()" */
  105.  
  106. /*** DEFINITIONS ***/
  107.  
  108. #define AZTEC    1    /* Aztec CII v1.06b (CP/M-80) */
  109. #define DESMET    0    /* DeSmet C88 v2.2 (CP/M-86) */
  110.  
  111. #if DESMET
  112. #define movmem(src,dest,len)    _mov(len,src,dest)
  113. #endif
  114.  
  115. #define ERROR       -1
  116. #define DEL       -1    /* Deleted fileref flag */
  117. #define ALL       -1    /* All user numbers flag */
  118. #define TRUE       -1
  119. #define FALSE        0
  120. #define SUCCESS     0
  121. #define O_RDONLY    0
  122. #define USER_ERR    0    /* Specified fileref must only be used
  123.                with -number command-line option */
  124. #define BAD_FREF    1    /* Illegal file reference */
  125. #define BAD_ARGS    2    /* Illegal command line */
  126. #define BAD_OPT     3    /* Illegal option */
  127. #define BAD_USER    4    /* Illegal user number */
  128. #define BAD_DRV     5    /* Illegal drive names */
  129. #define SAME_DRV    6    /* Same drive name for output as input */
  130. #define OPN_ERR     8    /* File open error */
  131. #define READ_ERR    9    /* File read error */
  132. #define CLS_ERR    10    /* File close error */
  133. #define BAD_VFY    11    /* File verify error */
  134. #define DIR_IO        6    /* BDOS Direct I/O service */
  135. #define RESET_DRV  13    /* BDOS Reset All Drives service */
  136. #define SEL_DRV       14    /* BDOS Select Drive service */
  137. #define SRCH_F       17    /* BDOS Search Next service */
  138. #define SRCH_N       18    /* BDOS Search Next service */
  139. #define GET_DRV       25    /* BDOS Get Default Drive service */
  140. #define SET_DMA       26    /* BDOS Set DMA Address service */
  141. #define SET_ATT       30    /* BDOS Set File Attributes service */
  142. #define USER_CODE  32    /* BDOS Get/Set User Code service */
  143. #define MAX_USER   32    /* 32 user codes under CP/M (see DR's
  144.                documentation for BDOS Service 32) */
  145.  
  146. /*** GLOBAL VARIABLES ***/
  147.  
  148. char ent_drv,    /* Entry drive code */
  149.      ent_user,    /* Entry user code */
  150.      cur_user;    /* Current user code */
  151.  
  152. /*** MAIN BODY OF CODE ***/
  153.  
  154. main(argc,argv)
  155. int argc;
  156. char *argv[];
  157. {
  158.   char in_dsk,        /* Drive name of input disk */
  159.        out_dsk,        /* Drive name of output (backup) disk */
  160.        in_drv,        /* Drive code of input disk */
  161.        out_drv,        /* Drive code of output disk */
  162.        seg_no,        /* Segment number for split files */
  163.        in_file[15],    /* Fileref of current input file */
  164.        out_file[15],    /* Fileref of current output file */
  165.        c,        /* Scratch variable */
  166.        *s,        /* Scratch string pointer */
  167.        *buffer,        /* Pointer to directory entry returned */
  168.             /* by "srch_file()" */
  169.        *srch_file(),
  170.        *malloc();
  171.  
  172.   /* File control blocks of current input and output files */
  173.  
  174.   static char in_fcb[33],    /* (Automatically initialized */
  175.           out_fcb[33];    /* to zero by compiler) */
  176.  
  177.   /* Structure for linked list of filerefs */
  178.  
  179.   struct file_ref
  180.   {
  181.     char name[12];        /* File reference */
  182.     struct file_ref *next;    /* Pointer to next instance */
  183.   } root,            /* Start of linked list */
  184.     *fref_1,            /* Scratch pointers to */
  185.     *fref_2;            /* linked list instances */
  186.  
  187.   /* Initialized file control block for "srch_file()". This FCB
  188.      is for a fully ambiguous fileref that causes "srch_file()"
  189.      to return all directory entries for the current default
  190.      drive. */
  191.  
  192.   static char fcb[] = {'?','?','?','?','?','?','?','?',
  193.                '?','?','?','?','?',0,0,0};
  194.  
  195.   int file_cnt = 0,    /* Count of file to be backed up */
  196.       dup_flag,        /* Duplicate fileref flag */
  197.       all_files,    /* All_files flag (cmd-line option) */
  198.       fast_copy,    /* Fast copy flag (cmd-line option) */
  199.       hard_disk,    /* Hard disk flag (cmd-line option) */
  200.       query,        /* Query flag (cmd-line option) */
  201.       system,        /* System flag (cmd-line option) */
  202.       user_no,        /* User number (cmd-line option) */
  203.       next_flag = FALSE;/* Flag to indicate to "srch_file()"
  204.                that a "search next" is required */
  205.  
  206.   register int i,j;    /* Loop indices */
  207.  
  208.   long begin,        /* Input file position variables */
  209.        end;
  210.  
  211.   /* Display program header */
  212.  
  213.   printf("\nBU Version 1.1");
  214.   printf("                           Copyright 1983, 1984");
  215.   printf(" byHeart Software\n\n");
  216.  
  217.   /* Initialize command-line options */
  218.  
  219.   all_files = FALSE;    /* Copy only non-archived files */
  220.   fast_copy = FALSE;    /* Copy files with verification */
  221.   hard_disk = FALSE;    /* Files will not be split across backup
  222.                disks if remaining capacity of backup
  223.                disk is less than current file size */
  224.   query = FALSE;    /* Backup without query */
  225.   system = FALSE;    /* Assign directory attribute to all
  226.                backup files */
  227.   user_no = ALL;    /* Backup files in all user areas */  
  228.  
  229.   /* Parse command line for user-selected options (if any) */
  230.             
  231.   if(argc < 3)
  232.     error(BAD_ARGS,NULL);    /* Illegal command line */
  233.   if(argc > 3)
  234.   {
  235.     i = 3;  /* Start with third command-line argument */
  236.     while(i < argc)
  237.     {
  238.       if(*argv[i] != '-')
  239.     error(BAD_OPT,argv[i]);  /* Missing leading '-' */
  240.       s = argv[i]+1;
  241.       while(*s)
  242.       {
  243.     if(*s == 'A')        /* Check for all_files option */
  244.       all_files = TRUE;
  245.     else if(*s == 'F')  /* Check for fast copy option */
  246.       fast_copy = TRUE;
  247.     else if(*s == 'H')  /* Check for hard disk option */
  248.       hard_disk = TRUE;
  249.     else if(*s == 'Q')  /* Check for query option */
  250.       query = TRUE;
  251.     else if(*s == 'S')  /* Check for system option */
  252.       system = TRUE;
  253.     else if(isdigit(*s))  /* Check for user number option */
  254.     {
  255.       user_no = *s++ - '0';
  256.       if(isdigit(*s))
  257.         user_no = user_no * 10 + *s++ - '0';
  258.       if(user_no < 0 || user_no > 31)
  259.         error(BAD_USER,argv[i]);
  260.       continue;
  261.     }
  262.     else
  263.       error(BAD_OPT,argv[i]);
  264.     s++;
  265.       }
  266.     i++;
  267.     }
  268.   }
  269.  
  270.   /* Validate input parameters */
  271.  
  272.   if(*(argv[1]+1) != '\0')  /* Check for specified fileref */
  273.   {
  274.     if(user_no == ALL)         /* Can only use with specified */
  275.       error(USER_ERR,NULL);  /* user number (-n option) */
  276.  
  277.     /* Modify "fcb[]" to incorporate fileref */
  278.  
  279.     if(copy_fref(fcb,argv[1]) == ERROR)
  280.       error(BAD_FREF,argv[1]);
  281.   }
  282.   if(*argv[1] < 'A' || *argv[1] > 'P' ||
  283.       *argv[2] < 'A' || *argv[2] > 'P')
  284.     error(BAD_DRV,NULL);    /* Illegal drive names */
  285.   if(*argv[1] == *argv[2])
  286.     error(SAME_DRV,NULL);    /* Drive names are same */
  287.  
  288.   /* Save entry drive and user codes */
  289.  
  290.   ent_drv = bdos(GET_DRV);
  291.   ent_user = bdos(USER_CODE,0xff);
  292.  
  293.   /* Calculate input and output drive codes */
  294.  
  295.   in_drv = (in_dsk = *argv[1]) - 'A' + 1;
  296.   out_drv = (out_dsk = *argv[2]) - 'A' + 1;
  297.  
  298.   /* Set default drive to input drive */
  299.  
  300.   bdos(SEL_DRV,in_drv-1);
  301.  
  302.   /* Set user code to "user_no" if -n option specified */
  303.  
  304.   if(user_no != ALL)
  305.     bdos(USER_CODE,user_no);
  306.  
  307.   /* Read first 12 bytes of updated active directory entries into
  308.      linked list of filerefs. If first byte of entry is 0xe5,
  309.      then file has been erased. */
  310.  
  311.   root.next = NULL;    /* Initialize linked list root */
  312.   fref_1 = &root;    /* Initialize linked list pointer */
  313.   while(buffer = srch_file(fcb,next_flag))
  314.   {
  315.     /* Bit 7 of third filetype byte (t3') in directory entry
  316.        is the Archive attribute indicator. The BDOS sets this
  317.        bit to zero whenever it updates a directory entry. */
  318.  
  319.     if(buffer[0] != 0xe5 &&
  320.       (buffer[0] == user_no || user_no == ALL) &&
  321.       (!(buffer[11] & 0x80) || all_files))
  322.     {
  323.       fref_1->next =    /* Allocate space for fileref instance */
  324.       (struct file_ref *) malloc(sizeof(struct file_ref));
  325.       fref_1 = fref_1->next;    /* Assign space to instance */
  326.       movmem(buffer,fref_1->name,12);  /* Move fileref to */
  327.                        /* linked list instance */
  328.       fref_1->next = NULL;  /* Indicate current end of list */
  329.     }
  330.     next_flag = TRUE;    /* Only first call to "srch_file()" */
  331.             /* should be made with "next_flag" */
  332.   }            /* set to FALSE */
  333.  
  334.   /* If no files have been backed up ... */
  335.  
  336.   if(!root.next)  /* Null "root.next" indicates no files have */
  337.     {          /* been changed */
  338.       printf("NO FILES HAVE BEEN UPDATED");
  339.       if(user_no != ALL)
  340.     printf(" in user area %d\n",user_no);
  341.       else
  342.     putchar('\n');
  343.       reset();    /* Reset user and drive codes to entry values */
  344.       exit(0);
  345.     }
  346.  
  347.   /* There may be duplicate filerefs in linked list due to some
  348.      files occupying more than one extent on the disk. These
  349.      duplicates must be marked as "deleted" in the list.
  350.      (Duplicate filerefs with different user codes are valid.) */
  351.  
  352.   /* For all filerefs ... */
  353.  
  354.   fref_1 = &root;  /* Initialize a linked list pointer */
  355.   while(fref_1->next)
  356.   {
  357.     fref_1 = fref_1->next;  /* Root instance is NULL entry */
  358.     dup_flag = FALSE;        /* Reset duplicate fileref flag */
  359.  
  360.     /* For all preceding filerefs ... */
  361.  
  362.     fref_2 = &root;  /* Initialize another linked list pointer */
  363.     fref_2 = fref_2->next;  /* Skip root instance */
  364.     while(fref_2->next != fref_1->next)
  365.     {
  366.       /* Compare filerefs (ignore deleted filerefs and different
  367.      user codes) */
  368.  
  369.       if(fref_2->name[0] != DEL && fref_1->name[0] == fref_2->name[0])
  370.     if(!strncmp(fref_1->name+1,fref_2->name+1,11))
  371.     {
  372.       dup_flag = TRUE;    /* Indicate duplicate fileref */
  373.       break;
  374.     }
  375.       fref_2 = fref_2->next;
  376.     }
  377.     if(dup_flag == TRUE)
  378.       fref_1->name[0] = DEL;    /* Delete if duplicate fileref */
  379.     else
  380.       file_cnt++;        /* Increment file count */
  381.   }
  382.  
  383.   /* Display file copy header */
  384.  
  385.   printf("Number of files to be copied: %d\n\n",file_cnt);
  386.   printf("User:    Files being copied to Drive %c:\n\n",
  387.      out_dsk);
  388.  
  389.   /* Initialize current input and output fileref templates */
  390.  
  391.   in_file[0] = in_dsk;
  392.   out_file[0] = out_dsk;
  393.   in_file[1] = out_file[1] = ':';
  394.   in_file[10] = out_file[10] = '.';
  395.   in_file[14] = out_file[14] = '\0';
  396.  
  397.   /* Initialize current input and output FCB templates */
  398.  
  399.   in_fcb[0] = in_drv;
  400.   out_fcb[0] = out_drv;
  401.  
  402.   /* For all validated filerefs do ... */
  403.  
  404.   for(cur_user = 0; cur_user < MAX_USER; cur_user++)
  405.   {
  406.     if(user_no != ALL)
  407.       if(cur_user != user_no)
  408.     continue;
  409.     bdos(USER_CODE,cur_user);    /* Set user code to "cur_user" */
  410.     fref_1 = &root;    /* Initialize linked list pointer */
  411.     while(fref_1->next)
  412.     {
  413.       fref_1 = fref_1->next;    /* Root instance is NULL entry */
  414.       if(fref_1->name[0] == cur_user)
  415.       {
  416.     /* Update the current input and output FCB's */
  417.  
  418.     movmem(fref_1->name+1,in_fcb+1,11);
  419.     movmem(fref_1->name+1,out_fcb+1,11);
  420.  
  421.     /* Reset the Read-Only and System attribute bits of the
  422.        FCB's so that the file can be copied and displayed
  423.        (unless the "system" flag is TRUE) */
  424.  
  425.     out_fcb[9] &= 0x7f;    /* Read-Only attribute */
  426.     if(!system)
  427.       out_fcb[10] &= 0x7f;    /* System attribute */
  428.  
  429.     /* Set the Archive attribute bit of the FCB's to indicate
  430.        that the file has been backed up */
  431.  
  432.     in_fcb[11] |= 0x80;
  433.     out_fcb[11] |= 0x80;
  434.  
  435.     /* Move the fileref from the FCB's to the initialized
  436.        input and output fileref templates */
  437.  
  438.     movmem(in_fcb+1,in_file+2,8);    /* Filename move */
  439.     movmem(out_fcb+1,out_file+2,8);
  440.     movmem(in_fcb+9,in_file+11,3);    /* Filetype move */
  441.     movmem(out_fcb+9,out_file+11,3);
  442.  
  443.     /* Strip high order bits off filerefs to form proper
  444.        ASCII characters */
  445.  
  446.     for(j = 2; j <= 13; j++)
  447.     {
  448.       in_file[j] &= 0x7f;
  449.       out_file[j] &= 0x7f;
  450.     }
  451.  
  452.     /* Display the filerefs */
  453.  
  454.     printf(" %2d      %s --> %s",cur_user,in_file,out_file);
  455.  
  456.     /* Query operator for backup if indicated by "query"
  457.        flag */
  458.  
  459.     if(query)
  460.     {
  461.       printf("  O.K. to backup?  ");
  462.       if((c = in_chr()) == 'y' || c == 'Y')
  463.         puts("Yes");
  464.       else
  465.       {
  466.         puts("No");
  467.         continue;    /* Go do next fileref if "No" */
  468.       }
  469.     }
  470.     else
  471.       putchar('\n');
  472.  
  473.     /* Copy file from the input disk to the output disk */
  474.  
  475.     if(hard_disk)    /* Split file across backup disks if */
  476.     {        /* necessary */
  477.       begin = 0L;    /* Initialize file position pointer */
  478.       seg_no = 0;    /* and split file segment number */
  479.       do
  480.       {
  481.         /* Reset the Read-Only attribute of the output file
  482.            (if it exists) so that the input file can be
  483.            copied to it */
  484.  
  485.         bdos(SET_ATT,out_fcb);
  486.  
  487.         end = copy_file(in_file,out_file,begin);
  488.         if(!fast_copy)  /* Verify file unless -F selected */
  489.           verify_file(in_file,out_file,begin);
  490.  
  491.         /* Set the Read-Only attribute of the output file */
  492.  
  493.         out_fcb[9] |= 0x80;
  494.         bdos(SET_ATT,out_fcb);
  495.         out_fcb[9] &= 0x7f;    /* ... and reset the fcb */
  496.         if(end != NULL)
  497.         {
  498.           /* File has been partially written on current
  499.          backup disk - new disk required to continue */
  500.  
  501.           new_disk(out_file,hard_disk);
  502.  
  503.           /* Append segment number to filename of output
  504.          fileref (e.g. - B:FILE.TYP will become
  505.          B:FILE--01.TYP) */
  506.  
  507.           seg_no++;
  508.           for(j = 2; j <= 7; j++)    /* Change spaces to */
  509.         if(out_file[j] == ' ')    /* '-' character */
  510.           out_file[j] = '-';
  511.           out_file[8] = seg_no/10 + '0';  /* Append segment */
  512.           out_file[9] = seg_no%10 + '0';  /* number */
  513.  
  514.           /* Display filerefs again */
  515.  
  516.           printf(" %2d      %s --> %s\n",
  517.           cur_user,in_file,out_file);
  518.         }
  519.         begin = end;
  520.       }
  521.       while(end != NULL);  /* Loop until file is written */
  522.     }
  523.     else
  524.     {
  525.       /* Reset the Read-Only attribute of the output file
  526.          (if it exists) */
  527.  
  528.       bdos(SET_ATT,out_fcb);
  529.  
  530.       if(copy_file(in_file,out_file,0L) != NULL)
  531.       {
  532.         /* Disk was full - erase partially written file, back
  533.            up fileref pointer and rewrite file to new disk */
  534.  
  535.         unlink(out_file);
  536.         new_disk(out_file,hard_disk);
  537.         i--;
  538.         continue;
  539.       }
  540.       if(!fast_copy)  /* Verify file unless -F selected */
  541.         verify_file(in_file,out_file,0L);
  542.  
  543.       /* Set the Read-Only attribute of the output file */
  544.  
  545.       out_fcb[9] |= 0x80;
  546.       bdos(SET_ATT,out_fcb);
  547.     }
  548.  
  549.     /* Set the Archive attribute of the input file to
  550.        indicate that the file was successfully backed up */
  551.  
  552.     bdos(SET_ATT,in_fcb);
  553.       }
  554.     }
  555.   }
  556.   reset();    /* Reset user and drive codes to entry values */
  557. }
  558.  
  559. /*** FUNCTIONS ***/
  560.  
  561. /* Search for first or next directory entry */
  562.  
  563. char *srch_file(fcb_ptr,next_flag)
  564. char *fcb_ptr;        /* Pointer to file control block */
  565. int next_flag;        /* Flag to indicate "search next" */
  566. {
  567.   static char sf_cur[32],  /* Current directory entry buffer */
  568.               sf_fcb[36];  /* File control block buffer */
  569.  
  570.   int index,  /* Index of directory entry in DMA buffer */
  571.       *ptr;   /* Pointer to directory entry in DMA buffer */
  572.  
  573.   bdos(SET_DMA,0x80);    /* Set DMA address to 80h */
  574.   if(!next_flag)
  575.   {
  576.     movmem(fcb_ptr,sf_fcb,16);    /* Initialize FCB buffer */
  577.     if((index = bdos(SRCH_F,sf_fcb)) == 0xff)  /* Find first */
  578.       return NULL;    /* Return NULL if unsuccessful */
  579.   }
  580.   else
  581.     if((index = bdos(SRCH_N,NULL)) == 0xff)  /* Find next */
  582.       return NULL;    /* Return NULL if unsuccessful */
  583.  
  584.   /* BDOS services 17 and 18 leave four consecutive directory
  585.      entries of 32 bytes each in the 128-byte DMA buffer and also
  586.      returns an index value of 0, 1, 2 or 3 to indicate the
  587.      correct directory entry in the accumulator. The "bdos()"
  588.      function returns this index value. */
  589.  
  590.   ptr = 0x80 + index * 32;  /* Calculate pointer to directory
  591.                    entry */
  592.   movmem(ptr,sf_cur,32);  /* Move directory entry to current
  593.                  directory entry buffer */
  594.   return sf_cur;
  595. }
  596.  
  597. /* Copy file starting at "offset" from beginning */
  598.  
  599. copy_file(in_file,out_file,offset)
  600. char *in_file,        /* Input fileref */
  601.      *out_file;        /* Output fileref */
  602. long offset;        /* Input file position offset */
  603. {
  604.   register int in_cnt,    /* Character counts for unbuffered I/O */
  605.            out_cnt;
  606.   int fd_in,        /* Input file descriptor */
  607.       fd_out,        /* Output file descriptor */
  608.       full_disk = FALSE;  /* Full disk flag */
  609.  
  610.   char *buffer,       /* Input file buffer */
  611.        *buff_ptr,  /* Pointer to current position in "buffer[]" */
  612.        *malloc();
  613.  
  614.   unsigned buf_size = 32640;  /* Initial memory allocation size */
  615.  
  616.   /* "read()" accepts a maximum of 32767 bytes at a time. Allocate
  617.      as much memory as possible up to this limit for the input
  618.      buffer, using 128 byte decrements. */
  619.  
  620.   do
  621.     if(buffer = malloc(buf_size))
  622.       break;
  623.   while(buf_size -= 128);
  624.  
  625.   /* Open input file for unbuffered Read-Only access */
  626.  
  627.   if((fd_in = open(in_file,O_RDONLY)) == ERROR)
  628.     error(OPN_ERR,in_file);
  629.  
  630.   /* Create the output file by first deleting it (if it
  631.      exists), then opening it for unbuffered Write-Only
  632.      access. */
  633.  
  634.   if((fd_out = creat(out_file,NULL)) == ERROR)
  635.     error(OPN_ERR,out_file);
  636.  
  637.   /* Initialize input file position pointer to "offset" */
  638.  
  639.   lseek(fd_in,offset,0);
  640.  
  641.   /* Copy input file to output file by buffering data
  642.      through "buffer[]" */
  643.  
  644.   do
  645.   {
  646.     if((in_cnt = read(fd_in,buffer,buf_size)) == ERROR)
  647.       error(READ_ERR,in_file);
  648.     buff_ptr = buffer;  /* Initialize "buffer[]" pointer */
  649.     out_cnt = 0;    /* and "out_cnt" */
  650.     do
  651.     {
  652.       /* Write contents of "buffer[]" to output file in 128
  653.      byte records until either the buffer is written or a
  654.      write error occurs. Since the 0x1a (^Z) character CP/M
  655.      uses as an EOF marker is a valid file character for non-
  656.      ASCII files, "read()" always reads the last 128 byte
  657.      record of a file under CP/M. */
  658.  
  659.       if(write(fd_out,buff_ptr,128) != 128)
  660.       {
  661.     /* The standard implementation of "write()" does not
  662.        distinguish between a full disk or directory and a
  663.        write error in its returned error code. Thus, it is
  664.        assumed that an error means a full disk/directory. */
  665.  
  666.     full_disk = TRUE;
  667.     break;
  668.       }
  669.       buff_ptr += 128;  /* Increment "buffer[]" ptr */
  670.       out_cnt += 128;  /* Update count of chars written */
  671.     }
  672.     while(in_cnt > out_cnt);    /* Until end of "buffer[]" */
  673.     offset += out_cnt;    /* Update input file position pointer */
  674.     if(full_disk == TRUE)
  675.       break;
  676.   }
  677.   while(in_cnt == buf_size);    /* Until end of file */
  678.   free(buffer);        /* Deallocate buffer space */
  679.   if(close(fd_in) == ERROR)    /* Close the files */
  680.     error(CLS_ERR,in_file);
  681.   if(close(fd_out) == ERROR)
  682.     error(CLS_ERR,out_file);
  683.  
  684.   /* If full disk return new offset for input file, else */
  685.   /* return NULL to indicate completion of file copy operation */
  686.  
  687.   return (full_disk ? offset : NULL);
  688. }
  689.  
  690. /* Compare portion of input file starting at "offset" from begin-
  691.    ning of file with output file */
  692.  
  693. verify_file(in_file,out_file,offset)
  694. char *in_file,        /* Input fileref */
  695.      *out_file;        /* Output fileref */
  696. long offset;        /* Input file position offset */
  697. {
  698.   register int match_cnt;    /* Scratch variable */
  699.  
  700.   int out_cnt,    /* Character counts for unbuffered I/O */
  701.       fd_in,    /* Input file descriptor */
  702.       fd_out;    /* Output file descriptor */
  703.  
  704.   char *buffer,        /* Dynamically-allocated buffer */
  705.        *in_ptr,        /* Input file buffer pointer */
  706.        *out_ptr,    /* Output file buffer pointer */
  707.        *malloc();
  708.  
  709.   unsigned buf_size = 65280;  /* Initial memory allocation size */
  710.  
  711.   /* "read()" and "write()" accept a maximum of 32767 bytes at a
  712.      time. Allocate as much memory as possible up to this limit
  713.      for both the input and output buffers, using 256 byte
  714.      decrements (128 bytes for each buffer). */
  715.  
  716.   do
  717.     if(buffer = malloc(buf_size))
  718.       break;
  719.   while(buf_size -= 256);
  720.  
  721.   /* Divide "buffer[]" in two for "in_ptr" and "out_ptr" */
  722.  
  723.   buf_size /= 2;
  724.  
  725.   if((fd_in = open(in_file,O_RDONLY)) == ERROR)  /* Open files */
  726.     error(OPN_ERR,in_file);
  727.   if((fd_out = open(out_file,O_RDONLY)) == ERROR)
  728.     error(OPN_ERR,out_file);
  729.   lseek(fd_in,offset,0);  /* Initialize file position pointer */
  730.  
  731.   /* Read in characters from both files and compare */
  732.  
  733.   do
  734.   {
  735.     in_ptr = buffer;    /* Assign buffer pointers */
  736.     out_ptr = in_ptr + buf_size;
  737.     if(read(fd_in,in_ptr,buf_size) == ERROR)
  738.       error(READ_ERR,in_file);
  739.     if((out_cnt = read(fd_out,out_ptr,buf_size)) == ERROR)
  740.       error(READ_ERR,out_file);
  741.     match_cnt = out_cnt;
  742.     while(match_cnt--)            /* Verify character */
  743.       if(*in_ptr++ != *out_ptr++)    /* by character, and */
  744.       {                    /* delete the output */
  745.     if(close(fd_out) == ERROR)    /* file if they fail */
  746.       error(CLS_ERR,out_file);    /* to match */
  747.     unlink(out_file);
  748.     error(BAD_VFY,out_file);
  749.       }
  750.   }
  751.   while(out_cnt == buf_size);    /* Until end of output file */
  752.   free(buffer);            /* Deallocate buffer space */
  753.   if(close(fd_in) == ERROR)    /* Close the files - verifi- */
  754.     error(CLS_ERR,in_file);    /* cation was successful */
  755.   if(close(fd_out) == ERROR)
  756.     error(CLS_ERR,out_file);
  757. }
  758.  
  759. /* Copy fileref to file control block */
  760.  
  761. copy_fref(fcb,fref)
  762. char *fcb,        /* Pointer to file control block */
  763.      *fref;        /* Pointer to fileref */
  764. {
  765.   char c;    /* Scratch variable */
  766.   int i,    /* Fileref index variable */
  767.       j,    /* FCB index variable */
  768.       k,    /* Scratch variable */
  769.       done;    /* Loop break flag */
  770.  
  771.   if(fref[1] != ':' || fref[2] == '\0')
  772.     return ERROR;  /* No drive code separator or null fileref */
  773.  
  774.   /* Calculate drive code from drive name and put in FCB */
  775.  
  776.   fcb[0] = fref[0] - 'A' + 1;
  777.  
  778.   /* Process remainder of fileref */
  779.  
  780.   done = FALSE;
  781.   for(i = 2,j = 1;i <= 9;i++,j++)    /* Skip drive code in */
  782.   {                    /* fileref */
  783.     switch(c = fref[i])
  784.     {
  785.       case '.':        /* Filetype separator */
  786.     if(i == 2)
  787.       return ERROR;    /* Null filename */
  788.     for( ; j <= 8; j++)    
  789.       fcb[j] = ' ';    /* Pad filename with trailing blanks */
  790.     done = TRUE;
  791.     break;
  792.       case '*':        /* Match any following string */
  793.     for( ; j <= 8; j++)
  794.       fcb[j] = '?';    /* Pad filename with trailing */
  795.     i++;        /* question marks */
  796.     done = TRUE;
  797.     break;
  798.       case '\0':    /* End of fileref */
  799.     for( ; j <= 11; j++)  /* Pad FCB with trailing spaces */
  800.       fcb[j] = ' ';
  801.     return SUCCESS;
  802.       case ',':        /* Illegal filename characters */
  803.       case ';':
  804.       case ':':
  805.       case '=':
  806.       case '[':
  807.       case ']':
  808.       case '_':
  809.       case '<':
  810.       case '>':
  811.     return ERROR;
  812.       default:
  813.     if(c >= '!' && c <= '~')
  814.       fcb[j] = c;    /* Copy character from fileref to FCB */
  815.     else
  816.       return ERROR;    /* Nonprintable character or ' ' */
  817.     }
  818.     if(done)
  819.       break;
  820.   }
  821.   c = fref[i];
  822.   if((c = fref[i]) == '\0')    /* End of fileref */
  823.   {
  824.     for( ; j <= 11; j++)  /* Pad FCB with trailing spaces */
  825.       fcb[j] = ' ';
  826.     return SUCCESS;
  827.   }
  828.   else if(c == '.')    /* Filetype separator */
  829.   {
  830.     i++;
  831.     k = i + 2;        /* Set limit of 3 characters */
  832.     for( ; i <= k; i++,j++)
  833.     {
  834.       done = FALSE;
  835.       switch(c = fref[i])
  836.       {
  837.     case '*':    /* Match any following string */
  838.       for( ; j <= 11; j++)
  839.         fcb[j] = '?';    /* Pad filetype with trailing */
  840.       return SUCCESS;    /* question marks */
  841.     case '\0':    /* End of fileref */
  842.       for( ; j <= 11; j++)  /* Pad FCB with trailing */
  843.         fcb[j] = ' ';      /* spaces */
  844.       return SUCCESS;
  845.     case '.':        /* Illegal filetype characters */
  846.     case ';':
  847.     case ',':
  848.     case ':':
  849.     case '=':
  850.     case '[':
  851.     case ']':
  852.     case '_':
  853.     case '<':
  854.     case '>':
  855.       return ERROR;
  856.     default:
  857.       if(c >= '!' && c <= '~')
  858.         fcb[j] = c;  /* Copy character from fileref */
  859.       else            /* to FCB */
  860.         return ERROR;  /* Nonprintable character or ' ' */
  861.       }
  862.     }
  863.  
  864.     /* Return ERROR if filetype too long */
  865.  
  866.     return (fref[i] == '\0' ? SUCCESS : ERROR);
  867.   }
  868.   else
  869.     return ERROR;  /* Filename too long  */
  870. }
  871.  
  872. /* Error report */
  873.  
  874. error(n,s)
  875. int n;        /* Error code */
  876. char *s;    /* Pointer to optional string */
  877. {
  878.   switch(n)
  879.   {
  880.     case USER_ERR:
  881.       printf("\007** ERROR - No user number specified **\n");
  882.       break;
  883.     case BAD_FREF:
  884.       printf("\007** ERROR - Illegal file reference: %s **\n",s);
  885.       break;
  886.     case BAD_ARGS:
  887.       printf("\007** ERROR - Illegal command line **\n");
  888.       break;
  889.     case BAD_OPT:
  890.       printf("\007** ERROR - Illegal command line option:");
  891.       printf(" %s **\n",s);
  892.       break;
  893.     case BAD_USER:
  894.       printf("\007** ERROR - User number must be inside range");
  895.       printf(" of 0 to 31 **\n");
  896.       break;
  897.     case BAD_DRV:
  898.       printf("\007** ERROR - Drive names must be inside range");
  899.       printf(" of 'A' to 'P' **\n");
  900.       break;
  901.     case SAME_DRV:
  902.       printf("\007** ERROR - Drive names cannot be equal **\n");
  903.       break;
  904.     case OPN_ERR:
  905.       printf("\007\n** ERROR - Cannot open file %s **\n",s);
  906.       reset();
  907.       exit(0);
  908.     case READ_ERR:
  909.       printf("\007\n** ERROR - Read error on file %s **\n",s);
  910.       reset();
  911.       exit(0);
  912.     case CLS_ERR:
  913.       printf("\007\n** ERROR - Cannot close file %s **\n",s);
  914.       reset();
  915.       exit(0);
  916.     case BAD_VFY:
  917.       printf("\007\n** ERROR - Failed verify on file %s **\n",s);
  918.       reset();
  919.       exit(0);
  920.   }
  921.   printf("\nUsage: BU x[:afn] y [-AFHQSn]\n\n");
  922.   printf("       where x = drive name of disk to be backed up\n");
  923.   printf("             y = drive name of backup disk\n\n");
  924.   printf("       and the optional arguments are:\n\n");
  925.   printf("             -A           All files, regardless of");
  926.   printf(" status\n");
  927.   printf("             -F           Fast copy (without ");
  928.   printf(" verification)\n");
  929.   printf("             -H           Hard disk (files may be");
  930.   printf(" split)\n");
  931.   printf("             -Q           Query each file before");
  932.   printf(" backup\n");
  933.   printf("             -S           System attribute copied to");
  934.   printf(" backup\n");
  935.   printf("             -n           Backup USER 'n' files only");
  936.   printf(" (0-31)\n");
  937.   printf("             -afn         Any legal CP/M ambiguouos");
  938.   printf(" fileref\n");
  939.   printf("                          (can only be used with -n");
  940.   printf(" option)\n");
  941.   exit(0);
  942. }
  943.  
  944. /* Request a new backup disk to be inserted in the output drive */
  945.  
  946. new_disk(name,hard_disk)
  947. char *name;
  948. int hard_disk;
  949. {
  950.   char d;
  951.  
  952.   printf("\007\n         ** BACKUP DISK FULL **\n\n");
  953.   if(hard_disk)
  954.     printf("WARNING: -H option active - FILE WILL BE SPLIT\n\n");
  955.   printf("Insert new disk into drive %c to continue.\n",name[0]);
  956.   printf("Type 'C' when ready or 'A' to abort ... ");
  957.   while((d = in_chr()) != 'c' && d != 'C' && d != 'a' && d != 'A')
  958.     ;
  959.   if(d == 'a' || d == 'A')
  960.   {
  961.     unlink(name);
  962.     exit(0);
  963.   }
  964.   else
  965.     printf("\n\n");
  966.   bdos(RESET_DRV,NULL);        /* Reset drives */
  967. }
  968.  
  969. /* Reset user and drive codes to entry values */
  970.  
  971. reset()
  972. {
  973.   bdos(USER_CODE,ent_user);
  974.   bdos(SEL_DRV,ent_drv);
  975. }
  976.  
  977. /* Get character from current CP/M CON: device without echo */
  978.  
  979. in_chr()
  980. {
  981.   int c;
  982.  
  983.   do
  984.     c = bdos(DIR_IO,0xff);
  985.   while(!c);
  986.   return c;
  987. }
  988.  
  989. /* Additional function required for DeSmet C version of BU86.C */
  990.  
  991. #if DESMET
  992. bdos(beta,delta)
  993. int beta,
  994.     delta;
  995. {
  996.   return _os(beta,delta);
  997. }
  998. #endif
  999.  
  1000. /*** EXPLANATION OF AZTEC CII STDIO.H FUNCTIONS ***/
  1001.  
  1002. /* bdos(bc,de)    | DeSmet equivalent is defined under FUNCTIONS |
  1003.  * int bc,de;
  1004.  *
  1005.  * Calls CP/M's BDOS with 8080 CPU register pair BC set to "bc"
  1006.  * and register pair DE set to "de". The value returned by the
  1007.  * 8080 CPU accumulator is the return value.
  1008.  *
  1009.  * movmem(src,dest,length)  | DeSmet equivalent is defined  |
  1010.  * char *dest, *src;        | under DEFINITIONS            |
  1011.  * int length;
  1012.  *
  1013.  * Moves data from "src" to "dest". The number of bytes is
  1014.  * specified by the parameter "length".
  1015.  *
  1016.  * strncmp(str1,str2,max)  | Identical for DeSmet |
  1017.  * char *str1,*str2;
  1018.  * int max;
  1019.  *
  1020.  * Compares "str1" to "str2" for at most "max" characters, and
  1021.  * returns NULL if strings are equal, -1 if "str1" is less than
  1022.  * "str2", and +1 if "str1" is greater than "str2".
  1023.  */
  1024.  
  1025. /* End of BU.C */
  1026.  Additional function required for DeSmet C version of BU86.C */
  1027.  
  1028. #if DESMET