/* >>:yam7.c 10-25-81 (diff 10-25-81) * File open and close stuff and more ... * This file assumes operation on a CP/M system */ #include "yam.h" /* dpb dph blocks from CP/M Interface Guide Sect 6.5 */ struct dpb { /* CP/M Version 2 Disk Parameter Block */ unsigned dpb_spt; /* sectors per track */ char dpb_bsh; /* block shift factor */ char dpb_blm; char dpb_exm; /* Extent Mask */ unsigned dpb_dsm; /* Highest block number on this disk */ unsigned dpb_drm; /* total number of directory entries -1 */ unsigned dpb_al; /* bit field corresponding to direc blocks */ unsigned dpb_cks; /* size of the directory check vector */ unsigned dpb_off; /* number of reserved tracks on this disk */ }; char *call(); struct dph { /* CP/M Version 2 Disk Parameter Header */ char **dph_xlt; /* logical to physical xlat vector */ int dph_ooo; char *dph_dirbuf; /* 128 byte scratchpad for directory use */ struct dpb *dph_dpb; /* disk param block for this type of disk */ char **dph_csv; /* scratch area for detecting changed disks */ char *dph_alv; /* pointer to bit vector alloc map for disk */ }; struct fcb { /* CP/M Version 2 fcb AS SEEN BY THE USER */ char dr; /* drive number */ char fname[8]; /* fname[1] used by TAG2 */ char ftype[3]; /* ftype[1] 8th bit set for $SYS */ char ex; /* file extent normally 0 */ char s1; /* reserved for bdos's benefit */ char s2; /* likewise, =0 on call to open,make,search */ char rc; /* record count for extent[ex] 0...128 */ char dmap[16]; char cr; /* current record, initialized to 0 by usr */ unsigned recn; /* highest record number */ char recovf; /* overflow of above */ }; /* following are BIOS calls */ #define CONST 2 /* bios console char ready */ #define CONIN 3 /* bios cons char input */ #define CONOUT 4 /* bios cons char output */ /* following are BDOS calls */ #define BDOS 5 /* address used to call bdos */ #define SELDSK 14 /* bdos select disk 0=default disk */ #define SRCH 17 /* bdos search for file pattern*/ #define SRCHNXT 18 /* search for next occurrence */ #define GETDEFDISK 25 /* get current disk (0-15) */ #define SETDMA 26 /* set address for read, write, etc. */ #define GETALLOCP 27 /* get address of allocation vector */ #define SETATTRIB 30 /* update file attributes */ #define GETDPBP 31 /* get DPB address for disk */ #define SETGETUSER 32 /* set or get user number */ #define COMPFILSIZ 35 /* compute file size into recn and recovf */ #define UFNSIZE 15 /* a:foobar12.urk\0 is 15 chars */ openrx(name) char *name; { #ifdef RESTRICTED char *s; if(s=cisubstr(name, ".com")) /* upload .com files as .obj */ strcpy(s, ".OBJ"); if(cisubstr(name, "$$$")) return ERROR; /* don't allow upload of $$$.sub */ for(s=name; *s; ) if(*s++ > 'z') return ERROR; /* no garbage names please */ #endif unspace(name); lprintf("'%s' ", name); /* show the name right away */ if(!Creamfile && fopen(name, fout) != ERROR) { fclose(fout); printf("Exists ", name); #ifdef XMODEM return ERROR; #else printf("Replace it (y/n)??"); if(tolower(getchar())!= 'y') return ERROR; #endif } if(fcreat(name, fout)==ERROR){ printf("Can't create %s\n", name); return ERROR; } Rfile= TRUE; strcpy(Rname, name); Dumping= !Squelch; lprintf("Created%s\n", Dumping? "" : " recording OFF"); return OK; } closerx(pad) { if(Rfile) { if(!Quiet) lpstat("Closing %s", Rname); #ifdef BDSC if(pad) do putc(CPMEOF, fout); while(fout._nleft % SECSIZ); #endif fflush(fout); fclose(fout); Rfile=FALSE; #ifdef LOGFILE logfile(Rname, pad?'*':'R'); /* record file xmsn */ #endif } } opentx(name) char *name; { struct fcb *fp, *fcbaddr(); lprintf("'%s' ", name); unspace(name); if(fopen(name, fin)==ERROR){ printf("Can't open %s\n", name); return ERROR; } #ifdef RESTRICTED if(cisubstr(name, ".bad") || (fp=fcbaddr(fin._fd))==ERROR || (fp->fname[1] & 0200) /* tag2 */ || (fp->ftype[1] & 0200) /* $SYS */ ) { fclose(fin); printf("\n'%s' Not for Distribution\n", name); return ERROR; } #endif Tfile= TRUE; strcpy(Tname, name); lprintf("Open\n"); return OK; } /* closetx(status) call with status != 0 if incomplete file xmsn */ closetx(status) { if(Tfile) { fclose(fin); if(!Quiet) lpstat("%s closed", Tname); #ifdef LOGFILE if(!status) logfile(Tname, 's'); /* record file xmsn */ #endif Tfile=FALSE; } } /* search the phone file for name */ getphone(name, buffer) char *name, *buffer; { closetx(TRUE); if(fopen("YAMPHONE.T", fin)==ERROR) { printf("Cannot open %s\n", "YAMPHONE.T"); return ERROR; } else { while(fgets(buffer, fin)) if(cmdeq(buffer, name)) { fclose(fin); return OK; } } printf("Can't find data for %s\n", name); fclose(fin); return ERROR; } /* channge default disk and optionally, user number */ chdir(p) char *p; { unsigned newuser; newuser=user; *p=toupper(*p); if(index(*p, DISKS)) { defdisk= *p - 'A'; bdos(SELDSK, defdisk); #ifdef CDOS return; #else printdfr(); if(!isdigit(p[1])) return; if((newuser=atoi(p+1)) <= MAXUSER) { bdos(SETGETUSER, newuser); user=newuser; return; } #endif } printf("Disk %c and/or User %d Illegal\n", *p, newuser); } /* fetch default disk and user number */ initdd() { Secpblk=SECPBLK; defdisk= bdos(GETDEFDISK,0); #ifdef CDOS user=0; #else user=bdos(SETGETUSER, 0377); #endif } /* * Z19 gets to use it's 25th line. pstat starts at 48th char * note that a call to lpstat will erase what pstat displays */ /*VARARGS*/ pstat(a,b,c) char *a, *b, *c; { #ifdef Z19 lprintf("\033x1\033j\033Y8P"); #endif lprintf(a,b,c); #ifdef Z19 lprintf("\033K\033k"); #else lprintf("\n"); #endif } /* * Z19 gets to use it's 25th line. lpstat starts at col 1 * Rest of line is erased */ /*VARARGS*/ lpstat(a,b,c) char *a, *b, *c; { #ifdef Z19 lprintf("\033x1\033j\033Y8 "); #endif lprintf(a,b,c); #ifdef Z19 lprintf("\033K\033k"); #else lprintf("\n"); #endif } char getcty() { return bios(CONIN,0); } char putcty(c) char c; { #ifdef RXNONO if( !index(c, RXNONO)) #endif bios(CONOUT, c); if(bios(CONST,0)) { if((c=bios(CONIN,0))==XOFF) bios(CONIN,0); else return c; } return FALSE; } dolist(argc, argp) char **argp; { int listfile(); #ifdef XMODEM printf("^S pauses, ^K skips to next file, ^X terminates\n"); expand(listfile, argc, argp); #else expand(listfile, argc, argp); #endif } listfile(name) char *name; { int c; #ifdef XMODEM printf("\nListing '%s'\n\022", name); #endif closetx(TRUE); if(opentx(name)==ERROR) return ERROR; else { while((c=getc(fin))!=EOF && c != CPMEOF) { if( !(c=putcty(c))) continue; if(c==003 || c==CAN || c==013) { c=0; break; } } /* record complete xmsn iff terminated by (CPM)EOF */ closetx(c==0); #ifdef XMODEM sendline(024); /* squelch in case user downloading */ #endif } /* cancel rest of files if ^C ^X */ if(c==003 || c==CAN) return ERROR; else return OK; } /* fill buf with count chars padding with ^Z for CPM */ filbuf(buf, count) char *buf; { register c, m; m=count; while((c=getc(fin))!=EOF) { *buf++ =c; if(--m == 0) break; } if(m==count) return 0; else while(--m>=0) *buf++ = 032; return count; } dodir(argc, argp) char **argp; { int pdirent(); cfast=0; /* counter for 4 across format */ expand(pdirent, argc, argp); #ifndef CDOS printdfr(); #endif } pdirent(name) { printf("%-14s%c", name, (++cfast&03)?' ':'\n'); } #ifndef CDOS /* docomp does a directory listing showing sectors for each matched file * and computes total transmission time of matched files in batch mode * time is sum of: * number of files * open/close time (assumed 5 seconds) * time to xmit and ACK each sector assuming no path delay or error * disk i/o time at each end, not dependent on baud rate */ docomp(argc,argp) char **argp; { unsigned compsecs(); unsigned spm; /* sectors per minute-baud */ unsigned dminutes; /* tenths of minutes */ cfast=Numsecs=Numblks=0; expand(compsecs, argc, argp); /* (Baudrate*60)/(10 bits each char * 136 chars) */ spm= Baudrate/23; dminutes= Numfiles+((10*(Numfiles+Numsecs))/spm)+(Numsecs/20); printf("\n%u Files %u Blocks %u K\n", Numfiles, Numblks, (Numblks*(Secpblk/8))); printf("%u Sectors %u.%u Minutes Xmsn Time at %u Baud\n", Numsecs, dminutes/10, dminutes%10, Baudrate); } /* add file length (in CP/M 128 byte records) to Numsecs */ unsigned compsecs(ufn) char *ufn; { struct fcb fstat; printf("%-14s", ufn); unspace(ufn); setfcb( &fstat, ufn); bdos(COMPFILSIZ, &fstat); Numsecs += fstat.recn; Numblks += (fstat.recn+Secpblk)/Secpblk; printf("%4u%c",fstat.recn, (++cfast&03)?' ':'\n'); return fstat.recn; } #endif expand(fnx, argc, argp) int (*fnx)(); char **argp; { char name[PATHLEN], *s; Numfiles=0; if(argc<=0) return e1xpand(fnx, "*.*"); else while(--argc>=0) { /* change b: to b:*.* */ strcpy(name, *argp++); if((s=index(':', name)) && *++s == 0) strcpy(s, "*.*"); if(e1xpand(fnx, name)==ERROR) return ERROR; } return OK; } /* * e1xpand expands ambiguous pathname afnp * calling fnx for each. * Modified from: Parameter list builder by Richard Greenlaw * 251 Colony Ct. Gahanna, Ohio 43230 */ e1xpand(fnx, afnp) int (*fnx)(); char *afnp; /* possible ambiguous file name*/ { struct fcb sfcb, *pfcb; FLAG first; char *p, *q, i, byteaddr; int filecount, m; char tbuf[SECSIZ]; struct { char xYxx[UFNSIZE]; /* unambiguous file name */ } *fp; int strcmp(); /* build CPM fcb */ unspace(afnp); if(setfcb(&sfcb, afnp) == ERROR) { printf("%s is bad pattern\n", afnp); return ERROR; } if(Wrapped || (bufend-bufcq)<2048) { dumprxbuff(); /* I need the space for building the pathlist */ clearbuff(); /* so the printer won't try to list dir's */ bufmark=bufst; } else bufmark=bufcq; /* Search disk directory for all ufns which match afn*/ for(fp=bufmark,filecount=0,first=TRUE;; fp++,filecount++) { tryanother: bdos(SETDMA, tbuf); /* seems CP/M outta know whether to use SRCH or SRCHNXT !! */ byteaddr=bdos(first? SRCH:SRCHNXT, &sfcb); first=FALSE; if(byteaddr==255) break; /* calculate pointer to filename fcb returned by bdos */ pfcb = (tbuf + 32 * (byteaddr % 4)); #ifdef RESTRICTED /* check for $SYS or tag bit on 2nd byte of filename (TAG2) */ if((pfcb->fname[1]&0200) ||(pfcb->ftype[1]&0200)) goto tryanother; #endif Numfiles++; p = fp; if(fp>bufend) { /* Note: assumes some slop after bufend! */ printf("Out of Memory for pathname expansion\n"); return ERROR; } if(*(afnp+1) == ':') { /* Drive spec.*/ *p++ = *afnp; *p++ = ':'; } /*Copy filename from directory*/ q = pfcb; for(i =8; i; --i) *p++ = (0177& *++q); *p++ = '.' ; /*Copy file extent*/ for(i = 3; i; --i) *p++ = (0177& *++q); *p = '\0' ; } if(filecount==0) { printf("'%s' NOT FOUND\n", afnp); return ERROR; } qsort(bufmark, filecount, UFNSIZE, strcmp); for(fp=bufmark; --filecount>=0;) { p=fp++; /* execute desired function with real pathname */ if((*fnx)(p)==ERROR) return ERROR; } return OK; } /* * cisubstr(string, token) searches for lower case token in string s * returns pointer to token within string if found, NULL otherwise */ char *cisubstr(s, t) char *s,*t; { char *ss,*tt; /* search for first char of token */ for(ss=s; *s; s++) if(tolower(*s)==*t) /* compare token with substring */ for(ss=s,tt=t; ;) { if(*tt==0) return s; if(tolower(*ss++) != *tt++) break; } return NULL; } #ifdef XMODEM /* * lprintf is like regular printf but uses direct output to console * This prevents status printouts from disrupting file transfers, etc. */ lprintf(a,b,c,d,e,f) char *a, *b, *c, *d, *e, *f; { char lbuf[CMDLEN], *s; /* format data into lbuf */ sprintf(lbuf, a,b,c,d,e,f); /* now send lbuf to console directly */ for(s=lbuf; *s; ) { if(*s=='\n') { while(!COREADY) /* expand \n to \r\n */ ; outp(CDATA, '\r'); } while(!COREADY) ; outp(CDATA, *s++); } } #endif /* copy string s onto itself deleting spaces "hello there" > "hellothere" */ unspace(s) char *s; { char *p; for(p=s; *s; s++) if(*s != ' ') *p++ = *s; *p++ =0; } #ifdef LOGFILE /* * logfile keeps a record of files transmitted. * Mode is 's' for send file, 'r' for receive file, 't' for type file * Lifted from xcmodem by J. Wierda,R. Hart, and W. Earnest */ #define RPB 4 /* log records per buffer */ #define LRL 32 /* logical rec length LRL*RPB == SECSIZ */ logfile(name, mode) char *name; char mode; { struct fcb *fcbaddr(),*fp; int fd,rnum,bnum,thedisk; char *i, *lrec, lbuf[SECSIZ+2]; /* find out what disk was used */ thedisk=defdisk + 'a'; if(i=index(':', name)) { thedisk = *name; name= ++i; } bdos(SETGETUSER, 0); /* get it from user 0 */ fd=open(LOGFILE,2); if(fd == ERROR) { /* if file absent, create and initialize it */ fd = creat(LOGFILE); if(fd == ERROR) return; else { rnum=1; fp=fcbaddr(fd); fp->fname[1] |= 0200; /* set TAG2 bit */ fp->ftype[1] |= 0200; /* set SYS bit */ bdos(SETATTRIB, fp); /* and update attributes */ } } else { read(fd, lbuf, 1); rnum=atoi(lbuf)+1; } setmem(lbuf, SECSIZ, 0); sprintf(lbuf, "%d\r\n", rnum); seek(fd,0,0); write(fd, lbuf, 1); /* update last record number */ bnum=rnum/RPB; rnum=(rnum%RPB)*LRL; seek(fd, bnum, 0); if(rnum==0) setmem(lbuf, SECSIZ, 0); else read(fd, lbuf, 1); sprintf(&lbuf[rnum], "\r\n%c %5u %c%02d: %-14s", mode, Baudrate, thedisk, user, name); seek(fd, bnum, 0); write(fd,lbuf,1); close(fd); bdos(SETGETUSER, user); } #endif docrck(argc, argp) char **argp; { int crckfile(); expand(crckfile, argc, argp); } /* Accumulate and print a "crck" for a file */ crckfile(name) char *name; { unsigned crck(); char crbuf[SECSIZ]; int fd,st; printf("%14s ", name); unspace(name); if((fd=open(name, 0))==ERROR) return ERROR; oldcrc=0; while((st=read(fd, crbuf, 1)) ==1) oldcrc=crck(crbuf, SECSIZ, oldcrc); close(fd); if(st != 0) printf("READ ERROR "); else printf("CRCK= %04x\n", oldcrc); return OK; } /* print number of free blocks on default disk */ printdfr() { struct dpb *dp; dp=call(BDOS, 0, 0, GETDPBP, defdisk); Secpblk= 1 << dp->dpb_bsh; printf("%u kb Free on %c", getfree(defdisk), defdisk+'A'); } /* return total free kilobytes of disk */ unsigned getfree(disk) { struct dpb *dp; /* unsigned */ char v, c, *s; unsigned total, count; bdos(SELDSK, disk); dp=call(BDOS, 0, 0, GETDPBP, disk); s=call(BDOS, 0, 0, GETALLOCP, disk); total=0; count=dp->dpb_dsm+1; for(;;) { v= *s++; for(c=0200; c; c>>=1) { if((v & c)==0) ++total; if(--count ==0) { bdos(SELDSK, defdisk); return total << dp->dpb_bsh-3; } } } } doerase(argc, argp) char **argp; { int erasefile(); expand(erasefile, argc, argp); #ifndef CDOS printdfr(); #endif } erasefile(ufn) char *ufn; { register c; unspace(ufn); printf("Erase %s (y/n/q)? ",ufn); c=getchar(); putchar('\n'); if(tolower(c)=='y') unlink(ufn); else if(tolower(c)=='n') return FALSE; else return ERROR; return FALSE; }