8-Sep-86 15:43:34-PDT,685;000000000000 Return-Path: Received: FROM UNIX.MACC.WISC.EDU BY B.ISI.EDU WITH TCP ; 8 Sep 86 15:42:14 PDT Received: by unix.macc.wisc.edu; id AA04894; 4.12/5; Mon, 8 Sep 86 17:29:02 cdt Date: Mon, 8 Sep 86 17:29:02 cdt From: Peter Wu Message-Id: <8609082229.AA04894@unix.macc.wisc.edu> To: info-ibmpc-request@mosis Subject: mv source Here comes the mv source. You should receive the following 17 files: absdr.asm break.c date.h dta.h error.c front.c fstat.c func32h.asm mv mv.c normal.c peek.h putn.asm readme.txt rm.c sector.c testmv.bat I believe you have mv.doc already. If not, let me know. peter 8-Sep-86 16:33:40-PDT,1479;000000000000 Return-Path: Received: FROM UNIX.MACC.WISC.EDU BY B.ISI.EDU WITH TCP ; 8 Sep 86 16:33:28 PDT Received: by unix.macc.wisc.edu; id AA05000; 4.12/5; Mon, 8 Sep 86 17:32:30 cdt Date: Mon, 8 Sep 86 17:32:30 cdt From: Peter Wu Message-Id: <8609082232.AA05000@unix.macc.wisc.edu> To: info-ibmpc-request@mosis Subject: readme.txt To make mv from source, you must have ibm C compiler version 1.0 or Microsoft C version 3.0 plus the macro assembler. Just type make mv and it will create mv.exe for you. If you use Microsoft C version 4.0, edit front.c to reverse the order of the parameters in the "rename" function. Files description mv makefile for mv absdr.asm absolute disk read/write BIOS call func32h.asm routine to call an undocumented DOS function putn.asm routine to print strings testmv.bat batch file I used to test mv break.c routines to check and set break status error.c routines to handle fatal errors front.c front end of the mv program fstat.c routines to find matching files and their attributes mv.c routines for moving sub-directories normal.c routines to normalize a path specification sector.c routines to do buffered disk read/write mv.exe executable version of mv peek.h macros for peeking and poking memory dta.h data structure at disk transfer address; used by fstat.c date.h defines the date when the last version was made 8-Sep-86 15:43:33-PDT,415;000000000000 Return-Path: Received: FROM UNIX.MACC.WISC.EDU BY B.ISI.EDU WITH TCP ; 8 Sep 86 15:40:13 PDT Received: by unix.macc.wisc.edu; id AA04928; 4.12/5; Mon, 8 Sep 86 17:30:43 cdt Date: Mon, 8 Sep 86 17:30:43 cdt From: Peter Wu Message-Id: <8609082230.AA04928@unix.macc.wisc.edu> To: info-ibmpc-request@mosis Subject: date.h #define date "8-20-1986" 8-Sep-86 15:59:17-PDT,818;000000000000 Return-Path: Received: FROM UNIX.MACC.WISC.EDU BY B.ISI.EDU WITH TCP ; 8 Sep 86 15:45:12 PDT Received: by unix.macc.wisc.edu; id AA04988; 4.12/5; Mon, 8 Sep 86 17:32:13 cdt Date: Mon, 8 Sep 86 17:32:13 cdt From: Peter Wu Message-Id: <8609082232.AA04988@unix.macc.wisc.edu> To: info-ibmpc-request@mosis Subject: peek.h /* pseudo functions to peek/poke byte or word. Written by Peter Wu. 6/5/86. ** Use /ze option when compiling. */ #define acc(seg,off) ((long) (seg) << 16 | (unsigned short) (off)) #define peekb(seg,off) (*(unsigned char far *)acc(seg,off)) #define pokeb(seg,off,val) (*(char far *)acc(seg,off) = (val)) #define peekw(seg,off) (*(short far *)acc(seg,off)) #define pokew(seg,off,val) (*(short far *)acc(seg,off) = (val)) 8-Sep-86 16:13:30-PDT,2449;000000000000 Return-Path: Received: FROM UNIX.MACC.WISC.EDU BY B.ISI.EDU WITH TCP ; 8 Sep 86 16:09:00 PDT Received: by unix.macc.wisc.edu; id AA04966; 4.12/5; Mon, 8 Sep 86 17:31:32 cdt Date: Mon, 8 Sep 86 17:31:32 cdt From: Peter Wu Message-Id: <8609082231.AA04966@unix.macc.wisc.edu> To: info-ibmpc-request@mosis Subject: func32h.asm ; Call DOS 32H from C. This is an undocumented function call documented in ; PC Tech. Journal May 1986. This function returns a pointer to a disk ; description table that contains the following: ; ; offset length what ; ------ ------ ---- ; 0 byte assigned physical disk (A=0, B=1, ...) ; 1 byte same as above but 0 for RAM disk ; 2 word bytes per sector ; 4 byte sectors per cluster minus 1 ; 5 byte #heads minus 1 ; 6 word reserved sectors ; 8 byte #copies of FAT (normally 2 for real disks, 1 for RAM disks) ; 9 word max directory entries ; 11 word first usable sector (i.e. data area) ; 13 word total cluster count plus 1 ; 15 byte #sectors occupied by each FAT ; 16 word first sector of the root's directory ; 18 dword device driver address ; 22 word media descriptor ; 24 dword chain to next disk table ; 28 word cluster of current working directory ; ; in C, use ; unsigned char drv, /* drive number: 1=A, 2=B, ...; 0=current drive */ ; status; /* if 0xFF means invalid drive */ ; unsigned short tabseg, /* disk description table segment */ ; taboff; /* disk description table offset */ ; ; status = func32h(drv, &tabseg, &taboff); ; ; The reason for writing this procedure in assembly is because the ; function returns the description table segment address in DS which ; cannot be accessed by using intdosx() function in C. ; ; Written by Peter Wu 6/27/86 ; _text segment public byte 'code' assume cs:_text public _func32h _func32h proc near push bp mov bp,sp push ds ; save DS mov dl,[bp+4] ; drive number mov ah,32h int 21h mov cx,bx ; stupid move, but I need bx mov si,ds pop ds mov bx,[bp+6] ; &tabseg mov [bx],si ; store tabseg mov bx,[bp+8] ; &taboff mov [bx],cx ; store taboff mov ah,0 ; al contains ffh if error pop bp ret _func32h endp _text ends end 8-Sep-86 16:13:31-PDT,2971;000000000000 Return-Path: Received: FROM UNIX.MACC.WISC.EDU BY B.ISI.EDU WITH TCP ; 8 Sep 86 16:09:21 PDT Received: by unix.macc.wisc.edu; id AA05017; 4.12/5; Mon, 8 Sep 86 17:32:54 cdt Date: Mon, 8 Sep 86 17:32:54 cdt From: Peter Wu Message-Id: <8609082232.AA05017@unix.macc.wisc.edu> To: info-ibmpc-request@mosis Subject: sector.c /* Routines to read and write sectors ** Only one copy of a particular sector will be stored, to prevent ** inconsistency. ** Written by Peter Wu 7/11/86 @ Faculty Support Center @ Univ. of Wisconsin ** This is used by the mv (move files and directory) program. ** Link with absdr (source absdr.asm) */ #define LINT_ARGS #define MAX_SECTOR_SIZE 4200 /* is this safe enough? */ #define MAX_COPIES 5 /* max # of sectors buffered */ #include /* global stuff */ char secbufx[MAX_SECTOR_SIZE * MAX_COPIES], dirty[MAX_COPIES]; int secind[MAX_COPIES], drvind[MAX_COPIES]; int num_sec; /* number of sectors currently buffered */ char *readsec(drv, sector) /* read a sector; return pointer to buffer */ int drv, sector; { int i, status; char *where; #ifdef debug printf("reading sector# %d\n",sector); #endif /* first see if the sector requested is already in buffer */ for (i=0; i < num_sec; i++) { if ((secind[i] == sector) && (drvind[i] == drv) ) { /* aha - sector already read */ return secbufx + i * MAX_SECTOR_SIZE; /* return pointer to buffer */ } } /* sector is not in buffer, so read it, but first allocate a buffer */ secind[num_sec] = sector; /* store sector number */ drvind[num_sec] = drv; where = secbufx + num_sec * MAX_SECTOR_SIZE; status = absdr(drv, 1, sector, where); if (status) { cputs("Error in readsec calling absdr\n\015"); return (char *) 0; } dirty[num_sec] = 0; /* not dirty */ num_sec++; /* successfully read a sector into buffer */ return where; } writesec(drv,sector) int drv, sector; { int i, status; #ifdef debug printf("writing sector# %d\n", sector); #endif for (i=0; i < num_sec; i++) { if ((secind[i] == sector) && (drvind[i] == drv)) { if (dirty[i]) { /* write the sector only if it's dirty */ status = absdw(drvind[i], 1, secind[i], secbufx + i * MAX_SECTOR_SIZE); if (status) { cputs("Error in flushsec calling absdw\n\015"); return -1; } else { dirty[i] = 0; } } return 0; } } /* for */ cputs("writesec: sector is not in buffer\n\015"); return -2; } flirt(drv,sector) /* turn on dirty flag */ int drv,sector; { int i; #ifdef debug printf("flirting sector# %d; num_sec = %d\n",sector,num_sec); #endif for (i=0; i < num_sec; i++) { if ((secind[i] == sector) && (drvind[i] == drv)) { dirty[i] = 1; return 0; } } cputs("flirt: can't find sector to flirt with\n\015"); return -1; } 8-Sep-86 16:13:34-PDT,3102;000000000000 Return-Path: Received: FROM UNIX.MACC.WISC.EDU BY B.ISI.EDU WITH TCP ; 8 Sep 86 16:09:52 PDT Received: by unix.macc.wisc.edu; id AA04934; 4.12/5; Mon, 8 Sep 86 17:30:54 cdt Date: Mon, 8 Sep 86 17:30:54 cdt From: Peter Wu Message-Id: <8609082230.AA04934@unix.macc.wisc.edu> To: info-ibmpc-request@mosis Subject: dta.h #define A_FIL 0x40 /* Peter's addition: this mask searches for file */ #define A_ARC 0x20 /* attributes bits of attribute byte in dta */ #define A_DIR 0x10 #define A_SYS 0x4 #define A_HID 0x2 #define A_MASK (A_FIL | A_DIR | A_SYS | A_HID) /* mask to find all files */ struct dtbuf3 { /* structure returned by dos function 0x4e and 0x4f */ /* The first 21 bytes are undocumented; use them at your own risk. ** Their use is guessed by Peter Wu. This applies only to DOS 3.xx */ unsigned char drv_no; /* drive number; 1=A 2=B 3=C ... */ char template[11]; /* file template; no period */ unsigned char match_attr; /* the search attribute */ unsigned char slotl; /* directory slot number of the matching file */ unsigned char sloth; unsigned char clusl, clush; /* cluster number of the directory being searched */ unsigned char unknown[4]; /* haven't figured out what these are */ /* end of first 21 bytes */ unsigned char attr; unsigned short time; unsigned short data; unsigned long size; /* 4 bytes */ char fn[13]; /* this is an asciiz */ unsigned char e_attr; /* extended (by peter) search attribute */ }; struct dtbuf2 { /* structure returned by dos function 0x4e and 0x4f */ /* The first 21 bytes are undocumented; use them at your own risk. ** Their use is guessed by Peter Wu. This applies only to DOS 2.xx */ unsigned char match_attr; /* the search attribute */ unsigned char drv_no; /* drive number; 1=A 2=B 3=C ... */ char template[11]; /* file template; no period */ unsigned char slotl; /* directory slot number of the matching file */ unsigned char sloth; unsigned char unknown[4]; /* haven't figured out what these are */ unsigned char clusl, clush; /* cluster number of the directory being searched */ /* end of first 21 bytes */ unsigned char attr; unsigned short time; unsigned short data; unsigned long size; /* 4 bytes */ char fn[13]; /* this is an asciiz */ unsigned char e_attr; /* extended (by peter) search attribute */ }; struct dtbufx { /* generic version (works for any DOS version */ unsigned char unknown[21]; unsigned char attr; unsigned short time; unsigned short data; unsigned long size; /* 4 bytes */ char fn[13]; /* this is an asciiz */ unsigned char e_attr; /* extended (by peter) search attribute */ /* the following info has to be copied from dtbuf2 or dtbuf3 by ** the ffmf and fnmf functions */ unsigned char drv_no; unsigned char slotl, sloth, clusl, clush; }; union dtbuf { struct dtbuf3 dos3; struct dtbuf2 dos2; struct dtbufx dos; }; 8-Sep-86 16:13:36-PDT,4915;000000000000 Return-Path: Received: FROM UNIX.MACC.WISC.EDU BY B.ISI.EDU WITH TCP ; 8 Sep 86 16:10:13 PDT Received: by unix.macc.wisc.edu; id AA04962; 4.12/5; Mon, 8 Sep 86 17:31:25 cdt Date: Mon, 8 Sep 86 17:31:25 cdt From: Peter Wu Message-Id: <8609082231.AA04962@unix.macc.wisc.edu> To: info-ibmpc-request@mosis Subject: fstat.c /* find first matching file and find next matching file ** written by Peter Wu @ Faculty Support Center @ Univ. of Wisconsin ** July 1986 ** ** These functions have an extended capability to search for ** directories only and ffmf does not return "." and "..", also ** ffmf will attempt to find root directory and return at least drive number */ #define LINT_ARGS #include "dta.h" #include #include char lastc(char *); ffmf(fn,attr,dta) /* extended version of ffmf1; fn must be normalized */ char *fn; short attr; union dtbuf *dta; { int status, len; char tmpfn[200]; union dtbuf tmpdta; if (lastc(fn) != '\\') { /* if not looking for root directory */ status = ffmf1(fn,attr,dta); if (status) { return status; } /* get rid of "." and ".." */ while (dta->dos.fn[0] == '.') { status = fnmf1(dta); if (status) { return status; } } /* now check to see if we should return plain files or not */ while ( ((dta->dos.e_attr & A_FIL) == 0) && ((dta->dos.attr & A_DIR) == 0) ) { /* user didn't ask for plain files and we found one, so skip it */ status = fnmf1(dta); if (status) { return status; } } fixdta(dta,dta); return 0; } /* fn ends with '\', i.e. looking for root directory. In DOS 3 ffmf1 won't ** find root directory, and in DOS 2 ffmf1 found root directory but reports ** it to have file status! Neither is right. So I have to fix it here. ** The reason for wanting to look for a root directory is to find out ** what drive it's on. */ if (attr & A_DIR) { /* look for '\*.*' */ strcpy(tmpfn, fn); strcat(tmpfn, "*.*"); status = ffmf1(tmpfn, A_DIR | A_FIL, &tmpdta); if (status) { /* can't even find root this way: root must be empty */ cputs("empty root directory; nothing to move\n\015"); exit(1); } /* found root; now fill in dta */ dta->dos.fn[0] = '\0'; dta->dos.attr = A_DIR; fixdta(&tmpdta,dta); return 0; } else { return 1; /* can't find root because attr is not set to A_DIR */ } } /* extended version of fnmf1; because of the way ffmf handle root directory, ** calling ffmf to find root directory and then call fnmf will cause an ** "allocation table bad" error */ fnmf(dta) union dtbuf *dta; { int status; status = fnmf1(dta); if (status) { return status; } while (((dta->dos.e_attr & A_FIL) == 0) && ((dta->dos.attr & A_DIR) == 0)) { /* user didn't ask for plain file and we found one, so skip it */ status = fnmf1(dta); if (status) { return status; } } fixdta(dta,dta); return 0; /* no error */ } /* copy dos dependent dta fields to the dos independent dta fields */ fixdta(from, to) union dtbuf *from, *to; { switch (_osmajor) { case 3: /* dos 3.xx */ to->dos.drv_no = from->dos3.drv_no; to->dos.slotl = from->dos3.slotl; to->dos.sloth = from->dos3.sloth; to->dos.clusl = from->dos3.clusl; to->dos.clush = from->dos3.clush; to->dos3.drv_no = from->dos3.drv_no; /* for compatiblity sake */ break; case 2: /* dos 2.xx */ to->dos.drv_no = from->dos2.drv_no + 1; to->dos.slotl = from->dos2.slotl; to->dos.sloth = from->dos2.sloth; to->dos.clusl = from->dos2.clusl; to->dos.clush = from->dos2.clush; to->dos2.drv_no = from->dos2.drv_no; /* for compatiblity sake */ break; default: cputs("unexpected DOS version\n\015"); error("fixdta", 0); } } ffmf1(fn,attr,dta) /* don't call this, call ffmf */ char *fn; short attr; union dtbuf *dta; { union REGS inregs,outregs; dta->dos.e_attr = attr; /* store the extended attribute */ bdos(0x1a, (int) dta, 0); /* set dta */ inregs.h.ah = 0x4e; inregs.x.dx = (int) fn; inregs.x.cx = attr & 0x3f; /* mask off the A_FIL bit */ intdos(&inregs, &outregs); /* now find first entry */ if (outregs.x.cflag) { return outregs.x.ax; /* return error code */ } return 0; /* no error */ } fnmf1(dta) /* find next matching file */ union dtbuf *dta; { union REGS inregs,outregs; bdos(0x1a, (int) dta, 0); /* set dta */ inregs.h.ah = 0x4f; intdos(&inregs, &outregs); /* now find next entry */ if (outregs.x.cflag) { return (outregs.x.ax); } else { return (0); }; } 8-Sep-86 16:13:37-PDT,7276;000000000000 Return-Path: Received: FROM UNIX.MACC.WISC.EDU BY B.ISI.EDU WITH TCP ; 8 Sep 86 16:10:31 PDT Received: by unix.macc.wisc.edu; id AA05007; 4.12/5; Mon, 8 Sep 86 17:32:42 cdt Date: Mon, 8 Sep 86 17:32:42 cdt From: Peter Wu Message-Id: <8609082232.AA05007@unix.macc.wisc.edu> To: info-ibmpc-request@mosis Subject: rm.c /* rm - removes everything you got ** Written by Peter Wu @ Faculty Support Center, MACC ** University of Wisconsin - Madison. August 1986. */ #include "dta.h" #include #include #include "date.h" char getkey(); main(argc, argv) int argc; char *argv[]; { int brgc, optc, i, status, mask; char *brgv[40], opt[26], *t, path[200], c; optc = 0; brgc = 0; /* my argc */ for (i=0; i < 26; i++) { /* clear options flags */ opt[i] = 0; }; for (i=1; i < argc; i++) { /* separate options and arguments */ if ((*argv[i] == '/') || (*argv[i] == '-')) { t = argv[i] + 1; while ((c = *t++) != '\0') { if (isalpha(c)) { opt[tolower(c) - 'a'] = 1; }; }; } else { brgv[brgc++] = argv[i]; }; }; if (brgc == 0) { putn("Usage: RM \15\n", " can have wild cards and attributes /f /d /h; E.g:\15\n", " *.* or *.*/f selects all visible file\15\n", " *.*/h or *.*/hf selects all visible and invisible files\15\n", " *.*/d selects all sub-directories\15\n\n", "For each file/sub-directory found, you will be prompted for\15\n", "one of the following actions:\15\n", " y - yes, delete this file\15\n", " Y - Yes, delete this sub-directory\15\n", " n - no, leave this file/sub-directory alone\15\n", " l - list all the rest of the files/sub-directories\15\n", " Z - delete ALL the rest of the files/sub-directories\15\n", " e - enter this sub-directory\15\n", " x - exit current sub-directory\15\n", " q - quit now\15\n\n", "Version 2.00 made ", date, ".\15\n", "Please report problems to Peter Wu.\15\n", 0); exit(0); } /* process parameters */ for (i=0; i < brgc; i++) { strcpy(path, brgv[i]); mask = extype(path); mask |= normal(path); if ((mask & (A_FIL | A_DIR)) == 0) { mask |= A_FIL; /* default is remove files only */ } rm(1, 1, mask, path); } } rm(confirm, echo, mask, path) /* use normalized path only */ int confirm, mask; char *path; { char headp[200], fullp[200], c; int status; union dtbuf mydta; strcpy(headp,path); chopath(headp); /* cut off the tail (might be wild-cards) */ status = ffmf(path, mask, &mydta); if (status) { /* not found; probably empty directory */ return 1; /* let the caller print out the error message */ } do { /* for all matching files */ strcpy(fullp, headp); catpath(fullp, mydta.dos.fn); /* now fullp has the expanded name */ do { if (confirm | echo) { /* echo files to be deleted */ cputs(fullp); if (mydta.dos.attr & A_DIR) { cputs(" "); } if (mydta.dos.attr & (A_HID | A_SYS)) { cputs(" (hidden)"); } } if (confirm) { /* ask for confirmation */ status = 0; if (mydta.dos.attr & A_DIR) { /* sub-dir */ cputs(" (Y/n/e/x/l/Z/q; ? for help): "); c = getkey("YynlxqeZ?"); } else { /* file */ cputs(" (y/n/x/l/Z/q; ? for help): "); c = getkey("YynlxqZx?"); } switch (c) { case '?': putn("\15\n", "Y - Yes, delete this sub-directory and its content\15\n", "y - yes, delete this file\15\n", "n - no, skip this file/sub-directory\15\n", "e - enter this sub-directory\15\n", "x - exit current sub-directory\15\n", "l - list all the rest of the files/sub-directories\15\n", "Z - delete ALL the rest without further prompts\15\n", "q - quit to DOS now\15\n", 0); cputs("\n"); break; case 'Z': putn("\15\n", "Really delete ALL the remaning files/sub-directories (Y/N)? ", 0); c = getkey("YNn"); if (c == 'Y') { confirm = 0; /* no more confirmation request */ status = 1; /* exit loop */ } cputs("\15\n"); break; case 'q': cputs("\15\nquiting to DOS\15\n"); exit(0); case 'x': cputs("\15\n"); return 2; /* exit current directory */ case 'y': if (mydta.dos.attr & A_DIR) { /* can't delete sub-dir with 'y' */ cputs(" use Y to delete sub-directory\15\n"); } else { /* delete this file */ putch(' '); /* move the cursor */ status = 1; /* delete */ } break; case 'Y': if (mydta.dos.attr & A_DIR) { putch(' '); /* move the cursor */ status = 1; /* delete */ } else { /* can't delete file with 'Y' */ cputs(" use y to delete file\15\n"); } break; case 'n': status = 2; break; case 'l': cputs("\15\n"); ls(mydta); break; case 'e': cputs(" entering sub-directory\15\n\n"); catpath(fullp,"*.*"); if (rm(1,1,A_MASK,fullp) == 1) { /* empty */ cputs("This sub-directory is empty. "); } chopath(fullp); cputs("Returning to previous directory.\15\n\n"); break; default: /* what did I miss? */ cputs("invalid key\15\n"); } } else { /* confirm == 0 */ status = 1; } } while (!status); /* status == 1 delete; status == 2 don't delete */ if (status == 1) { /* delete */ if (mydta.dos.attr & A_DIR) { /* delete sub-directory */ /* check if fullp is parent of current path */ /* call rm recursively to remove stuff */ catpath(fullp,"*.*"); rm(0,0,A_MASK,fullp); /* no confirmation for this */ chopath(fullp); /* cut off the "*.*" */ status = rmdir(fullp); if (echo || confirm) { if (status) { cputs(" can't delete\15\n"); } else { cputs(" Deleted\15\n"); } } } else { /* delete files */ status = unlink(fullp); if (echo || confirm) { if (status) { cputs(" can't delete\15\n"); } else { cputs(" deleted\15\n"); } } } } else { cputs(" not deleted\15\n"); } status = fnmf(&mydta); } while (status == 0); return 0; } ls(cdta) /* list the rest of the files */ union dtbuf cdta; { int i, status, col, slen; /* column */ col = 0; do { /* list the current one also */ col++; slen = strlen(cdta.dos.fn); cputs(cdta.dos.fn); if (cdta.dos.attr & A_DIR) { putch('\\'); slen++; }; if (col < 5) { for (i=slen; i < 16; i++) { putch(' '); } } else { cputs("\15\n"); col = 0; } status = fnmf(&cdta); } while (!status); if (col) { /* complete last line */ cputs("\15\n"); } } char getkey(valid) /* wait for valid key and echo it */ char *valid; { char c; int i; do { c = getch(); i = index(valid,c); if (i > -1) { putch(c); /* echo valid key */ return c; } else { /* beep at invalid key */ putch(7); } } while (1); } 8-Sep-86 16:13:38-PDT,8378;000000000000 Return-Path: Received: FROM UNIX.MACC.WISC.EDU BY B.ISI.EDU WITH TCP ; 8 Sep 86 16:10:58 PDT Received: by unix.macc.wisc.edu; id AA05024; 4.12/5; Mon, 8 Sep 86 17:33:02 cdt Date: Mon, 8 Sep 86 17:33:02 cdt From: Peter Wu Message-Id: <8609082233.AA05024@unix.macc.wisc.edu> To: info-ibmpc-request@mosis Subject: testmv.bat echo off rem *** run this batch file if you suspect mv does not work properly on rem *** your system echo This batch file is used to test MV. Run in an empty directory. echo Make sure path points to MV. echo  if %2.==root. goto ok if %2.==notroot. goto ok goto noarg :ok echo > a if not exist %1:a goto userr pause echo The DOS is ver rem *** create files and directories to play with echo Creating files and sub-directories to test MV echo > b echo > c echo > e md f echo > f\x.x md g echo > g\y.y rem *** test #1 - #19 exercise the "front" module echo test#1 (file rename) rem *** should not cause any error mv e d > mv.log if errorlevel 1 goto err if exist e goto wrong if not exist d goto wrong echo test#2 (sub-directory rename) rem *** should not cause any error mv f e > mv.log if errorlevel 1 goto err if exist f\x.x goto wrong if not exist e\x.x goto wrong echo test#3 (try to rename file to another existing file) rem *** MV should detect error mv a b > mv.log if errorlevel 2 goto err if not errorlevel 1 goto noerr if not exist a goto wrong echo test#4 (try to move multiple sources to a file name) rem *** MV should detect error mv * x. > mv.log if errorlevel 2 goto err if not errorlevel 1 goto noerr if exist x goto wrong echo test#5 (similar to test#4) rem *** MV should detect error mv a b c > mv.log if errorlevel 2 goto err if not errorlevel 1 goto noerr if not exist a goto wrong if not exist b goto wrong echo test#6 (try to move multiple source to a name) rem *** MV should detect error mv * x > mv.log if errorlevel 2 goto err if not errorlevel 1 goto noerr if exist x goto wrong echo test#7 (try to move file into non-existing sub-directory) rem *** MV should detect error mv a f\. > mv.log if errorlevel 2 goto err if not errorlevel 1 goto noerr if not exist a goto wrong echo test#8 (destination path not exist) rem *** MV should detect error mv a f\a > mv.log if errorlevel 2 goto err if not errorlevel 1 goto noerr if not exist a goto wrong echo test#9 (destination path includes a file) rem *** MV should detect error mv a b\c > mv.log if errorlevel 2 goto err if not errorlevel 1 goto noerr if not exist a goto wrong echo test#10 (destination specified as sub-directory but exists as a file) rem *** MV should detect error mv a b\. > mv.log if errorlevel 2 goto err if not errorlevel 1 goto noerr if not exist a goto wrong echo test#11 (destination is ambigious) rem *** MV should detect error mv a * > mv.log if errorlevel 2 goto err if not errorlevel 1 goto noerr if not exist a goto wrong echo test#12 (similar to test#11) rem *** MV should detect error mv a *\. > mv.log if errorlevel 2 goto err if not errorlevel 1 goto noerr if not exist a goto wrong echo test#13 (rename file to itself) rem *** MV should detect error mv a . > mv.log if errorlevel 1 goto err if not exist a goto wrong echo test#14 (source not exist) rem *** MV should detect error mv x y > mv.log if errorlevel 1 goto err if exist y goto wrong echo test#15 (move files and sub-directory into sub-directory) rem *** no error mv *. e > mv.log if errorlevel 1 goto err if not exist e\a goto wrong if not exist e\b goto wrong if not exist e\c goto wrong if not exist e\d goto wrong if not exist e\g\y.y goto wrong if exist a goto wrong if exist b goto wrong if exist c goto wrong if exist d goto wrong if exist g\y.y goto wrong echo test#16 (move sub-directory to current directory) rem *** no error mv e\g . > mv.log if errorlevel 1 goto err if not exist g\y.y goto wrong if exist e\g\y.y goto wrong echo test#17 (move one sub-directory into another) rem *** no error mv e g > mv.log if errorlevel 1 goto err if not exist g\e\a goto wrong if exist e\a goto wrong echo test#18 (move file to current directory) rem *** no error mv g\e\a .\a > mv.log if errorlevel 1 goto err if not exist a goto wrong if exist g\e\a goto wrong echo test#19 (multiple sources with one missing source) mv g\e\a g\e\b g\e\* . > mv.log if errorlevel 1 goto err if not exist b goto wrong if not exist c goto wrong if not exist d goto wrong rem *** the following tests exercise the "mv" module echo test#20 (try to move current directory) rem *** should cause error mv . x > mv.log if errorlevel 2 goto err rem *** if current directory is root it'll cause errorlevel 1; if %2==root if not errorlevel 1 goto noerr rem *** otherwise no errorlevel if %2==notroot if errorlevel 1 goto err echo test#21 (try to move parent into child) rem *** should cause error but no errorlevel mv g g\e > mv.log if errorlevel 1 goto err if not exist g\y.y goto wrong echo test#22 (similar to test#21) mv g g\e\egg > mv.log if errorlevel 1 goto err if not exist g\y.y goto wrong echo test#23 (similar to test#21) mv g g > mv.log if errorlevel 1 goto err if not exist g\y.y goto wrong echo test#24 (try to move parent of current directory) mv .. g > mv.log rem *** depending on where you are, you either get rem *** "sorry, but root directory has no parent" (errorlevel 1) or rem *** "not moved to preserve current directory" (errorlevel 0) or rem *** "move root directory? You're joking" (errorlevel 1) if not exist a goto wrong if %2==root if not errorlevel 1 goto noerr rem *** if %2==notroot if errorlevel 1 goto err rem ** the following tests exercise the "normal" module echo test#25 (path includes drive letters but no root) mv %1:a %1:h > mv.log if errorlevel 1 goto err if not exist h goto wrong if exist a goto wrong echo test#26 (path includes drive letters and root) mv %1:h %1:\i > mv.log if errorlevel 1 goto err if not exist \i goto wrong if exist h goto wrong echo test#27 (path has upper case letters) mv %1:\I A > mv.log if errorlevel 1 goto err if not exist a goto wrong if exist %1:\i goto wrong echo test#28 (missing sub-directory name before \..) mv a \\.. > mv.log if errorlevel 2 goto err if not errorlevel 1 goto noerr if not exist a goto wrong echo test#29 (sub-directory name too long) mv a aaaaaaaaaaaaaaaaaaaaa > mv.log if errorlevel 2 goto err if not errorlevel 1 goto noerr if not exist a goto wrong echo test#30 (referencing root's parent) mv a \.. > mv.log if errorlevel 2 goto err if not errorlevel 1 goto noerr if not exist a goto wrong echo test#31 (.. not preceded by \) mv a .... > mv.log if errorlevel 2 goto err if not errorlevel 1 goto noerr if not exist a goto wrong echo test#32 (source is "*\a\..") rem *** this is equal to mv g g which won't be performed mv *\a\.. g > mv.log if errorlevel 1 goto err if not exist a goto wrong if not exist g\y.y goto wrong echo test#33 (no \ before .) mv %1:\g:.\y.y . > mv.log if errorlevel 2 goto err if not errorlevel 1 goto noerr if not exist g\y.y goto wrong if exist y.y goto wrong echo MV passed all tests! Note the tests in here are not exhaustive. echo Now removing test files created awhile ago... del g\e\x.x > nul rd g\e > nul del g\y.y > nul rd g > nul del a > nul del b > nul del c > nul del d > nul del mv.log > nul goto dos rem *** error handling routines :err echo MV reported an error that shouldn't happen: goto abort :noerr echo MV failed to detect an error and did this: goto abort :wrong echo MV performed an operation incorrectly: goto abort :abort type mv.log echo Please report this problem to the author (type MV without parameters echo to get the author's email address). goto dos :userr del a echo You have entered the incorrect parameters to testmv. :noarg echo Usage: testmv [drv] [where] echo. echo [drv] is the drive letter of the drive you're running testmv on (e.g echo "A"). No quote, no colon. echo. echo [where] is echo "root" if you're running testmv in a root directory echo "notroot" if you're running testmv in a sub-directory goto dos :dos 8-Sep-86 16:13:40-PDT,10218;000000000000 Return-Path: Received: FROM UNIX.MACC.WISC.EDU BY B.ISI.EDU WITH TCP ; 8 Sep 86 16:11:21 PDT Received: by unix.macc.wisc.edu; id AA04984; 4.12/5; Mon, 8 Sep 86 17:32:05 cdt Date: Mon, 8 Sep 86 17:32:05 cdt From: Peter Wu Message-Id: <8609082232.AA04984@unix.macc.wisc.edu> To: info-ibmpc-request@mosis Subject: normal.c /* The normal routine "normalizes" a given path name to this form: ** drv:\id\id\id...\id ** so that two identical directory/file will yield the same normalized ** path regardless of how the user typed it in ** E.g. ** a:\x\y ** and a:\X\Y\. ** both yield A:\X\Y ** ** In addition, the later one would also set the flag "dir" indicating ** that the user specified it as an directory. ** ** Written by Peter Wu @ Faculty Support Center @ Univ. of Wisconsin ** 7/14/86 */ #define LINT_ARGS #define T_ID1 1 /* id without period */ #define T_ID2 2 /* id with period */ #define T_DOT 3 /* '.' */ #define T_DD 4 #define T_BS 5 #define T_COL 6 #define T_NUL 0 #define PLEN 200 #include "dta.h" #include #include #include char *insert(); index(str,c) /* first occurence of c in str */ char *str, c; { char *i, d; i = str; d = *i; while (d != '\0') { if (d == c) { return i - str; } i++; d = *i; } return -1; } char lastc(str) /* return last character of the string */ register char *str; { if (*str == '\0') { return '\0'; } do { str++; } while (*str != '\0'); return *(str-1); } normal(path) /* normalize a path */ char *path; { char work1[PLEN], work2[PLEN], token[13], token2[13], *ptr; int i, drv, marker, t, ftype, t2; /* first make the path a complete path (include drive, and root) */ i = index(path, ':'); /* search for ':' */ if (i < 0) { /* no drive specification */ if (*path == '\\') { /* start from root */ work1[0] = '@' + drive(); /* default disk */ work1[1] = ':'; work1[2] = '\0'; strcat(work1, path); /* now we have full path */ } else { /* no drive and no root */ current(0,work1); /* get current drive and path */ strcat(work1, path); /* now we have full path */ } } else { /* has drive specification */ if (path[i+1] != '\\') { /* but no root, so insert current path */ drv = toupper(path[i-1]) - '@'; /* get drive number */ current(drv,work1); /* put down drive and path */ strcat(work1, path+i+1); /* now we have full path */ } else { /* nothing to change */ strcpy(work1,path); } } #ifdef debug printf("full path is %s\n", work1); #endif /* now we have full path in work, but we need to normalize it to get rid ** of .. and . */ marker = -2; work2[PLEN-1] = '\0'; /* this is where we accumulate result backwards */ ptr = work2 + PLEN - 1; /* point to first char */ ftype = -1; /* -1=init, 0=unknown, A_DIR=dir */ do { t = scan(work1,&marker,token); #ifdef debug printf("ptr is %s so far, and t=%d\n", ptr, t); #endif switch (t) { case T_ID1: case T_ID2: ptr = insert(token, ptr); if (ftype == -1) { ftype = 0; /* this could be a file or a directory */ } break; #ifdef ha case T_ID2: if (ftype == -1) { ptr = insert(token, ptr); ftype = 0; /* this could be file or a directory */ } else { putn("invalid sub-directory name \"", token, "\"\n\015",0); exit(1); } break; #endif case T_DD: t2 = scan(work1,&marker,token); /* see what's in front of .. */ if (t2 != T_BS) { cputs("incorrect use of \"..\" in path name\n\015"); exit(1); } /* now delete \id\.. */ t2 = scan(work1,&marker,token); /* read the id (hopefully) */ if (t2 != T_ID1) { if (t2 == T_COL) { /* A:\.. is not permitted */ cputs("sorry, but root directory has no parent\n\015"); exit(1); } else { /* what could t2 be? */ cputs("missing directory name in front of \"\\..\"\n\015"); exit(1); } } t2 = scan(work1,&marker,token2); /* read the '\' */ if (t2 != T_BS) { /* can this ever happen? */ putn("missing \"\\\" before \"", token, "\" in path\n\015",0); exit(1); } /* a:\id\.. should yield a:\ not a: ** a:\id1\..\id2 should yield a:\id2 ** this is taken care of somewhere else */ /* ok, \id\.. deleted */ if (ftype == -1) { ftype = A_DIR; /* this has to be a directory */ } break; case T_DOT: t2 = scan(work1,&marker,token); /* read the '\' */ if (t2 != T_BS) { /* is this error possible? */ cputs("missing \"\\\" in front of \".\"\n\015"); exit(1); } if (ftype == -1) { ftype = A_DIR; /* this has to be a directory */ } /* special case is "a:\." we want this to turn into "A:\" and ** not "A:" */ break; case T_BS: ptr = insert("\\", ptr); break; case T_COL: ptr = insert(":", ptr); break; } } while (t != T_NUL); if (lastc(ptr) == ':') { /* special case like a: */ strcat(ptr,"\\"); } strcpy(path,ptr); /* copy normalized path back */ return ftype; } char *insert(str1,str2) /* insert str1 in front of str2 */ char *str1, *str2; { int i; i = strlen(str1); return strncpy(str2 - i, str1, i); } scan(path,marker,tstr) /* return tokens backwards */ char *path, *tstr; int *marker; { char c, d; if (*marker == -2) { /* signal new scan */ *marker = strlen(path) - 1; } if (*marker == -1) { return T_NUL; /* end of string */ } c = path[*marker]; if ( ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) ) { return collect_id(tstr, path, marker); } switch (c) { case '.': if (*marker == 0) { *marker = -1; return T_DOT; } else { d = path[*marker - 1]; if (d == '.') { *marker -= 2; return T_DD; } else { if ((d == '\\') || (d == ':')) { (*marker)--; return T_DOT; } else { /* must be an id with trailing dot */ return collect_id(tstr, path, marker); } } } cputs("how do I get here?\n\015"); error("scan", 0); case '\\': (*marker)--; return T_BS; case ':': (*marker)--; return T_COL; default: return collect_id(tstr, path, marker); /* non-alpha ID */ } } check_fn(fn) /* look for invalid characters in file name */ char *fn; { char c, *s; s = fn; while((c= *s) != '\0') { if ((c < 32) || (index("\"[]|<>+=;,/",c) > -1)) { cputs("invalid character '"); putch(c); putn("' in file/sub-directory name \"", fn, "\"", 0); exit(1); } s++; } } collect_id(tstr, path, marker) char *tstr, *path; int *marker; { char c, id[13]; int flag, i; id[13] = '\0'; i = 12; flag = T_ID1; /* no period in file name */ do { c = path[*marker]; if (c == '.') { /* indicate there's period in the file name */ flag = T_ID2; } else { c = toupper(c); if ((c == '\\') || (c == ':')) { /* end of file name */ strncpy(tstr, id+i+1, 13); check_fn(tstr); return flag; } } id[i] = c; (*marker)--; if (*marker < 0) { strncpy(tstr, id+i, 13); check_fn(tstr); return flag; } i--; } while (i >= 0); putn("file/sub-directory name \"...", id, "\" too long\n\015", 0); exit(1); } drive() /* returns current drive number 1=A 2=B */ { return (char) bdos(0x19, 0, 0) + 1; } current(drv,cpath) /* returns current path "drv:\id\..\id on drv */ char *cpath; int drv; { union REGS inregs, outregs; char tmp[64]; inregs.h.ah = 0x47; /* get current path */ inregs.x.si = (int) tmp; inregs.h.dl = drv; /* drive number 0=default, 1=A, 2=B */ intdos(&inregs, &outregs); if (drv == 0) { drv = drive(); } if (outregs.x.cflag) { cputs("cannot find current path on drive "); putch('@'+drv); cputs(":\n\015"); error("current", 0); } else { cpath[0] = '@' + drv; cpath[1] = ':'; cpath[2] = '\\'; cpath[3] = '\0'; if (*tmp != '\0') { /* if not root directory, then append path */ strcat(cpath,tmp); strcat(cpath,"\\"); } } } catpath(path, name) /* concatenate name to path, adding \ when neccessary */ char *path, *name; { if (path[strlen(path)-1] != '\\') { strcat(path, "\\"); } strcat(path, name); } chopath(path) /* chop off the last portion of a path */ char *path; { char *tmp; tmp = strrchr(path, '\\'); if (tmp == (char *)0) { putn("can't find '\\' in ", path, "\n\015"); error("chopath", 0); } if (*(tmp-1) == ':') { /* special case for root */ *(tmp+1) = '\0'; } else { *tmp = '\0'; } } #define A_INT 0x80 /* interactive flag */ /* extract the file-type specifier from the given path. ** E.g. x/f becomes x with file type, ** animal\dog/d becomes animal\dog with sub-directory type, ** *./f becomes *. with file type ** *.* becomes *.* with no type ** *./i becomes interactive (i.e. prompts for y/n for each file) */ extype(path) char *path; { char *s; int type; s = strrchr(path,'/'); /* find last '/' */ if (s == (char *) 0) { /* no type specified */ return 0; } type = 0; *s = '\0'; for (++s; *s != '\0'; s++) { /* start scanning after the '/' */ switch (*s) { case 'f': case 'F': type |= A_FIL; break; case 'd': case 'D': type |= A_DIR; break; case 'h': case 'H': type |= A_HID; /* search for hidden files */ break; case 'i': case 'I': type |= A_INT; /* requires user's confirmation for each file */ break; default: cputs("unrecognized file attribute '"); putch(*s); putn("' in path \"", path, "\"\n\015", "valid attributes are f, d, h, i (for file, directory, hidden, interac)\n\015", 0); exit(1); /* maybe the user doesn't know what he/she's doing */ } } return type; } 8-Sep-86 16:13:41-PDT,10586;000000000000 Return-Path: Received: FROM UNIX.MACC.WISC.EDU BY B.ISI.EDU WITH TCP ; 8 Sep 86 16:11:55 PDT Received: by unix.macc.wisc.edu; id AA04974; 4.12/5; Mon, 8 Sep 86 17:31:56 cdt Date: Mon, 8 Sep 86 17:31:56 cdt From: Peter Wu Message-Id: <8609082231.AA04974@unix.macc.wisc.edu> To: info-ibmpc-request@mosis Subject: mv.c /* move subdirectories ** Written by Peter Wu; July 86. ** compile with cc mv /ze */ #define LINT_ARGS #define ALLOC 26 /* starting cluster number in directory entry */ #define PLEN 200 /* max path len */ #include #include "dta.h" #include "peek.h" #include unsigned char func32h(); unsigned short findir(); unsigned short clus2sec(); char *readsec(); char lastc(char *); /* external var */ extern int num_sec, /* number of sectors buffered */ brk_st; /* orginal break status */ /* source & dest must be normalized ** mydta1 contains dta of source */ mvdir(mydta1,source,dest) /* move subdirectory */ union dtbuf mydta1; char *source, *dest; { unsigned short sector1, sector2, offset1, offset2, clus1, clus2, d1, d2, status, *p1, *p2, ds1, ds2, bps, parent, drv; char *secbuf1, *secbuf2, *secbuf3, cpath[PLEN]; union dtbuf mydta2; /* make sure source is not a root directory */ if (lastc(source) == '\\') { cputs("move root directory? You're joking!\n\015"); exit(1); } /* make sure source is not predesessor of destination */ if (apreb(source, dest)) { /* this will create a directory loop if allowed to go on or ** it's a redundant rename to itself (e.g. mv \ha\. \) */ cputs("not moved to avoid loop or redundancy\n\015"); return -1; } /* see if source is a predesessor of current directory */ drv = toupper(source[0]) - '@'; current(drv,cpath); if ( apreb(source,cpath) ) { cputs("not moved to preserve current directory\n\015"); return -2; } /* there should be a way to test if source is a predesessor of ** a 'subst' drive's current directory */ bset(0); /* do not allow user to break during this portion */ status = mkdir(dest); if (status) { cputs("can't; file exists already/disk write protected\n\015"); exit(1); } status = ffmf(dest, A_DIR, &mydta2); if (status) { cputs("error on ffmf after mkdir\n\015"); error("mvdir", 0); } num_sec = 0; /* clear sector buffers */ sector1 = findir(mydta1, &offset1, &d1, &secbuf1); if (sector1 < 0) { cputs("cannot find source directory\n\015"); error("mvdir", 0); } sector2 = findir(mydta2, &offset2, &d2, &secbuf2); if (sector2 < 0) { cputs("cannot get info on destination directory\n\015"); error("mvdir", 0); } if (d1 != d2) { /* this should have been detected earlier */ cputs("source and destination has to be on the same drive\n\015"); exit(1); } /* now switch the starting cluster of the two directories */ p1 = (unsigned short *) (secbuf1 + offset1 + ALLOC); p2 = (unsigned short *) (secbuf2 + offset2 + ALLOC); clus1 = *p1; clus2 = *p2; #ifdef debug printf("clus1=%4x clus2=%4x\n", clus1, clus2); #endif *p1 = clus2; *p2 = clus1; status = flirt(d1-1, sector1); /* mark sector dirty */ if (status) { cputs("error in flirt\n\015"); error("mvdir", 0); } status = flirt(d1-1, sector2); /* mark sectors as dirty */ if (status) { cputs("error in flirt\n\015"); error("mvdir", 0); } /* now we must find the cluster# of the parent directory of the ** destination directory (slot number two in clus2) so we can put ** it in the source directory (which will become the new destination ** directory. */ ds1 = clus2sec(d1, clus1, &bps); /* sector# of cluster 1 */ ds2 = clus2sec(d1, clus2, &bps); /* sector# of cluster 2 */ secbuf3 = readsec(d1-1,ds2); if (secbuf3 == (char *) 0) { cputs("error in calling readsec\n\015"); error("mvdir", 0); } parent = * (unsigned short *) (secbuf3 + 32 + ALLOC); #ifdef debug printf("parent is %4x\n", parent ); #endif /* now write this into source dir */ secbuf3 = readsec(d1-1,ds1); if (secbuf3 == (char *) 0) { cputs("error in calling readsec\n\015"); error("mvdir", 0); } *(unsigned short *) (secbuf3 + 32 + ALLOC) = parent; status = flirt(d1-1,ds1); if (status) { cputs("error in flirt\n\015"); error("mvdir", 0); } /* now write back all three (or less) modified sectors */ status = writesec(d1-1,ds1); /* the cluster containing parent pointer */ if (status) { /* what could cause this to happen? */ cputs("error in writing first modified sector\n\015"); error("mvdir", 1); } status = writesec(d1-1,sector2); if (status) { /* it's impossible for this to happen */ cputs("error in writing second modified sector\n\015"); error("mvdir", 1); } status = writesec(d1-1,sector1); if (status) { /* this is also impossible */ cputs("error in writing third modified sector\n\015"); error("mvdir", 1); } bdos(0xd,0,0); /* reset disk - neccessary for rmdir to work since DOS' ** buffer now contains invalid information (it didn't ** know that the disk was modified). */ status = rmdir(source); /* this dir should be empty now */ if (status) { putn( "Oops!\n\015", "cannot remove old directory \"", source, "\"\n\015", "Maybe one of your `subst' disk is using this directory. If this is\n\015", "the case, remove the subst disk and then remove this directory.\n\015", 0); exit(1); } bset(brk_st); /* restore break status */ return 0; /* no error */ } unsigned short findir(mydta, offsetp, drv, secbuf) char **secbuf; unsigned short *offsetp; unsigned int *drv; union dtbuf mydta; { unsigned int status, i, j, offset, sector, cluster, tmp, bps; union REGS inregs, outregs; struct SREGS segregs; char c, dir[13]; /* directory entry formatted like X.Y not "X Y " */ if (mydta.dos.attr != A_DIR) { /* guard against programmer's error */ putn(mydta.dos.fn, " is not a directory\n\015",0); error("findir", 0); } /* now find the cluster where the searched directory is */ cluster = mydta.dos.clusl + (mydta.dos.clush << 8); /* now convert the cluster number to sector number */ sector = clus2sec(mydta.dos.drv_no, cluster, &bps); /* calculate directory entry offset in the sector */ offset = ((mydta.dos.sloth << 8) + mydta.dos.slotl) * 32; sector += offset / bps; /* normalize sector & offset */ offset %= bps; #ifdef debug printf("dir at sector %d\n", sector); #endif *secbuf = readsec(mydta.dos.drv_no - 1, sector); if (*secbuf == (char *) 0) { cputs("error in calling readsec\n\015"); error("findir", 0); } #ifdef debug printf("First 11 bytes in sector: %11.11s\n", *secbuf+offset); printf("Fn returned by ffmf: %11.11s\n", mydta.fn); #endif /* redundant check to see if we have the correct directory entry */ /* first format the directory entry name in this form "*.*" instead of ** "???????????" */ i=0; c = (*secbuf)[offset]; while ((c != ' ') && (i < 8)) { /* copy the first 8 characters */ dir[i] = c; i++; c = (*secbuf)[offset+i]; } j = 8; c = (*secbuf)[offset+j]; if (c != ' ') { /* sub-dir name has extension */ /* now add '.' and copy the extension */ dir[i] = '.'; i++; while ((c != ' ') && (j < 11)) { dir[i] = c; i++; j++; c = (*secbuf)[offset + j]; } } dir[i] = '\0'; /* terminate string */ #ifdef debug printf("formatted directory entry string: %s\n", dir); #endif if (strcmp(mydta.dos.fn, dir) == 0) { #ifdef debug printf("Directory entry found!\n"); #endif } else { cputs("cannot find directory entry\n\015"); error("findir", 0); } /* redundant check to make sure file size of directory is zero */ if (*(unsigned long *)(*secbuf+offset+28) != 0L) { cputs("found directory with size > 0\n\015"); error("findir", 0); } *offsetp = offset; *drv = mydta.dos.drv_no; return sector; } unsigned short clus2sec(drv, clus_no, pbps) /* convert cluster to sector */ unsigned short clus_no, *pbps, drv; { unsigned char status; static unsigned short spc=0, ss, bps, tabseg, taboff; unsigned short sector; if (spc == 0) { /* first time function is called */ status = func32h(drv, &tabseg, &taboff); /* See PC Tech Journal */ if (status == 0xff) { cputs("func32h: invalid drive: "); putch(drv+'A'); cputs("\n\015"); error("clus2sec", 0); } spc = peekb(tabseg,taboff+4)+1; /* sector per cluster */ ss = peekw(tabseg,taboff+11); /* starting data sector */ bps = peekw(tabseg,taboff+2); /* bytes per sector */ #ifdef debug printf("drive #: %d\n", drv); printf("sectors per cluster: %d\n", spc); printf("# allocation units: %d\n", peekw(tabseg,taboff+13)-1 ); printf("sector size: %d\n", bps); printf("starting sector: %d\n", ss); #endif } *pbps = bps; /* return this value */ if (clus_no == 0) { #ifdef debug printf("parent directory is root! - special case\n"); #endif sector = peekw(tabseg,taboff+16); /* first sector of root directory */ } else { sector = (clus_no - 2) * spc + ss; /* see DOS Tech. Ref */ } return sector; } /* apreb test to see if patha is equal to or is a predessesor of pathb ** This is used to test whether moving a directory would result ** in a directory loop condition and also whether the source ** directory is a predessor of the current path (can't delete ** source directory in this case, so don't move) ** ** patha and pathb must be normalized paths ** E.g. ** apreb("A:\DEF", "A:\DEF") is true ** apreb("A:\DEF", "A:\DEF\GHI") is also true ** apreb("A:\DEF", "A:\DEFG") is false */ apreb(patha, pathb) char *patha, *pathb; { int i, lena, lenb; char c; lena = strlen(patha); lenb = strlen(pathb); if (lena > lenb) { /* if patha is longer, it can't be a predessesor */ return 0; } c = pathb[lena]; /* if patha is a predessesor, c should be '\\' or '\0' */ if ((c != '\\') && (c != '\0')) { return 0; } if (strncmp(patha, pathb, lena)) { /* not equal */ return 0; } return 1; } 8-Sep-86 16:13:42-PDT,14202;000000000000 Return-Path: Received: FROM UNIX.MACC.WISC.EDU BY B.ISI.EDU WITH TCP ; 8 Sep 86 16:12:30 PDT Received: by unix.macc.wisc.edu; id AA04958; 4.12/5; Mon, 8 Sep 86 17:31:19 cdt Date: Mon, 8 Sep 86 17:31:19 cdt From: Peter Wu Message-Id: <8609082231.AA04958@unix.macc.wisc.edu> To: info-ibmpc-request@mosis Subject: front.c /* Program to move files and/or subdirectories - like mv on unix ** Written by Peter Wu July, 86 @ Faculty Support Center @ UW-Madison ** Compile with IBM C version 1.00 (or Microsoft C 3.00). ** Link with fstat, mv, normal, sector, absdr, func32h, break, putn /stack:5000 ** ** This is module "front.c", the front end of mv. It looks at the sources ** and destination supplied by the user and determines whether to call ** mvdir to move/rename sub-directories or call rename to move/rename files. */ /* programmer's notes: -- won't work on network disk -- -- things to work on: -- -- be interactive: -- ask if user wants to replace existing file/directory -- verbose mode where user must confirm each move -- better error handling -- make it work on network disks -- detect and report write-protected disk if possible -- allow /f option on destination? -- -- bugs: +--------------------------------+ | c: | | subst p: /user/peter/mv/test | | mv /usr/peter/mv/test . | +--------------------------------+ -- The above sequence will prevent mv from removing the source directory after -- the content is moved to the destination directory. I don't know any way to -- detect this BEFORE moving the content. So my solution is to print a message -- telling the user to remove the source directory (now empty) by hand after -- he got rid of the 'subst drive'. */ #define LINT_ARGS #define UNK_TYP 0 #define A_INT 0x80 /* interactive file attribute */ #define LASTDRV 26 /* drive# larger than this is network share disk */ #define NULL (char *) 0 #define PLEN 200 /* max. path length; hope it's long enough */ #include "dta.h" #include "date.h" #include #include #include char lastc(char *); char getkey(char *); int brk_st; /* original break status: 0=break off, 1=break on */ /* When break is on, user can break the program when any DOS function is ** called. When break is off, user can only break the program when a DOS ** I/O function is called (if the user presses break before an I/O function, ** DOS will remember the break but let the program keep running until the ** program calls an IO function). It is undesirable to have break on ** all the time since for instance if the user breaks the program after I ** wrote one sector to disk (I need to write three sectors per sub-directory ** moved), he'll end up with an inconsistent directory. The solution is to ** make sure break is off when I'm doing disk writes, and restore it ** to its orginal status when I'm done writing the disk. ** If the user really wants to mess up his disk, he can still do it with ** ctrl-alt-del or switching the power off in the middle of running mv. */ main(argc,argv) int argc; char *argv[]; { unsigned int status, dtype, stype, status2, smask, dmask, i, drv, plimit, doit; union dtbuf mydta1, mydta2, tmpdta; char fsource[PLEN], fdest[PLEN], *source, *dest, *tmp, fdest2[PLEN]; if (argc < 3) { /* print help */ putn( "Usage: MV .. \n\15", " MV renames/moves the source files/directories to the destination.\n\15", " Wildcards ok. Specify source type with /d, /f, /h, and /i.\n\15", " /d=sub-directories, /f=files, /h=search hidden, /i=interactive.\n\15", " \"*.*\\.\" or \"*.*/d\" specifies all sub-directories only\n\15", " \"*.*/f\" specifies all visible files only\n\15", " \"*.*/hf\" specifies all visible and hidden files\n\15", " \"*.*/fi\" will prompt you (move or not) for every file found\n\15", " Version 1.20 made ", date, ". For DOS 2.xx and 3.xx.\n\n\15", "Please send comments/bug reports to one of the following addresses:\n\15", " Arpanet: pwu@unix.macc.wisc.edu\n\15", " Bitnet: WU at WISVMACC\n\15", " CompuServe: 76377,1332\n\15", " UUCP: {akgua|ihnp4|seismo|harvard|allegra|ucbvax}!uwvax!uwmacc!pwu\n\15", NULL); exit(0); } /* test DOS version */ switch (_osmajor) { case 2: /* dos 2.xx */ break; case 3: /* dos 3.xx */ if (_osminor <= 20) { /* make sure it's no later than version 3.20 */ break; } default: cputs("need DOS between version 2.00 and 3.20\n\15"); exit(1); } plimit = PLEN - 80; /* limit on user supplied path name */ /* process destination first */ dest = argv[argc-1]; if (strlen(dest) > plimit) { cputs("destination path too long\n\15"); exit(1); } strcpy(fdest,dest); dtype = normal(fdest); /* normalize destination path */ if (dtype == 0) { /* no specified type for destination */ dmask = A_FIL | A_DIR; } else { dmask = dtype; } status2 = ffmf(fdest, A_MASK, &mydta2); /* find info on dest */ #ifdef debug printf("fdest is %s, dmask=%d, status2 = %d\n", fdest, dmask, status2); #endif if (!status2) { /* if destination exists */ #ifdef debug printf("destination exists and has attr: %x\n", mydta2.dos.attr); #endif /* check if destination is ambigious here */ if (lastc(fdest) != '\\') { /* root would cause error, so don't check it */ tmpdta = mydta2; /* don't disturb mydta2, we need it later */ status = fnmf(&tmpdta); if (!status) { /* ha, there's more than one destination! */ cputs("destination is ambigious\n\15"); exit(1); } } /* if destination is an existing file, report error */ if ((mydta2.dos.attr & A_DIR) == 0) { cputs("destination is an existing file!\n\15"); exit(1); } /* fix fdest so that a destination of "*\." will have the expanded name ** by removing the * and appending the directory name found by ffmf. ** This will cause an error if fdest is the root of a 'subst' disk, ** so we must make sure fdest is not a root. */ if (lastc(fdest) != '\\') { /* if not root then */ tmp = strrchr(fdest,'\\'); /* find last '\' */ if (tmp == NULL) { /* no '\' ????!!!! */ cputs("error after strrchr: cannot find \\\n\15"); error("front",0); } strcpy(tmp+1, mydta2.dos.fn); /* dest with wild cards expanded */ } } else { /* destination not found */ #ifdef debug printf("destination not exists\n"); #endif if (dtype == A_DIR) { /* specified directory not exist */ cputs("can't find destination directory\n\15"); exit(1); } else { /* destination type not specified or of file type */ /* check: source better be unambigious */ if (argc > 3) { cputs("can't rename more than one source\n\15"); exit(1); } /* destination is not found, let's lookup destination's parent. ** This has to exist (e.g. if C:\X\Y is destination and doesn't ** exist, C:\X should still exists). This lookup also allows us ** to compare the drive number between the destination and the ** source to detect cross device move. */ strcpy(fdest2, fdest); chopath(fdest2); /* remove last portion of path */ status = ffmf(fdest2, A_MASK, &mydta2); if (status) { /* if destination's parent doesn't exist */ putn("path \"", fdest2, "\" not exist!\n\15", 0); exit(1); } else if ((mydta2.dos.attr & A_DIR) == 0) { /* not a directory */ putn("\"", fdest2, "\" is not a directory!\n\15", 0); exit(1); } } } brk_st = bstat(); /* get current break status (on or off) */ /***********************************************************/ /* now process SOURCE **************************************/ /***********************************************************/ for (i=1; i < argc - 1; i++) { /* for all source specification */ source = argv[i]; if (strlen(source) > plimit) { /* rare user error but just in case */ cputs("source path too long! skipped\n\15"); continue; /* next argument */ } strcpy(fsource,source); stype = extype(fsource); /* extract type */ stype |= normal(fsource); /* normalize source path */ #ifdef debug printf("normalized source path: %s\n", fsource); #endif if ((stype & (A_DIR | A_FIL)) == 0) { /* no specified type, use default */ smask = stype | A_FIL | A_DIR; /* look for file or directory */ } else { smask = stype; } status = ffmf(fsource, smask, &mydta1); /* find info on source */ if (status) { putn(source, " not found\n\15", 0); continue; } /* see if source is on a network disk */ if ((mydta1.dos.drv_no < 0) || (mydta1.dos.drv_no >= LASTDRV)) { cputs("sorry, mv doesn't work on network disks\n\15"); exit(1); } /* is source and destination on same drive? */ if (mydta1.dos.drv_no != mydta2.dos.drv_no) { cputs("source and destination must be on same physical drive\n\15"); exit(1); } /* check: if destination does not exist, source better be unique */ if (status2) { /* destination does not exist */ if (lastc(fsource) != '\\') { /* don't call fnmf on root */ tmpdta = mydta1; /* do not change mydta1, mess with a copy */ status = fnmf(&tmpdta); /* see if there's more than one source */ if (!status) { /* source is ambigious */ cputs("can't rename more than one source\n\15"); exit(1); } } } do { /* repeat for all source wildcard */ #ifdef debug printf("source found, fn: %s\n", mydta1.dos.fn); printf("found attribute is %2xh\n", mydta1.dos.attr); #endif /* form source name with wild cards expanded */ chopath(fsource); /* chop off wild cards */ catpath(fsource, mydta1.dos.fn); /* append expanded name */ if (status2) { /* destination not exist */ if (stype & A_INT) { /* interactive mode */ doit = prompt(fsource,fdest); } else { /* in batch mode, always do it */ putn(fsource, " DD ", fdest, " ", 0); doit = 1; /* do it! */ } if (doit) { if (mydta1.dos.attr & A_DIR) { /* rename or move directory */ status = mvdir(mydta1, fsource, fdest); if (!status) { cputs("OK.\n\15"); } } else { /* rename or move file */ /* don't know what rename does when user presses break key, ** so set break off to be safe */ bset(0); /* turn break off (disable break key) */ status = rename(fdest,fsource); /* use fdest or dest? */ bset(brk_st); /* restore break status to orginal value */ if (status) { putn("can't\n\15", "invalid file name/disk write protected\n\15", 0); } else { cputs("ok\n\15"); } /* fall thru to fnmf */ } } else { /* doit = 0, because the user press 'n' */ cputs("not moved\15\n"); } } else { /* now handle the case when destination do exists */ #ifdef debug printf("dest exists, let's see...\n"); #endif if (mydta2.dos.attr & A_DIR) { /* destination is a directory */ #ifdef debug printf("before strcat fdest is %s\n", fdest); printf("now cat with %13.13s\n", mydta1.dos.fn); #endif /* create destination + tail of source in fdest2 ** e.g. source = \user\peter\cat ** destination = \junk\haha ** fdest2 = \junk\haha\cat */ strcpy(fdest2, fdest); /* make a work copy */ catpath(fdest2, mydta1.dos.fn); if (stype & A_INT) { /* interactive mode */ doit = prompt(fsource,fdest2); } else { /* in batch mode, always do it */ putn(fsource, " DD ", fdest2, " ", 0); doit = 1; /* do it! */ } if (doit) { #ifdef debug printf("dest+tail of source: %s\n", fdest2); #endif if (mydta1.dos.attr & A_DIR) { /* source is also a directory */ status = mvdir(mydta1, fsource, fdest2); if (!status) { cputs("OK.\n\15"); } /* fall down to fnmf */ } else { /* move a file into a directory */ bset(0); /* turn break off (disable break key) */ status = rename(fdest2, fsource); bset(brk_st); /* set break status to orginal value */ if (status) { cputs("can't; file exists already/disk write-protected\n\15"); } else { cputs("ok.\n\15"); } } } else { /* doit = 0 */ cputs("not moved\15\n"); } } else { /* dest is an existing file! */ putn(fsource, " DD ", fdest, " can't rename: file exists already\n\15", 0); } } /* if dest exist or not */ status = fnmf(&mydta1); /* find info on next matching source */ } while (status == 0); /* while there are matching files */ } /* for different sources */ exit(0); /* no error */ } prompt(s,d) /* return 1 for 'y'; 0 for 'n' */ char *s, *d; /* part of the prompt */ { char c; do { putn(s, " DD ", d, " (y/n/q; ?=help): ", 0); c = getkey("yYnNqQ?"); putch(' '); switch (c) { case '?': putn("\15\n", "y - yes, move it\15\n", "n - no, skip it\15\n", "q - quit to DOS\15\n", "? - this help message\15\n\n", 0); break; case 'y': case 'Y': return 1; case 'n': case 'N': return 0; case 'q': case 'Q': cputs("Quit.\15\n"); exit(0); default: cputs("\15\nHow do I get here?\15\n"); error("prompt",0); } /* end switch */ } while (1); } char getkey(valid) /* wait for valid key and echo it */ char *valid; { char c; int i; do { c = getch(); i = index(valid,c); if (i > -1) { putch(c); /* echo valid key */ return c; } else { /* beep at invalid key */ putch(7); } } while (1); } 8-Sep-86 16:23:29-PDT,962;000000000000 Return-Path: Received: FROM UNIX.MACC.WISC.EDU BY B.ISI.EDU WITH TCP ; 8 Sep 86 16:18:48 PDT Received: by unix.macc.wisc.edu; id AA04970; 4.12/5; Mon, 8 Sep 86 17:31:37 cdt Date: Mon, 8 Sep 86 17:31:37 cdt From: Peter Wu Message-Id: <8609082231.AA04970@unix.macc.wisc.edu> To: info-ibmpc-request@mosis Subject: mv front.obj: front.c date.h dta.h cc front /OT; mv.obj: mv.c dta.h peek.h cc mv /ze /OT; fstat.obj: fstat.c dta.h cc fstat /OT; normal.obj: normal.c dta.h cc normal /OT; sector.obj: sector.c cc sector /OT; break.obj: break.c cc break /OT; error.obj: error.c cc error; func32h.obj: func32h.asm masm func32h; absdr.obj: absdr.asm masm absdr; putn.obj: putn.asm masm putn; mv.exe: mv.obj fstat.obj normal.obj front.c sector.obj func32h.obj absdr.obj clink mv fstat normal front sector break func32h absdr putn error /stack:5000; 8-Sep-86 16:23:33-PDT,1009;000000000000 Return-Path: Received: FROM UNIX.MACC.WISC.EDU BY B.ISI.EDU WITH TCP ; 8 Sep 86 16:19:01 PDT Received: by unix.macc.wisc.edu; id AA04924; 4.12/5; Mon, 8 Sep 86 17:30:36 cdt Date: Mon, 8 Sep 86 17:30:36 cdt From: Peter Wu Message-Id: <8609082230.AA04924@unix.macc.wisc.edu> To: info-ibmpc-request@mosis Subject: break.c /* routines to read break status and set break status (to on or off) */ #include bstat() /* get break status 0=off, 1=on */ { union REGS inregs, outregs; inregs.h.al = 0; /* request break status */ inregs.h.ah = 0x33; /* DOS function for ctrl-break check */ intdos(&inregs, &outregs); return outregs.h.dl; } bset(onoff) /* set break status 0=off 1=on */ { union REGS inregs, outregs; inregs.h.al = 1; /* set break status */ inregs.h.ah = 0x33; /* DOS function for ctrl-break check */ inregs.h.dl = onoff; /* set to on or off */ intdos(&inregs, &outregs); } 8-Sep-86 16:23:36-PDT,1153;000000000000 Return-Path: Received: FROM UNIX.MACC.WISC.EDU BY B.ISI.EDU WITH TCP ; 8 Sep 86 16:19:27 PDT Received: by unix.macc.wisc.edu; id AA04992; 4.12/5; Mon, 8 Sep 86 17:32:20 cdt Date: Mon, 8 Sep 86 17:32:20 cdt From: Peter Wu Message-Id: <8609082232.AA04992@unix.macc.wisc.edu> To: info-ibmpc-request@mosis Subject: putn.asm ; allows C program to call cputs with multiple arguments. ; E.g. putn("How ", "are ", "you?", 0); ; the last argument must be either 0 or "" _text segment public byte 'code' assume cs:_text extrn _cputs:near public _putn _putn proc near push si push di push bp mov bp,sp mov si,8 loop: mov di,[bp+si] ; get next string or di,di ; is it NULL je done ; if so, nothing more to print or byte ptr [di],0 ; it is "" (null string) je done ; if so, nothing more to print ; now call cputs to print the string push di ; push parameter on stack call _cputs pop di ; get rid of parameter on stack add si,2 ; point to next parameter jmp loop done: pop bp pop di pop si ret _putn endp _text ends end 8-Sep-86 16:23:47-PDT,1765;000000000000 Return-Path: Received: FROM UNIX.MACC.WISC.EDU BY B.ISI.EDU WITH TCP ; 8 Sep 86 16:19:46 PDT Received: by unix.macc.wisc.edu; id AA04919; 4.12/5; Mon, 8 Sep 86 17:30:29 cdt Date: Mon, 8 Sep 86 17:30:29 cdt From: Peter Wu Message-Id: <8609082230.AA04919@unix.macc.wisc.edu> To: info-ibmpc-request@mosis Subject: absdr.asm ; bios call to do absolute disk read/write ; using int86 from C to call int 25h and 26h will freeze the computer ; (because si and di were modified?), so I had to write this in assembly. _text segment public byte 'code' assume cs:_text public _absdr ; absolute disk read _absdr proc near push si push di push bp mov bp,sp mov al,[bp+8] ; drive number mov cx,[bp+10] ; number of sectors to read mov dx,[bp+12] ; beginning sector number mov bx,[bp+14] ; buffer address int 25h ; absolute disk read ; int 25h supposed to destroy all registers except segment registers jc error ; if error, leave error no in AX mov ax,0 ; clear error number if no error error: inc sp ; pop the flags inc sp ; pop the flags pop bp pop di pop si ret _absdr endp public _absdw ; absolute disk write _absdw proc near push si push di push bp mov bp,sp mov al,[bp+8] ; drive number mov cx,[bp+10] ; number of sectors to read mov dx,[bp+12] ; beginning sector number mov bx,[bp+14] ; buffer address int 26h ; absolute disk read ; int 26h supposed to destroy all registers except segment registers jc error1 ; if error, leave error no in AX mov ax,0 ; clear error number if no error error1: inc sp ; pop the flags inc sp ; pop the flags pop bp pop di pop si ret _absdw endp _text ends end 8-Sep-86 16:33:37-PDT,1389;000000000000 Return-Path: Received: FROM UNIX.MACC.WISC.EDU BY B.ISI.EDU WITH TCP ; 8 Sep 86 16:33:05 PDT Received: by unix.macc.wisc.edu; id AA04941; 4.12/5; Mon, 8 Sep 86 17:31:04 cdt Date: Mon, 8 Sep 86 17:31:04 cdt From: Peter Wu Message-Id: <8609082231.AA04941@unix.macc.wisc.edu> To: info-ibmpc-request@mosis Subject: error.c /* handle internal errors */ /* global variable */ int brk_st; /* orginal break status */ error(where,panic) char *where; /* call from what procedure */ int panic; /* should I tell the user to panic? 0=no 1=yes */ { putn("MV stopped due to internal error in '", where, "'\n\015", 0); if (panic) { /* this should never happen */ putn("Because of this error, your disk directory might be in an\n\015", "inconsistent state. Please run chkdsk on your disk to see\n\015", "if this is so. Sorry.\n\015", 0); } else { /* not to panic */ putn("Do not worry, your disk is not screwed up, only that the\n\015", "operation you requested was not completed.\n\015", 0); } putn("Please report this error to the author of this program. You can\n\015", "obtain his email address by typing 'mv' with no parameters.\n\015", 0); /* now restore break status to whatever the user set it to */ bset(brk_st); exit(2); /* indicate internal error */ }