diff --git a/Makefile b/Makefile index 96c529b..f3e8c7f 100644 --- a/Makefile +++ b/Makefile @@ -8,10 +8,28 @@ ifndef CC65_TARGET CC65_TARGET:=apple2enh endif +ifeq ($(OS),Windows_NT) + ifndef MERLIN_DIR + MERLIN_DIR=C:/opt/Merlin32_v1.0 + endif + MERLIN_LIB=$(MERLIN_DIR)/Library + MERLIN=$(MERLIN_DIR)/Windows/Merlin32 + COPY=copy + APPLEWIN="c:\opt\AppleWin1.26.2.3\applewin.exe" +else + ifndef MERLIN_DIR + MERLIN_DIR=$(HOME)/opt/Merlin32_v1.0 + endif + MERLIN_LIB=$(MERLIN_DIR)/Library + MERLIN=$(MERLIN_DIR)/Linux64/Merlin32 + COPY=cp + APPLEWIN=applewin +endif + CC=cl65 AS=ca65 -OBJS=copy.o cui.o fileinfo.o prodos.o -HDRS=fileinfo.h cui.h prodos.h +OBJS=copy.o cui.o prodos.o prodosext.o libgen.o +HDRS=cui.h prodos.h prodosext.h libgen.h CFLAGS=-O -t $(CC65_TARGET) -DTRACE # The -S $6000 makes the start address $6000 so that both hi-res # pages are available. @@ -31,14 +49,22 @@ BIN_LOAD_ADDR=0x0803 all: $(DISK) -$(DISK): $(PGM) +$(DISK): $(PGM) libgentest $(RM) $(DISK) $(AC) -pro140 $(DISK) $(DISK_VOL) $(AC) -as $(DISK) $(PGM) BIN < $(PGM) + $(AC) -as $(DISK) libgentest BIN < libgentest $(PGM): $(OBJS) $(CC) $(LDFLAGS) -o $@ $^ +libgentest: libgen.o libgentest.o + $(CC) $(LDFLAGS) -o $@ $^ + +test: $(DISK) + $(APPLEWIN) -d1 $(DISK) + #$(APPLEWIN) -s7 empty -d1 $(DISK) + clean: $(RM) *.o $(PGM) $(DISK) diff --git a/copy.c b/copy.c index 2462025..4214e04 100644 --- a/copy.c +++ b/copy.c @@ -1,54 +1,59 @@ #include /* fopen, fread, fwrite, fclose */ #include #include /* atexit */ +#include +#include "libgen.h" /* basename */ -#include "fileinfo.h" #include "prodos.h" +#include "prodosext.h" #include "cui.h" -#define BUFFER_SIZE 2048 +#define BF_SIZ 2048 void cleanup(void); FILE *openFile(const char *name, const char *mode); void closeFile(FILE *f, const char *name); void concatPath(char *dest, const char *src); -char sourceName[PRODOS_PATH_MAX + 1]; +char srcName[PRODOS_PATH_MAX + 1]; char destName[PRODOS_PATH_MAX + 1]; -FILE *sourceFile = NULL; -FILE *destFile = NULL; -struct FileInfo sourceInfo; -struct FileInfo destInfo; +FILE *src = NULL; +FILE *dest = NULL; +struct GetFileInfoParams srcInfo; +struct GetFileInfoParams destInfo; size_t n; -char buffer[BUFFER_SIZE]; +char buf[BF_SIZ]; size_t bytesRead; void main(void) { atexit(cleanup); - inputFileName("Source file or directory:", sourceName, sizeof(sourceName), &sourceInfo); - inputFileName("Destination file or directory:", destName, sizeof(destName), &destInfo); + if (! inputFileName("Source file or directory:", srcName, + sizeof(srcName), &srcInfo)) + return; - sourceFile = openFile(sourceName, "r"); + if (! inputFileName("Destination file or directory:", destName, + sizeof(destName), &destInfo)) + return; - if (isDir(&destInfo)) { - concatPath(destName, basename(sourceName)); - } + src = openFile(srcName, "r"); - destFile = openFile(destName, "w"); + if (isDirectory(&destInfo)) + concatPath(destName, basename(srcName)); - while ((bytesRead = fread(buffer, 1, sizeof(buffer), sourceFile)) == BUFFER_SIZE) { - if (fwrite(buffer, 1, bytesRead, destFile) < bytesRead) { + dest = openFile(destName, "w"); + + while ((bytesRead = fread(buf, 1, sizeof(buf), src)) == BF_SIZ) + if (fwrite(buf, 1, bytesRead, dest) < bytesRead) { perror(destName); break; } - } - if (bytesRead > 0 && !feof(destFile) && !ferror(destFile)) { - if (fwrite(buffer, 1, bytesRead, destFile) < bytesRead) { + + if (bytesRead > 0 && !feof(dest) && !ferror(dest)) + if (fwrite(buf, 1, bytesRead, dest) < bytesRead) perror(destName); - } - } + cleanup(); } @@ -83,7 +88,7 @@ void concatPath(char *dest, const char *src) void cleanup(void) { - closeFile(destFile, destName); - closeFile(sourceFile, sourceName); + closeFile(dest, destName); + closeFile(src, srcName); } diff --git a/cui.c b/cui.c index 516cf44..be36dd1 100644 --- a/cui.c +++ b/cui.c @@ -3,27 +3,44 @@ #include /* strlen */ #include "cui.h" -#include "fileinfo.h" +#include "prodos.h" +#include "prodosext.h" + +#define ESC '\x1b' static char *readLine(char *line, size_t capacity); static void chomp(char *line); static bool complete; +static uint8_t result; -void inputFileName(const char *prompt, char *name, size_t capacity, struct FileInfo *f) +bool inputFileName(const char *prompt, char *name, + size_t capacity, + struct GetFileInfoParams *params) { complete = false; while (! complete) { printf(prompt); readLine(name, capacity); - initFileInfo(f, name); - if (exists(f)) { + if (strlen(name) == 0) { + puts("Aborting"); + break; + } + else if (name[0] == ESC) { + puts("Escaping"); + break; + } + params->param_count = GET_FILE_INFO_PARAM_COUNT; + params->pathname = name; + printf("params address = %x\n", &(params->param_count)); + printf("params address = %x\n", params); + result = get_file_info(params); + if (result == PRODOS_E_NONE) complete = true; - } - else { - fprintf(stderr, "File '%s' does not exist\n", name); - } + else + fprintf(stderr, "%s: %s (code %d)\n", name, getMessage(result), result); } + return complete; } char *readLine(char *line, size_t capacity) @@ -32,9 +49,8 @@ char *readLine(char *line, size_t capacity) if ((result = fgets(line, capacity, stdin)) != NULL) chomp(line); - else - if (ferror(stdin)) - perror("stdin"); + else if (ferror(stdin)) + perror("stdin"); return result; } diff --git a/cui.h b/cui.h index bce173b..1c797ac 100644 --- a/cui.h +++ b/cui.h @@ -1,7 +1,8 @@ #ifndef CUI_H #define CUI_H -extern void inputFileName(const char *prompt, char *name, size_t capacity, struct FileInfo *f); +extern bool inputFileName(const char *prompt, char *name, size_t capacity, + struct GetFileInfoParams *params); #endif diff --git a/fileinfo.c b/fileinfo.c deleted file mode 100644 index da2a319..0000000 --- a/fileinfo.c +++ /dev/null @@ -1,27 +0,0 @@ -#include /* PRODOS_T_DIR */ -#include "fileinfo.h" -#include "prodos.h" - -static struct GetFileInfoParams *infoParams; - -void initFileInfo(struct FileInfo *f, const char *name) -{ - infoParams = &(f->infoParams); - infoParams->param_count = GET_FILE_INFO_PARAM_COUNT; - infoParams->pathname = name; - f->getFileInfoResult = get_file_info(infoParams); -} - -bool isDir(struct FileInfo *f) -{ - return f->infoParams.file_type == PRODOS_T_DIR; -} - -bool exists(struct FileInfo *f) -{ - if (f->getFileInfoResult == 0) - return true; - else - return false; -} - diff --git a/fileinfo.h b/fileinfo.h deleted file mode 100644 index 0db63d7..0000000 --- a/fileinfo.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef MY_FILE_H -#define MY_FILE_H - -#include -#include "prodos.h" - -struct FileInfo -{ - struct GetFileInfoParams infoParams; - uint8_t getFileInfoResult; -}; - -extern void initFileInfo(struct FileInfo *f, const char *name); -extern bool isDir(struct FileInfo *f); -extern bool exists(struct FileInfo *f); - -#endif - diff --git a/libgen.c b/libgen.c new file mode 100644 index 0000000..012b7ea --- /dev/null +++ b/libgen.c @@ -0,0 +1,23 @@ +#include + +char *basename(char *path) +{ + char *ptr; + + /* If path is NULL or the empty string, return "." */ + if (path == NULL || path[0] == '\0') + return "."; + + /* If path is "/", return "/" */ + if (path[0] == '/' && path[1] == '\0') + return path; + + /* If path does not contain a '/' then return path */ + if ((ptr = strrchr(path, '/')) == NULL) + return path; + + /* Path contains a slash so return the string following the last + slash. */ + return ++ptr; +} + diff --git a/libgen.h b/libgen.h new file mode 100644 index 0000000..81e49a9 --- /dev/null +++ b/libgen.h @@ -0,0 +1,9 @@ +#ifndef GUNGWALDS_LIBGEN_H +#define GUNGWALDS_LIBGEN_H + +/* Conforms to POSIX but does not modify path. A pointer into path + or a constant string value is returned. So don't attempt to + modify the return value. */ +char *basename(char *path); + +#endif diff --git a/libgentest b/libgentest new file mode 100644 index 0000000..ba4edd6 Binary files /dev/null and b/libgentest differ diff --git a/libgentest.c b/libgentest.c new file mode 100644 index 0000000..fbdb9f0 --- /dev/null +++ b/libgentest.c @@ -0,0 +1,20 @@ +#include +#include +#include "libgen.h" + +void assertStringEquals(char *s, char *t, char *name) +{ + if (strcmp(s, t) == 0) + printf("Success - %s\n", name); + else + printf("FAILURE - %s\n", name); +} + +void main(void) +{ + assertStringEquals(basename(NULL), ".", "NULL"); + assertStringEquals(basename(""), ".", "Empty"); + assertStringEquals(basename("/"), "/", "Slash"); + assertStringEquals(basename("/blah/"),"","Trailing slash"); + assertStringEquals(basename("/blah"), "blah", "Blah"); +} diff --git a/prodos.h b/prodos.h index 5f84bb9..93d8e30 100644 --- a/prodos.h +++ b/prodos.h @@ -4,20 +4,41 @@ #include /* PATH_MAX POSIX constant - ProDOS has a 64 char prefix that can be combined with partial - pathname of 64 characters. This is a total of 128 characters. */ + ProDOS has a 64 char prefix that can be combined + with partial pathname of 64 characters. This is + a total of 128 characters. */ #define PRODOS_PATH_MAX 128 - #define PRODOS_FILE_NAME_MAX 15 +#define PRODOS_E_NONE 0x00 +#define PRODOS_E_BAD_SYS_CALL_NUM 0x01 +#define PRODOS_E_BAD_SYS_PARAM_COUNT 0x04 +#define PRODOS_E_INTERRUPT_VECTOR_TABLE_FULL 0x25 #define PRODOS_E_IO_ERROR 0x27 +#define PRODOS_E_NO_DEVICE 0x28 +#define PRODOS_E_DISK_WRITE_PROTECTED 0x2b +#define PRODOS_E_DISK_SWITCHED 0x2e #define PRODOS_E_INVALID_PATH_SYNTAX 0x40 +#define PRODOS_E_FILE_CTRL_BLK_TABLE_FULL 0x42 +#define PRODOS_E_INVALID_REF_NUM 0x43 #define PRODOS_E_PATH_NOT_FOUND 0x44 #define PRODOS_E_VOLUME_DIR_NOT_FOUND 0x45 #define PRODOS_E_FILE_NOT_FOUND 0x46 -#define PRODOS_E_INCOMPAT_FILE_FORMAT 0x4A -#define PRODOS_E_UNSUP_STORAGE_TYPE 0x4B -#define PRODOS_E_INVALID_VALUE_IN_PARAM 0x53 +#define PRODOS_E_DUPLICATE_FILE_NAME 0x47 +#define PRODOS_E_OVERRUN_ERROR 0x48 +#define PRODOS_E_VOLUME_DIR_FULL 0x49 +#define PRODOS_E_INCOMPATIBLE_FILE_FORMAT 0x4A +#define PRODOS_E_UNSUPPORTED_STORAGE_TYPE 0x4B +#define PRODOS_E_END_OF_FILE 0x4C +#define PRODOS_E_POSITION_OUT_OF_RANGE 0x4D +#define PRODOS_E_ACCESS_ERROR 0x4E +#define PRODOS_E_FILE_IS_OPEN 0x50 +#define PRODOS_E_DIR_COUNT_ERROR 0x51 +#define PRODOS_E_NOT_PRODOS_DISK 0x52 +#define PRODOS_E_INVALID_PARAM 0x53 +#define PRODOS_E_VOLUME_CTRL_BLK_TABLE_FULL 0x55 +#define PRODOS_E_BAD_BUFFER_ADDR 0x56 +#define PRODOS_E_DUPLICATE_VOLUME 0x57 #define PRODOS_E_BITMAP_ADDR_IMPOSSIBLE 0x5A #define GET_FILE_INFO_PARAM_COUNT 0x0a diff --git a/prodos.s b/prodos.s index 55d156f..8a62d17 100644 --- a/prodos.s +++ b/prodos.s @@ -1,33 +1,46 @@ .export _get_file_info - MLI = $bf00 GET_FILE_INFO = $c4 +COUT = $FDED ;SUB TO OUTPUT A CHARACTER +PRBYTE = $FDDA ;SUB TO PRINT A BYTE +CROUT = $FD8E ;SUB TO OUTPUT CARRIAGE RETURN - - .bss - -_file_info_addr: .word $00 - - - - .code +.code ; This function is defined as "fastcall" which puts the right-most ; parameter into A/X and the return value in A/X. -_get_file_info: - sta _file_info_addr - stx _file_info_addr+1 +.proc _get_file_info + + sta params + stx params+1 + + ; Switch in ROM + bit $c082 + + lda params + jsr PRBYTE + jsr CROUT + + lda params+1 + jsr PRBYTE + jsr CROUT jsr MLI .byte GET_FILE_INFO - .word _file_info_addr +params: .word 0 ; The system call leaves the error status in the Accumulator. ; The "fastcall" calling convention returns the A register, ; which is what we want. rts +.endproc + + +.bss + +file_info_addr: .res 2 diff --git a/prodosext.c b/prodosext.c new file mode 100644 index 0000000..00e2a0f --- /dev/null +++ b/prodosext.c @@ -0,0 +1,78 @@ +#include +#include +#include "prodos.h" +#include "prodosext.h" + +bool isDirectory(struct GetFileInfoParams *params) +{ + return params->file_type == PRODOS_T_DIR; +} + +const char *getMessage(uint8_t errorCode) +{ + switch (errorCode) { + case PRODOS_E_NONE: + return "No error"; + case PRODOS_E_BAD_SYS_CALL_NUM: + return "Bad system call number"; + case PRODOS_E_BAD_SYS_PARAM_COUNT: + return "Bad system call parameter count"; + case PRODOS_E_INTERRUPT_VECTOR_TABLE_FULL: + return "Interrupt vector table full"; + case PRODOS_E_IO_ERROR: + return "I/O error"; + case PRODOS_E_NO_DEVICE: + return "No device detected/connected"; + case PRODOS_E_DISK_WRITE_PROTECTED: + return "Disk is write protected"; + case PRODOS_E_DISK_SWITCHED: + return "Disk switched"; + case PRODOS_E_INVALID_PATH_SYNTAX: + return "Invalid path syntax"; + case PRODOS_E_FILE_CTRL_BLK_TABLE_FULL: + return "File control block table full"; + case PRODOS_E_INVALID_REF_NUM: + return "Invalid reference number"; + case PRODOS_E_PATH_NOT_FOUND: + return "Path not found"; + case PRODOS_E_VOLUME_DIR_NOT_FOUND: + return "Volume directory not found"; + case PRODOS_E_FILE_NOT_FOUND: + return "File not found"; + case PRODOS_E_DUPLICATE_FILE_NAME: + return "Duplicate file name"; + case PRODOS_E_OVERRUN_ERROR: + return "No space left on device"; + case PRODOS_E_VOLUME_DIR_FULL: + return "No entries left in volume directory"; + case PRODOS_E_INCOMPATIBLE_FILE_FORMAT: + return "Incompatible file format"; + case PRODOS_E_UNSUPPORTED_STORAGE_TYPE: + return "Unsupported storage type"; + case PRODOS_E_END_OF_FILE: + return "End of file reached"; + case PRODOS_E_POSITION_OUT_OF_RANGE: + return "File position out of range"; + case PRODOS_E_ACCESS_ERROR: + return "File attribute forbids operation"; + case PRODOS_E_FILE_IS_OPEN: + return "File is open"; + case PRODOS_E_DIR_COUNT_ERROR: + return "Directory header / file entry mismatch"; + case PRODOS_E_NOT_PRODOS_DISK: + "Not a ProDOS disk"; + case PRODOS_E_INVALID_PARAM: + return "Invalid value in parameter"; + case PRODOS_E_VOLUME_CTRL_BLK_TABLE_FULL: + return "Volume control block table full"; + case PRODOS_E_BAD_BUFFER_ADDR: + return "Bad buffer address"; + case PRODOS_E_DUPLICATE_VOLUME: + return "Duplicate volume"; + case PRODOS_E_BITMAP_ADDR_IMPOSSIBLE: + return "Bitmap address impossible"; + default: + return "Unknown error"; + } +} + diff --git a/prodosext.h b/prodosext.h new file mode 100644 index 0000000..b877053 --- /dev/null +++ b/prodosext.h @@ -0,0 +1,10 @@ +#ifndef PRODOSEXT_H +#define PRODOSEXT_H + +#include +#include + +extern bool isDirectory(struct GetFileInfoParams *params); +extern const char *getMessage(uint8_t errorCode); + +#endif