/* copyright (c) by Aaron Wohl, 1981,1982 written by Aaron Wohl 12-24-81 This file may be used for non-profit use provided this this notice remains at the front of the file. This program reads and writes cpm format floppy disks. cpm is a trade mark of Digital Reasearch. It runs under version 7 unix. Unix is a trademark of Bell Labs. See the function help() for documentation. */ #define version 7 #define when "10-28-82" /* change log: ver when who why --- -------- ------------- -------------------- 2 12-27-81 wohl@cmuc allow a trailing * to cross the . in a ufn 3 12-28-81 wohl@cmuc remove refrences to cmu local functions honor the record count when reading 4 2- 9-82 wohl@cmuc fix a '=> [cpm]' string to be '[cpm] =>' put with one filename arg uses it for both 5 5- 8-82 wohl@cmuc initilize extent filler bytes to zero 6 5-17-82 wohl@cmuc fix printout of free space (was 2k low) add support for user numbers add a \n to the interleving off message 7 10-28-82 mz@gp for file get, close each file Things to (think about) doing: a) have the create file system function ask for confirmation b) multiple put should skip files with names that are too long or truncate the name c) skip files which are directories (multiple put) */ #include #include #include #include #define TRUE 1 #define FALSE 0 #define MAX_USER 15 /* maximum user number */ #define CP_NUMDIR 64 /* number of directory slots */ #define DIR_EXT (CP_NUMDIR+1) /* fake index to allocate directory space */ #define CP_DIRSEC 16 /* number of sectors of directory */ #define CP_NAMESIZE (8+3+1+1) /* file name, name+ext+dot+null */ #define CP_BASE (26*2) /* sector of start of file system */ #define CP_SIZE (26*77) /* sectors on a disk */ #define CP_CLUSTER 8 /* sectors in a allocation cluster */ #define CP_CLCHAR (CP_CLUSTER*128) /* number of bytes in a cluster */ #define CP_ALOC ((CP_SIZE-CP_BASE)/CP_CLUSTER) /* clusters per disk */ #define SPACE 040 /* ascii space character */ #define np(val) ((val) & 0177) /* no parity please */ #define FT_SZ 3 /* number of bytes in a file type */ #define FN_SZ 8 /* number of bytes in a file name */ #define SEC 128 /* bytes in a sector */ #define DM 16 /* number of disk map entries */ #define DELF 0xe5 /* entry type for a deleted file */ /* what a directory entry looks like */ struct cp_dir_ext { unsigned char cp_et; /* entry type */ unsigned char cp_fn[FN_SZ]; /* file name */ unsigned char cp_ft[FT_SZ]; /* file type */ unsigned char cp_ex; /* extent number */ unsigned char cp_fl[2]; /* filler */ unsigned char cp_rc; /* record count */ unsigned char cp_dm[DM]; /* disk allocation map */ }; struct cp_dir_ext cp_dir[CP_NUMDIR]; char cp_name[CP_NUMDIR][CP_NAMESIZE]; /* name as file.typ */ int ext_next[CP_NUMDIR]; /* slot of next higher extent */ int ext_prev[CP_NUMDIR]; /* slot of next lower extent */ int cp_btb[CP_ALOC]; /* allocation bit table */ int dsk_inuse; /* number of clusters in use */ int num_fil; /* number of files */ int num_ext; /* number of extents */ int dir_err; /* number of directory errors */ /* includes number of i/o errors in dir */ int io_err; /* number of i/o errors */ int user_num = 0; /* current user number */ int any_user = TRUE; /* true if files from any user can be seen */ #define flag(c) (flg[(c) - 'a']) char *man = { "clgp" }; char *flname; int fldes; long lseek(); int gcmd(),lcmd(),icmd(),ccmd(),pcmd(),ncmd(); /* command functions */ int (*comfun)(); char flg[26]; char mode = 'u'; int file; /* main program */ main(argc, argv) char *argv[]; { char *cp; fprintf(stdout,"cpmutl version(%d), date:%s\n",version,when); init_data(); /* initialize data */ cp = argv[1]; for(cp = argv[1]; *cp; cp++) switch(*cp) { case 'a': flag('a')++; continue; case 't': mode = 't'; continue; case 'b': mode = 'b'; continue; case 'o': flag('o')++; /* allow writes to a damaged directory */ continue; case 'i': flag('i')++; continue; case 'n': flag('w')++; /* will be writing */ setcom(ncmd); continue; case 'p': flag('w')++; /* will be writing */ setcom(pcmd); continue; case 'c': flag('w')++; /* will be writing */ setcom(ccmd); continue; case 'g': setcom(gcmd); continue; case 'l': setcom(lcmd); continue; case 'f': flname = argv[2]; argv++; argc--; continue; case 'h': help(); case 'u': user_num=0; /* read in the number */ while(isdigit(*(cp+1))) user_num = user_num * 10 + (*++cp - '0'); if(user_num > MAX_USER) quit(1,"\ncpmutl:user number out of range\n"); any_user = FALSE; continue; default: quit(1,"\ncpmutl: bad option '%c'. use h for help\n", *cp); } if(comfun == 0) { quit(1,"\ncpmutl: one of [%s] must be specified. use h for help\n",man); } (*comfun)(argc-2,&argv[2]); fprintf(stdout,"\n"); exit(0); } /* command parsing subroutines */ need_mode() { if(mode == 'u') quit(1,"\ncpmutl: mode t(ext) or b(inary) must be specified\n"); } setcom(fun) int (*fun)(); { if(comfun != 0) quit(1,"\ncpmutl: only one of [%s] allowed\n", man); comfun = fun; } help() { fprintf(stdout,"commands:\n"); fprintf(stdout," l afn list files matching afn\n"); fprintf(stdout," g afn [unix_prefix] get files matching afn\n"); fprintf(stdout," p ufn unix_file_name put a file to floppy\n"); fprintf(stdout," p '*' unix_files put a bunch of files\n"); fprintf(stdout," n afn nuke - delete matching files\n"); fprintf(stdout," c create a new file system\n"); fprintf(stdout,"\noptions:\n"); fprintf(stdout," t - text mode b - binary mode\n"); fprintf(stdout," i - don't do interleaving u - user number\n"); fprintf(stdout," a - absolute disk address, start.length e.g. 0.2002\n"); fprintf(stdout," f - use the named unix file instead of /dev/floppy\n"); fprintf(stdout," o - override, allows writes to a damaged directories\n"); fprintf(stdout,"\nterms:\n"); fprintf(stdout," afn - ambiguous cpm file name, e.g. *.asm or foo*\n"); fprintf(stdout," *** don't forget to quote afns to the shell, '*.asm'\n"); fprintf(stdout," ufn - unambiguous file name\n"); fprintf(stdout," interleaving - tracks 2-76 are normaly interleaved\n"); fprintf(stdout," with a skew of 6 sectors\n"); exit(1); } /* create a new file system */ ccmd() { int i; fprintf(stdout,"initializing file system "); fflush(stdout); lread(CP_BASE,CP_DIRSEC,cp_dir); /* read in the directory */ /* so we can zap disk to undo this */ for(i=0; i 2) toomany(); if(flag('a')) { /* want absolute disk addresses? */ if(argc < 2) toofew(); abs_read(cpm_name,argv[1]); return; } need_mode(); /* need have given a file mode */ if (argc == 1) unix_name = ""; /* no unix prefix */ else unix_name = argv[1]; /* use this unix prefix */ parse_ext(&wild,cpm_name); getdir(); /* read the directory */ cur = -1; /* start looking here */ while((cur = lookup(&wild,cur)) != -1) /* find the next matching file */ getfile(cur,unix_name); } /* pcmd - put files on the floppy */ pcmd(argc,argv) char *argv[]; int argc; { int i; char *uname; if(argc<1) toofew(); if(flag('a')) { /* want absolute disk addresses? */ if(argc<2) toofew(); if(argc > 2) toomany(); abs_write(argv[0],argv[1]); return; } need_mode(); getdir(); if (argc == 1) /* only one arg? */ writefile(argv[0],argv[0]); else for(i=1; i sector %d, length %d ",unix_name,start,size); fflush(stdout); while(size--) { if((status = fread(buf,1,SEC,ifile)) != SEC) /* read a sector from unix */ quit(1,"\ncpmutl:read error on unix file %s\n",unix_name); lwrite(start,1,buf); start++; /* advance to the next sector */ } reporterrs(); fclose(ifile); } /* ndcm - nuke some files */ ncmd(argc,argv) char *argv[]; int argc; { struct cp_dir_ext del_ext; if(argc < 1) toofew(); if(argc > 2) toomany(); getdir(); /* get the directory */ parse_ext(&del_ext,argv[0]); /* parse the file spec to delete */ nuke_ext(&del_ext); /* delete matching extents */ putdir(); } /* delete matching entries */ nuke_ext(nuk_me) struct cp_dir_ext *nuk_me; { int i; for(i=0; icp_fn[i] = src->cp_fn[i]; for(i=0; icp_ft[i] = src->cp_ft[i]; } /* lcmd - list the floppy directory */ lcmd(argc,argv) char *argv[]; int argc; { int i,cur,curext; int totrec=0,totext=0,totk=0; struct cp_dir_ext wild; char *cpm_name; if(argc > 1) toomany(); if(argc == 0) cpm_name = "*.*"; /* default file name */ else cpm_name = argv[0]; getdir(); /* read in the directory */ fprintf(stdout, "disk status: %d files, %d extents, %dK allocated, %dK free\n\n", num_fil,num_ext,dsk_inuse-2,CP_ALOC-dsk_inuse); fprintf(stdout," recs K ex usr name\n"); parse_ext(&wild,cpm_name); /* parse the cpm file name */ cur = -1; while((cur = curext = lookup(&wild,cur)) != -1) { /* find a file */ int recsiz=0,ksiz=0,numext=0; while(curext != -1) { /* do all of its extents */ numext++; /* found one more extent */ recsiz += cp_dir[curext].cp_rc; /* this many records */ ksiz += ((cp_dir[curext].cp_rc+CP_CLUSTER-1)/CP_CLUSTER); /* k size */ curext = ext_next[curext]; /* move on */ } fprintf(stdout, " %4d %3dK %2d [%d]%s\n",recsiz,ksiz,numext, cp_dir[cur].cp_et,cp_name[cur]); totrec += recsiz; totk += ksiz; totext += numext; } fprintf(stdout," ---- --- --\n"); fprintf(stdout," %4d %3d %2d\n",totrec,totk,totext); } /* read subroutines */ /* read a file from the floppy */ getfile(index,unix_prefix) int index; char *unix_prefix; { int clstr,cur_io_err,extsiz,currec; FILE *ofile; char *unix_name; char buf[100]; /* default to the prefix */ if (*unix_prefix == 0) /* is there a unix file name */ unix_name = cp_name[index]; /* no, just use the cpm name */ else { char *cp; for(cp = unix_prefix; *cp; *cp++ ); /* find the end */ cp--; /* point to the last char */ if (*cp != '/') /* is this a prefix */ unix_name = unix_prefix; /* no, its a name */ else strcpy(buf,unix_prefix), /* put on the prefix */ strcat(buf,cp_name[index]), /* then the name */ unix_name = buf; /* that's it */ } if ((ofile = fopen(unix_name,"w")) == NULL) /* try to open the unix file */ quit(1,"\ncpmutl:can't create unix file %s\n",unix_name); fprintf(stdout,"[%d]%s => %s ",cp_dir[index].cp_et, cp_name[index],unix_name); fflush(stdout); markerrs(); for( ; index != -1; index=ext_next[index]) { /* all extents */ extsiz = cp_dir[index].cp_rc; /* read all the records */ for(currec=0; currec < extsiz; currec++) { /* read all the records */ if(read_sector(cp_dir[index].cp_dm[currec/CP_CLUSTER], currec % CP_CLUSTER,ofile)) break; /* stop at eof */ } } reporterrs(); fclose(ofile); } /* more read subroutines */ /* read a bunch of sectors into a file */ abs_read(wherefrom,unix_name) { int start,size,status; char buf[SEC]; FILE *ofile; if(sscanf(wherefrom,"%d.%d",&start,&size) != 2) quit(1,"\ncpmutl:can't parse start.size address\n"); if ((ofile = fopen(unix_name,"w")) == NULL) /* try to open the unix file */ quit(1,"\ncpmutl:can't create unix file %s\n",unix_name); markerrs(); fprintf(stdout, "reading from sector %d, length %d => %s ",start,size,unix_name); fflush(stdout); while(size--) { lread(start,1,buf); /* read a sector from the floppy */ status=fwrite(buf,1,SEC,ofile); /* write it to the file */ if(status != SEC) quit(1,"\ncpmutl:file write error for unix file %s\n",unix_name); start++; /* advance to the next sector */ } reporterrs(); fclose(ofile); } /* read in a sector */ int read_sector(dsk_adr,sec,ofile) int dsk_adr,sec; FILE *ofile; { int i; char buf[SEC],cur; /* sector buffer */ lread((dsk_adr*CP_CLUSTER)+CP_BASE+sec,1,buf); /* read it in */ if(sec == 0) /* start of a cluster? */ putc('.',stdout), /* yes */ fflush(stdout); if (mode == 't') { for(i=0; icp_et != user_num) return FALSE; /* fail if not the correct user */ for(i=0; icp_fn[i]) != np(ext2->cp_fn[i])) && /* fail if not equal */ (np(ext1->cp_fn[i]) != '?')) /* and ext1 not wild */ return FALSE; for(i=0; icp_ft[i]) != np(ext2->cp_ft[i])) && /* fail if not equal */ (np(ext1->cp_ft[i]) != '?')) /* and ext1 not wild */ return FALSE; return TRUE; /* success */ } /* parse the passed extent */ parse_ext(rslt,name) struct cp_dir_ext *rslt; char *name; { int i; char cur; ini_ext(rslt); /* initialize the extent */ for(i=0; icp_fn[i] = '?'; /* fill out with ? then */ else rslt->cp_fn[i] = cur, name++; } if(*name == '*') { /* need to skip a star? */ *name++; /* get out of the * */ if(*name == 0) /* the end of the string? */ name = "*"; /* yes, start crosses the . then */ } if(*name == '.') *name++; /* maybe skip the separator */ for(i=0; icp_ft[i] = '?'; /* fill out with ? then */ else rslt->cp_ft[i] = cur, name++; } if(*name == '*') *name++; /* get out of the * */ if(*name != 0) quit(1,"\ncpmutl:cpm file name to long\n"); } ini_ext(rslt) struct cp_dir_ext *rslt; { int i; rslt->cp_et = user_num; /* mark this extent in use */ for(i=0; icp_fn[i] = SPACE; for(i=0; icp_ft[i] = SPACE; rslt->cp_rc = 0; /* no records yet */ rslt->cp_ex = 0; /* relative extent number */ for(i=0; icp_dm[i] = 0; /* no disk space assigned yet */ for(i=0; i<2; i++) rslt->cp_fl[i] = 0; /* initilize filler words */ } /* random small subroutines */ /* record io errors for a transfer */ int cur_io_err; markerrs() { cur_io_err = io_err; } /* print [ok] or number of io errors */ reporterrs() { if(cur_io_err != io_err) fprintf(stderr,"\ncpmutl:%d io errors\n",io_err-cur_io_err); else fprintf(stdout," [ok]\n"); } /* find the next file to match cpm_name */ lookup(wild,index) int index; struct cp_dir_ext *wild; { index++; /* skip over the current extent */ for( ; index= CP_ALOC) { /* does this cluster exist? */ dir_err++; fprintf(stderr,"\ncpmutl:illegal cluster %d in disk map for ",where); prname(who); fprintf(stderr,"\n"); } else if (cp_btb[where] != -1) { /* has someone else already claimed it? */ dir_err++; fprintf(stderr,"\ncpmutl:Cluster %d multiply linked:\n",where); fprintf(stderr," first file: "); prname(who); fprintf(stderr," second file: "); prname(cp_btb[where]); } else { cp_btb[where] = who; /* remember who has it for debugging */ dsk_inuse++; /* one more cluster in use */ } } /* print the name of a file */ prname(index) int index; { int i,temp; if(index == DIR_EXT) fprintf(stderr,"directory\n"); else fprintf(stderr,"name:[%d]%s, extent:%d, recs:%d, slot:%d\n", cp_dir[index].cp_et,cp_name[index],cp_dir[index].cp_ex, cp_dir[index].cp_rc,index); } /* check that the record count for index is consistent with */ /* the list of clusters allocated to it in the extent map */ check_rc(index) int index; { int bit_aloc,j; bit_aloc=(cp_dir[index].cp_rc+CP_CLUSTER-1)/CP_CLUSTER; if(bit_aloc > DM) { /* is it in range? */ dir_err++; /* no */ fprintf(stderr,"\ncpmutl:record count out of bounds\n for extent:"); prname(index); return; /* give up on this one */ } for(j=0; j= CP_NUMDIR) { /* was a lower extent found ? */ dir_err++; /* no, that is an error */ fprintf(stderr,"\ncpmutl:can't find lower extent: "); prname(index); } } } /* set cp_name from cp_fn and cp_ft */ setname(index) { int i,j,temp; j=0; /* index into the file name */ for(i=0; i [%d]%s ",unix_name, cp_dir[cur_ext].cp_et,cp_name[cur_ext]); fflush(stdout); while(TRUE) { i = fread((char *)&ch,1,1,ifile); /* get the next input byte */ if(i == 0) break; /* stop this at eof */ if(i != 1) quit(1,"\ncpmutl:read error on unix file %s\n",unix_name); if(mode == 't') { if((ch = np(ch)) == '\n') cpm_put('M'-64), /* new line is cr */ cpm_put('J'-64); /* then lf */ else cpm_put(ch); } else cpm_put(ch); } if(mode == 't') cpm_put('Z'-64); /* the finishing touch for text */ if((cur_pos % CP_CLCHAR) != 0) /* data left in the floppy buffer */ flbuf(); /* yes, flush the buffer */ fclose(ifile); /* close the input file */ putdir(); /* write the directory back */ reporterrs(); } /* write routines */ cpm_put(curchar) char curchar; { extern int cur_ext; extern int cur_pos; extern char buff[]; int new_ext,i,buf_ptr; if(cur_pos >= (CP_CLCHAR*DM)) { /* this extent already full? */ new_ext = aloc_ext(); /* make the new extent */ cur_pos = 0; /* reset position pointer */ copy_ext(&cp_dir[cur_ext],&cp_dir[new_ext]); /* copy over the name */ cp_dir[new_ext].cp_ex = cp_dir[cur_ext].cp_ex + 1; ext_next[cur_ext] = new_ext; /* forward link */ ext_prev[new_ext] = cur_ext; /* back link */ cur_ext = new_ext; setname(cur_ext); /* set up cp_name for it */ } if((cur_pos % SEC) == 0) /* going into a new record ? */ cp_dir[cur_ext].cp_rc++; /* yes */ buf_ptr = cur_pos % CP_CLCHAR; /* where in the buffer */ if(buf_ptr == 0) /* need a new cluster */ cp_dir[cur_ext].cp_dm[cur_pos / CP_CLCHAR] = aloc_cluster(cur_ext); buff[buf_ptr] = curchar; /* store the char */ if(buf_ptr == (CP_CLCHAR-1)) /* last char of the buffer? */ flbuf(); /* yes, flush the buffer */ cur_pos++; /* advance to the next character */ } flbuf() { extern int cur_ext; extern int cur_pos; extern char buff[]; lwrite((cp_dir[cur_ext].cp_dm[cur_pos / CP_CLCHAR])*CP_CLUSTER+CP_BASE, CP_CLUSTER,buff); /* write out the buffer */ putc('.',stdout); fflush(stdout); } start_new_file(name) char *name; { extern int cur_ext; extern int cur_pos; int cur,i; struct cp_dir_ext temp_ext; parse_ext(&temp_ext,name); /* parse the file name */ nuke_ext(&temp_ext); /* nuke matching current extents */ cur_pos = 0; cur_ext = aloc_ext(); /* make up an extent for the file */ copy_ext(&temp_ext,&cp_dir[cur_ext]); /* give it a name */ num_fil++; /* this extent is really a file */ setname(cur_ext); /* set up cp_name */ dirok(); /* make sure directory is ok */ } /* allocate a new extent */ int aloc_ext() { int i,newext; for(newext=0; newext= CP_NUMDIR) /* was a free extent found? */ quit(1,"\ncpmutl:allocate extent failure, floppy directory full\n"); num_ext++; /* one more extent now in use */ ini_ext(&cp_dir[newext]); /* clear it */ return(newext); /* return the extent to the user */ } /* allocate a new cluster */ int aloc_cluster(extent) int extent; { int i; for(i=0; i= CP_BASE) && /* is this in the directory? */ (startad < (CP_BASE + CP_DIRSEC))) dir_err++; /* it counts as a directory error */ } obuff += SEC; startad++; } } /* write some sectors to the floppy doing interleaving */ lwrite(startad,count,obuff) int startad, count; char *obuff; { long trans(); extern fldes; fl_init(); while(count--) { lseek(fldes, trans(startad)*SEC, 0); if (write(fldes,obuff,SEC) != SEC) { io_err++; fprintf(stderr,"\ncpmutl: write sector error %d\n",startad); if ((startad >= CP_BASE) && /* is this in the directory? */ (startad < (CP_BASE + CP_DIRSEC))) dir_err++; /* it counts as a directory error */ } obuff += SEC; startad++; } } /* quit - a cmu local function */ quit(status,fmt,args) int status; char *fmt; { _doprnt(fmt,&args,stderr); exit(status); }