├── VERSION-0.85.4 ├── cpcfs.doc ├── cpcfs.exe ├── cpcfs.hlp ├── makedoc.exe ├── .gitignore ├── cpcfs.cfg ├── drop_tag.bat ├── BUILD ├── src ├── Makefile ├── Makefile.bcc ├── unix.h ├── dos.h ├── makedoc.c ├── cpcfs.c ├── match.h ├── unix.c ├── cpcfs.h ├── tools.c ├── match.c ├── dos.c ├── ui.c └── fs.c ├── FILES ├── LICENSE ├── getcpm.bas ├── README.md └── template.doc /VERSION-0.85.4: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cpcfs.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derikz/cpcfs/HEAD/cpcfs.doc -------------------------------------------------------------------------------- /cpcfs.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derikz/cpcfs/HEAD/cpcfs.exe -------------------------------------------------------------------------------- /cpcfs.hlp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derikz/cpcfs/HEAD/cpcfs.hlp -------------------------------------------------------------------------------- /makedoc.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derikz/cpcfs/HEAD/makedoc.exe -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | #*.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # Debug files 32 | *.dSYM/ 33 | *.su 34 | -------------------------------------------------------------------------------- /cpcfs.cfg: -------------------------------------------------------------------------------- 1 | echo "+----------------------------------------------" 2 | echo "| CPCFS --- CPCEmu Filesystem Maintenance" 3 | echo "|" 4 | echo "| Version %V (c) Derik van Zuetphen" 5 | echo "+----------------------------------------------" 6 | 7 | page 25 8 | 9 | prompt "%i/%u> " 10 | # or use the next prompt if you can display ansi sequences 11 | #prompt "%e[1m%i/%u>%e[m " 12 | echo 13 | echo "Type HELP for an overview of CPCFS, EXIT for exit." 14 | -------------------------------------------------------------------------------- /drop_tag.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | if not "%1"=="" goto doit 3 | 4 | echo If CPCFS creates a new image, it places the time and date of creation 5 | echo after the required mark "MV - CPC". But CPE doesn't like it; it needs 6 | echo the complete (original) tag. 7 | echo DROP_TAG.BAT does the work with CPCFS' COMMENT -D command. 8 | echo. 9 | echo Use: DROP_TAG (imagename) 10 | goto end 11 | 12 | :doit 13 | echo Old and new comment of %1: 14 | cpcfs %1 -e comment; comment -d; comment 15 | :end 16 | -------------------------------------------------------------------------------- /BUILD: -------------------------------------------------------------------------------- 1 | 2 | Build on Windows 3 | 4 | - Tested with Windows 10 5 | - Fetch the freeware command line compiler 'bcc' from Embarcadero 6 | https://www.embarcadero.com/free-tools/ccompiler/start-for-free 7 | - Unpack, e.g. in c:\bcc101 8 | - Unpack the CPCFS zip file, e.g. in c:\cpcfs 9 | - Go to the src subdirectory inside c:\cpcfs 10 | - Call c:\bcc101\bin\make -f Makefile.bcc 11 | 12 | Build on Unix 13 | 14 | - Tested with FreeBSD 10.3 and Fedora Linux 23 15 | - Unpack, e.g. in ~/cpcfs 16 | - Go to the src subdirectory inside ~/cpcfs 17 | - Call make 18 | 19 | Build the documentation 20 | 21 | - Edit 'template.doc' and 'cpcfs.hlp' as you like 22 | - Call makedoc 23 | - Copy doc to cpcfs.doc 24 | 25 | For Fedora you may need 26 | - dnf groups install "Development Tools" 27 | - dnf install ncurses-term readline-devel 28 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | 2 | CC = cc 3 | CCOPT = -g -DLINUX=1 -DUSE_READLINE=1 -Wall 4 | #CCOPT = -O2 -DLINUX=1 -DUSE_READLINE=1 5 | LDOPT = 6 | 7 | CFLAGS = $(CCOPT) 8 | LDARGS = $(LDOPT) -lreadline -lhistory -L /usr/lib/termcap -ltermcap 9 | 10 | HEADER=cpcfs.h unix.h 11 | OBJECTS=unix.o match.o tools.o fs.o ui.o cpcfs.o 12 | 13 | default: makedoc cpcfs 14 | 15 | cpcfs: $(OBJECTS) 16 | $(CC) $(CFLAGS) $(OBJECTS) -o cpcfs $(LDARGS) 17 | 18 | makedoc: makedoc.c 19 | $(CC) $(CFLAGS) makedoc.c -o makedoc 20 | 21 | unix.o: unix.c unix.h 22 | match.o: match.c match.h 23 | 24 | tools.o: tools.c $(HEADER) 25 | fs.o: fs.c $(HEADER) 26 | ui.o: ui.c $(HEADER) 27 | cpcfs.o: cpcfs.c $(HEADER) 28 | 29 | 30 | strip: cpcfs makedoc 31 | strip cpcfs makedoc 32 | 33 | clean: 34 | -rm -f *.o 35 | 36 | distclean: clean 37 | -rm -f cpcfs makedoc 38 | 39 | new: distclean 40 | make 41 | -------------------------------------------------------------------------------- /src/Makefile.bcc: -------------------------------------------------------------------------------- 1 | 2 | # build with: 3 | # c:\bcc101\bin\make -f Makefile.bcc 4 | 5 | CC = c:\bcc101\bin\bcc32c.exe 6 | CFLAGS = -DDOS=1 -DUSE_READLINE=0 # or set in cpcfs.h instead 7 | LD = c:\bcc101\bin\bcc32c.exe 8 | LDFLAGS = 9 | MAKE = c:\bcc101\bin\make.exe 10 | 11 | HEADER=cpcfs.h dos.h match.h 12 | OBJECTS=dos.obj match.obj tools.obj fs.obj ui.obj cpcfs.obj 13 | 14 | .c.obj: 15 | $(CC) $(CFLAGS) /c $&.c 16 | 17 | default: makedoc.exe cpcfs.exe 18 | 19 | cpcfs.exe: $(OBJECTS) 20 | $(LD) $(LDFLAGS) $(OBJECTS) -o cpcfs.exe 21 | 22 | makedoc.exe: makedoc.c 23 | $(LD) $(LDFLAGS) makedoc.c -o makedoc.exe 24 | 25 | cpcfs.obj: cpcfs.c $(HEADER) 26 | dos.obj: dos.c $(HEADER) 27 | fs.obj: fs.c $(HEADER) 28 | match.obj: match.c $(HEADER) 29 | tools.obj: tools.c $(HEADER) 30 | ui.obj: ui.c $(HEADER) 31 | 32 | clean: 33 | del *.il? *.obj *.tds *.map 34 | 35 | distclean: clean 36 | del cpcfs.exe makedoc.exe 37 | 38 | new: distclean 39 | $(MAKE) -f Makefile.bcc 40 | -------------------------------------------------------------------------------- /FILES: -------------------------------------------------------------------------------- 1 | 2 | src/ C code & project files 3 | BUILD Build and compile instructions 4 | cpcfs.cfg Startup commands for CPCFS 5 | cpcfs.doc Documentation for everything 6 | cpcfs.hlp Online helpfile 7 | drop_tag.bat Removing timestamp from image (for CPE) 8 | FILES List of files 9 | getcpm.bas Loco Basic program to read CP/M Tracks 10 | LICENSE BSD-2-Clause License 11 | README.md Project overview 12 | template.doc Template for the documatation 13 | VERSION-* indicate current version 14 | 15 | src/cpcfs.c the main sourcecode 16 | src/cpcfs.h some prototypes, constants, includes, and so on 17 | src/dos.h Windows specific header 18 | src/dos.c Windows specific routines 19 | src/fs.c routines for manipulating the filesystem 20 | src/Makefile Unix Makefile 21 | src/Makefile.bcc C++ Builder CLI Makefile 22 | src/match.c some routines realising regular expression matching for CP/M wildcards 23 | src/match.h header for regular expressions 24 | src/makedoc.c merge cpcfs.hlp and template.doc to cpcfs.doc 25 | src/tools.c general routines for CPCFS 26 | src/ui.c routines for the user interface 27 | src/unix.h Unix specific header 28 | src/unix.c unix specific routines 29 | -------------------------------------------------------------------------------- /src/unix.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | ------------------------------------------------------------------------------ 4 | 5 | ===== 6 | CPCFS -- u n i x . h --- Unix specific header 7 | ===== 8 | 9 | Version 0.85 (c) Derik van Zuetphen 10 | ------------------------------------------------------------------------------ 11 | */ 12 | 13 | 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #if USE_READLINE 21 | #include 22 | #include 23 | #endif 24 | 25 | #define SHELLVAR "SHELL" 26 | #define SHELLDEFAULT "/bin/sh" 27 | #define PAGERDEFAULT "more" 28 | #define LDIRCOMMAND "ls -l" 29 | #define DIRSEPARATOR '/' 30 | #define FIRST_OPTIND 1 31 | #define O_BINARY 0 /* dummy to make DOS happy */ 32 | #define ENTER 10 /* keycode */ 33 | 34 | extern char Break_Wish; 35 | 36 | void break_handler(); 37 | void disable_break(); 38 | 39 | /* empty dummies */ 40 | #define save_path() 41 | #define rest_path() 42 | 43 | char *glob_file(char *pattern); 44 | char *glob_next(); 45 | 46 | char* tmp_nam(char*); /* because DOS' tmpnam() ignores $TEMP */ 47 | 48 | char wait_for_key (int,char); 49 | void clrscr(); 50 | void gotoxy(int col,int lin); 51 | 52 | void os_init(); 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2016, Derik van Zuetphen 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /src/dos.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | ------------------------------------------------------------------------------ 4 | 5 | ===== 6 | CPCFS -- d o s . h --- Borland C specific header 7 | ===== 8 | 9 | Version 0.85 (c) Derik van Zuetphen 10 | ------------------------------------------------------------------------------ 11 | */ 12 | 13 | #ifndef DOS_H_INCLUDED 14 | #define DOS_H_INCLUDED 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #define SHELLVAR "COMSPEC" 25 | #define SHELLDEFAULT "c:\\command" 26 | #define PAGERDEFAULT "more" 27 | #define LDIRCOMMAND "dir" 28 | #define DIRSEPARATOR '\\' 29 | #define FIRST_OPTIND 1 30 | #define F_OK 0 /* for access */ 31 | #define R_OK 4 /* dto. */ 32 | #define ENTER 13 33 | 34 | extern char Break_Wish; 35 | 36 | /* this getwd() is not exactly the same as Unix' one, but it works */ 37 | extern char cwdbuffer[256]; 38 | #define getwd(DUMMY) getcwd(cwdbuffer,256) 39 | 40 | void break_handler(); 41 | void disable_break(); 42 | 43 | void save_path(); 44 | void rest_path(); 45 | 46 | char *glob_file(char *pattern); 47 | char *glob_next(); 48 | 49 | int add_history(char*); 50 | 51 | char* tmp_nam(char*); 52 | char wait_for_key (int must_be_0, char must_be_TRUE); 53 | 54 | /* prototypes of getopt.c */ 55 | extern int optind; 56 | extern char *optarg; 57 | extern int opterr; 58 | int getopt(int argc, char *argv[], char *optionS); 59 | 60 | int inputs( char *data, int maxLen, int timeout ); 61 | void os_init(); 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /src/makedoc.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | ------------------------------------------------------------------------------ 4 | 5 | ===== 6 | CPCFS -- m a k e d o c . c -- Build documentation 7 | ===== 8 | 9 | Version 0.85 (c) Derik van Zuetphen 10 | ------------------------------------------------------------------------------ 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | 18 | #define LINELEN 256 19 | 20 | int main () { 21 | 22 | const char help_name[] = "cpcfs.hlp"; 23 | const char template_name[] = "template.doc"; 24 | const char doc_name[] = "doc"; 25 | 26 | char line[LINELEN]; 27 | char topic[20]; 28 | FILE *help, *template, *doc; 29 | int found; /* as bool */ 30 | 31 | help = fopen(help_name,"r"); 32 | if (help==NULL) { 33 | fprintf(stderr,"I cannot open \"%s\"",help_name); 34 | exit(1); 35 | } 36 | 37 | template = fopen(template_name,"r"); 38 | if (template==NULL) { 39 | fprintf(stderr,"I cannot open \"%s\"",template_name); 40 | exit(1); 41 | } 42 | 43 | doc = fopen(doc_name,"w"); 44 | if (doc==NULL) { 45 | fprintf(stderr,"I cannot open \"%s\"",doc_name); 46 | exit(1); 47 | } 48 | 49 | 50 | while (fgets(line,LINELEN,template) != NULL) { 51 | if (line[0]=='~') { 52 | strncpy(topic,line,20); 53 | if (topic[strlen(topic)-1]=='\n') { 54 | topic[strlen(topic)-1] = 0; 55 | } 56 | fseek(help,0L,SEEK_SET); 57 | found = 0; 58 | while (fgets(line,LINELEN,help) != NULL) { 59 | if (found && line[0]!='~') 60 | fputs(line,doc); 61 | if (found && line[0]=='~') { 62 | found=0; 63 | continue; 64 | } 65 | if (!found&& line[0]!='~') 66 | continue; 67 | if (!found&& line[0]=='~') 68 | found = (strstr(line,topic)!=NULL); 69 | } 70 | } else { 71 | fputs(line,doc); 72 | } 73 | } 74 | 75 | fclose(doc); 76 | fclose(template); 77 | fclose(help); 78 | 79 | printf("Merged \"%s\" and \"%s\" to \"%s\"\n", 80 | template_name, help_name, doc_name); 81 | printf("You can now copy \"%s\" to \"cpcfs.doc\"\n", 82 | doc_name); 83 | 84 | return 0; 85 | } 86 | -------------------------------------------------------------------------------- /getcpm.bas: -------------------------------------------------------------------------------- 1 | 10 PRINT TAB(50);CHR$(164);" Derik van Z\";CHR$(34);"utphen (jun94)" 2 | 20 PRINT "=== Dumping CP/M Tracks to a file ===" 3 | 30 PRINT 4 | 40 SYMBOL AFTER 256 5 | 50 MEMORY &4000 6 | 60 DEFINT a-z 7 | 70 sector = &4000 8 | 80 track = &4001 9 | 90 buf = &4002 10 | 100 errno = &4004 11 | 101 'far address = &4005 12 | 102 'rsx name = &4008 13 | 110 readsec = &4100 14 | 120 POKE &4008,&84 ' RSX-Command for READ-SECTOR 15 | 130 READ progsize 16 | 140 FOR addr=readsec TO readsec+progsize-1 17 | 150 READ code 18 | 160 POKE addr,code 19 | 170 NEXT 20 | 180 PRINT "Insert a CP/M Diskette and press a key" 21 | 190 WHILE INKEY$="":WEND 22 | 200 ptr=&5000 23 | 210 FOR t = 0 TO 1 24 | 220 FOR s = &41 TO &49 25 | 230 PRINT USING "Track=# Sector=\\"+CHR$(13);t;HEX$(s); 26 | 240 POKE sector,s 27 | 250 POKE track,t 28 | 260 POKE buf,ptr MOD 256 29 | 270 POKE buf+1,ptr \ 256 30 | 280 CALL readsec 31 | 290 IF PEEK(errno)>0 THEN PRINT "Something went wrong! ... Aborted!":END 32 | 300 ptr = ptr + 512 33 | 310 NEXT 34 | 320 NEXT 35 | 330 PRINT "Insert a Diskette to save the CP/M image and press a key" 36 | 340 WHILE INKEY$="":WEND 37 | 350 PRINT "Writing to SYSTEM.CPM" 38 | 360 SAVE "system.cpm",b,&5000,18*512 39 | 370 DATA 46 40 | 380 DATA &21,&08,&40,&cd,&d4,&bc,&d2,&28,&40,&22,&05,&40,&79,&32,&07,&40 41 | 390 DATA &1e,&00,&21,&01,&40,&56,&21,&00,&40,&4e,&2a,&02,&40,&df,&05,&40 42 | 400 DATA &d2,&28,&40,&af,&32,&04,&40,&c9,&3e,&01,&32,&04,&40,&c9 43 | 405 ' 44 | 410 ' The MC program is the following: 45 | 420 ' 46 | 430 ' org &4000 47 | 440 ' sector: db 0 48 | 450 ' track: db 0 49 | 460 ' mem: dw 0 50 | 470 ' errno: db 0 51 | 480 ' faraddr: dw 0 52 | 490 ' db 0 53 | 500 ' name: db &84 54 | 510 ' 55 | 520 ' kl-find-command = &BCD4 56 | 530 ' 57 | 540 ' org &4100 58 | 550 ' 21 08 40 ld hl,name 59 | 560 ' CD D4 BC call kl-find-command 60 | 570 ' D2 28 40 jp nc,error 61 | 580 ' 22 05 40 ld (faraddr),hl 62 | 590 ' 79 ld a,c 63 | 600 ' 32 07 40 ld (faraddr+2),a 64 | 610 ' 1E 00 ld e,0 65 | 620 ' 21 01 40 ld hl,track 66 | 630 ' 56 ld d,(hl) 67 | 640 ' 21 00 40 ld hl,sector 68 | 650 ' 4E ld c,(hl) 69 | 660 ' 2A 02 40 ld hl,(mem) 70 | 670 ' DF rst 3 71 | 680 ' 05 40 dw faraddr 72 | 690 ' D2 28 40 jp nc,error 73 | 700 ' AF xor a 74 | 710 ' 32 04 40 ld (errno),a 75 | 720 ' C9 ret 76 | 730 ' error: 77 | 740 ' 3E 01 ld a,1 78 | 750 ' 32 04 40 ld (errno),a 79 | 760 ' C9 ret 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | > :warning: There is also an [extended version CPCXFS](http://www.cpctech.org.uk/download/cpcxfs.zip), 3 | > that also allows to maintain extended DSK images (thanks to [Kevin 4 | > Thacker](http://www.cpctech.org.uk/)). 5 | 6 | # CPCFS 7 | 8 | CPCFS maintains diskimages (DSK files), needed by some Amstrad CPC 9 | emulators. 10 | 11 | ## What is CPCFS? 12 | 13 | CPCFS is a program to maintain filesystems that are needed by some CPC 14 | emulators (especially CPCEmu by Marco Vieth). 15 | 16 | If you are familiar with some emulators, you may have already 17 | encountered the problem, how to copy files form DOS into the image of 18 | an emulator, or vice versa. 19 | 20 | The methods described in the documentation of CPCEmu only deal with 21 | transferring whole CPC disks to CPCEmu disk images, but not single 22 | files. 23 | 24 | If you use CPCEmu you can use the cassette interface to load and save 25 | files from the CPC memory to DOS files. But this method lacks some (in 26 | my sense important) features, e.g. 27 | 28 | * transferring files > 42k 29 | * transferring data files without Amsdos-Header 30 | * installing CP/M, if you don't have a 5 1/4 inch drive at your CPC and 31 | can't copy whole disks. 32 | * transferring is so slow as the emulator 33 | * you can't transfer files in a batch 34 | 35 | If you don't use another emulator than CPCEmu, I do not know another 36 | solution other than CPCFS. 37 | 38 | ### Where can I download CPCFS? 39 | 40 | GO to Github's [release tab](https://github.com/derikz/cpcfs/releases) 41 | and download the latest zip file. 42 | 43 | ### How do I install CPCFS? 44 | 45 | Just unpack the archive. Then copy the *.EXE files, CPCFS.CFG and 46 | CPCFS.HLP to a place where they can be found (somewhere in the 47 | path) or where you like them to be. CPCFS.CFG must be in the 48 | current directory or in the same directory as CPCFS.EXE. 49 | 50 | ### How do I use CPCFS? 51 | 52 | Read [cpcfs.doc](https://github.com/derikz/cpcfs/blob/master/cpcfs.doc) 53 | or type HELP within CPCFS. 54 | 55 | ### What other products do I need? 56 | 57 | * A CPC-Emulator (CPCEmu, CPE, SimCPC, CPCEmuII, ...) 58 | * A CPC Computer with CP/M License, if you want to copy CP/M to an image. 59 | 60 | ### What is new in version 0.85.4? 61 | 62 | This version is a maintenance release only. There is no new 63 | functionality. 64 | 65 | New is: 66 | 67 | * runs on modern Windows 68 | * BSD 2-clause License 69 | * updated contact data 70 | * 'echo %M' to show free memory makes no sense nowadays 71 | 72 | ## Main features: 73 | 74 | * Vortex images are supported 75 | * User areas, file attributes and wildcards are fully supported 76 | * Comfortable command line interface including history, extensible help, 77 | and many options to each command 78 | * Special commands for getting an insight in the filesystem structure 79 | * Extensible help system containing nearly the same as the manual 80 | 81 | ### Some Commands: 82 | 83 | * PUT, GET, MPUT, MGET: copy to and from the image 84 | * OPEN, NEW, DIR, DBP: open, create and look at image 85 | * COPY, REN, ERA, ATTRIB: copy files 86 | * TYPE, DUMP: look at files 87 | * LDIR, LCD, !: access your local PC 88 | * HELP: accesses the online manual 89 | 90 | 91 | ### What files are distributed? 92 | 93 | See 'FILES' 94 | 95 | ### How do I make an exe file myself? 96 | 97 | See 'BUILD' 98 | -------------------------------------------------------------------------------- /src/cpcfs.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | ------------------------------------------------------------------------------ 4 | 5 | ===== 6 | CPCFS -- CPCEmu Filesystem Maintenance 7 | ===== especially for CPCEmu 8 | 9 | Version 0.85 (c) Derik van Zuetphen 10 | ------------------------------------------------------------------------------ 11 | */ 12 | 13 | 14 | #include "cpcfs.h" 15 | 16 | /*********** 17 | Variables 18 | ***********/ 19 | 20 | /****** CPC Filesystem ******/ 21 | 22 | char full_imagename[PATH_MAX]; /* full pathname, ... */ 23 | char *imagename; /* ... only name portion, and ... */ 24 | int imagefile; /* ... handle of imagefile */ 25 | 26 | int cur_user = 0; 27 | int cur_format; 28 | int BLKNR_SIZE = 1; 29 | int BLKNR = 16; 30 | 31 | uchar *track = NULL; /* 0x100 header + track data */ 32 | unsigned char filler; /* copied from track header */ 33 | 34 | uchar *block_buffer = NULL; /* Buffer for 1024 or more bytes */ 35 | 36 | uchar *blk_alloc = NULL; /* Block-Allocation-Table, */ 37 | /* chars point to fcb, FF = free block */ 38 | int allocated_blks; 39 | int free_blks; 40 | int total_blks; /* = allocated_blks + free_blks */ 41 | float percentage; /* of allocated blocks */ 42 | 43 | int mode; /* Mode for GET-commands */ 44 | bool force; 45 | 46 | struct d_header disk_header; 47 | 48 | 49 | /****** User Interface *****/ 50 | int nbof_args; 51 | char *arg[INPUTLEN]; 52 | char prompt[INPUTLEN]; 53 | int pagelen, lineno; 54 | bool Break_Wish; 55 | jmp_buf break_entry; 56 | bool Interactive; 57 | char installpath[INPUTLEN]; /* path of argv[0] */ 58 | int Verb = 9; 59 | 60 | DirEntry *directory = (DirEntry*)NULL; 61 | 62 | char stamp[] = "(C) 1995-98 Derik van Zuetphen"; 63 | 64 | 65 | /****** Disk Parameter Block ******/ 66 | 67 | /* DPB templates for SYSTEM, DATA, IBM, VORTEX, and user defined */ 68 | #define DPB_store_size 5 69 | DPB_type DPB_store[DPB_store_size] = { 70 | /* SYSTEM */ 71 | { SYSTEMFORMAT, /* ID */ 72 | SYSTEMFORMAT, /* SEC1 */ 73 | 9, /* SECS */ 74 | 40, /* TRKS */ 75 | 1, /* HDS */ 76 | 512, /* BPS */ 77 | 78 | 36, /* SPT */ 79 | 3, /* BSH */ 80 | 7, /* BLM */ 81 | 0, /* EXM */ 82 | 168, /* DSM */ 83 | 63, /* DRM */ 84 | 0xC0, /* AL0 */ 85 | 0x00, /* AL1 */ 86 | 16, /* CKS */ 87 | 2, /* OFS */ 88 | 0,0,0 89 | }, 90 | /* DATA */ 91 | { DATAFORMAT, /* ID */ 92 | DATAFORMAT, /* SEC1 */ 93 | 9, /* SECS */ 94 | 40, /* TRKS */ 95 | 1, /* HDS */ 96 | 512, /* BPS */ 97 | 98 | 36, /* SPT */ 99 | 3, /* BSH */ 100 | 7, /* BLM */ 101 | 0, /* EXM */ 102 | 177, /* DSM */ 103 | 63, /* DRM */ 104 | 0xC0, /* AL0 */ 105 | 0x00, /* AL1 */ 106 | 16, /* CKS */ 107 | 0, /* OFS */ 108 | 0,0,0 109 | }, 110 | /* IBM */ 111 | { IBMFORMAT, /* ID */ 112 | IBMFORMAT, /* SEC1 */ 113 | 8, /* SECS */ 114 | 40, /* TRKS */ 115 | 1, /* HDS */ 116 | 512, /* BPS */ 117 | 118 | 36, /* SPT */ 119 | 3, /* BSH */ 120 | 7, /* BLM */ 121 | 0, /* EXM */ 122 | 153, /* DSM */ 123 | 63, /* DRM */ 124 | 0xC0, /* AL0 */ 125 | 0x00, /* AL1 */ 126 | 16, /* CKS */ 127 | 1, /* OFS */ 128 | 0,0,0 129 | }, 130 | /* VORTEX */ 131 | { VORTEXFORMAT, /* ID */ 132 | 0x01, /* SEC1 */ 133 | 9, /* SECS */ 134 | 80, /* TRKS */ 135 | 2, /* HDS */ 136 | 512, /* BPS */ 137 | 138 | 36, /* SPT */ 139 | 5, /* BSH */ 140 | 31, /* BLM */ 141 | 3, /* EXM */ 142 | 176, /* DSM */ 143 | 127, /* DRM */ 144 | 0x80, /* AL0 */ 145 | 0x00, /* AL1 */ 146 | 32, /* CKS */ 147 | 2, /* OFS */ 148 | 0,0,0 149 | } 150 | /* user defined DPB is empty for now */ 151 | }; 152 | 153 | 154 | DPB_type *dpb = NULL; /* pointer to current DPB */ 155 | 156 | 157 | int main (int argc, char **argv) { 158 | int ui_main (int,char**); 159 | ui_main (argc,argv); 160 | exit(0); 161 | } 162 | -------------------------------------------------------------------------------- /src/match.h: -------------------------------------------------------------------------------- 1 | /* 2 | EPSHeader 3 | 4 | File: match.h 5 | Author: J. Kercheval 6 | Created: Sat, 01/05/1991 22:27:18 7 | */ 8 | /* 9 | EPSRevision History 10 | 11 | J. Kercheval Wed, 02/20/1991 22:28:37 Released to Public Domain 12 | J. Kercheval Sun, 03/10/1991 18:02:56 add is_valid_pattern 13 | J. Kercheval Sun, 03/10/1991 18:25:48 add error_type in is_valid_pattern 14 | J. Kercheval Sun, 03/10/1991 18:47:47 error return from matche() 15 | J. Kercheval Tue, 03/12/1991 22:24:49 Released as V1.1 to Public Domain 16 | */ 17 | 18 | /* 19 | Wildcard Pattern Matching 20 | */ 21 | 22 | #ifndef BOOLEAN 23 | # define BOOLEAN int 24 | # define TRUE 1 25 | # define FALSE 0 26 | #endif 27 | 28 | /* match defines */ 29 | #define MATCH_PATTERN 6 /* bad pattern */ 30 | #define MATCH_LITERAL 5 /* match failure on literal match */ 31 | #define MATCH_RANGE 4 /* match failure on [..] construct */ 32 | #define MATCH_ABORT 3 /* premature end of text string */ 33 | #define MATCH_END 2 /* premature end of pattern string */ 34 | #define MATCH_VALID 1 /* valid match */ 35 | 36 | /* pattern defines */ 37 | #define PATTERN_VALID 0 /* valid pattern */ 38 | #define PATTERN_ESC -1 /* literal escape at end of pattern */ 39 | #define PATTERN_RANGE -2 /* malformed range in [..] construct */ 40 | #define PATTERN_CLOSE -3 /* no end bracket in [..] construct */ 41 | #define PATTERN_EMPTY -4 /* [..] contstruct is empty */ 42 | 43 | /*---------------------------------------------------------------------------- 44 | * 45 | * Match the pattern PATTERN against the string TEXT; 46 | * 47 | * match() returns TRUE if pattern matches, FALSE otherwise. 48 | * matche() returns MATCH_VALID if pattern matches, or an errorcode 49 | * as follows otherwise: 50 | * 51 | * MATCH_PATTERN - bad pattern 52 | * MATCH_LITERAL - match failure on literal mismatch 53 | * MATCH_RANGE - match failure on [..] construct 54 | * MATCH_ABORT - premature end of text string 55 | * MATCH_END - premature end of pattern string 56 | * MATCH_VALID - valid match 57 | * 58 | * 59 | * A match means the entire string TEXT is used up in matching. 60 | * 61 | * In the pattern string: 62 | * `*' matches any sequence of characters (zero or more) 63 | * `?' matches any character 64 | * [SET] matches any character in the specified set, 65 | * [!SET] or [^SET] matches any character not in the specified set. 66 | * 67 | * A set is composed of characters or ranges; a range looks like 68 | * character hyphen character (as in 0-9 or A-Z). [0-9a-zA-Z_] is the 69 | * minimal set of characters allowed in the [..] pattern construct. 70 | * Other characters are allowed (ie. 8 bit characters) if your system 71 | * will support them. 72 | * 73 | * To suppress the special syntactic significance of any of `[]*?!^-\', 74 | * and match the character exactly, precede it with a `\'. 75 | * 76 | ----------------------------------------------------------------------------*/ 77 | 78 | BOOLEAN match (char *pattern, char *text); 79 | 80 | int matche(register char *pattern, register char *text); 81 | 82 | /*---------------------------------------------------------------------------- 83 | * 84 | * Return TRUE if PATTERN has any special wildcard characters 85 | * 86 | ----------------------------------------------------------------------------*/ 87 | 88 | BOOLEAN is_pattern (char *pattern); 89 | 90 | /*---------------------------------------------------------------------------- 91 | * 92 | * Return TRUE if PATTERN has is a well formed regular expression according 93 | * to the above syntax 94 | * 95 | * error_type is a return code based on the type of pattern error. Zero is 96 | * returned in error_type if the pattern is a valid one. error_type return 97 | * values are as follows: 98 | * 99 | * PATTERN_VALID - pattern is well formed 100 | * PATTERN_ESC - pattern has invalid escape ('\' at end of pattern) 101 | * PATTERN_RANGE - [..] construct has a no end range in a '-' pair (ie [a-]) 102 | * PATTERN_CLOSE - [..] construct has no end bracket (ie [abc-g ) 103 | * PATTERN_EMPTY - [..] construct is empty (ie []) 104 | * 105 | ----------------------------------------------------------------------------*/ 106 | 107 | BOOLEAN is_valid_pattern (char *pattern, int *error_type); 108 | -------------------------------------------------------------------------------- /src/unix.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | ------------------------------------------------------------------------------ 4 | 5 | ===== 6 | CPCFS -- u n i x . c --- Unix specific routines 7 | ===== 8 | 9 | Version 0.85 (c) Derik van Zuetphen 10 | ------------------------------------------------------------------------------ 11 | */ 12 | 13 | 14 | #include 15 | #include 16 | //#include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include "unix.h" 24 | 25 | 26 | 27 | char termcap_buffer[1024]; 28 | char *term_entries; 29 | char *cl_string, *cm_string; /* Clear Screen, Cursor Motion */ 30 | 31 | const char perr_str[] = "terminal"; 32 | 33 | void break_handler(); 34 | 35 | void disable_break() { 36 | /* ^^^^^^^^^^^^^ */ 37 | signal (SIGINT,break_handler); /* Ctrl-C */ 38 | /* signal (SIGQUIT,break_handler);*/ /* Ctrl-\, hard interrupt */ 39 | /* signal (SIGTSTP,break_handler);*/ /* Ctrl-Z, suspend */ 40 | } 41 | 42 | 43 | void break_handler () { 44 | /* ^^^^^^^^^^^^^ */ 45 | Break_Wish = 1; 46 | disable_break(); 47 | } 48 | 49 | 50 | 51 | glob_t glob_buffer; 52 | int current_glob; 53 | 54 | 55 | char *glob_file (char *pattern) { 56 | /* ^^^^^^^^^ */ 57 | current_glob=0; 58 | if (glob(pattern, 0, NULL, &glob_buffer)!=0) { 59 | return NULL; 60 | } 61 | return glob_buffer.gl_pathv[current_glob++]; 62 | } 63 | 64 | 65 | char *glob_next () { 66 | /* ^^^^^^^^^ */ 67 | 68 | if (current_glob 0 answer the next char or 0 after `dsec' 1/10 seconds 91 | dsec < 0 wait `-dsec' 1/10 seconds and answer then 0 92 | intr = TRUE ^C interrupts 93 | constraints: -MAXINT < d(ezi)sec < 256 94 | Characters > 127 may cause trouble! (resetting the terminal??) 95 | I am using the POSIX termios structure, SYSV needs termio, BSD needs sgtty 96 | */ 97 | 98 | struct termios tio, save; 99 | char answer=0; /* default answer */ 100 | int tty = 0; /* stdin (must be console) */ 101 | 102 | /* open terminal and set timeout */ 103 | if (tcgetattr(tty,&tio)) perror(perr_str); /* also test stdin */ 104 | memcpy(&save,&tio,sizeof(tio)); 105 | 106 | tio.c_lflag &= ~(ISIG | ICANON | ECHO); /* raw mode */ 107 | if (dsec == 0) { 108 | tio.c_cc[VMIN] = 1; /* one character */ 109 | tio.c_cc[VTIME] = 0; /* and don't wait */ 110 | } else if (dsec > 0) { 111 | tio.c_cc[VMIN]=0; /* no inter-character delay */ 112 | tio.c_cc[VTIME]=dsec; /* wait for ... seconds */ 113 | } 114 | if (intr) { 115 | tio.c_lflag |= ISIG; 116 | tio.c_cc[VQUIT]=3; /* 3=^C invokes Quit signal */ 117 | } 118 | if (tcsetattr(tty,TCSANOW,&tio)) perror(perr_str); 119 | 120 | /* read a character */ 121 | if (dsec >= 0) { 122 | fflush (stdout); 123 | read (tty,&answer,1); 124 | } else { 125 | /* or simply wait */ 126 | sleep((-dsec+5)/10); 127 | tcflush(tty,TCIFLUSH); /* flush pending input */ 128 | } 129 | 130 | /* reset terminal */ 131 | if (tcsetattr(tty,TCSANOW,&save)) perror(perr_str); 132 | return answer; 133 | } /* wait_for_key */ 134 | 135 | 136 | /***************** 137 | Terminal Access 138 | *****************/ 139 | 140 | void clrscr() { 141 | /* ^^^^^^ */ 142 | fflush(stdout); 143 | printf("%s",cl_string); 144 | } /*clrscr*/ 145 | 146 | 147 | void gotoxy(int col,int lin) { 148 | /* ^^^^^^ */ 149 | if (*cm_string!=0) 150 | printf ("%s",tgoto (cm_string,col-1,lin-1)); 151 | } /*gotoxy*/ 152 | 153 | 154 | /* usefull, but not necessary: 155 | #include 156 | bool kbhit () { 157 | / * ^^^^^ * / 158 | long x; / * holds # of pending chars * / 159 | fflush(stdout); 160 | return((ioctl(0,FIONREAD,&x) < 0) ? 0 : x); 161 | } / *kbhit* / 162 | */ 163 | 164 | 165 | /**************** 166 | Initialization 167 | ****************/ 168 | 169 | void os_init () { 170 | /* ^^^^ */ 171 | int err; 172 | char *term; 173 | struct termios tio; 174 | 175 | 176 | /* Init TERMCAP: */ 177 | if ((term = getenv("TERM")) == NULL) err = 2; 178 | else err = tgetent(termcap_buffer,term); 179 | switch (err) { 180 | case -1: fprintf(stderr,"Can't access TERMINFO Directory!\n"); 181 | break; 182 | case 0: fprintf(stderr,"No entry for terminal '%s'!\n",term); 183 | break; 184 | case 2: fprintf(stderr,"No $TERM Variable!\n"); 185 | break; 186 | } /*switch*/ 187 | if (err !=1) exit(1); 188 | term_entries = (char*) malloc(100); 189 | if ((cl_string=tgetstr("cl",&term_entries))==NULL) { 190 | fprintf (stderr,"\n'%s' can't clear the screen!\n",term); 191 | cl_string=""; 192 | } 193 | if ((cm_string=tgetstr("cm",&term_entries))==NULL) { 194 | fprintf(stderr,"\n'%s' can't move the cursor!\n",term); 195 | cm_string=""; 196 | } 197 | 198 | /* get attributes of stdin and stdout; if failed, output was redirected! */ 199 | if (tcgetattr(0,&tio) || tcgetattr(1,&tio)) { 200 | fprintf(stderr,"Redirection of stdin/stdout is not allowed!"); 201 | } 202 | 203 | } /*os_init*/ 204 | -------------------------------------------------------------------------------- /template.doc: -------------------------------------------------------------------------------- 1 | 2 | NAME 3 | ---- 4 | CPCFS - CPCEmu Filesystem Maintenance Version: 0.85 5 | 6 | 7 | SYNOPSIS 8 | -------- 9 | CPCFS to invoke an interactive session 10 | CPCFS ... to perform batch commands 11 | 12 | 13 | DESCRIPTION 14 | ----------- 15 | CPCFS is a program to copy files form a DOS filesystem to a 16 | disk-image appropriate for CPCEmu, an emulator for the 17 | CPC464/664/6128 homecomputer series by Amstrad. 18 | 19 | Note: This documentation is generated out of CPCFS' help file CPCFS.HLP. 20 | Some "See also"-hints can therefore be misleading! 21 | 22 | 23 | INVOCATION 24 | ---------- 25 | ~invocation~ 26 | Options are simply a shortcut for commands. The first argument can 27 | be the name of an imagefile, all following arguments are commands. 28 | If you omit a command and only give ``dir'' is assumed. 29 | 30 | ~variables~ 31 | 32 | 33 | COMMANDS 34 | -------- 35 | All commands are case insensitive. Some commands write an informational 36 | message, if they are invoked without an argument (mode, open, page, 37 | prompt, verbosity,...). Some commands have more names than one. You can 38 | use the name that you like most. 39 | 40 | ~commands~ 41 | 42 | ~options~ 43 | 44 | 45 | GENERAL commands 46 | ================ 47 | 48 | ~open~ 49 | Closes the one in memory, if any. It appends the string ".DSK" to the 50 | DOS filename, if no extension is given. 51 | 52 | 53 | ~close~ 54 | 55 | ~user~ 56 | 57 | 58 | TRANSFER commands 59 | ================= 60 | 61 | ~get~ 62 | 63 | ~mget~ 64 | 65 | ~put~ 66 | 67 | ~mput~ 68 | 69 | ~mode~ 70 | Printable characters are newlines (10,13) and characters between space (32) 71 | and tilde (126). can be abbreviated to its first character B, T, or A. 72 | 73 | 74 | ~sysgen~ 75 | It only works, if the image was formatted with two system tracks (e.g. 76 | system or vortex format). See STAT, if you want to know the format or 77 | whether CP/M is already in the image. 78 | 79 | To obtain a CP/M-image you can use the accompanying BASIC program 80 | GETCPM.BAS. It runs on a CPC and copies the system tracks off a real CPC 81 | disk to the file SYSTEM.CPM. Then transfer the file to DOS. 82 | 83 | If you want to "sysgen" your vortex image, you must prepare CP/M first 84 | (see your vortex documentation). WARNING: I do not own a vortex drive 85 | nor its documentation, so I cannot tell if it works. Feedback is 86 | highly appreciated!!! 87 | 88 | 89 | DOS ACCESS commands 90 | =================== 91 | 92 | ~!~ 93 | Executes the program in a DOS-Shell. If only a "!" is 94 | typed, CPCFS invokes the interactive shell given in the %COMSPEC% 95 | environment variable or C:\COMMAND.COM if the variable isn't set. You 96 | must type EXIT to return from the shell to CPCFS. Contrary to other 97 | commands there is no space needed behind the "!". 98 | 99 | 100 | ~cd~ 101 | 102 | The "L" in LCD (as well as in LDIR) reminds you, that this command 103 | affects the local filesystem of DOS. 104 | 105 | 106 | ~ldir~ 107 | 108 | 109 | CP/M MAINTENANCE commands 110 | ========================= 111 | 112 | Informational 113 | ------------- 114 | 115 | ~dir~ 116 | 117 | ~stat~ 118 | 119 | ~dpb~ 120 | ~background~ 121 | 122 | ~comment~ 123 | 124 | ~type~ 125 | 126 | ~dump~ 127 | 128 | Note: Options for DUMP can be packed, but this can be confusing 129 | E.g. DUMP -B0 -2B5 for blocks 0 to 5 130 | or DUMP -12 for blocks form beginning (1) to the same block (1) 131 | 132 | 133 | Changing files 134 | -------------- 135 | 136 | ~format~ 137 | 138 | ~del~ 139 | 140 | ~ren~ 141 | REN uses the well-known DOS order for renaming (think of the word TO 142 | between the filenames) and not the (possibly expected) CP/M order (think 143 | of ":=" between the filenames) 144 | 145 | 146 | ~attrib~ 147 | 148 | ~copy~ 149 | 150 | 151 | 152 | USER INTERFACE comamnds 153 | ======================= 154 | 155 | # 156 | This is a comment. Everything upto the next command is ignored. 157 | 158 | 159 | ~quit~ 160 | Quits CPCFS. You can hit Ctrl-Z to achieve the same purpose, only faster! 161 | (The Unix version uses Ctrl-D as shortcut for Quit) 162 | 163 | 164 | ~help~ 165 | The startup helppage is: 166 | ----------------------------------------------------------------------------- 167 | ~nothing~ 168 | ----------------------------------------------------------------------------- 169 | 170 | ~source~ 171 | 172 | ~echo~ 173 | 174 | ~prompt~ 175 | 176 | ~cls~ 177 | 178 | ~force~ 179 | 180 | ~verbosity~ 181 | 182 | ~page~ 183 | CPCFS has an internal pager for commands like DUMP, TYPE, and so on. 184 | With the PAGE command you can set the number of lines of your screen 185 | (the default is 25). If you set PAGE 0, you bypass the internal pager 186 | and things are displayed without stops. 187 | 188 | 189 | 190 | FILENAMES 191 | --------- 192 | ~filenames~ 193 | 194 | EDITING 195 | ------- 196 | ~editing~ 197 | 198 | VALUES & PERCENT EXPANSION 199 | -------------------------- 200 | ~values~ 201 | ~percent~ 202 | 203 | 204 | FILES 205 | ----- 206 | CPCFS.CFG 207 | The commands in this file are executed every time CPCFS starts in 208 | interactive mode. You can set the PROMPT here or open an often 209 | used image or ... 210 | 211 | CPCFS looks for it at first in the current directory and then in the 212 | directory where CPCFS.EXE resides. 213 | If the environment variable CPCFSHOME is set, then CPCFS looks here. 214 | 215 | *.DSK 216 | This is the default extension for imagfiles 217 | 218 | *.CPM 219 | This is the default extension for CP/M tracks saved by GETCPM.BAS 220 | and needed by SYSGEN. 221 | 222 | SEE ALSO 223 | -------- 224 | GETCPM.BAS, a Locomotive BASIC program to copy CP/M tracks off a 225 | diskette and save them to a file. Simply type it in your *original* CPC 226 | (you can leave out REMarks and PRINTs) and run it on a CP/M system disk. 227 | It creates a file, that you have to transfer in some way to DOS (read 228 | the CPCEmu documentation for various ways, how to arrange it). Finally 229 | use CPCFS' SYSGEN command to create a diskimage with CP/M. 230 | 231 | DROPTAG.BAT, a batch file replacing DROPTAG.EXE of the former distribution. 232 | 233 | RELATED WORKS 234 | ------------- 235 | CPCEmu 236 | (C) Marco Vieth (ali@uni-paderborn.de) 237 | Current version: 1.3b 238 | Available on: ftp://oak.oakland.edu/Simtel/msdos/emulator 239 | or any other Simtel site 240 | 241 | CPE 242 | (C) Bernd Schmidt (crux@pool.informatik.rwth-aachen.de) 243 | Current Version: 5.0 244 | Available on: ftp://oak.oakland.edu/Simtel/msdos/emulator 245 | or any other Simtel site 246 | 247 | comp.sys.amstrad.8bit 248 | A usenet newsgroup for discussing CPC related topics 249 | 250 | http://arachnid.cm.cf.ac.uk/User/K.E.W.Thacker/Amstrad 251 | A WWW page with a lot of CPC stuff. 252 | 253 | ftp://ftp.ibp.fr/pub/amstrad/amstrad.faq 254 | ftp://rtfm.mit.edu/pub/usenet/comp.sys.amstrad.8bit 255 | The Amstrad FAQ 256 | 257 | 258 | BUGS 259 | ---- 260 | ~bugs~ 261 | 262 | LEGAL STUFF 263 | ----------- 264 | ~about~ 265 | 266 | Trademarks: 267 | "CPC", "Amsdos" is from Amstrad 268 | "CP/M" is from Digital Research (now Novell) 269 | "Borland C" is from Borland 270 | "ACTlib 1.7" is public domain and written by Marc Stern 271 | 272 | 273 | AUTHOR 274 | ------ 275 | Derik van Zuetphen 276 | 277 | email: dz@426.ch 278 | www: http://www.426.ch 279 | 280 | The latest version of CPCFS and recent patches can be found in my 281 | Github repository on https://github.com/derikz/cpcfs 282 | -------------------------------------------------------------------------------- /src/cpcfs.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | ------------------------------------------------------------------------------ 4 | 5 | ===== 6 | CPCFS -- c p c f s . h Variable, Structures, Prototypes 7 | ===== 8 | 9 | Version 0.85 (c) Derik van Zuetphen 10 | ------------------------------------------------------------------------------ 11 | */ 12 | 13 | #ifndef CPCFS_H_INCLUDED 14 | #define CPCFS_H_INCLUDED 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include "match.h" 24 | 25 | 26 | /****** Configure here! (or in Makefile) ******/ 27 | 28 | /* One out of the constants LINUX or DOS must be set to 1. 29 | This is normally done in the Makefile, in the project file, or with the 30 | Options menu. */ 31 | 32 | #ifndef DOS 33 | #define DOS 0 34 | #endif 35 | #ifndef LINUX 36 | #define LINUX 0 37 | #endif 38 | 39 | 40 | #ifndef USE_READLINE 41 | #define USE_READLINE 0 /* either GNU "readline" or ACTlib17 "inputs" */ 42 | #endif 43 | 44 | /****** End of configuration ******/ 45 | 46 | #define MAJORVERSION 0 47 | #define MINORVERSION 85 48 | #define PATCHLEVEL 4 49 | 50 | 51 | /****** Operating System ******/ 52 | 53 | #define SYSV LINUX 54 | /*#define BSD FREEBSD*/ 55 | #define UNIX SYSV | BSD 56 | #if UNIX 57 | #include "unix.h" 58 | #elif DOS 59 | #include "dos.h" 60 | #endif 61 | 62 | 63 | 64 | 65 | typedef unsigned char uchar; 66 | /* defined in types.h, must be in INTEL byte-order! (low, high) */ 67 | #if DOS 68 | typedef unsigned short ushort; 69 | #endif 70 | 71 | typedef char bool; 72 | #define TRUE 1 73 | #define FALSE 0 74 | 75 | 76 | /*********** 77 | Variables 78 | ***********/ 79 | 80 | /****** CPC Filesystem ******/ 81 | extern int BLKNR_SIZE; 82 | extern int BLKNR; 83 | 84 | typedef enum { SYSTEMFORMAT=0x41, 85 | DATAFORMAT=0xC1, 86 | IBMFORMAT=0x01, 87 | VORTEXFORMAT=0x80 /* A flag, the sector offset is 01 too*/ 88 | } Format_ID; 89 | 90 | 91 | /* FORMATDIFF Must be less than all differences between Format_IDs. This 92 | constant is used to detect the type of interleaved formats. */ 93 | 94 | #define FORMATDIFF 0x20 95 | 96 | 97 | #define RECORDSIZE 128 /* 1/8 kByte, constant for CP/M */ 98 | #define EXTENTSIZE 16384 /* 16 kByte, constant for CP/M */ 99 | #define CPM_EOF 26 /* Ctrl-Z */ 100 | 101 | #ifndef PATH_MAX 102 | #define PATH_MAX 256 /* for getwd() */ 103 | #endif 104 | 105 | extern unsigned char filler; /* copied from track header */ 106 | 107 | extern char full_imagename[PATH_MAX]; /* full pathname, ... */ 108 | extern char *imagename; /* ... only name portion, and ... */ 109 | extern int imagefile; /* ... handle of imagefile */ 110 | 111 | extern int cur_user; 112 | extern int cur_format; 113 | 114 | extern uchar *track; /* 0x100 header + track data */ 115 | 116 | extern uchar *block_buffer; /* 1024 or more bytes for one block */ 117 | 118 | extern uchar *blk_alloc; /* Block-Allocation-Table, points to entry, */ 119 | /* FF = free block */ 120 | extern int allocated_blks; 121 | extern int free_blks; 122 | extern int total_blks; /* = allocated_blks + free_blks */ 123 | extern float percentage; /* of allocated blocks */ 124 | 125 | 126 | 127 | /****** User Interface *****/ 128 | #define CONFIGNAME "cpcfs.cfg" 129 | #define HELPFILE "cpcfs.hlp" 130 | #if UNIX 131 | #define INPUTLEN 4096 132 | #else 133 | #define INPUTLEN 256 134 | #endif 135 | extern int nbof_args; 136 | extern char *arg[INPUTLEN]; 137 | extern char prompt[INPUTLEN]; 138 | extern int pagelen, lineno; 139 | extern bool Break_Wish; 140 | extern jmp_buf break_entry; 141 | extern bool Interactive; 142 | extern char installpath[]; 143 | extern int mode; /* Mode for GET-commands */ 144 | #define M_TEXT 1 145 | #define M_BIN 2 146 | #define M_AUTO 3 147 | 148 | extern bool force; 149 | extern int Verb; 150 | 151 | 152 | /****** Utility Macros ******/ 153 | /* Answer a plural suffix, if nr is not 1 */ 154 | #define plural(nr) ((nr)==1?"":"s") 155 | #define plural_y(nr) ((nr)==1?"y":"ies") 156 | #define atoxi(CP) (int)strtol((CP),NULL,0) 157 | /* same as atoi, but recognizes 0x and 0 prefixes */ 158 | 159 | #ifndef max 160 | #define max(A,B) ((A)>=(B)?(A):(B)) 161 | #endif 162 | #ifndef min 163 | #define min(A,B) ((A)<=(B)?(A):(B)) 164 | #endif 165 | 166 | 167 | /****** Bitmasks for attributes and dir command */ 168 | #define DIR_DOUBLE 0x0 /* \ */ 169 | #define DIR_WIDE 0x1 /* \ bit 0 and 1 => 4 choices */ 170 | #define DIR_AMSHEAD 0x2 /* / */ 171 | #define DIR_LONG 0x3 /* / */ 172 | #define DIR_SORT 0x4 /* bit 2 => 1 choice */ 173 | 174 | #define ATTR_1 0x400 175 | #define ATTR_2 0x200 176 | #define ATTR_3 0x100 177 | #define ATTR_4 0x080 178 | #define ATTR_5 0x040 179 | #define ATTR_6 0x020 180 | #define ATTR_7 0x010 181 | #define ATTR_8 0x008 182 | #define ATTR_R 0x004 183 | #define ATTR_S 0x002 184 | #define ATTR_A 0x001 185 | 186 | 187 | /************ 188 | Structures 189 | ************/ 190 | 191 | struct d_header { /* disk header of size 0x100 */ 192 | uchar tag[0x30]; 193 | /*00-21 MV - CPC ... */ 194 | /*22-2F unused (0) */ 195 | uchar nbof_tracks; 196 | /*30 number of tracks (40) */ 197 | uchar nbof_heads; 198 | /*31 number of heads (1) 2 not yet supported by cpcemu */ 199 | short tracksize; /* short must be 16bit integer */ 200 | /*32-33 tracksize (including 0x100 bytes header) 201 | 9 sectors * 0x200 bytes each + header = 0x1300 */ 202 | uchar unused[0xcc]; 203 | /*34-FF unused (0)*/ 204 | }; 205 | 206 | extern struct d_header disk_header; 207 | 208 | 209 | 210 | struct s_info { /* sector info, used in track header */ 211 | /*Sector-Information (for each sector):*/ 212 | uchar track; 213 | /*18+i tracknumber \ */ 214 | uchar head; 215 | /*19+i headnumber | Sector-ID-Information */ 216 | uchar sector; 217 | /*1A+i sectornumber | */ 218 | uchar BPS; 219 | /*1B+i BPS / */ 220 | uchar status1; 221 | /*1C+i status 1 errorcode (0)*/ 222 | uchar status2; 223 | /*1D+i status 2 errorcode (0)*/ 224 | uchar unused[2]; 225 | /*1E+i,1F+i unused (0)*/ 226 | } ; 227 | 228 | struct t_header { /* track header of size 0x100 */ 229 | uchar tag[0x10]; 230 | /*00-0C Track-Info\r\n*/ 231 | /*0D-0F unused (0)*/ 232 | uchar track; 233 | /*10 tracknumber (0 to number-of-tracks - 1)*/ 234 | uchar head; 235 | /*11 headnumber (0)*/ 236 | uchar unused[2]; 237 | /*12-13 unused (0)*/ 238 | /*Format-Track-Parameter:*/ 239 | uchar BPS; 240 | /*14 BPS (bytes per sector) (2 for 0x200 Bytes)*/ 241 | uchar SPT; 242 | /*15 SPT (sectors per track) (9, max. 18 possible)*/ 243 | uchar GAP3; 244 | /*16 GAP#3 Format (gap for formatting: 0x4E)*/ 245 | uchar filler; 246 | /*17 Filling-Byte (filler for formatting: 0xE5)*/ 247 | struct s_info sector[29]; 248 | } ; 249 | 250 | 251 | /****** Directory ******/ 252 | 253 | typedef struct { 254 | uchar user; /* actually a byte */ 255 | uchar root[8]; /* padded with space */ 256 | uchar ext[3]; /* ditto */ 257 | uchar name[13]; /* +"."++"\0" (for globbing) */ 258 | uchar rec; /* size in 128 Byte records */ 259 | 260 | int attr; /* bit array of size 11 (87654321rsa) */ 261 | int blk[16]; /* 16 or 8 indices of 1 or 2 byte */ 262 | uchar extent; /* aka sorting criterion for dir entires */ 263 | 264 | /* only stored to write it back */ 265 | uchar unused[2]; /* used for internal CP/M purposes */ 266 | 267 | /* organisational */ 268 | bool first; /* this entry is first */ 269 | long size; /* length in Bytes, only set if */ 270 | int next; /* next entry for this file, -1 if last */ 271 | } DirEntry; 272 | 273 | extern DirEntry *directory; 274 | 275 | 276 | 277 | /****** Disk Parameter Block ******/ 278 | 279 | typedef struct { 280 | /* extended DPB info, needed for format, in DPB_store */ 281 | uchar ID; /* Identifier */ 282 | ushort SEC1;/* 1. SECtor number (0, >1, >41h, >C1h) */ 283 | ushort SECS;/* number of sectors per track (8, >9) */ 284 | ushort TRKS;/* number of tracks per side (>40, 80) */ 285 | ushort HDS; /* number of heads per disk (>1, 2) */ 286 | ushort BPS; /* Bytes Per Sector (128, 256, >512, 1024) */ 287 | 288 | /* original Disk Parameter Block (> marks CPC defaults) */ 289 | ushort SPT; /* records Per Track (18, 20, 30, 32, 34, >36, 40) */ 290 | uchar BSH; /* Block SHift ... 2^BSH = BLM+1 = Rec/Block */ 291 | uchar BLM; /* BLock Mask (>3/7, 4/15) */ 292 | uchar EXM; /* EXtent Mask (0, 1) Number of Extents/Entry - 1 */ 293 | ushort DSM; /* max. blocknumber = number of blocks - 1 */ 294 | ushort DRM; /* DiRectory size - 1 (31, >63, 127) */ 295 | uchar AL0; /* \ DRM in binary (80h/0, >C0h/0, F0h/0) */ 296 | uchar AL1; /* / (1 bit=1 block, 11000000/00000000 = 2 blocks) */ 297 | ushort CKS; /* ChecK recordS,nb of rec in dir (8, >16, 32) */ 298 | ushort OFS; /* OFfSet, reserved tracks (1, >2, 3, 4) */ 299 | 300 | /* extended DPB info, automatically calculated */ 301 | ushort BLS; /* BLock Size in bytes (>1024, 2048)*/ 302 | bool SYS; /* CP/M present in system tracks */ 303 | ushort DBL; /* Directory BLocks = CKS/8 */ 304 | } DPB_type; 305 | 306 | 307 | /* DPB templates for SYSTEM, DATA, IBM, VORTEX, and user defined */ 308 | extern const int DPB_store_size; 309 | typedef enum { SYSTEM_DPB=0, 310 | DATA_DPB=1, 311 | IBM_DPB=2, 312 | VORTEX_DPB=3, 313 | USER_DPB=4 314 | } DPB_Index_Type; 315 | extern DPB_type DPB_store[]; 316 | 317 | extern DPB_type *dpb; /* pointer to current DPB */ 318 | 319 | 320 | 321 | /******* 322 | Tools 323 | *******/ 324 | 325 | void putcharm(int,char); 326 | void printm(int,char*,...); 327 | char *lower(char*); 328 | char *upper(char*); 329 | char *append_suffix (char*,char*); 330 | int errorf (bool,const char*,...); 331 | const char *show_attr(int attr, int mask, bool always); 332 | const char* show_all_attr(int att, bool always); 333 | void do_break(); 334 | void newpage(char* keys); 335 | char nextline(); 336 | char *repstr(char c,int times); 337 | char *show_format (uchar sec_offset); 338 | char *show_mode (int m); 339 | void reparse (int argnb); 340 | void expand_percent(char *from, char *to, int max); 341 | void echom (int v, char *p); 342 | bool confirmed() ; 343 | bool tag_ok (); 344 | void alloc_block(int blk,int file); 345 | void free_block(int blk); 346 | bool is_free_block(int blk); 347 | void calc_allocation (); 348 | bool inactive (); 349 | int trk_calc (int blk); 350 | int sec_calc (int blk); 351 | void abandonimage(); 352 | int parse_cpm_filename(char *name, int *user, char *root, char *ext); 353 | int parse_filename(char *name, int *drive, char *path, char *root, char *ext); 354 | int pager(char *filename); 355 | bool has_wildcards(char os_tag, char *filename); 356 | void build_cpm_name (char *buf, int user, char *root, char *ext); 357 | void build_cpm_name_32(char *buf, int user, char *root, char *ext); 358 | void str2mem(char *mem, char *str, int spc); 359 | char *show_hex(int nr, uchar *buf, int size); 360 | void *Malloc(int bytes); 361 | 362 | 363 | /******** 364 | Tracks 365 | ********/ 366 | 367 | int read_track (int hd, int trk); 368 | int write_track(); 369 | bool next_sector(int*,int*,int*); 370 | 371 | /*********** 372 | Directory 373 | ***********/ 374 | 375 | void get_directory(); 376 | void put_directory(); 377 | void update_directory(); 378 | 379 | extern int glob_env; 380 | int glob_cpm_next(); 381 | int glob_cpm_file(char *pat); 382 | 383 | /******* 384 | Image 385 | *******/ 386 | 387 | void close_image(); 388 | int open_image(char *name); 389 | int format(char* name,DPB_type *dpb); 390 | int sysgen(char* name); 391 | int comment_image(const char *text); 392 | 393 | /******** 394 | Blocks 395 | ********/ 396 | 397 | int get_free_block(); 398 | 399 | 400 | /***************** 401 | FS Maintenance 402 | *****************/ 403 | 404 | long delete (bool silent, char *pattern) ; 405 | int parse_attr(char *str, int *mask, bool *set); 406 | int change_attrib(char* pattern, int set, int reset); 407 | 408 | /********** 409 | Transfer 410 | **********/ 411 | 412 | int detectmode (char *buf, int size) ; 413 | long get (char *src,char *target); 414 | long put (char *src, char *trg); 415 | int dir(char *pat, int mask); 416 | int ren_file(char *from, char *to); 417 | int ren_wild(char *pat, int user); 418 | int copy_file(char *from, char *to); 419 | int copy_wild(char *pat, int user); 420 | 421 | /********* 422 | Dumping 423 | *********/ 424 | 425 | int dump(FILE *file, int block, int head, int track, int sector); 426 | int dumpdir (FILE *file); 427 | int map (FILE *file); 428 | 429 | 430 | /**************** 431 | User Interface 432 | ****************/ 433 | 434 | int execute_file (char *name); 435 | int execute_cmd (char *cmd); 436 | /*int execute_one_cmd (char *cmd);*/ 437 | 438 | #if DOS 439 | #define vert 0xB3 440 | #define hori 0xC4 441 | #define cross 0xC5 442 | #else 443 | #define vert '|' 444 | #define hori '-' 445 | #define cross '+' 446 | #endif 447 | 448 | 449 | #endif 450 | -------------------------------------------------------------------------------- /src/tools.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | ------------------------------------------------------------------------------ 4 | 5 | ===== 6 | CPCFS -- t o o l s . c -- Auxiliary functions 7 | ===== 8 | 9 | Version 0.85 (c) Derik van Zuetphen 10 | ------------------------------------------------------------------------------ 11 | */ 12 | 13 | #include 14 | 15 | #if DOS 16 | #include "conio.h" 17 | #include "dos.h" 18 | #endif 19 | 20 | #include "cpcfs.h" 21 | 22 | 23 | /************* 24 | Auxiliaries 25 | *************/ 26 | 27 | void printm(int verb, char *fmt, ...) { 28 | /* ^^^^^^ */ 29 | va_list args; 30 | const char highlight[] = "\x1B[0;31;1m"; /* light red */ 31 | const char unhighlight[] = "\x1B[0m"; 32 | 33 | va_start (args, fmt); 34 | if (Verb >= verb) { 35 | if (verb>9) printf("%s",highlight); 36 | vprintf(fmt,args); 37 | if (verb>9) { 38 | printf("%s",unhighlight); 39 | fflush(stdout); 40 | } 41 | } 42 | va_end(args); 43 | } 44 | 45 | void putcharm(int verb, char ch) { 46 | /* ^^^^^^^^ */ 47 | if (Verb >= verb) putchar(ch); 48 | } 49 | 50 | 51 | char *lower(char *s) { 52 | /* ^^^^^ */ 53 | char *p=s; 54 | while (*s) { 55 | *s=tolower(*s); 56 | s++; 57 | } 58 | return p; 59 | } 60 | 61 | char *upper(char *s) { 62 | /* ^^^^^ */ 63 | char *p=s; 64 | while (*s) { 65 | *s=toupper(*s); 66 | s++; 67 | } 68 | return p; 69 | } 70 | 71 | 72 | char *append_suffix (char *name, char *suffix) { 73 | /* ^^^^^^^^^^^^^ 74 | Appends to , but not if the name already ends with . 75 | must point to sufficient space. Returns */ 76 | char *p, *ds, *dot; 77 | 78 | p=name; 79 | ds=dot=NULL; 80 | while (*p) { 81 | if (*p==DIRSEPARATOR) ds=p; 82 | if (*p=='.') dot=p; 83 | p++; 84 | } 85 | if ((dot==NULL)||(dot set. The message in should not end with \n. 97 | Always returns -1. */ 98 | 99 | va_list args; 100 | if (Verb<1) return -1; 101 | 102 | va_start(args,fmt); 103 | vfprintf(stderr,fmt,args); 104 | va_end(args); 105 | if (perr) { 106 | #if UNIX 107 | fprintf(stderr,": "); 108 | #endif 109 | /* DOS put always a colon before the error message, UNIX only if the argument 110 | to perror is not empty */ 111 | perror(""); 112 | } else 113 | fputc(10,stderr); 114 | return -1; 115 | } 116 | 117 | /* attribute names for parsing and writing attributes */ 118 | 119 | const char *attr_name[3][11] = { 120 | {"A=ON", "SYS", "R/O", "F8=ON", "F7=ON", "F6=ON", "F5=ON", /* set */ 121 | "F4=ON", "F3=ON", "F2=ON", "F1=ON" }, 122 | {"A=OFF","DIR", "R/W", "F8=OFF","F7=OFF","F6=OFF","F5=OFF", /* reset */ 123 | "F4=OFF","F3=OFF","F2=OFF","F1=OFF" }, 124 | {"A", "SYS", "R/O", "8", "7", "6", "5", "4", "3", "2", "1" } /* short */ 125 | }; 126 | 127 | const char *attr_empty[] = {"-", " ", " "}; 128 | 129 | 130 | const char *show_attr(int mask, int attr, bool always) { 131 | /* ^^^^^^^^^ 132 | Answer a representation for attribute , if it appears in , 133 | If is set, answer also a string for zero-attributes */ 134 | 135 | if (mask & attr) { /* attribute set */ 136 | switch (attr) { 137 | case ATTR_A: return attr_name[2][0]; 138 | case ATTR_S: return attr_name[2][1]; 139 | case ATTR_R: return attr_name[2][2]; 140 | case ATTR_8: return attr_name[2][3]; 141 | case ATTR_7: return attr_name[2][4]; 142 | case ATTR_6: return attr_name[2][5]; 143 | case ATTR_5: return attr_name[2][6]; 144 | case ATTR_4: return attr_name[2][7]; 145 | case ATTR_3: return attr_name[2][8]; 146 | case ATTR_2: return attr_name[2][9]; 147 | case ATTR_1: return attr_name[2][10]; 148 | } 149 | } else if (always) /* attribute not set and output required */ 150 | switch (attr) { 151 | case ATTR_S: return attr_name[1][1]; 152 | case ATTR_R: return attr_name[1][2]; 153 | default: return attr_empty[0]; 154 | } 155 | else { /* only spaces, attribute not set and no output */ 156 | switch (attr) { 157 | case ATTR_R: return attr_empty[1]; 158 | case ATTR_S: return attr_empty[1]; 159 | default: return attr_empty[2]; 160 | } 161 | } 162 | errorf(FALSE,"--==>>> show_attr"); 163 | return NULL; 164 | } 165 | 166 | 167 | const char* show_all_attr(int att, bool always) { 168 | /* ^^^^^^^^^^^^^ 169 | Show all attribues like "R/O SYS A 1234 5678" or "R/W DIR - ---- ----" 170 | */ 171 | 172 | static char buf[25]; 173 | sprintf(buf,"%s %s %s %s%s%s%s %s%s%s%s", 174 | show_attr(att,ATTR_R,always), 175 | show_attr(att,ATTR_S,always), 176 | show_attr(att,ATTR_A,always), 177 | show_attr(att,ATTR_1,always), 178 | show_attr(att,ATTR_2,always), 179 | show_attr(att,ATTR_3,always), 180 | show_attr(att,ATTR_4,always), 181 | show_attr(att,ATTR_5,always), 182 | show_attr(att,ATTR_6,always), 183 | show_attr(att,ATTR_7,always), 184 | show_attr(att,ATTR_8,always)); 185 | return buf; 186 | } 187 | 188 | 189 | int parse_attr(char *str, int *mask, bool *set) { 190 | /* ^^^^^^^^^^ 191 | Parse the given string as a attribute name. Set the <*mask> to the 192 | recognized attribut, and whether the attribut is requested to set. 193 | Answer -1, if is not a valid attribute name. 194 | */ 195 | int i, j; 196 | char s[INPUTLEN]; 197 | 198 | strcpy(s,str); 199 | upper(s); 200 | for (i=0;i<2;i++) { /* set, reset */ 201 | for (j=0;j<11;j++) { /* all attributs */ 202 | if (strcmp(s,attr_name[i][j])==0) { 203 | *set = (i==0); 204 | *mask = 0x1 << j; 205 | return 0; 206 | } 207 | } 208 | } 209 | 210 | return -1; 211 | } 212 | 213 | 214 | 215 | 216 | void do_break() { 217 | /* ^^^^^^^^ 218 | Jump to interactive mode or exit */ 219 | printm(3," [Aborted]\n"); 220 | if (Interactive) 221 | longjmp(break_entry,1); 222 | else 223 | exit(2); 224 | } 225 | 226 | 227 | void *Malloc(int bytes) { 228 | /* ^^^^^^ 229 | Allocate memory. If an error ocurrs, jump to input loop */ 230 | void *p; 231 | 232 | p = malloc(bytes); 233 | if (p==NULL) { 234 | errorf(TRUE,"Malloc"); 235 | abandonimage(); 236 | do_break(); 237 | return NULL; 238 | } 239 | return p; 240 | } 241 | 242 | 243 | char valid_keys[4]; /* can be c, q, and/or r */ 244 | 245 | void newpage(char *keys) { 246 | /* ^^^^^^^ 247 | Set up a new paging cycle. contains a list of characters that may be 248 | pressed. */ 249 | strcpy(valid_keys,keys); 250 | lineno=1; 251 | } 252 | 253 | 254 | char nextline() { 255 | /* ^^^^^^^^ 256 | Anvance the line counter of the internal pager. Stop at a prompt, if the 257 | screen is full. Return the pressed character or '\0' */ 258 | char answer; 259 | int i; 260 | 261 | if (Break_Wish) do_break(); 262 | if ((lineno==0) || (pagelen==0) || (Verb < 1)) return 0; 263 | lineno++; 264 | if (lineno+1>pagelen) { 265 | printm(1,"-- M o r e -- "); 266 | if (*valid_keys!=0) putcharm(1,'('); 267 | for (i=0; i0) printm(1,", "); 269 | switch (valid_keys[i]) { 270 | case 'c': printm(1,"`c' = continous"); break; 271 | case 'q': printm(1,"`q' = quit"); break; 272 | case 'r': printm(1,"`r' = restart"); break; 273 | default: errorf(FALSE,"--==>>> nextline"); 274 | } 275 | } 276 | if (*valid_keys!=0) putcharm(1,')'); 277 | 278 | answer = tolower(wait_for_key(0,TRUE)); 279 | putcharm(1,10); 280 | if (Break_Wish) do_break(); 281 | if (answer=='c' && strchr(valid_keys,'c')!=NULL) { 282 | lineno=-1; 283 | return 'c'; 284 | } 285 | lineno=1; 286 | if (strchr(valid_keys,answer)!=NULL) return answer; 287 | } 288 | return 0; 289 | } 290 | 291 | 292 | char *repstr(char c, int times) { 293 | /* ^^^^^^ 294 | Form a string out of . 295 | There must only be one repstr in a printf statement!!! 296 | */ 297 | int i; 298 | static char str[INPUTLEN]; 299 | for (i=0;ilen(%V)) as overflow area */ 358 | 359 | while (*from) { 360 | if (*from!='%') *t++=*from; 361 | else { 362 | from++; 363 | switch (*from) { 364 | case 'u': 365 | case 'U': if (active) 366 | t+=sprintf(t,"%d",cur_user); 367 | else 368 | *t++='#'; 369 | break; 370 | case 'i': if (active) 371 | t+=sprintf(t,"%s",imagename); 372 | else 373 | *t++='#'; 374 | break; 375 | case 'I': if (active) 376 | t+=sprintf(t,"%s",full_imagename); 377 | else 378 | *t++='#'; 379 | break; 380 | case 'f': if (active) 381 | t+=sprintf(t,"%5.1f",100.00-percentage); 382 | else 383 | *t++='#'; 384 | break; 385 | case 'F': if (active) 386 | t+=sprintf(t,"%ld", 387 | (long)free_blks*dpb->BLS); 388 | else 389 | *t++='#'; 390 | break; 391 | case 'a': if (active) 392 | t+=sprintf(t,"%5.1f",percentage); 393 | else 394 | *t++='#'; 395 | break; 396 | case 'A': if (active) 397 | t+=sprintf(t,"%ld", 398 | (long)allocated_blks*1024); 399 | else 400 | *t++='#'; 401 | break; 402 | case 'c': 403 | case 'C': t+=sprintf(t,"%s",getwd(buf)); 404 | break; 405 | case 'v': if (PATCHLEVEL==0) 406 | t+=sprintf(t,"%d.%d", 407 | MAJORVERSION,MINORVERSION); 408 | else 409 | t+=sprintf(t,"%d.%d.%d", 410 | MAJORVERSION,MINORVERSION,PATCHLEVEL); 411 | break; 412 | case 'V': t+=sprintf(t,"%d.%d.%d", 413 | MAJORVERSION,MINORVERSION,PATCHLEVEL); 414 | break; 415 | case '%': *t++='%'; break; 416 | case '_': *t++=' '; break; 417 | case '#': *t++='#'; break; 418 | case 'e': 419 | case 'E': *t++=27; break; 420 | case 'q': 421 | case 'Q': *t++='"'; break; 422 | case 's': 423 | case 'S': *t++=';'; break; 424 | case 'M': 425 | /* showing the free memory is not the duty of an application these days. */ 426 | t+=sprintf(t,"%lu",0L); break; 427 | default : *t++='%'; *t++=*from; break; 428 | } 429 | } 430 | if (*from) from++; 431 | 432 | /* check on string overflow */ 433 | if (t>t0+max) break; 434 | } 435 | *t = 0; 436 | 437 | strncpy(to,t0,max-1); 438 | to[max-1] = 0; /* if t0 is _exactly_ of length max-1 */ 439 | free(t0); 440 | } 441 | 442 | 443 | void echom (int v, char *p) { 444 | /* ^^^^^ */ 445 | 446 | char buf[256]; /* 3 lines should be enough */ 447 | if (Verb >= v) { 448 | expand_percent(p,buf,256); 449 | printf("%s",buf); 450 | } 451 | } 452 | 453 | 454 | bool confirmed() { 455 | /* ^^^^^^^^^ */ 456 | char answer; 457 | printm(1,"[y/N] _"); 458 | if (force||(Verb<1)) { 459 | printm(3," [Forced]\n"); 460 | return TRUE; 461 | } else { 462 | answer = wait_for_key(0,TRUE); 463 | putcharm(1,10); 464 | if (Break_Wish) return FALSE; 465 | return (tolower(answer) == 'y'); 466 | } 467 | } 468 | 469 | 470 | int parse_cpm_filename (char *name, int *user, char *root, char *ext) { 471 | /* ^^^^^^^^^^^^^^^^^^ 472 | Split the CP/M conformant "UU:RRRRRRRR.EEE" into its ASCIIZ parts. 473 | will be -1, if no UU: found; -2, if UU: is *: 474 | will be "", if no .EEE found. 475 | = "" is allowed and signals a missing filename (e.g. dir 1:). 476 | Filenames of kind ".xxx" are not possible. 477 | The or parts can be longer than 8 or 3 due to "[]" wildcards! 478 | Errorcode is 1 on error. 479 | */ 480 | char *p, *q, *r; /* temp pointer */ 481 | int i; 482 | 483 | *user = -1; 484 | *root = 0; 485 | *ext = 0; 486 | 487 | /* scan usernumber */ 488 | p = strchr(name,':'); 489 | if (p!=NULL) { 490 | if ((name[0]=='*') && (name[1]==':')) { 491 | *user = -2; 492 | name += 2; 493 | } else { 494 | *user = (int)strtoul(name,&q,0); 495 | if ((*user<0) || (*user>255) || (*q!=':') 496 | || (errno==ERANGE)) 497 | return 1; 498 | name = p+1; 499 | } 500 | } 501 | 502 | /* scan root part */ 503 | p = name; 504 | i = 0; 505 | r = root; 506 | while ((*p!='.') && *p && i<8) { 507 | *r++ = *p++; 508 | i++; 509 | } 510 | *r=0; 511 | q=strchr(p,'.'); 512 | if (q) p=q; 513 | 514 | /* scan extension part */ 515 | if (*p) strcpy(ext,p+1); 516 | 517 | 518 | /* convert to upper case */ 519 | upper(root); 520 | upper(ext); 521 | 522 | if (*root==0 && *ext!=0) return 1; 523 | return 0; 524 | } 525 | 526 | 527 | 528 | int parse_filename (char *name, int *drive, char *path, char *root, char *ext) { 529 | /* ^^^^^^^^^^^^^^ 530 | Split the DOS conformant "D:\PP\..\PP\RRRRRRRR.EEE" into its parts. 531 | will be 0, if no D: was found, and 1 for A:, 2 for B:, and so on; 532 | contains a trailing backslash (i.e. at least "\"). 533 | On UNIX systems may contain dots and is always empty. 534 | Errorcode is 1 on error 535 | */ 536 | char *p; /* temp pointer */ 537 | #if DOS 538 | char *r; 539 | int i; 540 | #endif 541 | 542 | *drive = 0; 543 | *path = 0; 544 | *root = 0; 545 | *ext = 0; 546 | 547 | #if DOS 548 | /* scan drive */ 549 | if ((*name!=0) && (name[1]==':')) { 550 | *drive = tolower(*name)-'a'+1; 551 | if ((*drive<1) || (*drive>26)) return 1; 552 | name += 2; 553 | } 554 | #endif 555 | 556 | /* scan path */ 557 | p = strrchr(name,DIRSEPARATOR); 558 | if (p!=NULL) { /* else no "\" found, path is empty */ 559 | strncpy(path,name,p-name+2); 560 | name = p+1; 561 | } 562 | 563 | /* scan root part */ 564 | if (*name == 0) return 1; 565 | 566 | #if UNIX 567 | strcpy(root,name); 568 | #else 569 | r = root; 570 | i = 0; 571 | while ((*name!='.') && *name && i<8) { 572 | if (*name==':') return 1; /* possibly mislead drive */ 573 | *r++ = *name++; 574 | } 575 | *r=0; 576 | 577 | /* scan extension part */ 578 | if (*name) strncpy(ext,name+1,4); 579 | 580 | #endif 581 | 582 | 583 | #if DOS 584 | /* convert to upper case */ 585 | upper(path); 586 | upper(root); 587 | upper(ext); 588 | #endif 589 | 590 | return 0; 591 | } 592 | 593 | 594 | int internal_pager(char *filename) { 595 | /* ^^^^^^^^^^^^^^ 596 | Display the file with stops after each screen */ 597 | FILE *file; 598 | char line[256]; 599 | char key; 600 | 601 | file = fopen(filename,"r"); 602 | if (file==NULL) return errorf(TRUE,"I can't display \"%s\"",filename); 603 | 604 | newpage("cqr"); 605 | while (fgets(line,256,file)) { 606 | printm(0,"%s",line); 607 | key = nextline(); 608 | if (key=='r') { 609 | fseek(file,0L,SEEK_SET); 610 | } 611 | if (key=='q') break; 612 | } 613 | fclose(file); 614 | return 0; 615 | } 616 | 617 | int pager(char *filename) { 618 | /* ^^^^^ 619 | Display the file with the lister program from the environment 620 | variable %PAGER or with the internal_pager() 621 | */ 622 | char buf[INPUTLEN]; 623 | char *pag; 624 | int err; 625 | 626 | pag = getenv("PAGER"); 627 | if (pag==NULL) { 628 | /* strcpy(buf,PAGERDEFAULT);*/ 629 | internal_pager(filename); 630 | return 0; 631 | } else { 632 | strcpy(buf,pag); 633 | } 634 | strcat(buf," "); 635 | strcat(buf,filename); 636 | 637 | err = system(buf); 638 | if ((err==-1) || (err==127)) return -1; 639 | 640 | return 0; 641 | } 642 | 643 | 644 | void str2mem(char *mem, char *str, int spc) { 645 | /* ^^^^^^^ 646 | Copy the ASCIIZ string at to position without trailing 0. 647 | Fill the memory at with spaces. 648 | */ 649 | int i; 650 | 651 | memset(mem,' ',spc); 652 | i=0; 653 | while (str[i]) { 654 | mem[i] = str[i]; 655 | i++; 656 | } 657 | } 658 | 659 | 660 | 661 | void build_cpm_name(char *buf, int user, char *root, char *ext) { 662 | /* ^^^^^^^^^^^^^^ 663 | Construct a CP/M name out of the given stuff. 664 | If = -1, no usernumber is prepended, 665 | if = -2, a * (wild user) is prepended. 666 | If = "", the last char is "."! 667 | must point to memory of at least 3+1+8+1+3+1=17 Byte (UUU:RRRRRRRR.EEE0). 668 | */ 669 | 670 | *buf = 0; 671 | if (user==-2) strcpy(buf,"*:"); 672 | if (user>=0) sprintf(buf,"%d:",user); 673 | 674 | strcat(buf,root); 675 | strcat(buf,"."); 676 | if (*ext) { 677 | strcat(buf,ext); 678 | } 679 | } 680 | 681 | 682 | void build_cpm_name_32(char *buf, int user, char *root, char *ext) { 683 | /* ^^^^^^^^^^^^^^^^^ 684 | The same function as except, that and are 685 | considered as padded with ' ' (= ASCII 32) (as in ) 686 | */ 687 | int j; 688 | 689 | *buf = 0; 690 | if (user==-2) {strcpy(buf,"*:"); buf +=2;} 691 | if (user>=0) buf += sprintf(buf,"%d:",user); 692 | 693 | memcpy(buf,root,8); 694 | j=7; while (buf[j] == ' ') {j--;}; j++; 695 | buf[j]='.'; j++; 696 | if (strncmp(ext," ",3)!=0) { 697 | memcpy(&buf[j],ext,3); j+=2; 698 | while (buf[j] == ' ') {j--;}; j++; 699 | } 700 | buf[j]=0; 701 | } 702 | 703 | 704 | bool has_wildcards(char os_tag, char *name) { 705 | /* ^^^^^^^^^^^^^ 706 | Checks whether contains wildcard characters according to match.h 707 | (if == 'c' ( *?[] )) or to DOS standards (if == 'd' 708 | ( *? )). 709 | */ 710 | char wild[10]; 711 | if (os_tag=='c') strcpy(wild,"*?[]"); /* !^- ??? */ 712 | else if (os_tag == 'd') strcpy(wild,"*?"); 713 | else return errorf(FALSE,"--==>>> has_wildcards"); 714 | 715 | while (*name) { 716 | if (strchr(wild,*name) != NULL) return TRUE; 717 | name++; 718 | } 719 | return FALSE; 720 | } 721 | 722 | 723 | char *show_hex(int nr, uchar *buf, int size) { 724 | /* ^^^^^^^^ 725 | Formats a hexdump line out out bytes at . Prefix the line with 726 | as address. This function uses a static variable as temporary buffer, so 727 | don't call it twice in a function. 728 | */ 729 | static char line[INPUTLEN]; 730 | int i; 731 | char *p; 732 | uchar c; 733 | 734 | p = line; 735 | p += sprintf(p,"%6X %c ",nr,vert); 736 | for (i=0;i=127) c='~'; 743 | else c=buf[i]; 744 | p += sprintf(p,"%c",c); 745 | } 746 | /* *(p++) = '\n';*/ 747 | return line; 748 | } 749 | -------------------------------------------------------------------------------- /src/match.c: -------------------------------------------------------------------------------- 1 | /* 2 | EPSHeader 3 | 4 | File: match.c 5 | Author: J. Kercheval 6 | Created: Sat, 01/05/1991 22:21:49 7 | */ 8 | /* 9 | EPSRevision History 10 | 11 | J. Kercheval Wed, 02/20/1991 22:29:01 Released to Public Domain 12 | J. Kercheval Fri, 02/22/1991 15:29:01 fix '\' bugs (two :( of them) 13 | J. Kercheval Sun, 03/10/1991 19:31:29 add error return to matche() 14 | J. Kercheval Sun, 03/10/1991 20:11:11 add is_valid_pattern code 15 | J. Kercheval Sun, 03/10/1991 20:37:11 beef up main() 16 | J. Kercheval Tue, 03/12/1991 22:25:10 Released as V1.1 to Public Domain 17 | */ 18 | 19 | /* 20 | Wildcard Pattern Matching 21 | */ 22 | 23 | 24 | #include "match.h" 25 | 26 | int matche_after_star (register char *pattern, register char *text); 27 | int fast_match_after_star (register char *pattern, register char *text); 28 | 29 | /*---------------------------------------------------------------------------- 30 | * 31 | * Return TRUE if PATTERN has any special wildcard characters 32 | * 33 | ----------------------------------------------------------------------------*/ 34 | 35 | BOOLEAN is_pattern (char *p) 36 | { 37 | while (*p) 38 | { 39 | switch (*p++) 40 | { 41 | case '?': 42 | case '*': 43 | case '[': 44 | case '\\': 45 | return TRUE; 46 | } 47 | } 48 | return FALSE; 49 | } 50 | 51 | 52 | /*---------------------------------------------------------------------------- 53 | * 54 | * Return TRUE if PATTERN has is a well formed regular expression according 55 | * to the above syntax 56 | * 57 | * error_type is a return code based on the type of pattern error. Zero is 58 | * returned in error_type if the pattern is a valid one. error_type return 59 | * values are as follows: 60 | * 61 | * PATTERN_VALID - pattern is well formed 62 | * PATTERN_ESC - pattern has invalid escape ('\' at end of pattern) 63 | * PATTERN_RANGE - [..] construct has a no end range in a '-' pair (ie [a-]) 64 | * PATTERN_CLOSE - [..] construct has no end bracket (ie [abc-g ) 65 | * PATTERN_EMPTY - [..] construct is empty (ie []) 66 | * 67 | ----------------------------------------------------------------------------*/ 68 | 69 | BOOLEAN is_valid_pattern (char *p, int *error_type) 70 | { 71 | /* init error_type */ 72 | *error_type = PATTERN_VALID; 73 | 74 | /* loop through pattern to EOS */ 75 | while (*p) 76 | { 77 | /* determine pattern type */ 78 | switch (*p) 79 | { 80 | /* check literal escape, it cannot be at end of pattern */ 81 | case '\\': 82 | if (!*++p) 83 | { 84 | *error_type = PATTERN_ESC; 85 | return FALSE; 86 | } 87 | p++; 88 | break; 89 | 90 | /* the [..] construct must be well formed */ 91 | case '[': 92 | p++; 93 | 94 | /* if the next character is ']' then bad pattern */ 95 | if (*p == ']') 96 | { 97 | *error_type = PATTERN_EMPTY; 98 | return FALSE; 99 | } 100 | 101 | /* if end of pattern here then bad pattern */ 102 | if (!*p) 103 | { 104 | *error_type = PATTERN_CLOSE; 105 | return FALSE; 106 | } 107 | 108 | /* loop to end of [..] construct */ 109 | while (*p != ']') 110 | { 111 | /* check for literal escape */ 112 | if (*p == '\\') 113 | { 114 | p++; 115 | 116 | /* if end of pattern here then bad pattern */ 117 | if (!*p++) 118 | { 119 | *error_type = PATTERN_ESC; 120 | return FALSE; 121 | } 122 | } 123 | else p++; 124 | 125 | /* if end of pattern here then bad pattern */ 126 | if (!*p) 127 | { 128 | *error_type = PATTERN_CLOSE; 129 | return FALSE; 130 | } 131 | 132 | /* if this a range */ 133 | if (*p == '-') 134 | { 135 | /* we must have an end of range */ 136 | if (!*++p || *p == ']') 137 | { 138 | *error_type = PATTERN_RANGE; 139 | return FALSE; 140 | } 141 | else 142 | { 143 | 144 | /* check for literal escape */ 145 | if (*p == '\\') 146 | p++; 147 | 148 | /* if end of pattern here 149 | then bad pattern */ 150 | if (!*p++) 151 | { 152 | *error_type = PATTERN_ESC; 153 | return FALSE; 154 | } 155 | } 156 | } 157 | } 158 | break; 159 | 160 | /* all other characters are valid pattern elements */ 161 | case '*': 162 | case '?': 163 | default: 164 | p++; /* "normal" character */ 165 | break; 166 | } 167 | } 168 | return TRUE; 169 | } 170 | 171 | 172 | /*---------------------------------------------------------------------------- 173 | * 174 | * Match the pattern PATTERN against the string TEXT; 175 | * 176 | * returns MATCH_VALID if pattern matches, or an errorcode as follows 177 | * otherwise: 178 | * 179 | * MATCH_PATTERN - bad pattern 180 | * MATCH_LITERAL - match failure on literal mismatch 181 | * MATCH_RANGE - match failure on [..] construct 182 | * MATCH_ABORT - premature end of text string 183 | * MATCH_END - premature end of pattern string 184 | * MATCH_VALID - valid match 185 | * 186 | * 187 | * A match means the entire string TEXT is used up in matching. 188 | * 189 | * In the pattern string: 190 | * `*' matches any sequence of characters (zero or more) 191 | * `?' matches any character 192 | * [SET] matches any character in the specified set, 193 | * [!SET] or [^SET] matches any character not in the specified set. 194 | * 195 | * A set is composed of characters or ranges; a range looks like 196 | * character hyphen character (as in 0-9 or A-Z). [0-9a-zA-Z_] is the 197 | * minimal set of characters allowed in the [..] pattern construct. 198 | * Other characters are allowed (ie. 8 bit characters) if your system 199 | * will support them. 200 | * 201 | * To suppress the special syntactic significance of any of `[]*?!^-\', 202 | * and match the character exactly, precede it with a `\'. 203 | * 204 | ----------------------------------------------------------------------------*/ 205 | 206 | int matche (register char *p, register char *t) 207 | { 208 | register char range_start, range_end; /* start and end in range */ 209 | 210 | BOOLEAN invert; /* is this [..] or [!..] */ 211 | BOOLEAN member_match; /* have I matched the [..] construct? */ 212 | BOOLEAN loop; /* should I terminate? */ 213 | 214 | for ( ; *p; p++, t++) 215 | { 216 | /* if this is the end of the text 217 | then this is the end of the match */ 218 | 219 | if (!*t) 220 | { 221 | return ( *p == '*' && *++p == '\0' ) ? 222 | MATCH_VALID : MATCH_ABORT; 223 | } 224 | 225 | /* determine and react to pattern type */ 226 | 227 | switch (*p) 228 | { 229 | case '?': /* single any character match */ 230 | break; 231 | 232 | case '*': /* multiple any character match */ 233 | return matche_after_star (p, t); 234 | 235 | /* [..] construct, single member/exclusion character match */ 236 | case '[': 237 | { 238 | /* move to beginning of range */ 239 | 240 | p++; 241 | 242 | /* check if this is a member match or exclusion match */ 243 | 244 | invert = FALSE; 245 | if (*p == '!' || *p == '^') 246 | { 247 | invert = TRUE; 248 | p++; 249 | } 250 | 251 | /* if closing bracket here or at range start then we have a 252 | malformed pattern */ 253 | 254 | if (*p == ']') 255 | { 256 | return MATCH_PATTERN; 257 | } 258 | 259 | member_match = FALSE; 260 | loop = TRUE; 261 | 262 | while (loop) 263 | { 264 | /* if end of construct then loop is done */ 265 | 266 | if (*p == ']') 267 | { 268 | loop = FALSE; 269 | continue; 270 | } 271 | 272 | /* matching a '!', '^', '-', '\' or a ']' */ 273 | 274 | if (*p == '\\') 275 | { 276 | range_start = range_end = *++p; 277 | } 278 | else range_start = range_end = *p; 279 | 280 | /* if end of pattern then bad pattern (Missing ']') */ 281 | 282 | if (!*p) 283 | return MATCH_PATTERN; 284 | 285 | /* check for range bar */ 286 | if (*++p == '-') 287 | { 288 | /* get the range end */ 289 | 290 | range_end = *++p; 291 | 292 | /* if end of pattern or construct 293 | then bad pattern */ 294 | 295 | if (range_end == '\0' || range_end == ']') 296 | return MATCH_PATTERN; 297 | 298 | /* special character range end */ 299 | if (range_end == '\\') 300 | { 301 | range_end = *++p; 302 | 303 | /* if end of text then 304 | we have a bad pattern */ 305 | if (!range_end) 306 | return MATCH_PATTERN; 307 | } 308 | 309 | /* move just beyond this range */ 310 | p++; 311 | } 312 | 313 | /* if the text character is in range then match found. 314 | make sure the range letters have the proper 315 | relationship to one another before comparison */ 316 | 317 | if (range_start < range_end) 318 | { 319 | if (*t >= range_start && *t <= range_end) 320 | { 321 | member_match = TRUE; 322 | loop = FALSE; 323 | } 324 | } 325 | else 326 | { 327 | if (*t >= range_end && *t <= range_start) 328 | { 329 | member_match = TRUE; 330 | loop = FALSE; 331 | } 332 | } 333 | } 334 | 335 | /* if there was a match in an exclusion set then no match */ 336 | /* if there was no match in a member set then no match */ 337 | 338 | if ((invert && member_match) || !(invert || member_match)) 339 | return MATCH_RANGE; 340 | 341 | /* if this is not an exclusion then skip the rest of 342 | the [...] construct that already matched. */ 343 | 344 | if (member_match) 345 | { 346 | while (*p != ']') 347 | { 348 | /* bad pattern (Missing ']') */ 349 | if (!*p) 350 | return MATCH_PATTERN; 351 | 352 | /* skip exact match */ 353 | if (*p == '\\') 354 | { 355 | p++; 356 | 357 | /* if end of text then 358 | we have a bad pattern */ 359 | 360 | if (!*p) 361 | return MATCH_PATTERN; 362 | } 363 | 364 | /* move to next pattern char */ 365 | 366 | p++; 367 | } 368 | } 369 | break; 370 | } 371 | case '\\': /* next character is quoted and must match exactly */ 372 | 373 | /* move pattern pointer to quoted char and fall through */ 374 | 375 | p++; 376 | 377 | /* if end of text then we have a bad pattern */ 378 | 379 | if (!*p) 380 | return MATCH_PATTERN; 381 | 382 | /* must match this character exactly */ 383 | 384 | default: 385 | if (*p != *t) 386 | return MATCH_LITERAL; 387 | } 388 | } 389 | /* if end of text not reached then the pattern fails */ 390 | 391 | if (*t) 392 | return MATCH_END; 393 | else return MATCH_VALID; 394 | } 395 | 396 | 397 | /*---------------------------------------------------------------------------- 398 | * 399 | * recursively call matche() with final segment of PATTERN and of TEXT. 400 | * 401 | ----------------------------------------------------------------------------*/ 402 | 403 | int matche_after_star (register char *p, register char *t) 404 | { 405 | register int match = 0; 406 | register nextp; 407 | 408 | /* pass over existing ? and * in pattern */ 409 | 410 | while ( *p == '?' || *p == '*' ) 411 | { 412 | /* take one char for each ? and + */ 413 | 414 | if (*p == '?') 415 | { 416 | /* if end of text then no match */ 417 | if (!*t++) 418 | return MATCH_ABORT; 419 | } 420 | 421 | /* move to next char in pattern */ 422 | 423 | p++; 424 | } 425 | 426 | /* if end of pattern we have matched regardless of text left */ 427 | 428 | if (!*p) 429 | return MATCH_VALID; 430 | 431 | /* get the next character to match which must be a literal or '[' */ 432 | 433 | nextp = *p; 434 | if (nextp == '\\') 435 | { 436 | nextp = p[1]; 437 | 438 | /* if end of text then we have a bad pattern */ 439 | 440 | if (!nextp) 441 | return MATCH_PATTERN; 442 | } 443 | 444 | /* Continue until we run out of text or definite result seen */ 445 | 446 | do 447 | { 448 | /* a precondition for matching is that the next character 449 | in the pattern match the next character in the text or that 450 | the next pattern char is the beginning of a range. Increment 451 | text pointer as we go here */ 452 | 453 | if (nextp == *t || nextp == '[') 454 | match = matche(p, t); 455 | 456 | /* if the end of text is reached then no match */ 457 | 458 | if (!*t++) 459 | match = MATCH_ABORT; 460 | 461 | } while ( match != MATCH_VALID && 462 | match != MATCH_ABORT && 463 | match != MATCH_PATTERN); 464 | 465 | /* return result */ 466 | 467 | return match; 468 | } 469 | 470 | 471 | /*---------------------------------------------------------------------------- 472 | * 473 | * match() is a shell to matche() to return only BOOLEAN values. 474 | * 475 | ----------------------------------------------------------------------------*/ 476 | 477 | BOOLEAN match( char *p, char *t ) 478 | { 479 | int error_type; 480 | 481 | error_type = matche(p,t); 482 | return (error_type == MATCH_VALID ) ? TRUE : FALSE; 483 | } 484 | 485 | 486 | #ifdef TEST 487 | 488 | /* 489 | ** This test main expects as first arg the pattern and as second arg 490 | ** the match string. Output is yaeh or nay on match. If nay on 491 | ** match then the error code is parsed and written. 492 | */ 493 | 494 | #include 495 | 496 | int main(int argc, char *argv[]) 497 | { 498 | int error; 499 | int is_valid_error; 500 | 501 | if (argc != 3) 502 | printf("Usage: MATCH Pattern Text\n"); 503 | else 504 | { 505 | printf("Pattern: %s\n", argv[1]); 506 | printf("Text : %s\n", argv[2]); 507 | 508 | if (!is_pattern(argv[1])) 509 | printf(" First Argument Is Not A Pattern\n"); 510 | else 511 | { 512 | error = matche(argv[1],argv[2]); 513 | is_valid_pattern(argv[1],&is_valid_error); 514 | 515 | switch (error) 516 | { 517 | case MATCH_VALID: 518 | printf(" Match Successful"); 519 | if (is_valid_error != PATTERN_VALID) 520 | printf(" -- is_valid_pattern() " 521 | "is complaining\n"); 522 | else printf("\n"); 523 | break; 524 | 525 | case MATCH_LITERAL: 526 | printf(" Match Failed on Literal\n"); 527 | break; 528 | 529 | case MATCH_RANGE: 530 | printf(" Match Failed on [..]\n"); 531 | break; 532 | 533 | case MATCH_ABORT: 534 | printf(" Match Failed on Early " 535 | "Text Termination\n"); 536 | break; 537 | 538 | case MATCH_END: 539 | printf(" Match Failed on Early " 540 | "Pattern Termination\n"); 541 | break; 542 | 543 | case MATCH_PATTERN: 544 | switch (is_valid_error) 545 | { 546 | case PATTERN_VALID: 547 | printf(" Internal Disagreement " 548 | "On Pattern\n"); 549 | break; 550 | 551 | case PATTERN_ESC: 552 | printf(" Literal Escape at " 553 | "End of Pattern\n"); 554 | break; 555 | 556 | 557 | case PATTERN_RANGE: 558 | printf(" No End of Range in " 559 | "[..] Construct\n"); 560 | break; 561 | 562 | case PATTERN_CLOSE: 563 | printf(" [..] Construct is Open\n"); 564 | break; 565 | 566 | case PATTERN_EMPTY: 567 | printf(" [..] Construct is Empty\n"); 568 | break; 569 | 570 | default: 571 | printf(" Internal Error in " 572 | "is_valid_pattern()\n"); 573 | } 574 | break; 575 | 576 | default: 577 | printf(" Internal Error in matche()\n"); 578 | break; 579 | } 580 | } 581 | } 582 | return(0); 583 | } 584 | 585 | #endif 586 | -------------------------------------------------------------------------------- /src/dos.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | ------------------------------------------------------------------------------ 4 | 5 | ===== 6 | CPCFS -- d o s . c --- Borland C specific routines 7 | ===== 8 | 9 | Version 0.85 (c) Derik van Zuetphen 10 | ------------------------------------------------------------------------------ 11 | */ 12 | 13 | 14 | #include 15 | #include 16 | #include 17 | #include /* the one in includedir of course */ 18 | 19 | #include "cpcfs.h" 20 | 21 | #ifndef min 22 | #define min(A,B) ((A)<=(B)?(A):(B)) 23 | #endif 24 | 25 | extern char Break_Wish; 26 | 27 | char cwdbuffer[256]; 28 | 29 | 30 | void break_handler() { 31 | Break_Wish = 1; /*TRUE*/; 32 | } 33 | 34 | void disable_break() { 35 | signal (SIGINT,break_handler); 36 | // ctrlbrk(break_handler); 37 | } 38 | 39 | char wait_for_key (int must_be_0, char must_be_TRUE) { 40 | /* ^^^^^^^^^^^^ */ 41 | char c; 42 | while (!kbhit()) ; 43 | if((c=getch())==0) 44 | return getch(); 45 | else 46 | return c; 47 | } /*wait_for_key*/ 48 | 49 | int saved_drive; 50 | char saved_path[MAXPATH]; 51 | 52 | void save_path () { 53 | /* ^^^^^^^^^ 54 | Saves current drive and directory path */ 55 | saved_drive = getdisk(); 56 | getcwd(saved_path,MAXPATH); 57 | } 58 | 59 | void rest_path () { 60 | /* ^^^^^^^^^ 61 | Restores saved drive and directory path */ 62 | setdisk(saved_drive); 63 | chdir(saved_path); 64 | } 65 | 66 | 67 | struct ffblk glob_buffer; 68 | char glob_dir[MAXPATH]; 69 | 70 | char *glob_file (char *pattern) { 71 | /* ^^^^^^^^^ */ 72 | static char 73 | n[MAXPATH]; 74 | char drive[MAXDRIVE]; 75 | char dir[MAXDIR]; 76 | char name[MAXFILE]; 77 | char ext[MAXEXT]; 78 | int flags; 79 | 80 | flags=fnsplit(pattern,drive,dir,name,ext); 81 | *glob_dir=0; 82 | if (DRIVE & flags) { 83 | strcpy(glob_dir,drive); 84 | strcat(glob_dir,":"); 85 | } 86 | if (DIRECTORY & flags) { 87 | strcat(glob_dir,dir); /* included trailing "\" */ 88 | } 89 | 90 | if (findfirst(pattern,&glob_buffer,0)) { 91 | return NULL; 92 | } 93 | strcpy(n,glob_dir); strcat(n,glob_buffer.ff_name); 94 | return n; 95 | } 96 | 97 | 98 | char *glob_next () { 99 | /* ^^^^^^^^^ */ 100 | static char 101 | n[MAXPATH]; 102 | 103 | if (findnext(&glob_buffer)) { 104 | return NULL; 105 | } 106 | strcpy(n,glob_dir); strcat(n,glob_buffer.ff_name); 107 | return n; 108 | } 109 | 110 | char* tmp_nam(char* buf) { 111 | /* ^^^^^^^ 112 | Calls tmpnam() and prepends the value of the %TEMP environment variable. 113 | Contrary to tmpnam(), must not be NULL! */ 114 | char *temp; 115 | char name[INPUTLEN]; 116 | 117 | temp = getenv("TEMP"); 118 | if (temp==NULL) strcpy(buf,"."); 119 | else strcpy(buf,temp); 120 | if (temp[strlen(temp)]!='\\') strcat(buf,"\\"); 121 | tmpnam(name); 122 | strcat(buf,name); 123 | return buf; 124 | } 125 | 126 | 127 | void os_init() { 128 | /* ^^^^^^^ */ 129 | // the screen is garbled, when 'screen buffer' (which is 300 by default) 130 | // is greater than 255. 131 | system("mode con lines=25"); 132 | } 133 | 134 | int getkey(void); 135 | 136 | /********************************************************************** 137 | History 138 | **********************************************************************/ 139 | 140 | #define MaxHistSize 100 141 | 142 | int hist_size = 0; /* number of entries in history */ 143 | int hist_last = 0; /* number of last entered entry */ 144 | char *history[MaxHistSize]; /* filled with NULLS */ 145 | 146 | int add_history(char *line) { 147 | /* ^^^^^^^^^^^ 148 | Add to the history list */ 149 | char *str; 150 | 151 | str = (char*)Malloc(strlen(line)+1); 152 | strcpy(str,line); 153 | if (hist_size < MaxHistSize) { /* append */ 154 | history[hist_size] = str; 155 | hist_last = hist_size++; 156 | } else { /* overwrite eldest entry */ 157 | hist_last = (hist_last+1) % MaxHistSize; 158 | free(history[hist_last]); 159 | history[hist_last] = str; 160 | } 161 | return 0; 162 | } 163 | 164 | /********************************************************************** 165 | The next lines in this file are insertions from ACTlib 1.7 166 | (slightly modified) 167 | former name: KEY.H 168 | **********************************************************************/ 169 | /* 170 | * Copyright (C) 1993 Marc Stern (internet: stern@mble.philips.be) 171 | * 172 | * File : key.h 173 | * 174 | * Description : key code definitions. 175 | * 176 | */ 177 | 178 | 179 | #ifndef __Key_H 180 | #define __Key_H 181 | 182 | 183 | #define RETURN 0x0d 184 | #ifndef ENTER 185 | #define ENTER RETURN 186 | #endif 187 | #define SPACE 0x20 188 | #define ESC 0x1b 189 | #define BKSP 0x08 190 | #define BACKSPACE 0x08 191 | 192 | #define UP 328 193 | #define DOWN 336 194 | #define LEFT 331 195 | #define RIGHT 333 196 | #define PGUP 329 197 | #define PGDN 337 198 | #define HOME 327 199 | #define END 335 200 | #define INSERT 338 201 | #define INS INSERT 202 | #define DELETE 339 203 | #define DEL DELETE 204 | 205 | enum { F1 = 315, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11 = 389, F12 }; 206 | 207 | #define CTRL_UP 397 208 | #define CTRL_DOWN 401 209 | #define CTRL_LEFT 371 210 | #define CTRL_RIGHT 372 211 | #define CTRL_PGUP 388 212 | #define CTRL_PGDN 374 213 | #define CTRL_HOME 375 214 | #define CTRL_END 373 215 | #define CTRL_INSERT 402 216 | #define CTRL_INS CTRL_INSERT 217 | #define CTRL_DELETE 403 218 | #define CTRL_DEL CTRL_DELETE 219 | 220 | enum { CTRL_F1 = 350, CTRL_F2, CTRL_F3, CTRL_F4, CTRL_F5, 221 | CTRL_F6, CTRL_F7, CTRL_F8, CTRL_F9, CTRL_F10, 222 | CTRL_F11 = 393, CTRL_F12 223 | }; 224 | 225 | #define CTRL_A 1 226 | #define CTRL_B 2 227 | #define CTRL_C 3 228 | #define CTRL_D 4 229 | #define CTRL_E 5 230 | #define CTRL_F 6 231 | #define CTRL_G 7 232 | #define CTRL_H 8 233 | #define CTRL_I 9 234 | #define CTRL_J 10 235 | #define CTRL_K 11 236 | #define CTRL_L 12 237 | #define CTRL_M 13 238 | #define CTRL_N 14 239 | #define CTRL_O 15 240 | #define CTRL_P 16 241 | #define CTRL_Q 17 242 | #define CTRL_R 18 243 | #define CTRL_S 19 244 | #define CTRL_T 20 245 | #define CTRL_U 21 246 | #define CTRL_V 22 247 | #define CTRL_W 23 248 | #define CTRL_X 24 249 | #define CTRL_Y 25 250 | #define CTRL_Z 26 251 | 252 | #define ALT_UP 408 253 | #define ALT_DOWN 416 254 | #define ALT_LEFT 411 255 | #define ALT_RIGHT 413 256 | #define ALT_PGUP 409 257 | #define ALT_PGDN 417 258 | #define ALT_HOME 407 259 | #define ALT_END 415 260 | #define ALT_INSERT 418 261 | #define ALT_INS ALT_INSERT 262 | #define ALT_DELETE 419 263 | #define ALT_DEL ALT_DELETE 264 | 265 | enum { ALT_F1 = 360, ALT_F2, ALT_F3, ALT_F4, ALT_F5, 266 | ALT_F6, ALT_F7, ALT_F8, ALT_F9, ALT_F10, 267 | ALT_F11 = 395, ALT_F12 268 | }; 269 | 270 | #define ALT_A 286 271 | #define ALT_B 304 272 | #define ALT_C 302 273 | #define ALT_D 288 274 | #define ALT_E 274 275 | #define ALT_F 289 276 | #define ALT_G 290 277 | #define ALT_H 291 278 | #define ALT_I 279 279 | #define ALT_J 292 280 | #define ALT_K 293 281 | #define ALT_L 294 282 | #define ALT_M 306 283 | #define ALT_N 305 284 | #define ALT_O 280 285 | #define ALT_P 281 286 | #define ALT_Q 272 287 | #define ALT_R 275 288 | #define ALT_S 287 289 | #define ALT_T 276 290 | #define ALT_U 278 291 | #define ALT_V 303 292 | #define ALT_W 273 293 | #define ALT_X 301 294 | #define ALT_Y 277 295 | #define ALT_Z 300 296 | 297 | #define SHIFT_UP 328 298 | #define SHIFT_DOWN 336 299 | #define SHIFT_LEFT 331 300 | #define SHIFT_RIGHT 333 301 | #define SHIFT_PGUP 329 302 | #define SHIFT_PGDN 337 303 | #define SHIFT_HOME 327 304 | #define SHIFT_END 335 305 | #define SHIFT_INSERT 338 306 | #define SHIFT_INS SHIFT_INSERT 307 | #define SHIFT_DELETE 339 308 | #define SHIFT_DEL SHIFT_DELETE 309 | 310 | enum { SHIFT_F1 = 340, SHIFT_F2, SHIFT_F3, SHIFT_F4, SHIFT_F5, 311 | SHIFT_F6, SHIFT_F7, SHIFT_F8, SHIFT_F9, SHIFT_F10, 312 | SHIFT_F11 = 391, SHIFT_F12 313 | }; 314 | 315 | #endif 316 | 317 | 318 | /********************************************************************** 319 | former name: INPUTS.C (history access added) 320 | **********************************************************************/ 321 | 322 | /* Copyright (C) 1993 Marc Stern (internet: stern@mble.philips.be) */ 323 | 324 | #include 325 | #include 326 | #include 327 | #include 328 | 329 | #define True 1 330 | #define False 0 331 | 332 | int insert_mode = True; 333 | 334 | 335 | /*** 336 | * Function : inputs 337 | * 338 | * Description : Input a string from keyboard 339 | * 340 | * Parameters : in/out char * data default and result 341 | * in int maxLen maximum length to accept 342 | * in int timeout maximum time (seconds) waiting 343 | * before a key is pressed 344 | * 345 | * 346 | * Decisions : Valid keys: Home Begin of line 347 | * End End of line 348 | * Left/Right One character left/right 349 | * Up/Down Browse thru history 350 | * Insert Toggle insert on/off 351 | * Delete Delete current character 352 | * BackSpace Delete previous character 353 | * Ctrl-home Erase to begin-of-line 354 | * Ctrl-end Erase to end-of-line 355 | * Escape Erase whole input 356 | * Enter Accept input 357 | * plus EMACS bindings 358 | * 359 | * Default string is erase if another input is given. 360 | * (if any non-editing character is hit). 361 | * 362 | * If no key is hit before timeout (in seconds) 363 | * the function returns. 364 | * timeout -1 means no timeout. 365 | * timeout 0 means check for type-ahead. 366 | * 367 | * Return : length of input 368 | * -1 if time-out 369 | * -2 if Ctrl-Z 370 | * 371 | * OS/Compiler : MS-DOS & Turbo-C 372 | ***/ 373 | 374 | int inputs( char *data, int maxLen, int timeout ) 375 | { 376 | int Xpos = wherex(), Ypos = wherey(), len = strlen( data ), oldLen = 0; 377 | char *curPos = data + len; 378 | int firstkey = True, x, y, c; 379 | struct text_info ti; 380 | int hist = -1; /* not in history yet */ 381 | char hist_used = False; /* flag to signify line redraw */ 382 | 383 | gettextinfo( &ti ); 384 | 385 | for (;;) 386 | { 387 | if ( hist_used || len != oldLen ) 388 | { 389 | hist_used = False; 390 | gotoxy(Xpos, Ypos); 391 | cputs( data ); 392 | for ( ; oldLen > len; oldLen-- ) cputs(" "); 393 | if ( oldLen < len ) oldLen = len; 394 | Ypos = min( Ypos, wherey() - (Xpos + len - 1) / ti.screenwidth ); 395 | } 396 | 397 | if ( firstkey && (timeout >= 0) ) 398 | { 399 | time_t now = time( NULL ); /* Gets system time */ 400 | 401 | do if ( kbhit() ) timeout = -1; 402 | while ( difftime(time(NULL), now) < timeout ); 403 | 404 | if ( timeout >= 0 ) return -1; 405 | } 406 | 407 | _setcursortype( insert_mode ? _NORMALCURSOR : _SOLIDCURSOR ); 408 | 409 | #pragma warn -sig 410 | for ( x = curPos - data + Xpos, y = Ypos; 411 | x > ti.screenwidth; 412 | x -= ti.screenwidth, y++ 413 | ); 414 | gotoxy(x, y ); 415 | #pragma warn .sig 416 | 417 | switch( c = getkey() ) 418 | { 419 | case LEFT: 420 | case CTRL_B: 421 | if ( curPos > data ) curPos--; 422 | break; 423 | 424 | case RIGHT: 425 | case CTRL_F: 426 | if ( curPos < data + len ) curPos++; 427 | break; 428 | 429 | case UP: 430 | case CTRL_P: 431 | if (hist==-1) hist = hist_last; 432 | else {hist--; hist = (hist<0? hist_size-1 : hist);} 433 | strcpy(data,history[hist]); 434 | len = strlen(data); 435 | curPos = data+len; 436 | hist_used = True; 437 | break; 438 | 439 | case DOWN: 440 | case CTRL_N: 441 | if (hist==-1) break; 442 | else hist = (hist+1)%hist_size; 443 | strcpy(data,history[hist]); 444 | len = strlen(data); 445 | curPos = data+len; 446 | hist_used = True; 447 | break; 448 | 449 | case HOME: 450 | case CTRL_A: 451 | curPos = data; 452 | break; 453 | 454 | case END: 455 | case CTRL_E: 456 | curPos = data + len; 457 | break; 458 | 459 | case BACKSPACE: 460 | if ( curPos > data ) 461 | { 462 | strcpy( curPos - 1, curPos ); 463 | curPos--; 464 | len--; 465 | } 466 | break; 467 | 468 | case CTRL_D: 469 | case DEL: 470 | if ( curPos < data + len ) 471 | { 472 | strcpy( curPos , curPos + 1 ); 473 | len--; 474 | } 475 | break; 476 | 477 | case INSERT: 478 | case CTRL_V: 479 | insert_mode = ! insert_mode; 480 | _setcursortype( insert_mode ? _NORMALCURSOR : _SOLIDCURSOR ); 481 | continue; /* break; */ 482 | 483 | case ESC : 484 | case CTRL_U: 485 | *data = '\0'; 486 | curPos = data; 487 | len = 0; 488 | break; 489 | 490 | case CTRL_Z: 491 | case ENTER: 492 | cputs( "\r\n" ); clreol(); 493 | _setcursortype( _NORMALCURSOR ); 494 | if (c==CTRL_Z) return -2; 495 | else return len; 496 | 497 | case CTRL_END: 498 | case CTRL_K: 499 | *curPos = '\0'; 500 | len = strlen( data ); 501 | break; 502 | 503 | case CTRL_HOME: 504 | strcpy( data , curPos ); 505 | curPos = data; 506 | len = strlen( data ); 507 | break; 508 | 509 | default: 510 | if ( c > 255 ) 511 | { 512 | break; 513 | } 514 | 515 | if ( firstkey ) 516 | { 517 | *data = '\0'; 518 | curPos = data; 519 | len = 0; 520 | ungetch( c ); 521 | break; 522 | } 523 | 524 | if ( ! insert_mode ) 525 | { 526 | cprintf( "%c", c ); 527 | *curPos++ = c; 528 | if ( curPos >= data + len ) 529 | { 530 | *curPos = '\0'; 531 | len++; 532 | } 533 | break; 534 | } 535 | 536 | if ( len == maxLen ) 537 | { 538 | break; 539 | } 540 | 541 | memmove( curPos + 1, curPos, strlen(curPos) + 1 ); 542 | *curPos++ = c; 543 | len++; 544 | break; 545 | } 546 | 547 | firstkey = False; 548 | } 549 | } 550 | 551 | 552 | 553 | /********************************************************************** 554 | former name: GETKEY.C 555 | **********************************************************************/ 556 | 557 | /* Copyright (C) 1993 Marc Stern (internet: stern@mble.philips.be) */ 558 | 559 | #include 560 | 561 | /*** 562 | * 563 | * Function : getkey 564 | * 565 | * Description: return a 2-bytes key pressed 566 | * (extended characters are added to 256). 567 | * 568 | */ 569 | 570 | int getkey( void ) 571 | 572 | { int car; 573 | 574 | if ( ! (car = getch()) ) car = 256 + getch(); 575 | 576 | return car; 577 | } 578 | 579 | /********************************************************************** 580 | The next lines of this file are insertions from Borland C++ 2.0 581 | (slightly modified) 582 | former name: GETOPT.C 583 | **********************************************************************/ 584 | 585 | /* 586 | Copyright (c) 1986,1991 by Borland International Inc. 587 | All Rights Reserved. 588 | */ 589 | 590 | #include 591 | #include 592 | #include 593 | #include 594 | 595 | int optind = 1; /* index of which argument is next */ 596 | char *optarg; /* pointer to argument of current option */ 597 | int opterr = 1; /* allow error message */ 598 | 599 | static char *letP = NULL; /* remember next option char's location */ 600 | static char SW = '-'; /* DOS switch character, either '-' or '/' */ 601 | 602 | /* 603 | Parse the command line options, System V style. 604 | 605 | Standard option syntax is: 606 | 607 | option ::= SW [optLetter]* [argLetter space* argument] 608 | 609 | where 610 | - SW is '-' 611 | - there is no space before any optLetter or argLetter. 612 | - opt/arg letters are alphabetic, not punctuation characters. 613 | - optLetters, if present, must be matched in optionS. 614 | - argLetters, if present, are found in optionS followed by ':'. 615 | - argument is any white-space delimited string. Note that it 616 | can include the SW character. 617 | - upper and lower case letters are distinct. 618 | 619 | There may be multiple option clusters on a command line, each 620 | beginning with a SW, but all must appear before any non-option 621 | arguments (arguments not introduced by SW). Opt/arg letters may 622 | be repeated: it is up to the caller to decide if that is an error. 623 | 624 | The character SW appearing alone as the last argument is an error. 625 | The lead-in sequence SWSW ("--" or "//") causes itself and all the 626 | rest of the line to be ignored (allowing non-options which begin 627 | with the switch char). 628 | 629 | The string *optionS allows valid opt/arg letters to be recognized. 630 | argLetters are followed with ':'. Getopt () returns the value of 631 | the option character found, or EOF if no more options are in the 632 | command line. If option is an argLetter then the global optarg is 633 | set to point to the argument string (having skipped any white-space). 634 | 635 | The global optind is initially 1 and is always left as the index 636 | of the next argument of argv[] which getopt has not taken. Note 637 | that if "--" or "//" are used then optind is stepped to the next 638 | argument before getopt() returns EOF. 639 | 640 | If an error occurs, that is an SW char precedes an unknown letter, 641 | then getopt() will return a '?' character and normally prints an 642 | error message via perror(). If the global variable opterr is set 643 | to false (zero) before calling getopt() then the error message is 644 | not printed. 645 | 646 | For example, if the MSDOS switch char is '/' (the MSDOS norm) and 647 | 648 | *optionS == "A:F:PuU:wXZ:" 649 | 650 | then 'P', 'u', 'w', and 'X' are option letters and 'F', 'U', 'Z' 651 | are followed by arguments. A valid command line may be: 652 | 653 | aCommand /uPFPi /X /A L someFile 654 | 655 | where: 656 | - 'u' and 'P' will be returned as isolated option letters. 657 | - 'F' will return with "Pi" as its argument string. 658 | - 'X' is an isolated option. 659 | - 'A' will return with "L" as its argument. 660 | - "someFile" is not an option, and terminates getOpt. The 661 | caller may collect remaining arguments using argv pointers. 662 | */ 663 | 664 | int getopt(int argc, char *argv[], char *optionS) 665 | { 666 | unsigned char ch; 667 | char *optP; 668 | 669 | if (argc > optind) { 670 | if (letP == NULL) { 671 | if ((letP = argv[optind]) == NULL || 672 | *(letP++) != SW) goto gopEOF; 673 | if (*letP == SW) { 674 | optind++; goto gopEOF; 675 | } 676 | } 677 | if (0 == (ch = *(letP++))) { 678 | optind++; goto gopEOF; 679 | } 680 | if (':' == ch || (optP = strchr(optionS, ch)) == NULL) 681 | goto gopError; 682 | if (':' == *(++optP)) { 683 | optind++; 684 | if (0 == *letP) { 685 | if (argc <= optind) goto gopError; 686 | letP = argv[optind++]; 687 | } 688 | optarg = letP; 689 | letP = NULL; 690 | } else { 691 | if (0 == *letP) { 692 | optind++; 693 | letP = NULL; 694 | } 695 | optarg = NULL; 696 | } 697 | return ch; 698 | } 699 | gopEOF: 700 | optarg = letP = NULL; 701 | return EOF; 702 | 703 | gopError: 704 | optarg = NULL; 705 | errno = EINVAL; 706 | if (opterr) 707 | perror ("get command line option"); 708 | return ('?'); 709 | } 710 | -------------------------------------------------------------------------------- /src/ui.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | ------------------------------------------------------------------------------ 4 | 5 | ===== 6 | CPCFS -- u i . c -- Main program, and Text Interface 7 | ===== 8 | 9 | Version 0.85 (c) Derik van Zuetphen 10 | ------------------------------------------------------------------------------ 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "cpcfs.h" 19 | 20 | #if DOS 21 | #include "dos.h" 22 | #endif 23 | 24 | /********************************************************************* 25 | Auxilaries 26 | *********************************************************************/ 27 | 28 | int cmd_error (const char *msg) { 29 | /* ^^^^^^^^^ */ 30 | return errorf(FALSE,"Use: %s",msg); 31 | } 32 | 33 | 34 | int saved_fo, saved_mo; 35 | 36 | void set_force_mode(int fo, int mo) { 37 | /* ^^^^^^^^^^^^^^ 38 | Save global and and set them to and (unless 9999)*/ 39 | saved_fo = force; 40 | saved_mo = mode; 41 | if (fo!=9999) force = fo; 42 | if (mo!=9999) mode = mo; 43 | } 44 | 45 | void restore_force_mode() { 46 | /* ^^^^^^^^^^^^^^^^^^ */ 47 | force = saved_fo; 48 | mode = saved_mo; 49 | } 50 | 51 | 52 | /* Relationship between and : 53 | if (optind > nbof_args) no non-option 54 | if (optind == nbof_args) exactly one non-option arg[optind] 55 | if (optind < nbof_args) non-options at arg[optind..nbof_args] 56 | */ 57 | 58 | #define REMAINING_ARGS (nbof_args-optind+1) 59 | 60 | /********************************************************************* 61 | Commands 62 | *********************************************************************/ 63 | 64 | int cmd_attrib() { 65 | /* ^^^^^^^^^^ */ 66 | int set_mask = 0; 67 | int reset_mask = 0; 68 | int mask; 69 | bool set; 70 | int i; 71 | char *pattern; 72 | const char errmsg[] = "ATTRIB ... ..."; 73 | int first_file = 0; /* 0 = reading attributes, >0 files found */ 74 | 75 | if (nbof_args==0) return cmd_error(errmsg); 76 | 77 | for (i=1;i<=nbof_args;i++) { 78 | if (first_file == 0) { 79 | if (arg[i][0]=='-') { 80 | if (parse_attr(arg[i]+1,&mask,&set)) { 81 | return errorf(FALSE, 82 | "Illegal attribute \"%s\"",arg[i]); 83 | } 84 | if (set) set_mask |= mask; 85 | else reset_mask |= mask; 86 | } else { 87 | first_file = i; 88 | } 89 | } else { 90 | if (arg[i][0]=='-') 91 | return errorf(FALSE, 92 | "FIRST attributes, THEN filenames"); 93 | } 94 | } 95 | 96 | for (i=first_file;i<=nbof_args;i++) { 97 | pattern = arg[i]; 98 | if (change_attrib(pattern,set_mask,reset_mask)) 99 | return 0; 100 | } 101 | 102 | return 0; 103 | } 104 | 105 | 106 | int cmd_close() { 107 | /* ^^^^^^^^^ */ 108 | if (nbof_args!=0) return cmd_error("CLOSE"); 109 | 110 | close_image(); 111 | return 0; 112 | } 113 | 114 | 115 | int cmd_cls() { 116 | /* ^^^^^^^ */ 117 | if (nbof_args!=0) return cmd_error("CLS"); 118 | 119 | clrscr(); 120 | return 0; 121 | } 122 | 123 | 124 | int cmd_comment() { 125 | /* ^^^^^^^^^^^ */ 126 | int i; 127 | time_t now; 128 | const char original_text[] = "EMU Disk-File\r\nDisk-Info\r\n"; 129 | char buf[256]; /* 3 lines should be enough */ 130 | const char errmsg[] = "COMMENT [ -d | -n | ]"; 131 | 132 | if (inactive()) return -1; 133 | if (nbof_args==0) { 134 | printm(0,"Comment is \""); 135 | for (i=0;i<40;i++) { 136 | if (*(disk_header.tag+8+i)==0) break; 137 | putcharm(0,*(disk_header.tag+8+i)); 138 | } 139 | printm(0,"\"\n"); 140 | return 0; 141 | } 142 | 143 | if (nbof_args!=1) return cmd_error(errmsg); 144 | 145 | if (arg[1][0]=='-') { 146 | switch (tolower(arg[1][1])) { 147 | case 'd': 148 | comment_image(original_text); 149 | break; 150 | case 'n': 151 | now = time(NULL); 152 | strcpy(buf,"EMU / "); /* 6 bytes */ 153 | strftime(buf+6,20,"%d %b %y %H:%M",localtime(&now)); 154 | buf[26]=0; 155 | comment_image(buf); 156 | break; 157 | default: return cmd_error(errmsg); 158 | } 159 | } else { 160 | expand_percent(arg[1],buf,256); 161 | comment_image(buf); 162 | } 163 | return 0; 164 | } 165 | 166 | 167 | int cmd_copy() { 168 | /* ^^^^^^^^ 169 | Copies one or more files to another file or in another user. 170 | */ 171 | 172 | int trg_user; 173 | int i; 174 | char root[INPUTLEN], ext[INPUTLEN]; 175 | 176 | const char errmsg[] = "\tCOPY [-f | -t | -b] \n" 177 | "\tCOPY [-f | -t | -b] .. "; 178 | int local_force, local_mode; 179 | char optchar; 180 | 181 | if (inactive()) return 0; 182 | if (nbof_args==0) 183 | return cmd_error(errmsg); 184 | 185 | /* parse options */ 186 | local_force = force; 187 | local_mode = mode; 188 | opterr = 0; /* no errormessages in getopt */ 189 | optind = FIRST_OPTIND; 190 | while ((optchar=getopt(nbof_args+1,arg,"ftb"))!= EOF) { 191 | switch (optchar) { 192 | case 'f': local_force = TRUE; break; 193 | case 't': local_mode = M_TEXT; break; 194 | case 'b': local_mode = M_BIN; break; 195 | case '?': return cmd_error(errmsg); 196 | } 197 | } 198 | if (REMAINING_ARGS < 2) 199 | return cmd_error(errmsg); 200 | 201 | set_force_mode(local_force,local_mode); 202 | 203 | parse_cpm_filename(arg[nbof_args],&trg_user,root,ext); 204 | if (*root==0) { 205 | if (trg_user==-1) trg_user = cur_user; 206 | if (trg_user==-2) 207 | return errorf(FALSE,"No wildcards allowed in user"); 208 | for (i=optind;i1) return cmd_error(errmsg); 248 | 249 | dir(pattern,what|how); 250 | return 0; 251 | } 252 | 253 | 254 | int cmd_dira() { 255 | /* ^^^^^^^^ */ 256 | execute_cmd("dump -d"); 257 | printm(1,"\nDIRA will be obsolete in future versions! " 258 | "Use DUMP -D instead!\n"); 259 | return 0; 260 | } 261 | 262 | 263 | int cmd_dpb() { 264 | /* ^^^^^^^ */ 265 | if (inactive()) return 1; 266 | if (nbof_args!=0) return cmd_error("DPB"); 267 | 268 | printm(0,"Standard Disk Parameter Block:\n"); 269 | printm(0,"%s\n",repstr(hori,79)); 270 | 271 | printm(0,"SPT = 0x%-2X = %-3u \trecords per track\n", 272 | dpb->SPT,dpb->SPT); 273 | printm(0,"BSH = 0x%1X = %-3u \t2^BSH = records/block \n", 274 | dpb->BSH,dpb->BSH); 275 | printm(0,"BLM = 0x%1X = %-3u \tBLM+1 = records/block \n", 276 | dpb->BLM,dpb->BLM); 277 | printm(0,"EXM = 0x%1X = %-3u \tEXM+1 = extents/entry\n", 278 | dpb->EXM,dpb->EXM); 279 | printm(0,"DSM = 0x%-2X = %-3u \tDSM+1 = total number of blocks\n", 280 | dpb->DSM,dpb->DSM); 281 | printm(0,"DRM = 0x%-2X = %-3u \tDRM+1 = entries/directory\n", 282 | dpb->DRM,dpb->DRM); 283 | printm(0,"AL0 = 0x%1X = %-3u \tfirst eight bit of allocation map\n", 284 | dpb->AL0,dpb->AL0); 285 | printm(0,"AL1 = 0x%1X = %-3u \tsecond eight bit of allocation map\n", 286 | dpb->AL1,dpb->AL1); 287 | printm(0,"CKS = 0x%-2X = %-3u \trecords/directory \n", 288 | dpb->CKS,dpb->CKS); 289 | printm(0,"OFS = 0x%-2X = %-3u \treserved tracks for system (offset)\n", 290 | dpb->OFS,dpb->OFS); 291 | 292 | putcharm(0,10); 293 | printm(0,"Additional info:\n"); 294 | printm(0,"%s\n",repstr(hori,79)); 295 | 296 | printm(0,"Heads = %d, ",dpb->HDS); 297 | printm(0,"Tracks = %d, ",dpb->TRKS); 298 | printm(0,"Sectors = %d\n",dpb->SECS); 299 | printm(0,"Number of first sector = 0x%-2X\n",dpb->SEC1); 300 | printm(0,"Bytes/sector = %d\n",dpb->BPS); 301 | printm(0,"Bytes/blocks = %d\n",dpb->BLS); 302 | printm(0,"Blocks/directory = %d\n",dpb->DBL); 303 | 304 | return 0; 305 | } 306 | 307 | 308 | int cmd_dump() { 309 | /* ^^^^^^^^ */ 310 | 311 | int blk[2], hd[2], trk[2], sec[2]; 312 | int what=0; /* 0=blkdump, 1=secdump, 2=dirdump, 3=map */ 313 | int how=1; /* 0=on stdout, 1=on pager, 2=on file */ 314 | int ind=0; 315 | char name[INPUTLEN] = ""; 316 | char optchar; 317 | FILE *file; 318 | int i,j,k; 319 | const char errmsg[] = "DUMP (-d | -m | (-b#|-h#|-t#|-s#|-1|-2)... ) " 320 | "[-f | -c] "; 321 | 322 | if (inactive()) return 0; 323 | if (nbof_args==0) return cmd_error(errmsg); 324 | 325 | blk[0] = hd[0] = trk[0] = sec[0] = 0; 326 | blk[1] = hd[1] = trk[1] = sec[1] = -1; 327 | 328 | 329 | /* parse options */ 330 | opterr = 0; /* no errormessages in getopt */ 331 | optind = FIRST_OPTIND; 332 | while ((optchar=getopt(nbof_args+1,arg,"b:h:t:s:f:cdm12"))!= EOF) { 333 | switch (optchar) { 334 | case 'b': blk[ind]=atoxi(optarg); what=0; break; 335 | case 'h': hd[ind]=atoxi(optarg); what=1; break; 336 | case 't': trk[ind]=atoxi(optarg); what=1; break; 337 | case 's': sec[ind]=atoxi(optarg); what=1; break; 338 | case 'f': strcpy(name,optarg); how=2; break; 339 | case 'c': how=0; break; 340 | case 'd': what=2; break; 341 | case 'm': what=3; break; 342 | case '1': ind=0; break; 343 | case '2': ind=1; break; 344 | case ':': 345 | case '?': return cmd_error(errmsg); 346 | } 347 | } 348 | 349 | if (REMAINING_ARGS>0) 350 | return cmd_error(errmsg); 351 | 352 | /* prepare the output medium */ 353 | switch (how) { 354 | case 0: /* stdout */ 355 | file=stdout;/*fdopen(1,"w");*/ 356 | break; 357 | case 1: /* pager */ 358 | tmp_nam(name); 359 | file = fopen(name,"w"); 360 | if (file==NULL) { 361 | return errorf(TRUE,"Cannot open temporary file \"%s\" " 362 | "for writing ",name); 363 | } 364 | case 2: /* file */ 365 | file = fopen(name,"w"); 366 | if (file==NULL) { 367 | return errorf(TRUE,"Cannot open \"%s\" for writing ", 368 | name); 369 | } 370 | } 371 | 372 | /* adjust addresses */ 373 | blk[0] = max(0,blk[0]); 374 | hd[0] = max(0,hd[0]); 375 | trk[0] = max(0,trk[0]); 376 | sec[0] = max(0,sec[0]); 377 | 378 | blk[1] = min(dpb->DSM,blk[1]); 379 | hd[1] = min(dpb->HDS-1,hd[1]); 380 | trk[1] = min(dpb->TRKS-1,trk[1]); 381 | sec[1] = min(dpb->SECS-1,sec[1]); 382 | 383 | if (blk[1]==-1) blk[1]=blk[0]; 384 | if (hd[1]==-1) hd[1] =hd[0]; 385 | if (trk[1]==-1) { 386 | if (hd[1]==hd[0]) 387 | trk[1]=trk[0]; 388 | else 389 | trk[1]=dpb->TRKS-1; 390 | } 391 | if (sec[1]==-1) { 392 | if (trk[1]==trk[0]) 393 | sec[1]=sec[0]; 394 | else 395 | sec[1]=dpb->SECS-1; 396 | } 397 | 398 | /* do the output */ 399 | switch (what) { 400 | case 0: /* blkdump */ 401 | for (i=blk[0];i<=blk[1];i++) { 402 | if (Break_Wish) { 403 | if (how!=0) fclose(file); 404 | do_break(); 405 | } 406 | if (dump(file,i,-1,-1,-1)) break; 407 | } 408 | break; 409 | case 1: /* secdump */ 410 | i=hd[0]; j=trk[0]; k=sec[0]; 411 | for (;;) { 412 | if (Break_Wish) { 413 | if (how!=0) fclose(file); 414 | do_break(); 415 | } 416 | if (dump(file,-1,i,j,k)) break; 417 | if (i==hd[1] && j==trk[1] && k==sec[1]) break; 418 | next_sector(&i,&j,&k); 419 | } 420 | break; 421 | case 2: /* dirdump */ 422 | dumpdir(file); 423 | break; 424 | case 3: /* map */ 425 | map(file); 426 | break; 427 | } 428 | 429 | fflush(file); 430 | if (how!=0) fclose(file); /* do not close stdout */ 431 | if (how==1) { 432 | if (pager(name)) { 433 | unlink(name); 434 | return errorf(TRUE,"DUMP"); 435 | } 436 | unlink(name); 437 | } 438 | 439 | return 0; 440 | } 441 | 442 | 443 | int cmd_echo () { 444 | /* ^^^^^^^^ */ 445 | if (nbof_args>1) { 446 | return cmd_error("ECHO "); 447 | } 448 | 449 | if (nbof_args == 1) { 450 | echom(0,arg[1]); 451 | } 452 | putcharm(0,10); 453 | return 0; 454 | } 455 | 456 | 457 | int cmd_era() { 458 | /* ^^^^^^^ */ 459 | long freed = 0; 460 | int files = 0; 461 | int i; 462 | char optchar; 463 | int local_force; 464 | const char errmsg[] = "(DEL|ERA) [-f] ..."; 465 | 466 | if (inactive()) return 0; 467 | if (nbof_args==0) return cmd_error(errmsg); 468 | 469 | /* parse options */ 470 | local_force = force; 471 | opterr = 0; /* no errormessages in getopt */ 472 | optind = FIRST_OPTIND; 473 | while ((optchar=getopt(nbof_args+1,arg,"f"))!= EOF) { 474 | switch (optchar) { 475 | case 'f': local_force = TRUE; break; 476 | case '?': return cmd_error(errmsg); 477 | } 478 | } 479 | 480 | if (REMAINING_ARGS==0) 481 | return cmd_error(errmsg); 482 | 483 | set_force_mode(local_force,9999); 484 | for (i=optind;i<=nbof_args;i++) { 485 | freed += delete(FALSE,arg[i]); 486 | files++; /* actually filespecs, not files!! */ 487 | } 488 | printm(2,"Total: %ld Bytes\n",freed); 489 | 490 | restore_force_mode(); 491 | put_directory(); 492 | return 0; 493 | } 494 | 495 | 496 | int cmd_exit() { 497 | /* ^^^^^^^ */ 498 | close_image(); 499 | exit(0); return 0; 500 | } 501 | 502 | 503 | int cmd_force() { 504 | /* ^^^^^^^^^ */ 505 | if (nbof_args > 0 ) return cmd_error("FORCE"); 506 | 507 | force = !force; 508 | 509 | printm(2,"Force is switched "); 510 | if (force) {printm(2,"*ON*!\n");} 511 | else {printm(2,"-off-!\n");} 512 | 513 | return 0; 514 | } 515 | 516 | 517 | int cmd_get() { 518 | /* ^^^^^^^ */ 519 | char trg[INPUTLEN]; 520 | int user; 521 | int drive; 522 | char path[INPUTLEN]; 523 | char root[INPUTLEN]; 524 | char ext[INPUTLEN]; 525 | long done; 526 | char *src = ""; 527 | int i; 528 | const char errmsg[] = "\tGET [-f | -t | -b] []\n" 529 | "\tGET [-f | -t | -b] ... "; 530 | int local_force, local_mode; 531 | char optchar; 532 | 533 | if (inactive()) return 0; 534 | if (nbof_args==0) { 535 | return cmd_error(errmsg); 536 | } 537 | 538 | /* parse options */ 539 | local_force = force; 540 | local_mode = mode; 541 | opterr = 0; /* no errormessages in getopt */ 542 | optind = FIRST_OPTIND; 543 | while ((optchar=getopt(nbof_args+1,arg,"ftb"))!= EOF) { 544 | switch (optchar) { 545 | case 'f': local_force = TRUE; break; 546 | case 't': local_mode = M_TEXT; break; 547 | case 'b': local_mode = M_BIN; break; 548 | case '?': return cmd_error(errmsg); 549 | } 550 | } 551 | if (REMAINING_ARGS==0) 552 | return cmd_error(errmsg); 553 | 554 | set_force_mode(local_force,local_mode); 555 | 556 | /* last arg end with backslash */ 557 | if (REMAINING_ARGS > 1 558 | && arg[nbof_args][strlen(arg[nbof_args])-1]==DIRSEPARATOR) { 559 | parse_filename(arg[nbof_args],&drive,path,root,ext); 560 | if (*root) { 561 | cmd_error(errmsg); 562 | restore_force_mode(); 563 | return 1; 564 | } 565 | for (i=optind;i0) {trg[0] = drive+'@'; trg[1]=0;} 570 | else {trg[0]=0;} 571 | strcat(trg,path); 572 | strcat(trg,root); 573 | if (*ext) {strcat(trg,"."); strcat(trg,ext);} 574 | 575 | printm(2,"Getting \"%s\": ",src); 576 | done = get (src,trg); 577 | if (done>=0) 578 | printm(2,"%ld Bytes\n",done); 579 | else 580 | printm(2,"[skipped]\n"); 581 | } 582 | 583 | } else { /* one or two args */ 584 | src = arg[optind]; 585 | if (REMAINING_ARGS==1) { 586 | parse_cpm_filename(src,&user,trg,ext); 587 | if (ext[0]!=0) { 588 | strcat(trg,"."); 589 | strcat(trg,ext); 590 | } 591 | lower(trg); 592 | } else 593 | strcpy(trg,arg[optind+1]); 594 | 595 | printm(2,"Getting \"%s\": ",src); 596 | done = get (src,trg); 597 | if (done>=0) 598 | printm(2,"%ld Bytes\n",done); 599 | else 600 | printm(2,"[skipped]\n"); 601 | } 602 | 603 | 604 | restore_force_mode(); 605 | return 0; 606 | } 607 | 608 | 609 | int cmd_help() { 610 | /* ^^^^^^^^ 611 | Writes a part of CPCFS.HLP to stdout. 612 | */ 613 | char topic[20]; 614 | char line[INPUTLEN]; 615 | FILE *file; 616 | bool found = FALSE; 617 | bool ok = FALSE; 618 | 619 | if (nbof_args>1) return cmd_error("HELP or HELP "); 620 | else if (nbof_args==0) strcpy(topic,"~nothing~"); 621 | else { 622 | strcpy(topic,"~"); 623 | strcat(topic,arg[1]); 624 | strcat(topic,"~"); 625 | } 626 | lower(topic); 627 | 628 | strcpy(line,installpath); 629 | strcat(line,HELPFILE); 630 | file = fopen(line,"r"); 631 | if (file==NULL) return errorf(TRUE,"I cannot read \"%s\"",line); 632 | 633 | /* Scans for a line with ~~, puts the lines until the next ~ is reached */ 634 | 635 | while (fgets(line,INPUTLEN,file)!=NULL) { 636 | if (found && line[0]!='~') {printm(0,"\r%s",line); ok = TRUE;} 637 | if (found && line[0]=='~') {found=FALSE; continue;} 638 | if (!found&& line[0]!='~') continue; 639 | if (!found&& line[0]=='~') found = (strstr(line,topic)!=NULL); 640 | } 641 | 642 | fclose(file); 643 | 644 | if (!ok) { 645 | topic[0]='"'; 646 | topic[strlen(topic)-1]='"'; 647 | return errorf(FALSE,"No help for %s",topic); 648 | } 649 | return 0; 650 | } 651 | 652 | 653 | int cmd_lcd() { 654 | /* ^^^^^^^ */ 655 | char buf[PATH_MAX]; 656 | int err; 657 | #if DOS 658 | char *p; 659 | #endif 660 | 661 | if (nbof_args==0) 662 | printm(0,"Working directory is \"%s\"\n",getwd(buf)); 663 | else if (nbof_args>1) { 664 | return cmd_error("(CD|LCD) "); 665 | } else { 666 | #if DOS 667 | if (arg[1][1]==':') { 668 | setdisk(toupper(arg[1][0])-'A'); 669 | p=arg[1]+2; 670 | } else { 671 | p=arg[1]; 672 | } 673 | if (p[1]!=0 && p[strlen(p)-1]==DIRSEPARATOR) { 674 | p[strlen(p)-1] = 0; 675 | } 676 | err = *p==0? 0 : chdir(p); 677 | #else 678 | if (arg[1][1]!=0 && arg[1][strlen(arg[1])-1]==DIRSEPARATOR) { 679 | arg[1][strlen(arg[1])-1] = 0; 680 | } 681 | err = chdir(arg[1]); 682 | #endif 683 | if (err) { 684 | return errorf (FALSE,"I cannot cd to \"%s\"!",arg[1]); 685 | } 686 | printm(2,"Working directory is now \"%s\"\n",getwd(buf)); 687 | } 688 | return 0; 689 | } 690 | 691 | 692 | int cmd_ldir() { 693 | /* ^^^^^^^^ */ 694 | char buf[INPUTLEN]; 695 | int i; 696 | *buf = 0; 697 | strcat(buf,LDIRCOMMAND); 698 | for (i=1;i<=nbof_args;i++) { 699 | strcat(buf," "); 700 | strcat(buf,arg[i]); 701 | } 702 | if (system (buf)) { 703 | return errorf(TRUE,"Error executing \"%s\"",buf); 704 | } 705 | return 0; 706 | } 707 | 708 | 709 | int cmd_map() { 710 | /* ^^^^^^^ */ 711 | execute_cmd("dump -m"); 712 | printm(1,"\nMAP will be obsolete in future versions! " 713 | "Use DUMP -M instead!\n"); 714 | return 0; 715 | } 716 | 717 | 718 | int cmd_mget() { 719 | /* ^^^^^^^^ */ 720 | char src[INPUTLEN]; 721 | char trg[INPUTLEN]; 722 | int ent; 723 | int i; 724 | int files=0; 725 | long done=0, 726 | total=0; 727 | const char errmsg[] = "MGET [-f | -t | -b] ..."; 728 | int local_force, local_mode; 729 | char optchar; 730 | 731 | if (nbof_args==0) return cmd_error(errmsg); 732 | 733 | /* parse options */ 734 | local_force = force; 735 | local_mode = mode; 736 | opterr = 0; /* no errormessages in getopt */ 737 | optind = FIRST_OPTIND; 738 | while ((optchar=getopt(nbof_args+1,arg,"ftb"))!= EOF) { 739 | switch (optchar) { 740 | case 'f': local_force = TRUE; break; 741 | case 't': local_mode = M_TEXT; break; 742 | case 'b': local_mode = M_BIN; break; 743 | case '?': return cmd_error(errmsg); 744 | } 745 | } 746 | if (REMAINING_ARGS==0) 747 | return cmd_error(errmsg); 748 | 749 | set_force_mode(local_force,local_mode); 750 | 751 | for (i=optind;i<=nbof_args;i++) { 752 | ent = glob_cpm_file(arg[i]); 753 | if (ent<0) { 754 | return errorf(FALSE,"\"%s\" not found",arg[i]); 755 | continue; 756 | } 757 | while (ent>=0) { 758 | /* prepare CP/M name */ 759 | sprintf(src,"%u:%s",directory[ent].user, 760 | directory[ent].name); 761 | 762 | /* prepare DOS name */ 763 | strcpy(trg,(char*)directory[ent].name); 764 | lower(trg); 765 | if (trg[strlen(trg)-1]=='.') trg[strlen(trg)-1]=0; 766 | 767 | printm(2,"Getting \"%s\": ",src); 768 | glob_env++; /* itself uses */ 769 | done = get(src,trg); 770 | glob_env--; 771 | 772 | if (done>=0) { 773 | printm(2,"%ld Bytes\n",done); 774 | files++; 775 | total += done; 776 | } else 777 | printm(2,"[skipped]\n"); 778 | 779 | ent = glob_cpm_next(); 780 | } 781 | } /* for i */ 782 | 783 | printm(2,"Total: %ld Bytes, %d file%s\n", total, files, plural(files)); 784 | restore_force_mode(); 785 | return 0; 786 | } 787 | 788 | 789 | int cmd_mode() { 790 | /* ^^^^^^^^ */ 791 | if (nbof_args == 0) { 792 | printm(0,"Mode is %s\n",show_mode(mode)); 793 | return 0; 794 | } 795 | if (nbof_args> 1 ) { 796 | return cmd_error("MODE bin | text | auto"); 797 | } 798 | 799 | switch (tolower(arg[1][0])) { 800 | case 'b': 801 | mode = M_BIN; 802 | break; 803 | case 't': 804 | mode = M_TEXT; 805 | break; 806 | case 'a': 807 | mode = M_AUTO; 808 | break; 809 | default: 810 | return errorf(FALSE,"Unrecognized mode \"%s\". " 811 | "Valid are Auto, Bin, and Text; or A, B, T.\n",arg[1]); 812 | } 813 | printm(2,"Mode set to %s\n",show_mode(mode)); 814 | return 0; 815 | } 816 | 817 | 818 | int cmd_mput() { 819 | /* ^^^^^^^^ */ 820 | char trg[13]; 821 | char *src; 822 | int files; 823 | int total_files = 0; 824 | long done=0, 825 | total=0; 826 | int drive; 827 | char path[INPUTLEN]; 828 | char rootname[INPUTLEN]; 829 | char extension[INPUTLEN]; 830 | const char errmsg[] = "MPUT [-f | -t | -b] ..."; 831 | int local_force, local_mode; 832 | char optchar; 833 | int i; 834 | 835 | if (inactive()) return 0; 836 | if (nbof_args==0) { 837 | return cmd_error(errmsg); 838 | } 839 | 840 | /* parse options */ 841 | local_force = force; 842 | local_mode = mode; 843 | opterr = 0; /* no errormessages in getopt */ 844 | optind = FIRST_OPTIND; 845 | while ((optchar=getopt(nbof_args+1,arg,"ftb"))!= EOF) { 846 | switch (optchar) { 847 | case 'f': local_force = TRUE; break; 848 | case 't': local_mode = M_TEXT; break; 849 | case 'b': local_mode = M_BIN; break; 850 | case '?': return cmd_error(errmsg); 851 | } 852 | } 853 | if (REMAINING_ARGS==0) 854 | return cmd_error(errmsg); 855 | 856 | set_force_mode(local_force,local_mode); 857 | 858 | 859 | for (i=optind;i<=nbof_args;i++) { 860 | src = glob_file(arg[i]); 861 | files = 0; 862 | while (src!=NULL) { 863 | parse_filename(src,&drive,path,rootname,extension); 864 | strcpy(trg,rootname); 865 | if (*extension) { 866 | strcat(trg,"."); 867 | strcat(trg,extension); 868 | } 869 | 870 | printm(2,"Putting \"%s\": ",src); 871 | done = put(src,trg); 872 | if (done>=0) { 873 | printm(2,"%ld Bytes\n",done); 874 | total += done; 875 | files++; 876 | } else if (done==-1) 877 | printm(2,"[skipped]\n"); 878 | else { /* done==-2 */ 879 | printm(2,"[aborted]\n"); 880 | restore_force_mode(); 881 | return 1; 882 | } 883 | 884 | src=glob_next(); 885 | } 886 | if (files==0) { 887 | printm(1,"\"%s\" not found!\n",arg[i]); 888 | } else { 889 | total_files += files; 890 | } 891 | 892 | } /* for i*/ 893 | 894 | printm(2,"Total: %ld Bytes, %d file%s\n",total,total_files, 895 | plural(total_files)); 896 | restore_force_mode(); 897 | return 0; 898 | } 899 | 900 | 901 | int cmd_new() { 902 | /* ^^^^^^^ */ 903 | DPB_type *dpb; 904 | char name[INPUTLEN]; 905 | const char errmsg[] = "(NEW|FORMAT) [-s | -d | -i | -v] "; 906 | char optchar; 907 | 908 | if (nbof_args==0) { 909 | return cmd_error(errmsg); 910 | } 911 | 912 | /* parse options */ 913 | opterr = 0; /* no errormessages in getopt */ 914 | optind = FIRST_OPTIND; 915 | dpb = &DPB_store[DATA_DPB]; 916 | while ((optchar=getopt(nbof_args+1,arg,"sdiv"))!= EOF) { 917 | switch (optchar) { 918 | case 's': dpb = &DPB_store[SYSTEM_DPB]; break; 919 | case 'd': dpb = &DPB_store[DATA_DPB]; break; 920 | case 'i': dpb = &DPB_store[IBM_DPB]; break; 921 | case 'v': dpb = &DPB_store[VORTEX_DPB]; break; 922 | case '?': return cmd_error(errmsg); 923 | } 924 | } 925 | if (REMAINING_ARGS!=1) 926 | return cmd_error(errmsg); 927 | 928 | close_image(); 929 | strcpy(name,arg[optind]); 930 | append_suffix(name,"dsk"); 931 | 932 | if (access(name,F_OK)==0) { 933 | if (Verb > 0) { 934 | printm(1,"\"%s\" already exists! Overwrite? ",name); 935 | if (!confirmed()) { 936 | return 0; 937 | } 938 | } 939 | } 940 | 941 | if (format(name,dpb)) return 0; 942 | 943 | open_image(name); /* reopen and initialize */ 944 | return 0; 945 | } 946 | 947 | 948 | int cmd_open() { 949 | /* ^^^^^^^^ */ 950 | char buf[INPUTLEN]; 951 | 952 | 953 | if (nbof_args==0) { 954 | if (*disk_header.tag) 955 | printm(0,"Image in use is \"%s\"\n",imagename); 956 | else 957 | printm(0,"No Image loaded!\n"); 958 | } 959 | else if (nbof_args>1) { 960 | return cmd_error("OPEN "); 961 | } 962 | else { 963 | strcpy(buf,arg[1]); 964 | append_suffix(buf,"dsk"); 965 | open_image(buf); 966 | } 967 | return 0; 968 | } 969 | 970 | 971 | int cmd_page() { 972 | /* ^^^^^^^^ */ 973 | if (nbof_args>1) return cmd_error("PAGE "); 974 | 975 | if (nbof_args==0) 976 | printm(0,"Page length is %d\n",pagelen); 977 | else { 978 | pagelen = atoxi(arg[1]); 979 | printm(2,"Page length set to %d\n",pagelen); 980 | } 981 | return 0; 982 | } 983 | 984 | 985 | int cmd_prompt() { 986 | /* ^^^^^^^^^^ */ 987 | if (nbof_args == 0) { 988 | printm(0,"Prompt is \"%s\"\n",prompt); 989 | return 0; 990 | } 991 | if (nbof_args>1) { 992 | return cmd_error("PROMPT "); 993 | } 994 | 995 | strcpy(prompt,arg[1]); 996 | return 0; 997 | } 998 | 999 | 1000 | int cmd_put() { 1001 | /* ^^^^^^^ */ 1002 | char trg[INPUTLEN]; /* CP/M name */ 1003 | long done; 1004 | int drive; 1005 | char path[INPUTLEN]; 1006 | char rootname[INPUTLEN]; 1007 | char extension[INPUTLEN]; 1008 | const char errmsg[] = "\tPUT [-f | -t | -b] []\n" 1009 | "\tPUT [-f | -t | -b] []\n"; 1010 | int local_force, local_mode; 1011 | char optchar; 1012 | 1013 | if (inactive()) return 0; 1014 | if (nbof_args==0) { 1015 | return cmd_error(errmsg); 1016 | } 1017 | 1018 | /* parse options */ 1019 | local_force = force; 1020 | local_mode = mode; 1021 | opterr = 0; /* no errormessages in getopt */ 1022 | optind = FIRST_OPTIND; 1023 | while ((optchar=getopt(nbof_args+1,arg,"ftb"))!= EOF) { 1024 | switch (optchar) { 1025 | case 'f': local_force = TRUE; break; 1026 | case 't': local_mode = M_TEXT; break; 1027 | case 'b': local_mode = M_BIN; break; 1028 | case '?': return cmd_error(errmsg); 1029 | } 1030 | } 1031 | if (REMAINING_ARGS>2 || REMAINING_ARGS==0) 1032 | return cmd_error(errmsg); 1033 | 1034 | set_force_mode(local_force,local_mode); 1035 | 1036 | /* build target name */ 1037 | parse_filename(arg[optind],&drive,path,rootname,extension); 1038 | if (REMAINING_ARGS==1) { 1039 | strcpy(trg,rootname); 1040 | if (*extension) { 1041 | strcat(trg,"."); 1042 | strcat(trg,extension); 1043 | } 1044 | } else if (arg[optind+1][strlen(arg[optind+1])-1]==':') {/* only user */ 1045 | strcpy(trg,arg[optind+1]); 1046 | strcat(trg,rootname); 1047 | if (*extension) { 1048 | strcat(trg,"."); 1049 | strcat(trg,extension); 1050 | } 1051 | } else { 1052 | strcpy(trg,arg[optind+1]); 1053 | } 1054 | 1055 | printm(2,"Putting \"%s\": ",arg[optind]); 1056 | done = put(arg[optind],trg); 1057 | if (done>=0) 1058 | printm(2,"%ld Bytes\n",done); 1059 | else if (done==-1) 1060 | printm(2,"[skipped]\n"); 1061 | else /* done==-2 */ 1062 | printm(2,"[aborted]\n"); 1063 | restore_force_mode(); 1064 | return 0; 1065 | } 1066 | 1067 | 1068 | int cmd_ren() { 1069 | /* ^^^^^^^ 1070 | Reanmes a file to another name or shift a lot of files to another user. */ 1071 | int i; 1072 | int trg_user; 1073 | char root[INPUTLEN], ext[INPUTLEN]; 1074 | 1075 | const char errmsg[] = "\tREN \n" 1076 | "\tREN ... "; 1077 | 1078 | if (inactive()) return 0; 1079 | if (nbof_args<2) { 1080 | return cmd_error(errmsg); 1081 | } 1082 | 1083 | parse_cpm_filename(arg[nbof_args],&trg_user,root,ext); 1084 | if (*root==0) { 1085 | if (trg_user==-1) trg_user = cur_user; 1086 | if (trg_user==-2) 1087 | return errorf(FALSE,"No wildcards allowed in user"); 1088 | for (i=1;i"); 1106 | execute_file (arg[1]); 1107 | return 0; 1108 | } 1109 | 1110 | 1111 | int cmd_spawn() { 1112 | /* ^^^^^^^^^ */ 1113 | char *buf; 1114 | const char errmsg[] ="Error executing \"%s\""; 1115 | 1116 | if (nbof_args==0) { 1117 | printm(3,"[EXIT to return to cpcfs]\n"); 1118 | if ((buf=getenv(SHELLVAR))==NULL) { 1119 | save_path(); 1120 | if (system(SHELLDEFAULT)) { 1121 | return errorf(TRUE,errmsg,SHELLDEFAULT); 1122 | } 1123 | rest_path(); 1124 | } else { 1125 | save_path(); 1126 | if (system (buf)) { 1127 | return errorf(TRUE,errmsg,buf); 1128 | } 1129 | rest_path(); 1130 | } 1131 | } else { 1132 | reparse(1); 1133 | save_path(); 1134 | if (system(arg[1])) { 1135 | return errorf(TRUE,errmsg,arg[1]); 1136 | } 1137 | rest_path(); 1138 | } 1139 | if ((*disk_header.tag) && (access(full_imagename,R_OK))) { 1140 | abandonimage(); 1141 | return -1; 1142 | } 1143 | return 0; 1144 | } 1145 | 1146 | 1147 | int cmd_stat() { 1148 | /* ^^^^^^^^ */ 1149 | char buf[256]; 1150 | if (nbof_args!=0) return cmd_error("STAT"); 1151 | 1152 | putcharm(0,10); 1153 | if (*disk_header.tag) { 1154 | printm(0,"Image File : %s\n",imagename); 1155 | printm(0,"Format : %s\n",show_format(cur_format)); 1156 | putcharm(0,10); 1157 | printm(0,"CP/M : "); 1158 | if (dpb->SYS) { 1159 | printm(0,"Yes\n"); 1160 | } else { 1161 | printm(0,"No"); 1162 | if (dpb->OFS > 0) 1163 | printm(0,", but %d track%s reserved\n", 1164 | dpb->OFS,plural(dpb->OFS)); 1165 | else 1166 | printm(0,"\n"); 1167 | } 1168 | printm(0,"Current user : %d\n",cur_user); 1169 | putcharm(0,10); 1170 | 1171 | printm(0,"Capacity : %d Blocks = %ld Bytes\n", 1172 | dpb->DSM+1, ((long)dpb->DSM+1)*dpb->BLS); 1173 | printm(0,"Directory : %d Block%s\n", 1174 | (dpb->DRM+1)*32/(dpb->BLS), 1175 | plural((dpb->DRM+1)*32/(dpb->BLS))); 1176 | printm(0,"Allocated : %d Block%s = %ld Bytes = %5.1f%%\n", 1177 | allocated_blks, plural(allocated_blks), 1178 | (long)allocated_blks*dpb->BLS, percentage); 1179 | printm(0,"Free : %d Block%s = %ld Bytes = %5.1f%%\n", 1180 | free_blks, plural(free_blks), 1181 | (long)free_blks*dpb->BLS, 100.0-percentage); 1182 | putcharm(0,10); 1183 | } else { 1184 | printm(0,"No image loaded!\n"); 1185 | putcharm(0,10); 1186 | } 1187 | 1188 | printm(0,"Prompt=\"%s\"; ",prompt); 1189 | printm(0,"Local directory=\"%s\"\n", getwd(buf)); 1190 | printm(0,"Verbosity=%d; ",Verb); 1191 | printm(0,"Page length=%d; ",pagelen); 1192 | printm(0,"Mode=%s; ",show_mode(mode)); 1193 | printm(0,"Force="); 1194 | if (force) printm(0,"*ON*"); else printm(0,"-off-"); 1195 | printm(0,"\n"); 1196 | putcharm(0,10); 1197 | 1198 | return 0; 1199 | } 1200 | 1201 | 1202 | int cmd_sysgen() { 1203 | /* ^^^^^^^^^^ */ 1204 | if (inactive()) return 0; 1205 | if (nbof_args!=1) return cmd_error("SYSGEN "); 1206 | 1207 | if (dpb->OFS == 0) { 1208 | return errorf(FALSE,"No system tracks reserved in %s", 1209 | show_format(cur_format)); 1210 | } 1211 | 1212 | if (dpb->SYS) { 1213 | if (Verb > 0) { 1214 | printm(1,"CP/M already in image! Overwrite? "); 1215 | if (!confirmed()) return 0; 1216 | } 1217 | } 1218 | 1219 | if (dpb->OFS < 2) { 1220 | return errorf(FALSE,"Too few space for system (I need 2 " 1221 | "tracks, not %d)",dpb->OFS); 1222 | } 1223 | 1224 | sysgen(arg[1]); 1225 | return 0; 1226 | } 1227 | 1228 | 1229 | int cmd_type() { 1230 | /* ^^^^^^^^ 1231 | Puts the contents of a CP/M file to a pager [def], a file [-f] or stdout [-c]. 1232 | */ 1233 | char outname[INPUTLEN]; 1234 | char tempname[INPUTLEN]; 1235 | char *cpmname; 1236 | char optchar; 1237 | FILE *outfile; 1238 | int tempfile; 1239 | int local_mode; 1240 | int how=1; /* 0=on stdout, 1=on pager, 2=on file */ 1241 | int counter=0; 1242 | int i, r; 1243 | int err; 1244 | uchar *buf; 1245 | const char errmsg[] = "TYPE [-f | -c | -t | -b] "; 1246 | 1247 | buf = block_buffer; /* a shortcut */ 1248 | 1249 | if (inactive()) return 0; 1250 | if (nbof_args==0) return cmd_error(errmsg); 1251 | 1252 | /* parse options */ 1253 | opterr = 0; /* no errormessages in getopt */ 1254 | optind = FIRST_OPTIND; 1255 | local_mode = mode; 1256 | while ((optchar=getopt(nbof_args+1,arg,"f:ctb"))!= EOF) { 1257 | switch (optchar) { 1258 | case 'f': strcpy(outname,optarg); how=2; break; 1259 | case 'c': how=0; break; 1260 | case 't': local_mode = M_TEXT; break; 1261 | case 'b': local_mode = M_BIN; break; 1262 | case ':': 1263 | case '?': return cmd_error(errmsg); 1264 | } 1265 | } 1266 | 1267 | if (REMAINING_ARGS!=1) return cmd_error(errmsg); 1268 | 1269 | set_force_mode(9999,local_mode); 1270 | cpmname = arg[optind]; 1271 | 1272 | /* prepare the output medium */ 1273 | switch (how) { 1274 | case 0: /* stdout */ 1275 | outfile=fdopen(1,"w"); 1276 | break; 1277 | case 1: /* pager */ 1278 | tmp_nam(outname); 1279 | outfile = fopen(outname,"w"); 1280 | if (outfile==NULL) { 1281 | return errorf(TRUE,"Cannot open temporary file \"%s\" " 1282 | "for writing ",outname); 1283 | } 1284 | case 2: /* file */ 1285 | outfile = fopen(outname,"w"); 1286 | if (outfile==NULL) { 1287 | return errorf(TRUE,"Cannot open \"%s\" for writing ", 1288 | outname); 1289 | } 1290 | } 1291 | 1292 | 1293 | /* do the output */ 1294 | tmp_nam(tempname); 1295 | err=get(cpmname,tempname); 1296 | if (err==-1) { 1297 | restore_force_mode(); 1298 | /* return errorf(FALSE,"\"%s\" not found",cpmname);*/ 1299 | return -1; 1300 | } 1301 | 1302 | tempfile=open(tempname,O_RDONLY|O_BINARY); 1303 | if (tempfile==-1) { 1304 | errorf(TRUE,"I cannot read \"%s\"",tempname); 1305 | restore_force_mode(); 1306 | return -1; 1307 | } 1308 | 1309 | 1310 | r=read(tempfile,buf,(dpb->BLS)); 1311 | if (local_mode==M_AUTO) { 1312 | local_mode = detectmode((char*)buf,max((dpb->BLS),r)); 1313 | } 1314 | 1315 | while (r>0) { 1316 | if (local_mode==M_TEXT) { 1317 | for (i=0;iBLS);i+=16) { 1324 | err=fprintf(outfile,"%s\n", 1325 | show_hex(counter,buf+i,16)); 1326 | if (err<0) break; 1327 | counter += 16; 1328 | } 1329 | } 1330 | if (err<0) { 1331 | close(tempfile); 1332 | unlink(tempname); 1333 | return errorf(TRUE,""); 1334 | } 1335 | r=read(tempfile,buf,(dpb->BLS)); 1336 | } 1337 | putc(10,outfile); 1338 | 1339 | close(tempfile); 1340 | unlink(tempname); 1341 | 1342 | fflush(outfile); 1343 | if (how!=0) fclose(outfile); /* do not close stdout */ 1344 | if (how==1) { 1345 | if (pager(outname)) errorf(TRUE,"TYPE"); 1346 | unlink(outname); 1347 | } 1348 | 1349 | restore_force_mode(); 1350 | return 0; 1351 | } 1352 | 1353 | 1354 | int cmd_user() { 1355 | /* ^^^^^^^^ */ 1356 | int u; 1357 | const char errmsg[] = "\tUSER \n\t:"; 1358 | if (inactive()) return 0; 1359 | if (nbof_args == 0) printm(0,"User is %d\n",cur_user); 1360 | else if (nbof_args > 1) { 1361 | return cmd_error(errmsg); 1362 | } else { 1363 | if (!isdigit(arg[1][0])) return cmd_error(errmsg); 1364 | u=atoxi(arg[1]); 1365 | if (u>255) return cmd_error(" must be < 256."); 1366 | if (u>15) printm(4,"User numbers > 15 may cause trouble.\n"); 1367 | cur_user=u; 1368 | } 1369 | return 0; 1370 | } 1371 | 1372 | 1373 | int cmd_verbosity() { 1374 | /* ^^^^^^^^^^^^^ */ 1375 | int v; 1376 | if (nbof_args>1) return cmd_error("VERBOSITY []"); 1377 | 1378 | if (nbof_args==0) 1379 | printm(0,"Verbosity is %d\n",Verb); 1380 | else { 1381 | v = atoxi(arg[1]); 1382 | printm(2,"Verbosity set to %d\n",v); 1383 | Verb = v; 1384 | } 1385 | return 0; 1386 | } 1387 | 1388 | 1389 | /********************************************************************* 1390 | User Interface 1391 | *********************************************************************/ 1392 | 1393 | #define NBOFCMDS 40 1394 | struct { 1395 | char *name; 1396 | int (*proc)(); 1397 | } command[NBOFCMDS] = { 1398 | {"!", cmd_spawn }, 1399 | {"?", cmd_help }, 1400 | {"attrib", cmd_attrib }, 1401 | {"bye", cmd_exit }, 1402 | {"cd", cmd_lcd }, 1403 | {"close", cmd_close }, 1404 | {"cls", cmd_cls }, 1405 | {"copy", cmd_copy }, 1406 | {"comment", cmd_comment}, 1407 | {"del", cmd_era }, 1408 | {"dir", cmd_dir }, 1409 | {"dira", cmd_dira }, 1410 | {"dpb", cmd_dpb }, 1411 | {"dump", cmd_dump }, 1412 | {"echo", cmd_echo }, 1413 | {"era", cmd_era }, 1414 | {"exit", cmd_exit }, 1415 | {"force", cmd_force }, 1416 | {"format", cmd_new }, 1417 | {"get", cmd_get }, 1418 | {"help", cmd_help }, 1419 | {"lcd", cmd_lcd }, 1420 | {"ldir", cmd_ldir }, 1421 | {"map", cmd_map }, 1422 | {"mget", cmd_mget }, 1423 | {"mode", cmd_mode }, 1424 | {"mput", cmd_mput }, 1425 | {"new", cmd_new }, 1426 | {"open", cmd_open }, 1427 | {"page", cmd_page }, 1428 | {"prompt", cmd_prompt }, 1429 | {"put", cmd_put }, 1430 | {"quit", cmd_exit }, 1431 | {"ren", cmd_ren }, 1432 | {"source", cmd_source }, 1433 | {"stat", cmd_stat }, 1434 | {"sysgen", cmd_sysgen }, 1435 | {"type", cmd_type }, 1436 | {"user", cmd_user }, 1437 | {"verbosity", cmd_verbosity}, 1438 | }; 1439 | 1440 | 1441 | int execute_one_cmd (char *input) { 1442 | /* ^^^^^^^^^^^^^^^ 1443 | Execute a single command. */ 1444 | char buffer[INPUTLEN]; 1445 | char userbuffer[INPUTLEN]; 1446 | char *line; 1447 | char spawn[] = "!"; 1448 | int i; 1449 | 1450 | Break_Wish = FALSE; 1451 | strncpy(buffer,input,INPUTLEN-1); 1452 | #if DOS 1453 | line=strchr(buffer,13); 1454 | if (line) *line=0; 1455 | #endif 1456 | line=buffer; 1457 | nbof_args=0; 1458 | if (!line) return 0; 1459 | for (;;) { 1460 | while ((*line==' ')||(*line=='\n')||(*line=='\t')||*line=='\r') 1461 | line++; /*skip white*/ 1462 | if (*line==0) break; 1463 | if (*line=='#') break; /*comment*/ 1464 | if (nbof_args==0) { 1465 | if (*line=='!') { /*! special*/ 1466 | arg[0]=spawn; 1467 | line++; 1468 | nbof_args++; 1469 | continue; 1470 | } 1471 | } 1472 | arg[nbof_args++] = line; /*remember arg*/ 1473 | 1474 | if (*line=='"') { /* quoted string */ 1475 | arg[nbof_args-1]++; /* skip quote */ 1476 | line++; 1477 | while (*line!='"' && *line!=0) line ++; 1478 | if (*line==0) { 1479 | return errorf(FALSE,"Missing closing quote!"); 1480 | } 1481 | *line++ = 0; /* replace quote */ 1482 | } else { 1483 | while (*line!=' ' && *line!='\n' && *line!='\t' 1484 | && *line!=0 && *line!='\r') 1485 | line++; 1486 | if (*line==0) break; 1487 | else *line++=0; /*set end-of-arg*/ 1488 | } 1489 | 1490 | } 1491 | nbof_args--; 1492 | 1493 | 1494 | if (nbof_args==-1) { 1495 | nbof_args=0; 1496 | return 0; 1497 | } 1498 | 1499 | lower(arg[0]); 1500 | 1501 | /* shortcut for USER command */ 1502 | if (nbof_args==0 && arg[0][strlen(arg[0])-1]==':') { 1503 | sprintf(userbuffer,"user %s",arg[0]); 1504 | execute_one_cmd(userbuffer); 1505 | return 0; 1506 | } 1507 | 1508 | for (i=0;i "); 1546 | *disk_header.tag=0; 1547 | /* cur_trk = -1; set in fs.c */ 1548 | DPB_store[USER_DPB] = DPB_store[DATA_DPB]; /* set default user DPB */ 1549 | pagelen=25; 1550 | disable_break(); 1551 | Break_Wish = FALSE; 1552 | mode = M_AUTO; 1553 | force = FALSE; 1554 | #if DOS 1555 | _fmode = O_BINARY; 1556 | #endif 1557 | #if USE_READLINE && UNIX 1558 | /* configure GNU readline */ 1559 | rl_bind_key ('\t', rl_insert); 1560 | #endif 1561 | 1562 | p = getenv("CPCFSHOME"); 1563 | if (p) { 1564 | strcpy(installpath,p); 1565 | installpath[strlen(installpath)]= DIRSEPARATOR; 1566 | installpath[strlen(installpath)+1]= 0; 1567 | } else { 1568 | /* works only for DOS, in Unix argv[0] may be a relative path, 1569 | more precisely: BCC has an absolute path in argv[0], where gcc has not */ 1570 | strcpy(installpath,argv0); 1571 | p = strrchr(installpath,DIRSEPARATOR); 1572 | if (p!=NULL) *(++p) = 0; 1573 | else installpath[0] = 0; 1574 | } 1575 | } 1576 | 1577 | 1578 | int execute_file (char *name) { 1579 | /* ^^^^^^^^^^^^ */ 1580 | FILE *file; 1581 | char line[INPUTLEN]; 1582 | 1583 | if ((file=fopen(name,"r")) == NULL) { 1584 | return errorf(TRUE,"\"%s\" not found",name); 1585 | } 1586 | while (fgets(line,INPUTLEN,file)) 1587 | execute_cmd(line); 1588 | fclose(file); 1589 | return 0; 1590 | } 1591 | 1592 | 1593 | void read_cfg_file() { 1594 | /* ^^^^^^^^^^^^^ */ 1595 | 1596 | char name[INPUTLEN]; 1597 | int notfound; 1598 | 1599 | strcpy(name,CONFIGNAME); 1600 | notfound = access(name,R_OK); 1601 | if (notfound) { 1602 | strcpy(name,installpath); 1603 | strcat(name,CONFIGNAME); 1604 | notfound = access (name,R_OK); 1605 | if (notfound) { 1606 | return; 1607 | } 1608 | } 1609 | execute_file(name); 1610 | } 1611 | 1612 | 1613 | void interaction (char *argv0) { 1614 | /* ^^^^^^^^^^^ */ 1615 | #if USE_READLINE && UNIX 1616 | char prompt_buf[INPUTLEN]; 1617 | char *line; 1618 | #elif USE_READLINE && DOS 1619 | char prompt_buf[INPUTLEN]; 1620 | char line[INPUTLEN]; 1621 | int len; 1622 | #else 1623 | char line[INPUTLEN]; 1624 | #endif 1625 | 1626 | read_cfg_file(); 1627 | 1628 | setjmp(break_entry); 1629 | for (;;) { 1630 | #if USE_READLINE && UNIX 1631 | *prompt_buf = 0; 1632 | if (Verb >= 1) expand_percent(prompt,prompt_buf,INPUTLEN); 1633 | line = readline(prompt_buf); 1634 | if (line==NULL) { 1635 | printm(3,"[Quit]\n"); 1636 | execute_cmd("exit"); 1637 | } 1638 | if (line && *line) add_history(line); 1639 | execute_cmd (line); 1640 | free(line); 1641 | #elif USE_READLINE && DOS 1642 | *prompt_buf = 0; 1643 | if (Verb >= 1) expand_percent(prompt,prompt_buf,INPUTLEN); 1644 | printf("%s",prompt_buf); 1645 | *line=0; 1646 | len = inputs(line,INPUTLEN-strlen(prompt_buf)-1,-1); 1647 | if (len==-2) { 1648 | printm(3,"[Quit]\n"); 1649 | execute_cmd("exit"); 1650 | } 1651 | if (line && *line) add_history(line); 1652 | execute_cmd(line); 1653 | #else /* ! USE_READLINE */ 1654 | echom(1,prompt); 1655 | if (fgets(line,INPUTLEN,stdin)) 1656 | execute_cmd(line); 1657 | #endif 1658 | } 1659 | } 1660 | 1661 | 1662 | 1663 | void usage (bool err) { 1664 | /* ^^^^^ */ 1665 | char buf[INPUTLEN]; 1666 | if (err) { 1667 | printm(1,"Error in command line!\n\n"); 1668 | } else { 1669 | printm(1,"CPCFS - CPCEmu Filessystem Maintenance\n"); 1670 | expand_percent("%V",buf,INPUTLEN); 1671 | printm(1," %s\n\n",buf); 1672 | printm(1,"SYNOPSIS:\n"); 1673 | printm(1," cpcfs Enter interactive mode\n"); 1674 | printm(1,"or "); 1675 | } 1676 | printm(1,"cpcfs [] ...\n"); 1677 | printm(1,"where\n"); 1678 | printm(1," is implicitly opened\n"); 1679 | printm(1," = -d dir Directory (default command)\n"); 1680 | printm(1," -s stat Statistics\n"); 1681 | printm(1," -g get Get a file\n"); 1682 | printm(1," -p put Put a file\n"); 1683 | printm(1," -mg mget Get many files\n"); 1684 | printm(1," -mp mput Put many files\n"); 1685 | printm(1," -nX new Create new image (X=dsv, Data, " 1686 | "System, or Vortex)\n"); 1687 | printm(1," -f force Force overwrite, if file exists\n"); 1688 | printm(1," -t text ASCII Mode\n"); 1689 | printm(1," -b bin Binary Mode\n"); 1690 | printm(1," -e Execute arbitrary commands, " 1691 | "separated by ';'\n"); 1692 | printm(1," -x source Execute commands from file\n"); 1693 | printm(1," -h, -? This help\n"); 1694 | } 1695 | 1696 | 1697 | 1698 | 1699 | void ui_main (int argc, char **argv) { 1700 | /* ^^^^^^^ 1701 | Main function for CPCFS based on Text User Interface */ 1702 | 1703 | char line[INPUTLEN]; 1704 | int i; 1705 | bool more_switches = TRUE; 1706 | 1707 | init(argv[0]); 1708 | 1709 | /* no arguments => interactive mode */ 1710 | if (argc==1) { 1711 | Interactive = TRUE; 1712 | interaction(argv[0]); 1713 | exit(0); 1714 | } 1715 | 1716 | Interactive = FALSE; 1717 | /* only filename => dir all */ 1718 | if ((argc==2) && argv[1][0]!='-') { 1719 | strcpy(line,"open "); strcat(line,argv[1]); 1720 | if (execute_cmd(line)) exit(1); 1721 | if (execute_cmd("dir *:*.*")) exit(1); 1722 | exit(0); 1723 | } 1724 | 1725 | /* execute commandline */ 1726 | strcpy(line,"open"); /* if no command is given */ 1727 | for (i=1;i1) { 1730 | if (execute_cmd(line)) { 1731 | exit(1); 1732 | } 1733 | } 1734 | *line = 0; 1735 | switch (tolower(argv[i][1])) { 1736 | case 'g': strcpy(line,"get"); break; 1737 | case 'p': strcpy(line,"put"); break; 1738 | case 'f': strcpy(line,"force"); break; 1739 | case 't': strcpy(line,"mode text"); break; 1740 | case 'b': strcpy(line,"mode bin"); break; 1741 | case 'm': 1742 | switch (tolower(argv[i][2])) { 1743 | case 'g': strcpy(line,"mget"); break; 1744 | case 'p': strcpy(line,"mput"); break; 1745 | default: usage(TRUE); exit(1); 1746 | } 1747 | break; 1748 | case 'n': 1749 | switch (tolower(argv[i][2])) { 1750 | case 'd': strcpy(line,"new -d"); break; 1751 | case 's': strcpy(line,"new -s"); break; 1752 | case 'v': strcpy(line,"new -v"); break; 1753 | default: usage(TRUE); exit(1); 1754 | } 1755 | break; 1756 | case 's': strcpy(line,"stat"); break; 1757 | case 'e': more_switches = FALSE; break; 1758 | case 'd': strcpy(line,"dir"); break; 1759 | case 'x': strcpy(line,"source"); break; 1760 | case 'h': 1761 | case '?': usage(FALSE); exit(0); 1762 | default: usage(TRUE); exit(1); 1763 | } 1764 | } else { 1765 | if (strlen(line)+strlen(argv[i])+2 > INPUTLEN) { 1766 | errorf(FALSE,"Commandline too long (>%d chars)", INPUTLEN); 1767 | exit(1); 1768 | } 1769 | strcat(line," "); 1770 | strcat(line,argv[i]); 1771 | } 1772 | } 1773 | execute_cmd(line); 1774 | execute_cmd("close"); 1775 | } 1776 | -------------------------------------------------------------------------------- /src/fs.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | ------------------------------------------------------------------------------ 4 | 5 | ===== 6 | CPCFS -- f s . c -- Managing the Filesystem 7 | ===== 8 | 9 | Version 0.85 (c) Derik van Zuetphen 10 | ------------------------------------------------------------------------------ 11 | */ 12 | 13 | #if DOS 14 | #include 15 | #endif 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "cpcfs.h" 23 | #include "match.h" 24 | 25 | /****** Variables ******/ 26 | 27 | int cur_trk = -1; /* flag for no track loaded */ 28 | int cur_hd = 0; 29 | int cur_blk = -1; 30 | 31 | 32 | bool tag_ok () { 33 | /* ^^^^^^ */ 34 | return (strncmp("MV - CPC",(char*)disk_header.tag,8)==0); 35 | } 36 | 37 | 38 | void alloc_block(int blk,int file) { 39 | /* ^^^^^^^^^^^ 40 | is only informational, real CP/M uses bitmaps */ 41 | if (file >= 0xFF) file = 0xFE; /* FF marks a free block */ 42 | if (blk > dpb->DSM) 43 | printm(1,"Warning: Directory entry %d currupt!\n",file); 44 | else 45 | blk_alloc[blk]=file; 46 | } 47 | 48 | 49 | void free_block(int blk) { 50 | /* ^^^^^^^^^^ */ 51 | if (blk <= dpb->DSM) 52 | blk_alloc[blk]=0xFF; 53 | } 54 | 55 | 56 | bool is_free_block(int blk) { 57 | /* ^^^^^^^^^^^^^ */ 58 | return blk_alloc[blk]==0xFF; 59 | } 60 | 61 | 62 | void calc_allocation () { 63 | /* ^^^^^^^^^^^^^^^ */ 64 | int i; 65 | allocated_blks = 0; 66 | free_blks = 0; 67 | for (i=dpb->DBL; i<=dpb->DSM; i++) 68 | if (is_free_block(i)) free_blks++; 69 | else allocated_blks++; 70 | percentage=100*allocated_blks/(float)(allocated_blks+free_blks); 71 | total_blks = allocated_blks + free_blks; 72 | } 73 | 74 | 75 | bool inactive () { 76 | /* ^^^^^^^^ */ 77 | if (*disk_header.tag==0) { 78 | printm(1,"No image loaded!\n"); 79 | return TRUE; 80 | } else 81 | return FALSE; 82 | } 83 | 84 | 85 | /* Calculate track, sector, and head number of the first sector of block 86 | . 87 | 88 | A quick picture: 89 | 90 | secs 91 | .------^-------. 92 | _______________ 93 | / -2- - - - - >/| } heads 94 | /______________/ | } 95 | / | -1- - - - - > | | 96 | : | | | 97 | : | | | 98 | : | | | 99 | trks = | -4- - - - - -| | 100 | : | | | 101 | : | -3- - - - - > | | 102 | : | | | 103 | \ |_______________|/ 104 | 105 | 106 | numbering scheme: 107 | first secs, then heads, then tracks 108 | %s /s%h /s/h%t has to be applied on running sec number 109 | 110 | s = sec/trk, h = heads, t = tracks (here infinite) 111 | 112 | */ 113 | 114 | 115 | int trk_calc(int blk) { 116 | /* ^^^^^^^^ */ 117 | return ((long)blk*dpb->BLS/dpb->BPS + dpb->OFS*dpb->SECS) 118 | / (dpb->SECS*dpb->HDS); 119 | } 120 | 121 | int sec_calc(int blk) { 122 | /* ^^^^^^^^ 123 | The returned number is not shifted by ! */ 124 | return ((long)blk*dpb->BLS/dpb->BPS + dpb->OFS*dpb->SECS) % dpb->SECS; 125 | } 126 | 127 | int hd_calc(int blk) { 128 | /* ^^^^^^^ */ 129 | return ((long)blk*dpb->BLS/dpb->BPS + dpb->OFS*dpb->SECS) 130 | / dpb->SECS % dpb->HDS; 131 | } 132 | 133 | 134 | int blk_calc(int hd, int trk, int sec) { 135 | /* ^^^^^^^^ 136 | Return the blocknumber of position , , or -1, if it is a 137 | reserved position 138 | */ 139 | if (trk*dpb->HDS+hd < dpb->OFS) return -1; 140 | return ((long)sec + hd*dpb->SECS 141 | + trk*dpb->SECS*dpb->HDS 142 | - dpb->OFS*dpb->SECS) 143 | / (dpb->BLS/dpb->BPS); 144 | } 145 | 146 | 147 | 148 | void abandonimage() { 149 | /* ^^^^^^^^^^^^ */ 150 | *disk_header.tag = 0; 151 | cur_trk = -1; 152 | if (track) {free(track); track=NULL;} 153 | if (blk_alloc) {free(blk_alloc); blk_alloc=NULL;} 154 | if (directory) {free(directory); directory=(DirEntry*)NULL;} 155 | if (block_buffer){free(block_buffer); block_buffer=(uchar*)NULL;} 156 | errorf(FALSE,"Image \"%s\" abandoned!",imagename); 157 | } 158 | 159 | 160 | 161 | /******** 162 | Tracks 163 | ********/ 164 | 165 | int read_track (int hd, int trk) { 166 | /* ^^^^^^^^^^ 167 | WARNING: the DPB does not nessecarily exist here! */ 168 | long n, pos; 169 | if (trk == cur_trk && hd == cur_hd) { 170 | return 0; 171 | } 172 | 173 | printm(11,"[rt(%d,%d)] ",hd,trk); 174 | 175 | pos = (long)(trk*disk_header.nbof_heads + hd) 176 | * (long)disk_header.tracksize; 177 | n = lseek(imagefile,0x100L+pos,SEEK_SET); 178 | if (n == -1L) { 179 | errorf(TRUE,"Image currupt! I cannot position on track %d!", 180 | trk); 181 | abandonimage(); 182 | return -1; 183 | } 184 | 185 | n = read(imagefile,track,disk_header.tracksize); 186 | if (n != disk_header.tracksize) { 187 | errorf(TRUE,"Image currupt! I can only read %ld bytes " 188 | "of track %d (instead of %d bytes)!", 189 | n,trk,disk_header.tracksize); 190 | abandonimage(); 191 | return -1; 192 | } 193 | cur_trk = trk; 194 | cur_hd = hd; 195 | 196 | return 0; 197 | } 198 | 199 | 200 | int write_track() { 201 | /* ^^^^^^^^^^^ */ 202 | long n,pos; 203 | if (cur_trk == -1) return 0; 204 | 205 | printm(11,"[wt(%d,%d)] ",cur_hd,cur_trk); 206 | pos = (long)(cur_trk*dpb->HDS + cur_hd) * (long)disk_header.tracksize; 207 | n = lseek(imagefile,0x100L+pos,SEEK_SET); 208 | if (n == -1L) { 209 | errorf(TRUE,"Image currupt! I cannot position on track %d!", 210 | cur_trk); 211 | abandonimage(); 212 | return -1; 213 | } 214 | 215 | n = write(imagefile,track,disk_header.tracksize); 216 | if (n != disk_header.tracksize) { 217 | errorf(TRUE,"Something wrong! I cannot write %d bytes " 218 | "to track %d (only %d bytes written)!", 219 | disk_header.tracksize,cur_trk,n); 220 | abandonimage(); 221 | return -1; 222 | } 223 | return 0; 224 | } 225 | 226 | 227 | bool next_sector (int *hd, int *trk, int *sec) { 228 | /* ^^^^^^^^^^^ 229 | Assumes without offset. Answer TRUE if the or is changed. */ 230 | (*sec)++; 231 | if (*sec >= dpb->SECS) { 232 | *sec -= dpb->SECS; 233 | (*hd)++; 234 | if (*hd >= dpb->HDS) { 235 | *hd = 0; 236 | (*trk)++; 237 | } 238 | return TRUE; 239 | } 240 | return FALSE; 241 | } 242 | 243 | 244 | int interleave(uchar *track,int sec) { 245 | /* Searches the sector numbers of for the sectorid and answers 246 | its potition */ 247 | int i; 248 | for (i=0;iSECS;i++) { 249 | if (((struct t_header*)track)->sector[i].sector 250 | == sec+dpb->SEC1) { 251 | return i; 252 | } 253 | } 254 | errorf(FALSE,"Image uses invalid interleave! Sector 0x%X in Track %d " 255 | "and Head %d",sec,cur_trk,cur_hd); 256 | abandonimage(); 257 | return 0; 258 | } 259 | 260 | 261 | uchar *read_block (int blk) { 262 | /* ^^^^^^^^^^ 263 | Read block into a buffer and return its address, or NULL on error */ 264 | int trk, sec, hd; 265 | int filled = 0; 266 | 267 | if (blk==cur_blk) return block_buffer; 268 | 269 | printm(11,"[rb(%d)] ",blk); 270 | 271 | trk = trk_calc (blk); 272 | sec = sec_calc (blk); 273 | hd = hd_calc (blk); 274 | 275 | while (filled < dpb->BLS) { 276 | if (read_track(hd,trk)) return NULL; 277 | memcpy(block_buffer+filled, 278 | track+0x100+interleave(track,sec)*dpb->BPS,dpb->BPS); 279 | filled += dpb->BPS; 280 | next_sector (&hd,&trk,&sec); 281 | } 282 | cur_blk = blk; 283 | return block_buffer; 284 | } 285 | 286 | 287 | uchar *write_block (int blk, char *buf) { 288 | /* ^^^^^^^^^^^ 289 | Return the written buffer or NULL on error */ 290 | int trk, sec, hd; 291 | int filled = 0; 292 | 293 | printm(11,"[wb(%d)] ",blk); 294 | 295 | trk = trk_calc (blk); 296 | sec = sec_calc (blk); 297 | hd = hd_calc (blk); 298 | 299 | while (filled < dpb->BLS) { 300 | if (read_track(hd,trk)) return NULL; 301 | memcpy(track+0x100+interleave(track,sec)*dpb->BPS, 302 | buf+filled,dpb->BPS); 303 | filled += dpb->BPS; 304 | if (next_sector (&hd,&trk,&sec)) write_track(); 305 | } 306 | write_track(); 307 | return (uchar*)buf; 308 | } 309 | 310 | 311 | 312 | /*********** 313 | Directory 314 | ***********/ 315 | 316 | 317 | /* local definitions for glob_cpm_* */ 318 | 319 | #define GLOB_ENV_MAX 3 320 | int glob_env = 0; 321 | /* determines which global variable-set should use, */ 322 | /* necessary for nested globs */ 323 | 324 | uchar pattern[GLOB_ENV_MAX][INPUTLEN]; 325 | int last_entry[GLOB_ENV_MAX]; 326 | 327 | int glob_cpm_next() { 328 | /* ^^^^^^^^^^^^^ 329 | Answer the next entry (or -1) with the same pattern as before */ 330 | int i; 331 | uchar name[20]; 332 | 333 | for (i=last_entry[glob_env]+1;i<=dpb->DRM;i++) { 334 | if (!directory[i].first) continue; 335 | build_cpm_name_32((char*)name, directory[i].user, 336 | (char*)directory[i].root, 337 | (char*)directory[i].ext); 338 | if (match((char*)pattern[glob_env],(char*)name)) { 339 | last_entry[glob_env] = i; 340 | return i; 341 | } 342 | } 343 | return -1; 344 | } 345 | 346 | 347 | int glob_cpm_file(char *pat) { 348 | /* ^^^^^^^^^^^^^ 349 | Scan the entries for the first file that matches and answer its 350 | first (according to flag, see DirEntry) entry number, 351 | If the pattern contains no filename part, *.* is assumed. 352 | Errorcode is -1, if pattern not found. 353 | The work is mostly deferred to . */ 354 | 355 | int user; 356 | uchar root[INPUTLEN], ext[INPUTLEN]; 357 | const char errmsg[] = "Illegal filename \"%s\""; 358 | 359 | if (parse_cpm_filename(pat,&user,(char*)root,(char*)ext)) 360 | return errorf(FALSE,errmsg,pat); 361 | upper((char*)root); 362 | upper((char*)ext); 363 | if (*root==0) { 364 | if (user >= 0) { 365 | strcpy((char*)root,"*"); 366 | strcpy((char*)ext,"*"); 367 | } else { 368 | return errorf(FALSE,errmsg,pat); 369 | 370 | } 371 | } 372 | if (user==-1) user = cur_user; 373 | build_cpm_name((char*)pattern[glob_env], user, 374 | (char*)root, (char*)ext); 375 | last_entry[glob_env] = -1; /* thus start with 0 */ 376 | return glob_cpm_next(); 377 | } 378 | 379 | 380 | /* local definitions for update_directory */ 381 | 382 | struct pair {uchar en; uchar ex;}; 383 | 384 | int cmp_pair(struct pair *x, struct pair *y) { 385 | if (x->ex < y->ex) return -1; 386 | if (x->ex > y->ex) return 1; 387 | return 0; 388 | } 389 | 390 | void update_directory() { 391 | /* ^^^^^^^^^^^^^^^^ 392 | (Re-)Organizes the directory structure (Packing the name, making a linked 393 | list) */ 394 | 395 | int i, j; 396 | 397 | /* a dynamic array of (entry,extent) pairs */ 398 | struct pair *map; 399 | 400 | 401 | printm(10,"[ud] "); 402 | map = (struct pair*)Malloc(sizeof(struct pair)*(dpb->DRM+1)); 403 | 404 | /****** packing a name of kind "FOO BAR" to "FOO.BAR\0" ******/ 405 | for (i=0;i<=dpb->DRM;i++) { 406 | if (directory[i].user == 0xE5) continue; 407 | build_cpm_name_32((char*)directory[i].name, -1, 408 | (char*)directory[i].root, 409 | (char*)directory[i].ext); 410 | } 411 | 412 | 413 | /****** organizing the directory structure as linked list ******/ 414 | 415 | /* set entries in the directory to "not visited" 416 | = 0 : never visit; = -1 : not yet visited */ 417 | for (i=0;i<=dpb->DRM;i++) { 418 | if (directory[i].user == 0xE5) /*NOT , E5 is required*/ 419 | directory[i].size = 0; /* never visit empty entries */ 420 | else 421 | directory[i].size = -1; /* not visited */ 422 | directory[i].first = FALSE; 423 | directory[i].next = -1; 424 | }; 425 | 426 | /* scan the entries */ 427 | for (i=0;i<=dpb->DRM;i++) { 428 | if (directory[i].size > -1) continue; 429 | 430 | /* reset the map */ 431 | for (j=0;j<=dpb->DRM;j++) {map[j].en=j;map[j].ex=0xFF;} 432 | 433 | /* fill the map with from the directory */ 434 | map[i].ex = directory[i].extent; 435 | for (j=0;j<=dpb->DRM;j++) { 436 | if ((directory[j].size == -1) && 437 | (directory[j].user == directory[i].user) && 438 | (i!=j) && 439 | (strcmp((char*)directory[i].name, 440 | (char*)directory[j].name)==0)) { 441 | map[j].ex = directory[j].extent; 442 | directory[j].size = 0; 443 | } 444 | } 445 | /* sort the map according to map[].ex, not necessary in most cases */ 446 | qsort(map,dpb->DRM+1,sizeof(struct pair), 447 | (int(*)(const void*,const void*))cmp_pair); 448 | 449 | /* fill , and from the map */ 450 | directory[map[0].en].first = TRUE; 451 | j=1; 452 | while (map[j].ex < 0xFF) { 453 | directory[map[j-1].en].next = map[j].en; 454 | j++; 455 | } 456 | directory[map[j-1].en].next = -1; 457 | 458 | /* the filesize located in the first fileentry can be calculated from the 459 | and fields of the last fileentry */ 460 | 461 | directory[map[0].en].size = 462 | (long)directory[map[j-1].en].extent * EXTENTSIZE 463 | + directory[map[j-1].en].rec * RECORDSIZE; 464 | 465 | 466 | } /* for i */ 467 | 468 | free(map); map=NULL; 469 | } 470 | 471 | 472 | void get_directory() { 473 | /* ^^^^^^^^^^^^^ */ 474 | int i,j,off; 475 | uchar *buf; 476 | int mask; 477 | 478 | printm(10,"[rd] "); 479 | /* reading the directory data */ 480 | for (i=0;i<=dpb->DRM;i++) { 481 | buf = read_block((signed)i*32/dpb->BLS); 482 | off = i*32 % dpb->BLS; 483 | /* user, name, ... */ 484 | directory[i].user = buf[off+0]; 485 | for (j=0;j<8;j++) directory[i].root[j] = buf[off+j+1] & 0x7F; 486 | for (j=0;j<3;j++) directory[i].ext[j] = buf[off+j+9] & 0x7F; 487 | directory[i].name[0] = 0; 488 | 489 | directory[i].extent = buf[off+12]; 490 | directory[i].unused[0] = buf[off+13]; 491 | directory[i].unused[1] = buf[off+14]; 492 | directory[i].rec = buf[off+15]; 493 | 494 | /* attributes */ 495 | mask=0x1; 496 | directory[i].attr = 0; 497 | for (j=11;j>0;j--) { 498 | if (buf[off+j]&0x80) directory[i].attr |= mask; 499 | mask <<= 1; 500 | } 501 | 502 | /* block pointer */ 503 | for (j=0;j<16;j++) directory[i].blk[j] = 0; 504 | if (BLKNR_SIZE==1) { 505 | for (j=0;j<16;j++) { 506 | directory[i].blk[j] = buf[off+16+j]; 507 | } 508 | } else if (BLKNR_SIZE==2) { 509 | for (j=0;j<8;j++) { 510 | directory[i].blk[j] = buf[off+16+2*j] 511 | + 256 * buf[off+17+2*j]; 512 | } 513 | } 514 | } 515 | 516 | update_directory(); 517 | for (i=0;iDBL;i++) alloc_block(i,0); /* 0 is not correct! */ 518 | 519 | /* marking the blocks as allocated */ 520 | for (j=0;j<=dpb->DSM;j++) free_block(j); 521 | for (i=0;i<=dpb->DRM;i++) { 522 | for (j=0;jDRM;i++) { 542 | off=i*32 % dpb->BLS; 543 | 544 | buf[off] = directory[i].user; 545 | for (j=0;j<8;j++) buf[off+j+1] = directory[i].root[j]; 546 | for (j=0;j<3;j++) buf[off+j+9] = directory[i].ext[j]; 547 | 548 | buf[off+12] = directory[i].extent; 549 | buf[off+13] = directory[i].unused[0]; 550 | buf[off+14] = directory[i].unused[1]; 551 | buf[off+15] = directory[i].rec; 552 | 553 | mask=0x1; 554 | for (j=11;j>0;j--) { 555 | if (directory[i].attr & mask) 556 | buf[off+j] |= 0x80; 557 | mask <<= 1; 558 | }; 559 | 560 | if (BLKNR_SIZE==1) { 561 | for (j=0;j<16;j++) { 562 | buf[off+16+j] = directory[i].blk[j]; 563 | } 564 | } else if (BLKNR_SIZE==2) { 565 | for (j=0;j<8;j++) { 566 | buf[off+16+2*j] = directory[i].blk[j] % 256; 567 | buf[off+17+2*j] = directory[i].blk[j] / 256; 568 | } 569 | } 570 | 571 | /* if next entry is in the next block, then write the current block */ 572 | if ((i+1)*32/(signed)dpb->BLS > block) { 573 | write_block(block,(char*)buf); 574 | block++; 575 | } 576 | } 577 | } 578 | 579 | 580 | 581 | /******* 582 | Image 583 | *******/ 584 | 585 | void update_dpb(DPB_type *dpb, uchar *track) { 586 | /* ^^^^^^^^^^ 587 | Determine the extended DPB data out of and the sample track . 588 | Complete the extension parts of . must be read in first! 589 | */ 590 | dpb->BLS = 1 << (dpb->BSH + 7); /* or 2^BSH*128 */ 591 | 592 | /* an image must exist, do not call form ! */ 593 | /* dpb->SEC1 = keep from DPB template */ 594 | dpb->SECS = ((struct t_header*)track)->SPT; 595 | /* the next two elements should already be set */ 596 | dpb->TRKS = disk_header.nbof_tracks; 597 | dpb->HDS = disk_header.nbof_heads; 598 | 599 | dpb->SYS = (dpb->OFS>0) && (*(track+0x100)) != filler; 600 | dpb->DBL = 32 * (dpb->DRM+1) / dpb->BLS; /* or often CKS/8 */ 601 | 602 | dpb->DSM = (dpb->TRKS*dpb->HDS*dpb->SECS) / (dpb->BLS/dpb->BPS) - 1; 603 | /* subtract reserved tracks */ 604 | dpb->DSM -= dpb->OFS * dpb->SECS / (dpb->BLS/dpb->BPS); 605 | /* subtract one for a slack block, because neither 18/8 nor 9/2 is integral: */ 606 | dpb->DSM--; 607 | 608 | if (dpb->DSM>=255) { 609 | /* 2 byte pointer and 8 pointer per entry */ 610 | BLKNR_SIZE = 2; 611 | BLKNR = 8; 612 | } else { 613 | /* 1 byte pointer and 16 pointer per entry */ 614 | BLKNR_SIZE = 1; 615 | BLKNR = 16; 616 | } 617 | } 618 | 619 | 620 | void close_image() { 621 | /* ^^^^^^^^^^^ */ 622 | if (*disk_header.tag) { 623 | printm(10,"[ci] "); 624 | if (cur_trk > -1) write_track(); 625 | put_directory(); 626 | free(blk_alloc); blk_alloc=NULL; 627 | free(track); track=NULL; 628 | free(directory); directory=(DirEntry*)NULL; 629 | free(block_buffer); block_buffer=(uchar*)NULL; 630 | *disk_header.tag = 0; 631 | dpb = NULL; 632 | close(imagefile); 633 | } 634 | cur_trk = -1; 635 | cur_blk = -1; 636 | } 637 | 638 | 639 | int open_image(char *name) { 640 | /* ^^^^^^^^^^ 641 | alloc track buffer, blk_alloc, buffer, read directory */ 642 | 643 | int n; 644 | char dirsep[2] = {DIRSEPARATOR, 0}; 645 | char *p; 646 | 647 | if (*disk_header.tag) close_image(); 648 | /* open file */ 649 | printm(10,"[oi] "); 650 | imagefile = open(name,O_RDWR|O_BINARY,0); 651 | imagename = name; /* temporary, for */ 652 | if (imagefile < 0) { 653 | return errorf(TRUE,"Cannot open \"%s\"",name); 654 | } 655 | n = read(imagefile,&disk_header,0x100); 656 | if (n!=0x100) { 657 | errorf(FALSE,"Image corrupt! I cannot read image header " 658 | "(only %d bytes)!",n); 659 | abandonimage(); 660 | return -1; 661 | } 662 | if (!tag_ok()) { 663 | errorf(FALSE,"\"%s\" is not a DSK image!",name); 664 | abandonimage(); 665 | return -1; 666 | } 667 | if ((disk_header.nbof_heads<1) || (disk_header.nbof_tracks<1)) { 668 | errorf(FALSE,"--==>>> open_image: \"%s\"",name); 669 | abandonimage(); 670 | return -1; 671 | } 672 | 673 | /* allocate memory */ 674 | track = Malloc(disk_header.tracksize); 675 | 676 | /* set up varaibles */ 677 | filler = 0xE5; 678 | cur_user=0; 679 | 680 | p = getwd(full_imagename); 681 | if (p) strcpy(full_imagename,p); 682 | if (full_imagename[strlen(full_imagename)-1]==DIRSEPARATOR) 683 | full_imagename[strlen(full_imagename)-1]=0; 684 | strcat(full_imagename,dirsep); 685 | strcat(full_imagename,name); 686 | #if DOS 687 | lower(full_imagename); 688 | #endif 689 | if((imagename=strrchr(full_imagename,DIRSEPARATOR))) 690 | imagename++; 691 | else imagename=full_imagename; 692 | 693 | /* determine system/data-disk */ 694 | read_track(0,0); 695 | cur_format = ((struct t_header*)track)->sector[0].sector; 696 | 697 | if (cur_format>=SYSTEMFORMAT && cur_format=IBMFORMAT && cur_format=DATAFORMAT && cur_formatDSM+1); 729 | directory = (DirEntry*)Malloc(sizeof(DirEntry)*(dpb->DRM+1)); 730 | 731 | /* allocate block buffer */ 732 | block_buffer = (uchar*)Malloc(dpb->BLS); 733 | 734 | /* get directory information */ 735 | get_directory(); 736 | calc_allocation(); 737 | 738 | return 0; 739 | } 740 | 741 | 742 | int comment_image(const char *text) { 743 | /* ^^^^^^^^^^^^^ 744 | Place in the comment field of the image and save the image 745 | 48 bytes tag = 8 bytes required + 40 bytes free */ 746 | 747 | int i; 748 | memset(disk_header.tag+8,0,40); 749 | i=0; 750 | while (text[i] && i<40) { 751 | *(disk_header.tag+8+i) = text[i]; 752 | i++; 753 | } 754 | 755 | lseek(imagefile,0L,SEEK_SET); 756 | if (write(imagefile,&disk_header,sizeof(disk_header)) < 0) { 757 | return errorf(TRUE,"--==>>> comment_image"); 758 | } 759 | return 0; 760 | } 761 | 762 | 763 | 764 | /******** 765 | Blocks 766 | ********/ 767 | 768 | int get_free_block() { 769 | /* ^^^^^^^^^^^^^^ */ 770 | static int next = 0; 771 | int i; 772 | 773 | if (next > dpb->DSM) next = 0; 774 | /* try to allocate next block, if there was a previos one (next!=0) */ 775 | if ((next != 0) && (is_free_block(next))) return next++; 776 | /* try to find the first free block */ 777 | for (i=dpb->DBL;i<=dpb->DSM;i++) { 778 | if (is_free_block(i)) return i; 779 | } 780 | return -1; 781 | } 782 | 783 | 784 | /***************** 785 | FS Maintenance 786 | *****************/ 787 | 788 | 789 | struct { 790 | int flag; 791 | uchar type; 792 | ushort load; 793 | ushort jump; 794 | ushort size; 795 | ushort checksum; 796 | 797 | } amsdos_header; 798 | 799 | 800 | void get_amshead(int ent) { 801 | /* ^^^^^^^^^^^ 802 | Read the first 128 bytes from the file and store them in the structure 803 | . Set the to 2, if it's a valid header; to 1, if it's 804 | invalid; and to 0 if the files is empty. 805 | */ 806 | 807 | int i; 808 | ushort sum = 0; 809 | uchar *buf; 810 | 811 | if (directory[ent].blk[0] == 0) { 812 | amsdos_header.flag = 0; 813 | return; 814 | } 815 | buf = read_block((signed)directory[ent].blk[0]); 816 | amsdos_header.type = buf[18]; 817 | amsdos_header.load = buf[21] + 256*buf[22]; 818 | amsdos_header.jump = buf[26] + 256*buf[27]; 819 | amsdos_header.size = buf[64] + 256*buf[65]; 820 | amsdos_header.checksum = buf[67] + 256*buf[68]; 821 | 822 | for (i=0;i<=66;i++) { 823 | sum += buf[i]; 824 | } 825 | if (sum==amsdos_header.checksum) 826 | amsdos_header.flag = 2; 827 | else 828 | amsdos_header.flag = 1; 829 | 830 | return; 831 | } 832 | 833 | /****** local function for dir() ******/ 834 | 835 | int cmp_array(int *x, int *y) { 836 | /* ^^^^^^^^^ 837 | Compares two filenames, whose entry numbers are and */ 838 | int res; 839 | 840 | if (directory[*x].user < directory[*y].user) res = -1; 841 | else if (directory[*x].user > directory[*y].user) res = 1; 842 | else { 843 | res = strncmp((char*)directory[*x].root, 844 | (char*)directory[*y].root, 8); 845 | if (res==0) 846 | res = strncmp((char*)directory[*x].ext, 847 | (char*)directory[*y].ext, 3); 848 | } 849 | return res; 850 | } 851 | 852 | 853 | int dir(char *pat, int mask) { 854 | /* ^^^ 855 | is a "bit-or" of the following bits: 856 | DIR_DOUBLE default --00 857 | DIR_WIDE only names and sizes --01 858 | DIR_AMSHEAD incl. AMSDOS Header --10 859 | DIR_LONG incl. entries, records, all attributes --11 860 | 861 | DIR_SORT sorted -1-- 862 | */ 863 | 864 | 865 | long total_bytes = 0; 866 | int used_entries = 0; 867 | int i,j; 868 | int ent; 869 | int files; 870 | int mode; 871 | int *array; /* temporary dynamic array */ 872 | char upbuffer[INPUTLEN]; /* buffer for upper case conversion*/ 873 | uchar *buf; 874 | int user; 875 | char root[INPUTLEN]; 876 | char ext[INPUTLEN]; 877 | 878 | array = (int*)Malloc(sizeof(int)*max(256,dpb->DRM+1)); 879 | 880 | parse_cpm_filename(pat,&user,root,ext); 881 | if (user==-1) user = cur_user; 882 | 883 | /* calculate active users, entries */ 884 | files = 0; 885 | for (i=0;i<256;i++) array[i]=0; 886 | for (i=0;i<=dpb->DRM;i++) { 887 | if (directory[i].user != 0xE5) used_entries++; 888 | if (directory[i].first) { 889 | array[directory[i].user]++; 890 | files++; 891 | } 892 | } 893 | 894 | newpage("c"); 895 | 896 | /*** header ***/ 897 | strcpy(upbuffer,imagename); 898 | upper(upbuffer); 899 | printm(0,"Directory of Image: %s, User ", upbuffer); 900 | if (user==-2) printm(0,"ALL\n\n"); 901 | else printm(0,"%-d\n\n",user); 902 | nextline(); nextline(); 903 | 904 | /*** used users ***/ 905 | if (files>0) { 906 | printm(0,"Used Users: "); 907 | for (i=0;i<256;i++) { 908 | if (array[i]!=0) { 909 | printm(0,"%d with %d file%s \t",i,array[i], 910 | plural(array[i])); 911 | } 912 | } 913 | printm(0,"\n\n"); nextline(); nextline(); 914 | } 915 | /*** files ***/ 916 | 917 | /* fetch all needed files */ 918 | /* fill the array with 64+1 large, but nonnegative values */ 919 | for (i=0;i<=dpb->DRM+1;i++) array[i]=0xFFF; 920 | 921 | ent=glob_cpm_file(pat); 922 | files=0; 923 | while (ent>=0) { 924 | array[files] = ent; 925 | total_bytes += directory[ent].size; 926 | ent=glob_cpm_next(); 927 | files++; 928 | 929 | } 930 | 931 | if (mask & DIR_SORT) qsort(array,files,sizeof(int), 932 | (int(*)(const void*,const void*))cmp_array); 933 | 934 | /* contains now the entry numbers of the requested files in 935 | the requested order */ 936 | 937 | 938 | /* output of the filenames */ 939 | 940 | if (files==0) { 941 | printm(0,"No files\n"); 942 | goto footer; 943 | } 944 | 945 | i=0; 946 | switch (mask&0x3) { 947 | case DIR_DOUBLE: 948 | printm(0," U Name Size Attr Ent " 949 | "%c U Name Size Attr Ent\n",vert); 950 | nextline(); 951 | printm(0,"%s%c",repstr(hori,39),cross); 952 | printm(0,"%s\n",repstr(hori,37)); 953 | nextline(); 954 | while (array[i]<0xFFF) { 955 | j=array[i]; ent=1; 956 | while(directory[j].next>-1) { 957 | ent++; 958 | j=directory[j].next; 959 | } 960 | 961 | printm(0," %u %-12s %6lu %3s %c %3d ", 962 | directory[array[i]].user, 963 | directory[array[i]].name, 964 | directory[array[i]].size, 965 | show_attr(directory[array[i]].attr,ATTR_R,FALSE), 966 | ( directory[array[i]].attr&~ATTR_R? '+' : ' '), 967 | ent); 968 | if (i%2==0) printm(0," %c",vert); 969 | else {putcharm(0,10); nextline();} 970 | i++; 971 | }; 972 | break; 973 | case DIR_WIDE: 974 | printm(0,"Name Size %c" 975 | "Name Size %c" 976 | "Name Size %c" 977 | "Name Size\n",vert,vert,vert); 978 | nextline(); 979 | printm(0,"%s%c",repstr(hori,19),cross); 980 | printm(0,"%s%c",repstr(hori,19),cross); 981 | printm(0,"%s%c",repstr(hori,19),cross); 982 | printm(0,"%s\n",repstr(hori,19)); 983 | nextline(); 984 | while (array[i]<0xFFF) { 985 | printm(0,"%-12s%6lu ", 986 | directory[array[i]].name, 987 | directory[array[i]].size); 988 | if (i%4!=3) printm(0,"%c",vert); 989 | else {putcharm(0,10); nextline();} 990 | i++; 991 | }; 992 | break; 993 | case DIR_AMSHEAD: 994 | printm(0," U Name Size Attr Amsdos-Header\n"); 995 | nextline(); 996 | printm(0,"%s\n",repstr(hori,74)); 997 | nextline(); 998 | while (array[i]<0xFFF) { 999 | printm(0,"%2u %-12s %6lu %3s %c ", 1000 | directory[array[i]].user, 1001 | directory[array[i]].name, 1002 | directory[array[i]].size, 1003 | show_attr(directory[array[i]].attr,ATTR_R,FALSE), 1004 | ( directory[array[i]].attr&~ATTR_R? '+' : ' ')); 1005 | 1006 | get_amshead(array[i]); 1007 | if (amsdos_header.flag==0) { 1008 | printm(0,"Empty"); 1009 | } else if (amsdos_header.flag==1) { 1010 | if (strncmp((char*) 1011 | directory[array[i]].ext,"COM",3)==0) 1012 | printm(0,"CP/M Program"); 1013 | else 1014 | printm(0,"---"); 1015 | } else { 1016 | if (amsdos_header.type & 0x1) 1017 | printm(0,"protected "); 1018 | switch ((amsdos_header.type>>1) & 0x7) { 1019 | case 0: printm(0,"BASIC "); break; 1020 | case 1: printm(0,"Binary"); break; 1021 | case 2: printm(0,"Screen"); break; 1022 | case 3: printm(0,"ASCII "); break; 1023 | default:printm(0,"Type=&%2X", 1024 | amsdos_header.type); 1025 | } 1026 | 1027 | printm(0," Load=&%-4X, Jump=&%-4X, Size=&%-4X", 1028 | amsdos_header.load, 1029 | amsdos_header.jump, 1030 | amsdos_header.size); 1031 | } 1032 | 1033 | putcharm(0,10); nextline(); 1034 | 1035 | i++; 1036 | }; 1037 | break; 1038 | case DIR_LONG: 1039 | printm(0,"User Name Size Attr Ext. Attr. " 1040 | "Detect Entries Records Blocks\n"); 1041 | nextline(); 1042 | printm(0,"%s\n",repstr(hori,77)); 1043 | nextline(); 1044 | while (array[i]<0xFFF) { 1045 | strcpy(upbuffer, 1046 | (char*)directory[array[i]].name); 1047 | j=array[i]; ent=1; 1048 | /* detect mode */ 1049 | if (directory[array[i]].blk[0]==0) 1050 | mode = -1; 1051 | else { 1052 | /* the only other function using here is , 1053 | but they do not interfere! */ 1054 | buf = read_block(directory[array[i]].blk[0]); 1055 | mode = detectmode((char*)buf,dpb->BLS); 1056 | } 1057 | /* count entries */ 1058 | while(directory[j].next>-1) { 1059 | ent++; 1060 | j=directory[j].next; 1061 | } 1062 | 1063 | printm(0,"%2u %-12s %6lu %s %-6s%5u%8lu%7lu", 1064 | directory[array[i]].user, 1065 | upbuffer, 1066 | directory[array[i]].size, 1067 | show_all_attr(directory[array[i]].attr,TRUE), 1068 | mode==-1? "Empty" 1069 | : (mode==M_TEXT? "Text" : "Bin"), 1070 | ent, 1071 | (directory[array[i]].size+RECORDSIZE-1)/RECORDSIZE, 1072 | (directory[array[i]].size+(dpb->BLS-1))/dpb->BLS); 1073 | putcharm(0,10); nextline(); 1074 | i++; 1075 | }; 1076 | break; 1077 | } /* switch */ 1078 | printm(0,"\n"); nextline(); 1079 | 1080 | 1081 | /*** footer ***/ 1082 | footer: 1083 | 1084 | printm(0,"\n%d file%s in %lu Bytes\n", 1085 | files, plural(files), 1086 | total_bytes); 1087 | nextline(); nextline(); 1088 | printm(0,"(%lu Bytes free, %lu Bytes allocated, %d entr%s of %d)\n", 1089 | (long)free_blks*dpb->BLS, 1090 | (long)allocated_blks*dpb->BLS, 1091 | used_entries, plural_y(used_entries), 1092 | dpb->DRM+1); 1093 | nextline(); 1094 | 1095 | free(array); 1096 | return 0; 1097 | } 1098 | 1099 | 1100 | long delete(bool silent, char *pat) { 1101 | /* ^^^^^^ 1102 | Delete all files that match . 1103 | Answer the amount of deleted bytes (at least 0) */ 1104 | long freed = 0; 1105 | long total_freed = 0; 1106 | int ent, i; 1107 | 1108 | 1109 | /* warn, if contains *.* or is only usernumber */ 1110 | if (match("*\\*.\\**",pat) || match("*:",pat)) { 1111 | if (!silent && Verb > 0) { 1112 | printm(1,"Delete all in \"%s\"? ",pat); 1113 | if (!confirmed()) return 0; 1114 | } 1115 | } 1116 | 1117 | ent = glob_cpm_file(pat); 1118 | if (ent<0) { 1119 | if (!silent) errorf(FALSE,"\"%s\" not found",pat); 1120 | return 0; 1121 | } 1122 | 1123 | 1124 | while (ent>=0) { 1125 | freed = 0; 1126 | if (directory[ent].attr & ATTR_R) { 1127 | if (!silent && Verb > 0) { 1128 | printm(1,"\"%u:%s\" readonly. Delete? ", 1129 | directory[ent].user,directory[ent].name); 1130 | if (!confirmed()) { 1131 | ent = glob_cpm_next(); 1132 | continue; 1133 | } 1134 | } 1135 | } 1136 | 1137 | if (!silent) printm(3,"Deleting \"%u:%s\": ", 1138 | directory[ent].user,directory[ent].name); 1139 | freed += directory[ent].size; 1140 | while (ent>=0) { 1141 | directory[ent].user = 0xE5; 1142 | for (i=0;i to in all files matching 1164 | . Answer -1 on error. 1165 | */ 1166 | int ent, ent0; 1167 | 1168 | ent = glob_cpm_file(pattern); 1169 | if (ent<0) 1170 | return errorf(FALSE,"\"%s\" not found",pattern); 1171 | while (ent>=0) { 1172 | printm(3,"Changing \"%s\" from \"%s\"", 1173 | directory[ent].name, 1174 | show_all_attr(directory[ent].attr,TRUE)); 1175 | ent0=ent; 1176 | do { 1177 | directory[ent].attr |= set; 1178 | directory[ent].attr &= ~reset; 1179 | ent = directory[ent].next; 1180 | } while (ent>=0); 1181 | printm(3," to \"%s\"\n", 1182 | show_all_attr(directory[ent0].attr,TRUE)); 1183 | ent=glob_cpm_next(); 1184 | } 1185 | 1186 | 1187 | return 0; 1188 | } 1189 | 1190 | 1191 | int sysgen(char *filename) { 1192 | /* ^^^^^^ 1193 | Copies the CP/M system from to the first two tracks of the image. 1194 | Does not test on Data-Format or sufficient space! 1195 | Answer -1 on error 1196 | */ 1197 | char buf[INPUTLEN]; 1198 | int cpm; 1199 | int t; 1200 | short tracksize = dpb->BPS*dpb->SECS; /* = Disk_header.tracksize - 0x100 */ 1201 | long n; 1202 | 1203 | strcpy(buf,filename); 1204 | if (strchr(buf,'.')==NULL) strcat(buf,".cpm"); 1205 | 1206 | cpm = open(buf,O_RDONLY|O_BINARY,0); 1207 | if (cpm < 0) { 1208 | return errorf(TRUE,"Cannot open \"%s\" for reading",buf); 1209 | } 1210 | 1211 | for (t=0;tOFS;t++) { 1212 | /* skip AMSDOS header and position on track */ 1213 | n = lseek(cpm,(long)128+t*tracksize,SEEK_SET); 1214 | if (n == -1L) { 1215 | close(cpm); 1216 | return errorf(TRUE,"CP/M Image currupt! " 1217 | "I cannot position on track %d",t); 1218 | } 1219 | read_track(t%dpb->HDS,t/dpb->HDS); 1220 | n = read(cpm,track+0x100,tracksize); 1221 | if (n != tracksize) { 1222 | close(cpm); 1223 | return errorf(TRUE,"CP/M Image currupt! " 1224 | "I can only read %d bytes (instead of %d bytes) ", 1225 | n, tracksize); 1226 | } 1227 | write_track(); 1228 | } 1229 | printm(2,"CP/M \"%s\" copied to CPC filesystem\n",buf); 1230 | 1231 | close(cpm); 1232 | dpb->SYS = TRUE; 1233 | 1234 | return 0; 1235 | } 1236 | 1237 | 1238 | int format(char* name, DPB_type *dpb) { 1239 | /* ^^^^^^ 1240 | Creates a new image with name and format . must be out of 1241 | the , not processed by ! 1242 | Answers -1 on error. 1243 | */ 1244 | int file; 1245 | int i,h,j; 1246 | struct t_header *trhd; 1247 | time_t now; 1248 | 1249 | file = creat(name,0644); 1250 | if (file<0) { 1251 | return errorf(TRUE,"Cannot open \"%s\" for writing",name); 1252 | } 1253 | 1254 | 1255 | /* fill disk_header */ 1256 | printm(3,"Formatting (%s) ",show_format(dpb->ID)); 1257 | for (j=0;j<0x2F;j++) disk_header.tag[j] = 0; 1258 | strcpy ((char*)disk_header.tag,"MV - CPCEMU / "); 1259 | memset((disk_header.tag)+14,' ',20); 1260 | now = time(NULL); 1261 | strftime(((char*)disk_header.tag)+14,20,"%d %b %y %H:%M", 1262 | localtime(&now)); 1263 | disk_header.nbof_tracks = dpb->TRKS; 1264 | disk_header.nbof_heads = dpb->HDS; 1265 | disk_header.tracksize = 0x100 + dpb->BPS*dpb->SECS; 1266 | memset(disk_header.unused,0,0xCC); 1267 | if (write(file,&disk_header,0x100) < 0) { 1268 | return errorf(TRUE,"FORMAT"); 1269 | } 1270 | 1271 | track = Malloc(disk_header.tracksize); 1272 | trhd = (struct t_header*)track; 1273 | for (i=0;itag,"Track-Info\r\n",0x10); 1283 | trhd->track = i; 1284 | trhd->head = h; 1285 | trhd->unused[0] = 0; 1286 | trhd->unused[1] = 0; 1287 | trhd->BPS = dpb->BPS/0x100; 1288 | trhd->SPT = dpb->SECS; 1289 | trhd->GAP3 = 0x4E; 1290 | trhd->filler = 0xE5; 1291 | for (j=0;jSECS;j++) { 1292 | trhd->sector[j].track = i; 1293 | trhd->sector[j].head = h; 1294 | trhd->sector[j].sector = dpb->SEC1+j; 1295 | trhd->sector[j].BPS = dpb->BPS/0x100; 1296 | trhd->sector[j].status1 = 0; 1297 | trhd->sector[j].status2 = 0; 1298 | trhd->sector[j].unused[0]=0; 1299 | trhd->sector[j].unused[1]=0; 1300 | } 1301 | for (j=dpb->SECS; j<29; j++) { 1302 | memset(&(trhd->sector[j]),0,8); 1303 | } 1304 | 1305 | memset(track+0x100,trhd->filler,disk_header.tracksize-0x100); 1306 | if (write(file,track,disk_header.tracksize) < 0) { 1307 | return errorf(TRUE,"FORMAT"); 1308 | } 1309 | } /* end for i,h */ 1310 | printm(3," done\n"); 1311 | free(track); 1312 | close(file); 1313 | *disk_header.tag = 0; 1314 | cur_trk = -1; 1315 | 1316 | return 0; 1317 | } 1318 | 1319 | 1320 | int ren_file(char *from, char *to) { 1321 | /* ^^^^^^^^ 1322 | Renames a file. 1323 | Wildcards are allowed and complete filenames must be given. 1324 | See ren_wild(). 1325 | If a file is renamed to itself, do nothing. 1326 | The directory must be updated and written afterwards! 1327 | Answer -1 on error. 1328 | */ 1329 | int to_user; 1330 | char to_root[INPUTLEN]; 1331 | char to_ext[INPUTLEN]; 1332 | char to_full[INPUTLEN]; 1333 | int from_user; 1334 | char from_root[INPUTLEN]; 1335 | char from_ext[INPUTLEN]; 1336 | char from_full[INPUTLEN]; 1337 | int ent; 1338 | const char wild_fmt[] = "\"%s\" may not contain wildcards"; 1339 | 1340 | upper(to); 1341 | upper(from); 1342 | if (has_wildcards('c',from)) { 1343 | return errorf(FALSE,wild_fmt,from); 1344 | } 1345 | if (has_wildcards('c',to)) { 1346 | return errorf(FALSE,wild_fmt,to); 1347 | } 1348 | 1349 | parse_cpm_filename(from,&from_user,from_root,from_ext); 1350 | if (from_user==-1) from_user = cur_user; 1351 | if (from_user==-2) return errorf(FALSE,"--==>>> ren_file: wild user"); 1352 | if (*from_root==0) 1353 | return errorf(FALSE,"No name in \"%s\"",from); 1354 | build_cpm_name((char*)from_full, from_user, 1355 | (char*)from_root, (char*)from_ext); 1356 | 1357 | parse_cpm_filename(to,&to_user,to_root,to_ext); 1358 | if (to_user==-1) to_user = cur_user; 1359 | if (*to_root==0) { 1360 | strcpy(to_root,from_root); 1361 | strcpy(to_ext,from_ext); 1362 | } 1363 | build_cpm_name((char*)to_full, to_user, 1364 | (char*)to_root, (char*)to_ext); 1365 | 1366 | /* test on identity of and */ 1367 | if (strcmp(to_full,from_full)==0) { 1368 | printm(2,"Renaming \"%s\" to itself\n",from_full); 1369 | return 0; 1370 | } 1371 | 1372 | /* check if already exists */ 1373 | if (glob_cpm_file(to_full)>=0) { 1374 | if (Verb > 0) { 1375 | printm(1,"\"%s\" already exists! Overwrite? ",to_full); 1376 | if (confirmed()) delete(TRUE,to_full); 1377 | else return 0; 1378 | } else return errorf(FALSE,"\"%s\" already exists",to_full); 1379 | } 1380 | 1381 | ent = glob_cpm_file(from_full); 1382 | if (ent<0) return errorf(FALSE,"\"%s\" not found",from_full); 1383 | 1384 | printm(2,"Renaming \"%u:%s\" to \"%s\"\n", 1385 | directory[ent].user, directory[ent].name, to_full); 1386 | 1387 | do { 1388 | directory[ent].user = to_user; 1389 | str2mem((char*)directory[ent].root, 1390 | (char*)to_root, 8); 1391 | str2mem((char*)directory[ent].ext, 1392 | (char*)to_ext, 3); 1393 | ent = directory[ent].next; 1394 | } while (ent>=0); 1395 | 1396 | return 0; 1397 | } 1398 | 1399 | 1400 | int ren_wild(char *pat, int us) { 1401 | /* ^^^^^^^^ 1402 | Renames all files that match to . 1403 | The directory must be updated and written afterwards! 1404 | Answer -1 on error. */ 1405 | int ent; 1406 | char src[20], trg[20]; 1407 | 1408 | ent=glob_cpm_file(pat); 1409 | if (ent<0) { 1410 | errorf(FALSE,"\"%s\" not found",pat); 1411 | return -1; 1412 | } 1413 | while (ent>=0) { 1414 | sprintf(src,"%u:%s",directory[ent].user,directory[ent].name); 1415 | sprintf(trg,"%u:%s",us,directory[ent].name); 1416 | glob_env++; 1417 | ren_file(src,trg); 1418 | glob_env--; 1419 | ent=glob_cpm_next(); 1420 | } 1421 | return 0; 1422 | } 1423 | 1424 | 1425 | int copy_file(char *from, char *to) { 1426 | /* ^^^^^^^^^ 1427 | Copies a file to a new file with another name. 1428 | This preliminary version goes the way through put() and get() with a 1429 | temporary file. 1430 | Answer -1 on error. */ 1431 | 1432 | char tempname[INPUTLEN]; 1433 | int err; 1434 | 1435 | tmp_nam(tempname); 1436 | 1437 | printm(3,"Copying \"%s\" to ",from); 1438 | err=get(from,tempname); 1439 | if (err==-1) { 1440 | unlink(tempname); 1441 | return -1; 1442 | } 1443 | err=put(tempname,to); 1444 | if (err < 0) { 1445 | unlink(tempname); 1446 | return -1; 1447 | } 1448 | printm(3,"\"%s\"\n",to); 1449 | 1450 | unlink(tempname); 1451 | return 0; 1452 | } 1453 | 1454 | 1455 | int copy_wild(char *pat, int us) { 1456 | /* ^^^^^^^^^ 1457 | Copies all files that match to . 1458 | Answer -1 on error. */ 1459 | int ent; 1460 | char src[20], trg[20]; 1461 | 1462 | ent=glob_cpm_file(pat); 1463 | if (ent<0) { 1464 | errorf(FALSE,"\"%s\" not found",pat); 1465 | return -1; 1466 | } 1467 | while (ent>=0) { 1468 | sprintf(src,"%u:%s",directory[ent].user,directory[ent].name); 1469 | sprintf(trg,"%u:%s",us,directory[ent].name); 1470 | glob_env++; 1471 | copy_file(src,trg); 1472 | glob_env--; 1473 | ent=glob_cpm_next(); 1474 | } 1475 | return 0; 1476 | } 1477 | 1478 | 1479 | 1480 | /********* 1481 | Dumping 1482 | *********/ 1483 | 1484 | 1485 | int dumpdir (FILE* file) { 1486 | /* ^^^^^^^ */ 1487 | int i,j; 1488 | char n[INPUTLEN],e[INPUTLEN]; 1489 | 1490 | fprintf(file," # U NAME EX RE ATR BLOCKS\t\t\t\t\t NEX\n"); 1491 | for (i=0;i<=dpb->DRM;i++) { 1492 | strncpy(n,(char*)directory[i].root,8); n[8] = 0; 1493 | strncpy(e,(char*)directory[i].ext,3); e[3] = 0; 1494 | fprintf(file,"%2X%c%2X %s.%s %2X %2X ", 1495 | i, (directory[i].first?'>':' '), 1496 | directory[i].user, 1497 | n, e, 1498 | directory[i].extent, directory[i].rec); 1499 | fprintf(file,"%2X%1X",(directory[i].attr&~0x7)>>3, 1500 | directory[i].attr&0x7); 1501 | for (j=0;j=0) 1509 | fprintf(file,">%2X",directory[i].next); 1510 | else 1511 | fprintf(file,"<<<"); 1512 | putc(10,file); 1513 | if (fflush(file)!=0) 1514 | return errorf(TRUE,"DUMP -D"); 1515 | } 1516 | return 0; 1517 | } 1518 | 1519 | 1520 | int dump(FILE *file, int block, int h, int t, int s) { 1521 | /* ^^^^ 1522 | Dump the contents of block to or if = -1, the contents 1523 | of ,, to . 1524 | */ 1525 | 1526 | int hd, trk, sec; 1527 | int i, j; 1528 | int secs, k; 1529 | uchar *p, *q; 1530 | 1531 | if (block == -1) { 1532 | hd = h; trk = t; sec = s; 1533 | secs=1; 1534 | block = blk_calc(hd,trk,sec); 1535 | } else { 1536 | hd = hd_calc(block); 1537 | trk = trk_calc(block); 1538 | sec = sec_calc(block); 1539 | secs=dpb->BLS/dpb->BPS; 1540 | } 1541 | 1542 | 1543 | for (k=0;kBPS; 1551 | while (iBPS) { 1552 | fprintf(file,"%3X %c ",i,vert); 1553 | q=p; 1554 | for (j=0;j<16;j++) fprintf(file,"%2X ",*p++); 1555 | fprintf(file," %c ",vert); 1556 | p=q; 1557 | for (j=0;j<16;j++) { 1558 | if (*p<32) 1559 | putc(' ',file); 1560 | else if (*p>=127) 1561 | putc('~',file); 1562 | else 1563 | putc(*p,file); 1564 | p++; 1565 | } 1566 | i+=16; 1567 | putc(10,file); 1568 | if (fflush(file)!=0) 1569 | return errorf(TRUE,"DUMP"); 1570 | } 1571 | 1572 | next_sector(&hd,&trk,&sec); 1573 | }; 1574 | return 0; 1575 | } 1576 | 1577 | 1578 | int map(FILE *file) { 1579 | /* Writes the disk allocation map on . */ 1580 | int h, t, s, b; 1581 | char *str; 1582 | 1583 | str = repstr(' ',max(0,(dpb->SECS*3+2)-9)); 1584 | /* 9 = len("%c Head %-2d") */ 1585 | 1586 | fprintf(file," "); 1587 | for (h=0;hHDS;h++) { 1588 | fprintf(file,"%c Head %-2d%s",vert,h,str); 1589 | } 1590 | fprintf(file,"\n"); 1591 | 1592 | fprintf(file,"Track "); 1593 | for (h=0;hHDS;h++) { 1594 | fprintf(file,"%c ",vert); 1595 | for (s=0;sSECS;s++) { 1596 | fprintf(file,"%-2d ",s); 1597 | } 1598 | } 1599 | fprintf(file,"\n"); 1600 | 1601 | str=repstr(hori,6); fprintf(file,"%s",str); 1602 | for (h=0;hHDS;h++) { 1603 | str = repstr(hori,dpb->SECS*3+1); 1604 | fprintf(file,"%c%s",cross,str); 1605 | } 1606 | fprintf(file,"\n"); 1607 | 1608 | for (t=0;tTRKS;t++) { 1609 | fprintf(file,"%-4d ",t); 1610 | for (h=0;hHDS;h++) { 1611 | fprintf(file,"%c ",vert); 1612 | for (s=0;sSECS;s++) { 1613 | 1614 | b = blk_calc(h,t,s); 1615 | if (h+dpb->HDS*t < dpb->OFS) { 1616 | if (dpb->SYS) { 1617 | fprintf(file,"$$ "); 1618 | } else { 1619 | fprintf(file,"-- "); 1620 | } 1621 | continue; 1622 | } 1623 | if (b < dpb->DBL) { 1624 | fprintf(file,"DD "); 1625 | continue; 1626 | } 1627 | if (is_free_block(b)) { 1628 | fprintf(file,"-- "); 1629 | } else { 1630 | fprintf(file,"%2X ",blk_alloc[b]); 1631 | } 1632 | } 1633 | } 1634 | fprintf(file,"\n"); 1635 | if (fflush(file)!=0) 1636 | return errorf(TRUE,"DUMP -M"); 1637 | } 1638 | return 0; 1639 | } 1640 | 1641 | 1642 | /********** 1643 | Transfer 1644 | **********/ 1645 | 1646 | 1647 | int detectmode (char *buf, int size) { 1648 | /* ^^^^^^^^^^ 1649 | Count characters in . If more than 70% are printable, the 1650 | file is considered as Text, otherwise Binary. 1651 | Returns M_TEXT or M_BIN. 1652 | */ 1653 | long printable, total; 1654 | int k; 1655 | 1656 | printable = total = 0; 1657 | for (k=0;k<=size; k++) { 1658 | if (buf[k] == CPM_EOF) break; 1659 | if ((buf[k]==10)||(buf[k]==13)|| 1660 | ((buf[k]>=32)&&(buf[k]<=126))) { 1661 | printable++; 1662 | } 1663 | total++; 1664 | } 1665 | if (total==0) return M_BIN; /* i.e. first char = ^Z */ 1666 | if ((100*printable/total) > 70) 1667 | return M_TEXT; 1668 | else 1669 | return M_BIN; 1670 | } 1671 | 1672 | 1673 | long get(char *src, char *target) { 1674 | /* ^^^ 1675 | Get a file from CPC filesystem to local filesystem. 1676 | is the CPC filename 1677 | is the local filename 1678 | Returns number of bytes read or -1 if not found 1679 | */ 1680 | int ent, i, k; 1681 | int file; 1682 | long bytes = 0; /* sum of bytes copied */ 1683 | int size; /* size to copy at a chunk */ 1684 | bool last; /* in last entry? */ 1685 | int localmode; 1686 | int err; 1687 | uchar *buf, *p; 1688 | 1689 | /* open CP/M file */ 1690 | if (has_wildcards('c',src)) { 1691 | return errorf(FALSE,"\"%s\" may not contain wildcards",src); 1692 | } 1693 | ent = glob_cpm_file(src); 1694 | if (ent<0) return errorf(FALSE,"\"%s\" not found",src); 1695 | 1696 | 1697 | /* open DOS file */ 1698 | if (access(target,F_OK)==0) { 1699 | if (Verb > 0) { 1700 | printm(1,"\"%s\" already exists! " 1701 | "Overwrite? ",target); 1702 | if (!confirmed()) { 1703 | return -1; 1704 | } 1705 | } 1706 | } 1707 | file=creat(target,0644); 1708 | if (file<0) return errorf(TRUE,"Cannot open \"%s\" for writing",target); 1709 | 1710 | 1711 | localmode = -1; /* i.e. unknown */ 1712 | do { 1713 | last = directory[ent].next == -1; /* last entry? */ 1714 | for (i=0;iBPS); 1734 | /* printm(3,"%s detected.\n", 1735 | show_mode(localmode)); 1736 | */ 1737 | } 1738 | } 1739 | 1740 | /* copy a whole block, except if on last blockpointer in last entry, then 1741 | copy records */ 1742 | if (last && 1743 | (i==BLKNR-1 || 1744 | directory[ent].blk[i+1]==0)) { 1745 | size = directory[ent].rec*RECORDSIZE % dpb->BLS; 1746 | if (size==0) size=dpb->BLS; 1747 | /* works, because .rec==0 is impossible */ 1748 | } else { 1749 | size = dpb->BLS; 1750 | } 1751 | 1752 | if (localmode==M_BIN) { 1753 | err=write(file,buf,size); 1754 | bytes += size; 1755 | } else { /* up to ^Z */ 1756 | p = memchr(buf,CPM_EOF,size); 1757 | if (p==NULL) {/* no ^Z */ 1758 | err=write(file,buf,size); 1759 | bytes += size; 1760 | } else {/* get only a fraction of block */ 1761 | k = p-buf; 1762 | err=write(file,buf,k); 1763 | bytes += k; 1764 | } 1765 | } 1766 | if (err<0) { 1767 | close(file); 1768 | return errorf(TRUE,"GET"); 1769 | } 1770 | } 1771 | ent = directory[ent].next; 1772 | } while (ent>=0); 1773 | 1774 | close(file); 1775 | return bytes; 1776 | } 1777 | 1778 | 1779 | 1780 | long put(char *src, char *trg) { 1781 | /* ^^^ 1782 | Writes the DOS file as CP/M File . 1783 | Returns number of bytes written or -1 if skipped, -2 if error */ 1784 | 1785 | uchar *buf; 1786 | int file; 1787 | int entry, /* current dir entry */ 1788 | blk, /* pointer in data space */ 1789 | i; 1790 | long size; 1791 | long total; /* total bytes */ 1792 | long entry_total; /* total bytes for one entry */ 1793 | int used_entries; 1794 | long bytes_to_copy; /* size of DOS files */ 1795 | 1796 | int usr; 1797 | char rootname[INPUTLEN]; 1798 | char extension[INPUTLEN]; 1799 | const char wild_fmt[] = "\"%s\" may not contain wildcards"; 1800 | struct stat stat_buf; 1801 | 1802 | buf = block_buffer; /* simply a shortcut */ 1803 | 1804 | if (has_wildcards('d',src)) { 1805 | errorf(FALSE,wild_fmt,src); 1806 | return -2; 1807 | } 1808 | if (has_wildcards('c',trg)) { 1809 | errorf(FALSE,wild_fmt,trg); 1810 | return -2; 1811 | } 1812 | upper(trg); 1813 | 1814 | 1815 | /* test on existence and size of DOS file */ 1816 | if ((file = open(src, O_RDONLY|O_BINARY, 0)) == -1) { 1817 | errorf(TRUE,"Cannot read \"%s\"",src); 1818 | return -2; 1819 | } 1820 | if (fstat(file,&stat_buf)) { 1821 | errorf(TRUE,"--==>>> put: cannot stat \"%s\"",src); 1822 | return -2; 1823 | } 1824 | bytes_to_copy = stat_buf.st_size; 1825 | if (bytes_to_copy > (long)(dpb->DSM+1)*dpb->BLS) { 1826 | errorf(FALSE,"\"%s\" is bigger than image",src); 1827 | return -1; 1828 | } 1829 | 1830 | /* spilt the into name and extension */ 1831 | if (parse_cpm_filename(trg,&usr,rootname,extension)) return -1; 1832 | if (usr==-1) usr = cur_user; 1833 | if (*rootname==0) { 1834 | errorf(FALSE,"No filename in \"%s\"",trg); 1835 | return -2; 1836 | } 1837 | 1838 | 1839 | /* test on existence in CP/M directory */ 1840 | if (glob_cpm_file(trg) >= 0) { 1841 | if (Verb > 0) { 1842 | printm(1,"\"%s\" already exists! Overwrite? ",trg); 1843 | if (!confirmed()) { 1844 | close(file); 1845 | return -1; 1846 | } 1847 | } 1848 | delete (TRUE,trg); 1849 | } 1850 | 1851 | /* walk thru the directory */ 1852 | total = 0; 1853 | used_entries = 0; 1854 | for (entry=0;entry<=dpb->DRM;entry++) { 1855 | if (directory[entry].user != 0xE5) continue; 1856 | 1857 | /* preread the first part, necessary if filesize multipe of 16k */ 1858 | size = read(file,buf,dpb->BLS); 1859 | if (size==0) break; 1860 | 1861 | /* fill name, user ... */ 1862 | directory[entry].user = usr; 1863 | str2mem((char*)directory[entry].root, 1864 | (char*)rootname, 8); 1865 | str2mem((char*)directory[entry].ext, 1866 | (char*)extension, 3); 1867 | directory[entry].attr = 0x00; 1868 | directory[entry].unused[0] = 0; /* reserved for CP/M */ 1869 | directory[entry].unused[1] = 0; 1870 | 1871 | /* walk thru the block pointer area */ 1872 | entry_total = 0; 1873 | for (i=0;i0) size = read(file,buf,dpb->BLS); 1875 | total += size; 1876 | entry_total += size; 1877 | 1878 | if (size==0) break; 1879 | /* get a free block and copy the data */ 1880 | blk = get_free_block(); 1881 | if (blk==-1) { 1882 | errorf(FALSE,"CPC Disk full!"); 1883 | update_directory(); 1884 | close(file); 1885 | delete(TRUE,trg); 1886 | return -2; 1887 | } 1888 | directory[entry].blk[i] = blk; 1889 | /* add a ^Z at the end */ 1890 | if (sizeBLS) { 1891 | buf[size] = CPM_EOF; 1892 | size++; 1893 | } 1894 | /* write the block */ 1895 | alloc_block(blk,entry); 1896 | if (write_block(blk,(char*)buf)==NULL) { 1897 | errorf(FALSE,"Write error!"); 1898 | close(file); 1899 | return -2; 1900 | } 1901 | bytes_to_copy -= dpb->BLS; 1902 | } 1903 | 1904 | /* finish up this direntry */ 1905 | while (i = *16k + *128byte */ 1908 | directory[entry].extent = entry_total/EXTENTSIZE 1909 | + (dpb->EXM+1) * used_entries; 1910 | directory[entry].rec = (entry_total+RECORDSIZE-1) 1911 | / RECORDSIZE % RECORDSIZE; 1912 | /* if =0 and > 0, then set :=80h and decrement */ 1913 | if (directory[entry].rec==0 && directory[entry].extent>0) { 1914 | directory[entry].rec = RECORDSIZE; 1915 | directory[entry].extent--; 1916 | } 1917 | 1918 | used_entries++; 1919 | if (size==0) break; 1920 | } 1921 | 1922 | close(file); 1923 | update_directory(); 1924 | 1925 | /* uncopied data left */ 1926 | if (bytes_to_copy > 0) { 1927 | errorf(FALSE,"CPC Directory full!"); 1928 | delete(TRUE,trg); 1929 | return -2; 1930 | } 1931 | 1932 | /* needs too, but it's available now after 1933 | is nearly complete */ 1934 | put_directory(); 1935 | calc_allocation(); 1936 | return total; 1937 | } 1938 | --------------------------------------------------------------------------------