/* BU.C a hard disk file backup utility for CP/M-80 * Copyright : Ian Ashdown * by Heart Software * 2 - 2016 West First Avenue * Vancouver, B.C. V6J 1G8 * Canada * BU was taken from Dr DOBBS #99 (Jan 1985). See the article for * more information on how to use BU * * The following code was painfully typed in by D.L. Finley from the * article. Most of the comments were left out. * The equates were deleted for DeSmet C. I only have AZTEC C * * ------------------ History ---------------------- * Feb 3, 1985 -- D.L. Finley * Fixed problem of creating zero length files when backup disk is * completely full. Problem was that calling routine was receiving * a zero offset result which it interpreted as NULL. * * Feb 2, 1985 -- D.L. Finley * added do{} while around copy_file() -- no hard disk routine if main() * Bug did not recopy file if disk got full * added -B option to turn off copying of .BAK files * * Jan 20, 1985 -- D.L. Finley * Modified printf statements to display file names in the form DU:name.ext * Added "Verifying..." message * * Jan 19, 1985 -- D.L. Finley * BUG--- The program as published in Dr. DOBBS had a bug. The variables * in_cnt and out_cnt in copy_file() had to be changed to unsigned to make it * work. (32768 is a negative number). * * BU utilizes the undocumented "archive" file attribute feature of CP/M 80 * versions 2.x and CP/M 86 to automatically detect files that have been * changed since the disk was last "backed up" and copy them (with * verification) to a backup disk. This program performs the same action * as the "A" option in PIP under Digital Research MP/M 2, for which the * archive bit is documented. * * Usage: BU source[:afn] destin [-AFHQSn] * * Where source = drive name of disk to be backed up * destin = drive name of backup disk * * The optional arguments are: * -A All files, regardless of status * -F Fast copy (without verification) * -H Hard disk (files may be split) * -Q Query each file before backup * -S System attribute copied to backup * -B Don't copy .BAK files * -n Backup USER 'n' files only (0-31) * afn Any legal CP/M ambiguous fileref * (can only be used with -n option) * */ #include "stdio.h" #include "ctype.h" /* definitions */ #define ERROR -1 #define DEL -1 #define ALL -1 #define TRUE -1 #define FALSE 0 #define SUCCESS 0 #define O_RDONLY 0 #define USER_ERR 0 #define QUIT 3 /* ctrl-c */ #define BAD_FREF 1 #define BAD_ARGS 2 #define BAD_OPT 3 #define BAD_USER 4 #define BAD_DRV 5 #define SAME_DRV 6 #define OPN_ERR 8 #define READ_ERR 9 #define CLS_ERR 10 #define BAD_VFY 11 #define DIR_IO 6 #define RESET_DRV 13 #define SEL_DRV 14 #define SRCH_F 17 #define SRCH_N 18 #define GET_DRV 25 #define SET_DMA 26 #define SET_ATT 30 #define USER_CODE 32 #define MAX_USER 32 /* GLOBAL VARIABLES */ char ent_drv,ent_user,cur_user; /* main body of code */ main(argc,argv) int argc; char *argv[]; { char in_dsk,out_dsk,in_drv,out_drv,seg_no; char in_file[15],out_file[15],c,*s,*buffer, *srch_file(),*malloc(); int fcount; /* file count */ static char in_fcb[33],out_fcb[33]; struct file_ref { char name[12]; struct file_ref *next; } root,*fref_1,*fref_2; static char fcb[]={'?','?','?','?','?','?','?','?', '?','?','?','?','?',0,0,0}; int file_cnt=0; int dup_flag,all_files,fast_copy,hard_disk,query,system,user_no,no_bacs; int next_flag=FALSE; register int i,j; long begin,end,copy_file(); printf("\nBU Version 1.1"); printf(" Copyright 1983,1984"); printf(" by Heart Software \n\n"); all_files=FALSE; fast_copy=FALSE; hard_disk=FALSE; query=FALSE; system=FALSE; user_no=ALL; no_bacs=FALSE; fcount=0; if(argc < 3)error(BAD_ARGS,NULL); if(argc > 3){ i=3; while(i < argc){ if(*argv[i] != '-')error(BAD_OPT,argv[i]); s=argv[i]+1; while(*s){ if(*s== 'A') all_files=TRUE; else if(*s=='F') fast_copy=TRUE; else if(*s=='H') hard_disk=TRUE; else if(*s=='Q') query=TRUE; else if(*s=='S') system=TRUE; else if(*s=='B') no_bacs=TRUE; else if(isdigit(*s)){ user_no=*s++-'0'; if(isdigit(*s))user_no=user_no*10 **s++-'0'; if(user_no <0 || user_no > 31)error(BAD_USER,argv[i]); continue; } else error(BAD_OPT,argv[i]); s++; } i++; } } /* validate input parameters */ if(*(argv[1]+1) != '\0'){ if(user_no == ALL)error(USER_ERR,NULL); if(copy_fref(fcb,argv[1]) == ERROR)error(BAD_FREF,argv[1]); } if(*argv[1] < 'A' || *argv[1] >'P' || *argv[2] < 'A' || *argv[2] > 'P')error(BAD_DRV,NULL); if(*argv[1] == *argv[2])error(SAME_DRV,NULL); ent_drv=bdos(GET_DRV); ent_user=bdos(USER_CODE,0xff); in_drv=(in_dsk=*argv[1]) - 'A' +1; out_drv=(out_dsk=*argv[2]) -'A' + 1; /* set default drive to input drive */ bdos(SEL_DRV,in_drv-1); /* set user number to "user_no" if -n option specified */ if(user_no != ALL)bdos(USER_CODE,user_no); root.next=NULL; fref_1= &root; while(buffer = srch_file(fcb,next_flag)){ if(buffer[0] != 0xe5 && (buffer[0] == user_no || user_no==ALL) && (!(buffer[11] & 0x80) || all_files)){ fref_1->next=(struct file_ref *) malloc(sizeof(struct file_ref)); fref_1=fref_1->next; movmem(buffer,fref_1->name,12); fref_1->next = NULL; } next_flag=TRUE; } if(!root.next){ printf("NO FILES HAVE BEEN UPDATED"); if(user_no != ALL)printf(" in user area %d.",user_no); putchar('\n'); reset(); exit(0); } fref_1 = &root; while(fref_1 -> next){ fref_1 = fref_1 -> next; dup_flag=FALSE; fref_2=&root; fref_2=fref_2 -> next; while(fref_2 -> next != fref_1 -> next){ if(fref_2 -> name[0] != DEL && fref_1 -> name[0] == fref_2 -> name[0]) if(!strncmp(fref_1->name+1,fref_2->name+1,11)){ dup_flag=TRUE; break; } fref_2=fref_2->next; } if(dup_flag==TRUE)fref_1->name[0] = DEL; else file_cnt++; } printf("Number of files to be copied: %d\n\n",file_cnt); printf("Files being copied to Drive %c:\n\n",out_dsk); in_file[0]=in_dsk; out_file[0]=out_dsk; in_file[1]=out_file[1]=':'; in_file[10]=out_file[10]='.'; in_file[14]=out_file[14]='\0'; in_fcb[0]=in_drv; out_fcb[0]=out_drv; for(cur_user =0; cur_user < MAX_USER; cur_user++){ if(user_no != ALL) if(cur_user != user_no) continue; bdos(USER_CODE,cur_user); fref_1=&root; while(fref_1->next){ fref_1=fref_1->next; if(fref_1->name[0] == cur_user){ if(key_hit() == QUIT){ puts("\n ** Aborted by user **\n"); reset(); } movmem(fref_1->name+1,in_fcb+1,11); movmem(fref_1->name+1,out_fcb+1,11); if(no_bacs){ if(mycmp(&in_fcb[9],"BAK",3)) continue; } out_fcb[9] &= 0x7f; if(!system)out_fcb[10] &= 0x7f; in_fcb[11] |= 0x80; out_fcb[11] |= 0x80; movmem(in_fcb+1,in_file+2,8); movmem(out_fcb+1,out_file+2,8); movmem(in_fcb+9,in_file+11,3); movmem(out_fcb+9,out_file+11,3); for(j=2;j<=13;j++){ in_file[j] &= 0x7f; out_file[j] &= 0x7f; } out_file[14]='\0'; fcount++; /* display the filerefs */ printf("%3d %c%d%s --> ",fcount,in_file[0],cur_user,&in_file[1]); printf("%c%d:",out_file[0],cur_user); /* query operator for backup if indicated by query flag */ if(query){ printf(" O.K. to backup? "); if(in_chr() == 'Y')printf("Yes "); else{ puts("No"); /* puts will add "newline" */ continue; } } if(hard_disk){ begin=0L; seg_no=0; do{ bdos(SET_ATT,out_fcb); end=copy_file(in_file,out_file,begin); if(end != -1L){ if(!fast_copy)verify_file(in_file,out_file,begin); putchar('\n'); out_fcb[9] |= 0x80; bdos(SET_ATT,out_fcb); out_fcb[9] &= 0x7f; } if(end != NULL){ new_disk(out_file,hard_disk); if(end != -1L){ seg_no++; for(j=2;j<=7;j++){ if(out_file[j] == ' ')out_file[j]='-'; } out_file[8]=seg_no/10 + '0'; out_file[9]=seg_no%10 + '0'; } else end=begin; printf("%3d %c%d%s --> ",fcount,in_file[0],cur_user,&in_file[1]); printf("%c%d%s",out_file[0],cur_user,&out_file[1]); } begin=end; } while (end != NULL); } else { end=NULL; do{ bdos(SET_ATT,out_fcb); if(end != NULL){ printf("%3d %c%d%s --> ",fcount,in_file[0],cur_user,&in_file[1]); printf("%c%d:",out_file[0],cur_user); } if((end=copy_file(in_file,out_file,0L)) != NULL){ unlink(out_file); new_disk(out_file,hard_disk); continue; } if(!fast_copy)verify_file(in_file,out_file,0L); putchar('\n'); out_fcb[9] |= 0x80; bdos(SET_ATT,out_fcb); } while (end != NULL); } bdos(SET_ATT,in_fcb); } } } reset(); } /* FUNCTIONS FOR BU.C PROGRAM */ /* search for first or next directory entry */ char *srch_file(fcb_ptr,next_flag) char *fcb_ptr; int next_flag; { static char sf_cur[32],sf_fcb[36]; int index,*ptr; bdos(SET_DMA,0x80); if(!next_flag){ movmem(fcb_ptr,sf_fcb,16); if((index=bdos(SRCH_F,sf_fcb))==0xff)return NULL; } else if((index=bdos(SRCH_N,NULL))==0xff)return NULL; ptr=0x80+index*32; movmem(ptr,sf_cur,32); return sf_cur; } /* copy file starting at "offset" from beginning */ long copy_file(in_file,out_file,offset) char *in_file,*out_file; long offset; { register unsigned in_cnt,out_cnt; int fd_in,fd_out,full_disk=FALSE; char *buffer,*buff_ptr,*malloc(); unsigned buf_size=32768; do{ if(buffer = malloc(buf_size))break; }while(buf_size -=128); if((fd_in=open(in_file,O_RDONLY))== ERROR)error(OPN_ERR,in_file); if((fd_out=creat(out_file,NULL))== ERROR)error(OPN_ERR,out_file); lseek(fd_in,offset,0); do{ if((in_cnt=read(fd_in,buffer,buf_size))==ERROR)error(READ_ERR,in_file); buff_ptr=buffer; out_cnt=0; do{ if(write(fd_out,buff_ptr,128) != 128){ full_disk=TRUE; break; } buff_ptr+=128; out_cnt+=128; } while(in_cnt > out_cnt); offset+=out_cnt; if(full_disk==TRUE) break; } while(in_cnt == buf_size); free(buffer); if(close(fd_in)==ERROR)error(CLS_ERR,in_file); if(close(fd_out)==ERROR)error(CLS_ERR,out_file); if(offset==0)offset=-1L; return(full_disk ? offset : NULL); } /* compare portion of input file starting at "offset" from beginning of file with output file */ verify_file(in_file,out_file,offset) char *in_file,*out_file; long offset; { register int match_cnt; int out_cnt,fd_in,fd_out; char *buffer,*in_ptr,*out_ptr,*malloc(); unsigned buf_size=65280; do{ if(buffer=malloc(buf_size))break; } while (buf_size -= 265); buf_size/=2; if((fd_in=open(in_file,O_RDONLY))==ERROR)error(OPN_ERR,in_file); if((fd_out=open(out_file,O_RDONLY))==ERROR)error(OPN_ERR,out_file); printf(" Verifying.."); lseek(fd_in,offset,0); do{ in_ptr=buffer; out_ptr=in_ptr+buf_size; if(read(fd_in,in_ptr,buf_size) == ERROR)error(READ_ERR,in_file); if((out_cnt = read(fd_out,out_ptr,buf_size))==ERROR) error(READ_ERR,out_file); match_cnt=out_cnt; while(match_cnt--){ if(*in_ptr++ != *out_ptr++){ if(close(fd_out) == ERROR)error(CLS_ERR,out_file); unlink(out_file); error(BAD_VFY,out_file); } } } while(out_cnt == buf_size); free(buffer); if(close(fd_in)== ERROR)error(CLS_ERR,in_file); if(close(fd_out)==ERROR)error(CLS_ERR,out_file); printf(" ok."); } /* copy fileref to file control block */ copy_fref(fcb,fref) char *fcb,*fref; { char c; int i,j,k,done; if(fref[1] != ':' || fref[2] == '\0')return ERROR; fcb[0]=fref[0]-'A'+1; done=FALSE; for(i=2,j=1;i<=9;i++,j++){ switch(c=fref[i]){ case '.' : if(i==2)return ERROR; for( ;j<=8;j++)fcb[j]=' '; done=TRUE; break; case '*' : for( ;j<=8;j++)fcb[j]='?'; i++; done=TRUE; break; case '\0' : for( ;j<=11;j++)fcb[j]=' '; return SUCCESS; case ',' : case ';' : case ':' : case '=' : case '[' : case ']' : case '_' : case '<' : case '>' : return ERROR; default : if(c>= '!' && c<='~')fcb[j]=c; else return ERROR; } if(done)break; } c=fref[i]; if((c=fref[i]) == '\0'){ for( ;j<=11;j++)fcb[j]=' '; return SUCCESS; } else if(c=='.'){ i++; k=i+2; for( ;i<=k;i++,j++){ done=FALSE; switch(c=fref[i]){ case '*' : for( ;j<=11;j++)fcb[j]='?'; return SUCCESS; case '\0' : for( ;j<=11;j++)fcb[j]=' '; return SUCCESS; case '.': case ';': case ':': case '=': case '[': case ']': case '_': case '<': case '>': return ERROR; default : if(c>='!' && c<='~')fcb[j]=c; else return ERROR; } } return (fref[i]== '\0' ? SUCCESS : ERROR); } else return ERROR; } /* error report */ error(n,s) int n; char *s; { switch(n){ case USER_ERR: printf("** ERROR - No user number specified **\n"); break; case BAD_FREF: printf("** ERROR - Illegal file reference: %s **\n",s); break; case BAD_ARGS: printf("** ERROR - Illegal command line **\n"); break; case BAD_OPT: printf("** ERROR - Illegal command line option **\n"); break; case BAD_USER: printf("** ERROR - User number must be inside range"); printf(" of 0 to 31 **\n"); break; case BAD_DRV: printf("** ERROR - Drive names must be inside range"); printf(" of 'A' to 'P' **\n"); break; case SAME_DRV: printf("** ERROR - Drive names cannot be the same **\n"); break; case OPN_ERR: printf("** ERROR - Cannot open file %s\n",s); reset(); exit(0); break; case READ_ERR: printf("** ERROR - Read error on file %s\n",s); reset(); exit(0); case CLS_ERR: printf("** ERROR - Cannot close file %s\n",s); reset(); exit(0); case BAD_VFY: printf("** ERROR - Failed verify of file %s\n",s); reset(); exit(0); } printf("\nUsage: BU x[:afn] y [-AFHQSn]\n\n"); printf(" where x = drive name of disk to be backed up\n"); printf(" y = drive name of backup disk\n\n"); printf(" and the optional arguments are:\n\n"); printf(" -A All files, regardless of status\n"); printf(" -F Fast copy (without verification)\n"); printf(" -H Hard disk (files may be split)\n"); printf(" -Q Query each file before backup\n"); printf(" -S System attribute copied to backup\n"); printf(" -B Don't copy files of type .BAK\n"); printf(" -n Backup user 'n' files only\n"); printf(" afn Any legal CP/M ambiguous fileref\n"); printf(" (can only be used with -n option)\n"); exit(0); } /* request a new backup disk to be inserted in the output drive */ new_disk(name,hard_disk) char *name; int hard_disk; { char d; printf("\n ** BACKUP DISK FULL **\n\n"); if(hard_disk)printf("WARNING: -H option active - FILE WILL BE SPLIT\n\n"); printf("Insert new disk into drive %c to continue.\n",name[0]); printf("Type RETURN to continue or 'A' to abort ...."); while((d=in_chr()) != 0x0d && d != 'A'); if(d == 'A'){ unlink(name); exit(0); } printf("\n\n"); bdos(RESET_DRV,NULL); } reset() { bdos(USER_CODE,ent_user); bdos(SEL_DRV,ent_drv); } in_chr() { int c; do{ c=bdos(DIR_IO,0xff); } while(!c); return toupper(c); } key_hit() { return bdos(DIR_IO,0xff); } mycmp(s,s1,n) char *s,*s1; int n; { int i; for(i=0;i