/* >>:yam7.c 9-17-83 rev. for BDS C 1.5a (fappend) * 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 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; { char option; #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 option='y'; unspace(name); lprintf("'%s' ", name); /* show the name right away */ if(!Creamfile && fopen(name, fout) != ERROR) { fclose(fout); #ifdef XMODEM printf("Can't upload '%s': file exists\n", name); return ERROR; #else printf("Exists ", name); printf("Replace/Append/Quit (r/a/q)??"); if(!index(option=getopt(), "ary")) return ERROR; putchar('\n'); #endif } if((option=='a'?fappend(name,fout):fcreat(name, fout))==ERROR){ printf(" Can't create %s\n", name); return ERROR; } Rfile= TRUE; strcpy(Rname, name); Dumping= !Squelch; lprintf(" Open: Capture O%s\n", Dumping? "n" : "FF"); #ifdef STATLINE lpstat("Receiving %s", Rname); #endif return OK; } getopt() { int c; c = tolower(getcty()); putcty(c); return c; } closerx(pad) { if(Rfile) { #ifdef BDSC if(pad) do putc(CPMEOF, fout); while(fout._nleft % SECSIZ); #endif fflush(fout); fclose(fout); Rfile=FALSE; #ifdef RESTRICTED if (pad==ERROR) { unlink(Rname); printf("%s removed\n"); } else #endif #ifdef LOGRX logfile(LOGRX, Rname, pad?'*':'R'); /* record file xmsn */ #endif } } opentx(name) char *name; { int printf(); #ifdef XMODEM int lprintf(); #endif struct fcb *fp, *fcbaddr(); int (*fnx)(); #ifndef CDOS unsigned spm, dminutes; struct fcb fstat; spm= Baudrate/23; #endif #ifdef XMODEM fnx=Batch? lprintf : printf; #else fnx=printf; #endif (*fnx)("'%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); #ifndef CDOS setfcb( &fstat, name); bdos(COMPFILSIZ, &fstat); dminutes= 1+((10*(1+fstat.recn))/spm)+(fstat.recn/20); (*fnx)("Open %u Sectors %dk %u.%u Minutes\n", fstat.recn, fstat.recn>>3, dminutes/10, dminutes%10); #ifdef STATLINE if (!UsePutchar) lpstat("%s %u Sectors %dk %u.%u Min", Tname, fstat.recn, fstat.recn>>3, dminutes/10, dminutes%10); #endif #else (*fnx)("Open\n"); #endif return OK; } /* closetx(status) call with status != 0 if incomplete file xmsn */ closetx(status) { if(Tfile) { fclose(fin); #ifdef LOGTX if(!status) logfile(LOGTX, Tname, 's'); /* record file xmsn */ #endif Tfile=FALSE; } } #ifdef PHONES /* search the phone file for name */ getphone(name, buffer) char *name, *buffer; { closetx(TRUE); if(fopen(PHONES, fin)==ERROR) { printf("Cannot open %s\n", PHONES); 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; } #endif /* channge default disk and optionally, user number */ chdir(p) char *p; { unsigned newuser; if(Rfile||Tfile) { printf("Must close files first"); return ERROR; } newuser=user; *p=toupper(*p); if(index(*p, DISKS)) { defdisk= *p - 'A'; bdos(SELDSK, defdisk); #ifdef CDOS return OK; #else printdfr(); if(!isdigit(p[1])) return; if((newuser=atoi(p+1)) <= MAXUSER) { bdos(SETGETUSER, newuser); user=newuser; return OK; } #endif } printf("Disk %c and/or User %d Illegal\n", *p, newuser); return ERROR; } /* fetch default disk and user number */ initdd() { defdisk= bdos(GETDEFDISK,0); #ifdef CDOS Secpblk=SECPBLK; user=0; #else user=bdos(SETGETUSER, 0377); printdfr(); #endif Phone[0] = 0; /* initialize Phone to blank line */ #ifdef XMODEM bdos(SETGETUSER, LOGUSER); /* read user's name to Phone */ if(fopen(LASTCALR, fin)!=ERROR) { fgets(Phone, fin); fclose(fin); } bdos(SETGETUSER, user); #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; { char pbuf[40]; #ifdef Z19 lputs("\033x1\033j\033Y8P"); #endif sprintf(pbuf, a,b,c); lputs(pbuf); #ifdef Z19 lputs("\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,d,e,f,g) char *a, *b, *c, *d, *e, *f, *g; { #ifdef Z19 lputs("\033x1\033j\033Y8 "); #endif lprintf(a,b,c,d,e,f,g); #ifdef Z19 lputs("\033K\033k"); #else lprintf("\n"); #endif } dolist(argc, argp) char **argp; { int listfile(); #ifdef XMODEM printf("^S pauses, ^K skips to next file, ^X terminates\n"); #endif expand(listfile, argc, argp); } 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>>3))); 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-1)/Secpblk; if(CIREADY) return ERROR; 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; int bdoscmd; 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; } snarfbuf(); /* get some buffer */ /* Search disk directory for all ufns which match afn*/ for(fp=bufmark,filecount=0,bdoscmd=SRCH;; fp++,filecount++) { tryanother: bdos(SETDMA, tbuf); /* seems CP/M outta know whether to use SRCH or SRCHNXT !! */ byteaddr=bdos(bdoscmd, &sfcb); bdoscmd=SRCHNXT; 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\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; } /* * snarfbuf "guarantees" that bufmark points to at least SNARFBUNCH bytes * of "unused" space in the big circular buffer */ snarfbuf() { if(Wrapped || (bufend-bufcq) "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. */ logfile(log, name, mode) char *log, *name; char mode; { int thedisk; char *i; /* find out what disk was used */ thedisk=defdisk + 'a'; if(i=index(':', name)) { thedisk = *name; name= ++i; } if(i=index('\t', Phone)) /* keep just the system name */ *i = 0; #ifdef XMODEM if(i=index('\r', Phone)) /* keep just user's name */ *i = 0; if(i=index('\n', Phone)) /* keep just user's name */ *i = 0; #endif bdos(SETGETUSER, LOGUSER); /* get it from user 0 */ if(fappend(log, fin)!=ERROR) { fprintf(fin, "%c %5u %c%02d:%-14s %s\n", mode, Baudrate, thedisk, user, name, Phone); putc(CPMEOF, fin); fflush(fin); fclose(fin); } bdos(SETGETUSER, user); } #endif #ifdef BIGYAM crcfile(name) char *name; { unspace(name); oldcrc = 0; wcj = 0; if (fopen(name, Utility.ufbuf)==ERROR) return ERROR; while ((firstch=getc(Utility.ufbuf))!=EOF) { ++wcj; oldcrc = updcrc(firstch, oldcrc); } oldcrc=updcrc(0,updcrc(0,oldcrc)); fclose(Utility.ufbuf); if(CIREADY) return ERROR; printf("%04x %7u %s\n", oldcrc, wcj, name); return OK; } docrc(argc, argp) char **argp; { expand(crcfile, argc, argp); } 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, nsec; unspace(name); if((fd=open(name, 0))==ERROR) return ERROR; nsec=oldcrc=0; while((st=read(fd, crbuf, 1)) ==1) { ++nsec; oldcrc=crck(crbuf, SECSIZ, oldcrc); } close(fd); if(CIREADY) return ERROR; if(st != 0) printf("READ ERROR"); else printf("%04x %4d ", oldcrc, nsec); printf(" %s\n", name); return OK; } #endif /* 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; { unspace(ufn); printf("Erase %s (y/n/q)? ",ufn); switch(getopt()) { case 'y': unlink(ufn); case 'n': putchar('\n'); return FALSE; } return ERROR; } #ifdef BIGYAM /* * Sum bytes in file mod 2^16 discard CR and CPMEOF+junk */ sumfile(name) char *name; { unsigned nbytes; oldcrc = 0; nbytes = 0; unspace(name); if (fopen(name, Utility.ufbuf)==ERROR) { fprintf(stderr, "Can't open %s\n", name); return ERROR; } for (; (cfast=getc(Utility.ufbuf))!=EOF && cfast!=032; ) { if (cfast=='\r') continue; ++nbytes; if (oldcrc&01) oldcrc = (oldcrc>>1) + 0x8000; else oldcrc >>= 1; oldcrc += cfast; } fclose(Utility.ufbuf); if(CIREADY) return ERROR; printf("%5u%7u %s\n", oldcrc, nbytes, name); return OK; } dosum(argc, argp) char **argp; { int sumfile(); expand(sumfile, argc, argp); } /* * count lines words bytes and carriage returns in file */ wcfile(name) char *name; { unsigned lines, words, ncr; unspace(name); if (fopen(name, Utility.ufbuf)==ERROR) return ERROR; lines = words = firstch = ncr = 0; checksum = 0; for (;;) { cfast = getc(Utility.ufbuf); if (cfast == EOF || cfast == 032) break; ++firstch; if (cfast>040 && cfast<0177) { if (!checksum) { ++words; ++checksum; } continue; } if (cfast=='\n') ++lines; else if (cfast=='\r') ++ncr; else if (cfast!=' ' && cfast!='\t') continue; checksum = 0; } fclose(Utility.ufbuf); if(CIREADY) return ERROR; printf("%7u%7u%7u%7u %s\n", lines,words, firstch-ncr, firstch, name); return OK; } dowc(argc, argp) char **argp; { expand(wcfile, argc, argp); } dodumph() {} /* requres non BDS-C features */ #endif /* * Perform various operating systetm dependent initializations that should * be performed but once. */ onceonly() { char *endext(), *topofmem(), *codend(), *externs(); if(codend() > externs()) { /* check for bad -e value! */ printf("urk"); exit(); } bufst=endext(); bufend=topofmem()-512; /* fudge so we don't crash ... */ T1pause=311*CLKMHZ; /* calibration for deciseconds in readline() */ } /* * init resets all variables to their "standard" values (whatever that * is ...). Init is called at the beginning of yam and when the "init" * command is given. */ init() { #ifdef USERINIT userinit(); /* allows extra user externs' to be initialized */ #endif initdd(); /* fetch default disk and user number */ Dport=DPORT; Sport=SPORT; #ifndef DEFBAUD readbaud(); #else Baudrate=DEFBAUD; #endif #ifdef INITBAUD setbaud(Baudrate); #endif Mstatus=0; Onetrip=Exitchar=Rfile=Tfile=Pflag=FALSE; Cis02=Twxmode=Image=Waitbunch=Exoneof=Hdx=Zeof=Squelch=FALSE; Txgo=TRUE; Parity= NORMAL; Originate= TRUE; Txeoln= EOL_NOTHING; Tpause=1500*CLKMHZ; Throttle=80*CLKMHZ; Waitnum=1; GOchar= '\n'; clearbuff(); }