/* GSPLUS - Advanced Apple IIGS Emulator Environment Based on the KEGS emulator written by Kent Dickey See COPYRIGHT.txt for Copyright information See LICENSE.txt for license (GPL v2) */ #define _BSD_SOURCE #include #include #include #include #include #include #include #include #include #if defined(__APPLE__) #include #include #include #endif #ifndef XATTR_FINDERINFO_NAME #define XATTR_FINDERINFO_NAME "com.apple.FinderInfo" #endif #ifndef XATTR_RESOURCEFORK_NAME #define XATTR_RESOURCEFORK_NAME "com.apple.ResourceFork" #endif #include "defc.h" #include "gsos.h" #include "fst.h" #include "host_common.h" extern Engine_reg engine; /* direct page offsets */ #define dp_call_number 0x30 #define dp_param_blk_ptr 0x32 #define dp_dev1_num 0x36 #define dp_dev2_num 0x38 #define dp_path1_ptr 0x3a #define dp_path2_ptr 0x3e #define dp_path_flag 0x42 #define global_buffer 0x009a00 struct directory { int displacement; int num_entries; char *entries[1]; }; struct fd_entry { struct fd_entry *next; char *path; int cookie; int type; int access; int fd; int translate; struct directory *dir; }; static void free_directory(struct directory *dd); static struct directory *read_directory(const char *path, word16 *error); #define COOKIE_BASE 0x8000 static word32 cookies[32] = {}; static int alloc_cookie() { for (int i = 0; i < 32; ++i) { word32 x = cookies[i]; for (int j = 0; j < 32; ++j, x >>= 1) { if (x & 0x01) continue; cookies[i] |= (1 << j); return COOKIE_BASE + (i * 32 + j); } } return -1; } static int free_cookie(int cookie) { if (cookie < COOKIE_BASE) return -1; cookie -= COOKIE_BASE; if (cookie >= 32 *32) return -1; int chunk = cookie / 32; int offset = 1 << (cookie % 32); word32 x = cookies[chunk]; if ((x & offset) == 0) return -1; x &= ~offset; cookies[chunk] = x; return 0; } static struct fd_entry *fd_head = NULL; static struct fd_entry *find_fd(int cookie) { struct fd_entry *head = fd_head; while(head) { if (head->cookie == cookie) return head; head = head->next; } return NULL; } static void free_fd(struct fd_entry *e) { if (!e) return; if (e->cookie) free_cookie(e->cookie); if (e->fd >= 0) close(e->fd); if (e->dir) free_directory(e->dir); free(e->path); free(e); } static struct fd_entry *alloc_fd() { struct fd_entry *e = calloc(sizeof(struct fd_entry), 1); e->fd = -1; return e; } static word32 remove_fd(int cookie) { word32 rv = invalidRefNum; struct fd_entry *prev = NULL; struct fd_entry *head = fd_head; while (head) { if (head->cookie == cookie) { if (prev) prev->next = head->next; else fd_head = head->next; free_fd(head); rv = 0; break; } prev = head; head = head->next; } return rv; } static ssize_t safe_read(struct fd_entry *e, byte *buffer, size_t count) { int fd = e->fd; int tr = e->translate; for (;;) { ssize_t ok = read(fd, buffer, count); if (ok >= 0) { size_t i; if (tr == translate_crlf) { for (i = 0; i < ok; ++i) { if (buffer[i] == '\n') buffer[i] = '\r'; } } if (tr == translate_merlin) { for (i = 0; i < ok; ++i) { unsigned char c = buffer[i]; if (c == '\t') c = 0xa0; if (c == '\n') c = '\r'; if (c != ' ') c |= 0x80; buffer[i] = c; } } return ok; } if (ok < 0 && errno == EINTR) continue; return ok; } } static ssize_t safe_write(struct fd_entry *e, byte *buffer, size_t count) { int fd = e->fd; int tr = e->translate; if (tr == translate_crlf) { size_t i; for (i = 0; i < count; ++i) { if (buffer[i] == '\r') buffer[i] = '\n'; } } if (tr == translate_merlin) { size_t i; for (i = 0; i < count; ++i) { unsigned char c = buffer[i]; if (c == 0xa0) c = '\t'; c &= 0x7f; if (c == '\r') c = '\n'; buffer[i] = c; } } for (;;) { ssize_t ok = write(fd, buffer, count); if (ok >= 0) return ok; if (ok < 0 && errno == EINTR) continue; return ok; } } /* * if this is an absolute path, verify and skip past :Host: * */ static const char *check_path(const char *in, word32 *error) { word32 tmp; if (!error) error = &tmp; *error = 0; if (!in) return ""; /* check for .. */ const char *cp = in; do { while (*cp == '/') ++cp; if (cp[0] == '.' && cp[1] == '.' && (cp[2] == '/' || cp[2] == 0)) { *error = badPathSyntax; return NULL; } cp = strchr(cp, '/'); } while(cp); if (in[0] != '/') return in; if (strncasecmp(in, "/Host", 5) == 0 && (in[5] == '/' || in[5] == 0)) { in += 5; while (*in == '/') ++in; return in; } *error = volNotFound; return NULL; } static char * get_gsstr(word32 ptr) { if (!ptr) return NULL; int length = get_memory16_c(ptr, 0); ptr += 2; char *str = host_gc_malloc(length + 1); for (int i = 0; i < length; ++i) { char c = get_memory_c(ptr+i, 0); if (c == ':') c = '/'; str[i] = c; } str[length] = 0; return str; } static char * get_pstr(word32 ptr) { if (!ptr) return NULL; int length = get_memory_c(ptr, 0); ptr += 1; char *str = host_gc_malloc(length + 1); for (int i = 0; i < length; ++i) { char c = get_memory_c(ptr+i, 0); if (c == ':') c = '/'; str[i] = c; } str[length] = 0; return str; } static word32 set_gsstr(word32 ptr, const char *str) { if (!ptr) return paramRangeErr; int l = str ? strlen(str) : 0; word32 cap = get_memory16_c(ptr, 0); ptr += 2; if (cap < 4) return paramRangeErr; set_memory16_c(ptr, l, 0); ptr += 2; if (cap < l + 4) return buffTooSmall; for (int i = 0; i < l; ++i) { char c = *str++; if (c == '/') c = ':'; set_memory_c(ptr++, c, 0); } return 0; } static word32 set_gsstr_truncate(word32 ptr, const char *str) { if (!ptr) return paramRangeErr; int l = str ? strlen(str) : 0; word32 cap = get_memory16_c(ptr, 0); ptr += 2; if (cap < 4) return paramRangeErr; set_memory16_c(ptr, l, 0); ptr += 2; // get dir entry copies data even // if buffTooSmall... int rv = 0; if (cap < l + 4) { l = cap - 4; rv = buffTooSmall; } for (int i = 0; i < l; ++i) { char c = *str++; if (c == '/') c = ':'; set_memory_c(ptr++, c, 0); } return rv; } static word32 set_pstr(word32 ptr, const char *str) { if (!ptr) return paramRangeErr; int l = str ? strlen(str) : 0; if (l > 255) return buffTooSmall; // / is the pascal separator. set_memory_c(ptr++, l, 0); for (int i = 0; i < l; ++i) { set_memory_c(ptr++, *str++, 0); } return 0; } static word32 set_option_list(word32 ptr, word16 fstID, const byte *data, int size) { if (!ptr) return 0; // totalSize word32 cap = get_memory16_c(ptr, 0); ptr += 2; if (cap < 4) return paramRangeErr; // reqSize set_memory16_c(ptr, size + 2, 0); ptr += 2; if (cap < size + 6) return buffTooSmall; // fileSysID. set_memory16_c(ptr, fstID, 0); ptr += 2; for (int i = 0; i < size; ++i) set_memory_c(ptr++, *data++, 0); return 0; } static char *get_path1(void) { word32 direct = engine.direct; word16 flags = get_memory16_c(direct + dp_path_flag, 0); if (flags & (1 << 14)) return get_gsstr( get_memory24_c(direct + dp_path1_ptr, 0)); return NULL; } static char *get_path2(void) { word32 direct = engine.direct; word16 flags = get_memory16_c(direct + dp_path_flag, 0); if (flags & (1 << 6)) return get_gsstr( get_memory24_c(direct + dp_path2_ptr, 0)); return NULL; } /* * shutdown is called when switching to p8. * startup is ONLY called during initial boot. */ static word32 fst_shutdown(void) { // close any remaining files. struct fd_entry *head = fd_head; while (head) { struct fd_entry *next = head->next; free_fd(head); head = next; } //host_shutdown(); return 0; } static word32 fst_startup(void) { // if restart, close any previous files. fst_shutdown(); host_shutdown(); memset(&cookies, 0, sizeof(cookies)); return host_startup(); } static word32 fst_create(int class, const char *path) { word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0); struct file_info fi; memset(&fi, 0, sizeof(fi)); word16 pcount = 0; if (class) { pcount = get_memory16_c(pb, 0); if (pcount >= 2) fi.access = get_memory16_c(pb + CreateRecGS_access, 0); if (pcount >= 3) fi.file_type = get_memory16_c(pb + CreateRecGS_fileType, 0); if (pcount >= 4) fi.aux_type = get_memory32_c(pb + CreateRecGS_auxType, 0); if (pcount >= 5) fi.storage_type = get_memory16_c(pb + CreateRecGS_storageType, 0); if (pcount >= 6) fi.eof = get_memory32_c(pb + CreateRecGS_eof, 0); if (pcount >= 7) fi.resource_eof = get_memory32_c(pb + CreateRecGS_resourceEOF, 0); if (pcount >= 4) { host_file_type_to_finder_info(fi.finder_info, fi.file_type, fi.aux_type); fi.has_fi = 1; } } else { fi.access = get_memory16_c(pb + CreateRec_fAccess, 0); fi.file_type = get_memory16_c(pb + CreateRec_fileType, 0); fi.aux_type = get_memory32_c(pb + CreateRec_auxType, 0); fi.storage_type = get_memory16_c(pb + CreateRec_storageType, 0); fi.create_date = host_get_date_time(pb + CreateRec_createDate); host_file_type_to_finder_info(fi.finder_info, fi.file_type, fi.aux_type); fi.has_fi = 1; } int ok; if (fi.storage_type == 0 && fi.file_type == 0x0f) fi.storage_type = 0x0d; if (fi.storage_type == 0x0d) { ok = mkdir(path, 0777); if (ok < 0) { return host_map_errno_path(errno, path); } if (class) { if (pcount >= 5) set_memory16_c(pb + CreateRecGS_storageType, fi.storage_type, 0); } else { set_memory16_c(pb + CreateRec_storageType, fi.storage_type, 0); } return 0; } if (fi.storage_type <= 3 || fi.storage_type == 0x05) { // normal file. // 0x05 is an extended/resource file but we don't do anything special. ok = open(path, O_WRONLY | O_CREAT | O_EXCL, 0666); if (ok < 0) return host_map_errno_path(errno, path); // set ftype, auxtype... host_set_file_info(path, &fi); close(ok); fi.storage_type = 1; if (class) { if (pcount >= 5) set_memory16_c(pb + CreateRecGS_storageType, fi.storage_type, 0); } else { set_memory16_c(pb + CreateRec_storageType, fi.storage_type, 0); } return 0; } if (fi.storage_type == 0x8005) { // convert an existing file to an extended file. // this checks that the file exists and has a 0-sized resource. word32 rv = host_get_file_info(path, &fi); if (rv) return rv; if (fi.storage_type == extendedFile) return resExistsErr; if (fi.storage_type < seedling || fi.storage_type > tree) return resAddErr; return 0; } // other storage types? return badStoreType; } static word32 fst_destroy(int class, const char *path) { struct stat st; if (!path) return badStoreType; if (stat(path, &st) < 0) { return host_map_errno_path(errno, path); } // can't delete volume root. if (host_is_root(&st)) return badStoreType; int ok = S_ISDIR(st.st_mode) ? rmdir(path) : unlink(path); if (ok < 0) return host_map_errno_path(errno, path); return 0; } static word32 fst_set_file_info(int class, const char *path) { word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0); struct file_info fi; memset(&fi, 0, sizeof(fi)); // load up existing file types / finder info. host_get_file_xinfo(path, &fi); word32 option_list = 0; if (class) { word16 pcount = get_memory16_c(pb, 0); if (pcount >= 2) fi.access = get_memory16_c(pb + FileInfoRecGS_access, 0); if (pcount >= 3) fi.file_type = get_memory16_c(pb + FileInfoRecGS_fileType, 0); if (pcount >= 4) fi.aux_type = get_memory32_c(pb + FileInfoRecGS_auxType, 0); // reserved. //if (pcount >= 5) fi.storage_type = get_memory16_c(pb + FileInfoRecGS_storageType, 0); if (pcount >= 6) fi.create_date = host_get_date_time_rec(pb + FileInfoRecGS_createDateTime); if (pcount >= 7) fi.modified_date = host_get_date_time_rec(pb + FileInfoRecGS_modDateTime); if (pcount >= 8) option_list = get_memory24_c(pb + FileInfoRecGS_optionList, 0); // remainder reserved if (pcount >= 4) { host_file_type_to_finder_info(fi.finder_info, fi.file_type, fi.aux_type); fi.has_fi = 1; } } else { fi.access = get_memory16_c(pb + FileRec_fAccess, 0); fi.file_type = get_memory16_c(pb + FileRec_fileType, 0); fi.aux_type = get_memory32_c(pb + FileRec_auxType, 0); // reserved. //fi.storage_type = get_memory32_c(pb + FileRec_storageType, 0); fi.create_date = host_get_date_time(pb + FileRec_createDate); fi.modified_date = host_get_date_time(pb + FileRec_modDate); host_file_type_to_finder_info(fi.finder_info, fi.file_type, fi.aux_type); fi.has_fi = 1; } if (option_list) { // total size, req size, fst id, data... int total_size = get_memory16_c(option_list + 0, 0); int req_size = get_memory16_c(option_list + 2, 0); int fst_id = get_memory16_c(option_list + 4, 0); int size = req_size - 6; if ((fst_id == proDOSFSID || fst_id == hfsFSID || fst_id == appleShareFSID) && size >= 32) { fi.has_fi = 1; for (int i = 0; i <32; ++i) fi.finder_info[i] = get_memory_c(option_list + 6 + i, 0); } } return host_set_file_info(path, &fi); } static word32 fst_get_file_info(int class, const char *path) { word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0); struct file_info fi; int rv = 0; rv = host_get_file_info(path, &fi); if (rv) return rv; if (class) { word16 pcount = get_memory16_c(pb, 0); if (pcount >= 2) set_memory16_c(pb + FileInfoRecGS_access, fi.access, 0); if (pcount >= 3) set_memory16_c(pb + FileInfoRecGS_fileType, fi.file_type, 0); if (pcount >= 4) set_memory32_c(pb + FileInfoRecGS_auxType, fi.aux_type, 0); if (pcount >= 5) set_memory16_c(pb + FileInfoRecGS_storageType, fi.storage_type, 0); if (pcount >= 6) host_set_date_time_rec(pb + FileInfoRecGS_createDateTime, fi.create_date); if (pcount >= 7) host_set_date_time_rec(pb + FileInfoRecGS_modDateTime, fi.modified_date); if (pcount >= 8) { word16 fst_id = hfsFSID; //if (fi.storage_type == 0x0f) fst_id = mfsFSID; word32 option_list = get_memory24_c(pb + FileInfoRecGS_optionList, 0); rv = set_option_list(option_list, fst_id, fi.finder_info, fi.has_fi ? 32 : 0); } if (pcount >= 9) set_memory32_c(pb + FileInfoRecGS_eof, fi.eof, 0); if (pcount >= 10) set_memory32_c(pb + FileInfoRecGS_blocksUsed, fi.blocks, 0); if (pcount >= 11) set_memory32_c(pb + FileInfoRecGS_resourceEOF, fi.resource_eof, 0); if (pcount >= 12) set_memory32_c(pb + FileInfoRecGS_resourceBlocks, fi.resource_blocks, 0); } else { set_memory16_c(pb + FileRec_fAccess, fi.access, 0); set_memory16_c(pb + FileRec_fileType, fi.file_type, 0); set_memory32_c(pb + FileRec_auxType, fi.aux_type, 0); set_memory16_c(pb + FileRec_storageType, fi.storage_type, 0); host_set_date_time(pb + FileRec_createDate, fi.create_date); host_set_date_time(pb + FileRec_modDate, fi.modified_date); set_memory32_c(pb + FileRec_blocksUsed, fi.blocks, 0); } return rv; } static word32 fst_judge_name(int class, char *path) { if (class == 0) return invalidClass; word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0); word16 pcount = get_memory16_c(pb, 0); word16 name_type = get_memory16_c(pb + JudgeNameRecGS_nameType, 0); word32 name = pcount >= 5 ? get_memory24_c(pb + JudgeNameRecGS_name, 0) : 0; // 255 max length. if (pcount >= 4) set_memory16_c(pb + JudgeNameRecGS_maxLen, 255, 0); if (pcount >= 6) set_memory16_c(pb + JudgeNameRecGS_nameFlags, 0, 0); word16 nameFlags = 0; word16 rv = 0; if (name) { word16 cap = get_memory16_c(name, 0); if (cap < 4) return buffTooSmall; word16 length = get_memory16_c(name + 2, 0); if (length == 0) { nameFlags |= 1 << 13; rv = set_gsstr(name, "A"); } else { // if volume name, only allow "Host" ? if (length > 255) nameFlags |= 1 << 14; for (int i = 0; i < length; ++i) { char c = get_memory_c(name + 4 + i, 0); if (c == 0 || c == ':' || c == '/' || c == '\\') { nameFlags |= 1 << 15; set_memory_c(name + 4 + i, '.', 0); } } } } if (pcount >= 6) set_memory16_c(pb + JudgeNameRecGS_nameFlags, nameFlags, 0); return rv; } static word32 fst_volume(int class) { word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0); word32 rv = 0; if (class) { word16 pcount = get_memory16_c(pb, 0); if (pcount >= 2) rv = set_gsstr(get_memory24_c(pb + VolumeRecGS_volName, 0), ":Host"); // finder bug -- if used blocks is 0, doesn't display header. if (pcount >= 3) set_memory32_c(pb + VolumeRecGS_totalBlocks, 0x007fffff, 0); if (pcount >= 4) set_memory32_c(pb + VolumeRecGS_freeBlocks, 0x007fffff-1, 0); if (pcount >= 5) set_memory16_c(pb + VolumeRecGS_fileSysID, mfsFSID, 0); if (pcount >= 6) set_memory16_c(pb + VolumeRecGS_blockSize, 512, 0); // handled via gs/os //if (pcount >= 7) set_memory16_c(pb + VolumeRecGS_characteristics, 0, 0); //if (pcount >= 8) set_memory16_c(pb + VolumeRecGS_deviceID, 0, 0); } else { // prodos 16 uses / sep. rv = set_pstr(get_memory24_c(pb + VolumeRec_volName, 0), "/Host"); set_memory32_c(pb + VolumeRec_totalBlocks, 0x007fffff, 0); set_memory32_c(pb + VolumeRec_freeBlocks, 0x007fffff-1, 0); set_memory16_c(pb + VolumeRec_fileSysID, mfsFSID, 0); } return rv; } static word32 fst_clear_backup(int class, const char *path) { return invalidFSTop; } static int open_data_fork(const char *path, word16 *access, word16 *error) { int fd = -1; for (;;) { switch(*access) { case readEnableAllowWrite: case readWriteEnable: fd = open(path, O_RDWR); break; case readEnable: fd = open(path, O_RDONLY); break; case writeEnable: fd = open(path, O_WRONLY); break; } if (*access == readEnableAllowWrite) { if (fd < 0) { *access = readEnable; continue; } *access = readWriteEnable; } break; } if (fd < 0) { *error = host_map_errno_path(errno, path); } return fd; } #if defined(__APPLE__) static int open_resource_fork(const char *path, word16 *access, word16 *error) { // os x / hfs/apfs don't need to specifically create a resource fork. // or do they? char *rpath = host_gc_append_path(path, _PATH_RSRCFORKSPEC); int fd = -1; for (;;) { switch(*access) { case readEnableAllowWrite: case readWriteEnable: fd = open(rpath, O_RDWR | O_CREAT, 0666); break; case readEnable: fd = open(rpath, O_RDONLY); break; case writeEnable: fd = open(rpath, O_WRONLY | O_CREAT, 0666); break; } if (*access == readEnableAllowWrite) { if (fd < 0) { *access = readEnable; continue; } *access = readWriteEnable; } break; } if (fd < 0) { *error = host_map_errno_path(errno, path); } return fd; } #elif defined __sun static int open_resource_fork(const char *path, word16 *access, word16 *error) { int tmp = open(path, O_RDONLY); if (tmp < 0) { *error = host_map_errno_path(errno, path); return -1; } int fd = -1; for(;;) { switch(*access) { case readEnableAllowWrite: case readWriteEnable: fd = openat(tmp, XATTR_RESOURCEFORK_NAME, O_RDWR | O_CREAT | O_XATTR, 0666); break; case readEnable: fd = openat(tmp, XATTR_RESOURCEFORK_NAME, O_RDONLY | O_XATTR); break; case writeEnable: fd = openat(tmp, XATTR_RESOURCEFORK_NAME, O_WRONLY | O_CREAT | O_XATTR, 0666); break; } if (*access == readEnableAllowWrite) { if (fd < 0) { *access = readEnable; continue; } *access = readWriteEnable; } break; } if (fd < 0) { *error = host_map_errno_path(errno, path); close(tmp); return -1; } close(tmp); return fd; } #elif defined __linux__ static int open_resource_fork(const char *path, word16 *access, word16 *error) { *error = resForkNotFound; return -1; } #else static int open_resource_fork(const char *path, word16 *access, word16 *error) { *error = resForkNotFound; return -1; } #endif static word32 fst_open(int class, const char *path) { word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0); struct file_info fi; word16 rv = 0; rv = host_get_file_info(path, &fi); if (rv) return rv; int fd = -1; int type = file_regular; struct directory *dd = NULL; word16 pcount = 0; word16 request_access = readEnableAllowWrite; word16 access = 0; word16 resource_number = 0; if (class) { pcount = get_memory16_c(pb, 0); if (pcount >= 3) request_access = get_memory16_c(pb + OpenRecGS_requestAccess, 0); if (pcount >= 4) resource_number = get_memory16_c(pb + OpenRecGS_resourceNumber, 0); } if (resource_number) { if (resource_number > 1) return paramRangeErr; type = file_resource; } if (access > 3) return paramRangeErr; // special access checks for directories. if (fi.storage_type == 0x0d || fi.storage_type == 0x0f) { if (resource_number) return resForkNotFound; switch (request_access) { case readEnableAllowWrite: request_access = readEnable; break; case writeEnable: case readWriteEnable: return invalidAccess; } type = file_directory; } if (g_cfg_host_read_only) { switch (request_access) { case readEnableAllowWrite: request_access = readEnable; break; case readWriteEnable: case writeEnable: return invalidAccess; break; } } access = request_access; switch(type) { case file_regular: fd = open_data_fork(path, &access, &rv); break; case file_resource: fd = open_resource_fork(path, &access, &rv); break; case file_directory: dd = read_directory(path, &rv); break; } if (rv) return rv; if (class) { if (pcount >= 5) set_memory16_c(pb + OpenRecGS_access, access, 0); if (pcount >= 6) set_memory16_c(pb + OpenRecGS_fileType, fi.file_type, 0); if (pcount >= 7) set_memory32_c(pb + OpenRecGS_auxType, fi.aux_type, 0); if (pcount >= 8) set_memory16_c(pb + OpenRecGS_storageType, fi.storage_type, 0); if (pcount >= 9) host_set_date_time_rec(pb + OpenRecGS_createDateTime, fi.create_date); if (pcount >= 10) host_set_date_time_rec(pb + OpenRecGS_modDateTime, fi.modified_date); if (pcount >= 11) { word16 fst_id = hfsFSID; //if (fi.storage_type == 0x0f) fst_id = mfsFSID; word32 option_list = get_memory24_c(pb + OpenRecGS_optionList, 0); word32 tmp = set_option_list(option_list, fst_id, fi.finder_info, fi.has_fi ? 32 : 0); if (!rv) rv = tmp; } if (pcount >= 12) set_memory32_c(pb + OpenRecGS_eof, fi.eof, 0); if (pcount >= 13) set_memory32_c(pb + OpenRecGS_blocksUsed, fi.blocks, 0); if (pcount >= 14) set_memory32_c(pb + OpenRecGS_resourceEOF, fi.resource_eof, 0); if (pcount >= 15) set_memory32_c(pb + OpenRecGS_resourceBlocks, fi.resource_blocks, 0); } // prodos 16 doesn't return anything in the parameter block. struct fd_entry *e = alloc_fd(); if (!e) { if (fd >=0) close(fd); if (dd) free_directory(dd); return outOfMem; } e->fd = fd; e->dir = dd; e->cookie = alloc_cookie(); if (!e->cookie) { free_fd(e); return tooManyFilesOpen; } if (type == file_regular) { if (g_cfg_host_crlf) { if (fi.file_type == 0x04 || fi.file_type == 0xb0) e->translate = translate_crlf; } if (g_cfg_host_merlin && fi.file_type == 0x04) { int n = strlen(path); if (n >= 3 && path[n-1] == 'S' && path[n-2] == '.') e->translate = translate_merlin; } } e->access = access; e->path = strdup(path); e->type = type; // insert it in the linked list. e->next = fd_head; fd_head = e; engine.xreg = e->cookie; engine.yreg = access; // actual access, needed in fcr. return rv; } static word32 fst_read(int class) { int cookie = engine.yreg; struct fd_entry *e = find_fd(cookie); if (!e) return invalidRefNum; switch (e->type) { case file_directory: return badStoreType; } word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0); word32 data_buffer = 0; word32 request_count = 0; word32 transfer_count = 0; if (class) { data_buffer = get_memory24_c(pb + IORecGS_dataBuffer, 0); request_count = get_memory24_c(pb + IORecGS_requestCount, 0); // pre-zero transfer count set_memory32_c(pb + IORecGS_transferCount, 0, 0); } else { data_buffer = get_memory24_c(pb + FileIORec_dataBuffer, 0); request_count = get_memory24_c(pb + FileIORec_requestCount, 0); set_memory32_c(pb + FileIORec_transferCount, 0, 0); } if (request_count == 0) return 0; word16 newline_mask; word32 rv = 0; ssize_t ok; newline_mask = get_memory16_c(global_buffer, 0); if (newline_mask) { byte newline_table[256]; for (int i = 0; i < 256; ++i) newline_table[i] = get_memory_c(global_buffer + 2 + i, 0); for (word32 i = 0; i < request_count; ++i) { byte b; ok = safe_read(e, &b, 1); if (ok < 0) return host_map_errno(errno); if (ok == 0) break; transfer_count++; set_memory_c(data_buffer++, b, 0); if (newline_table[b & newline_mask]) break; } if (transfer_count == 0) rv = eofEncountered; } else { byte *data = host_gc_malloc(request_count); if (!data) return outOfMem; ok = safe_read(e, data, request_count); if (ok < 0) rv = host_map_errno(errno); if (ok == 0) rv = eofEncountered; if (ok > 0) { transfer_count = ok; for (size_t i = 0; i < ok; ++i) { set_memory_c(data_buffer + i, data[i], 0); } } } if (transfer_count) { if (class) set_memory32_c(pb + IORecGS_transferCount, transfer_count, 0); else set_memory32_c(pb + FileIORec_transferCount, transfer_count, 0); } return rv; } static word32 fst_write(int class) { int cookie = engine.yreg; struct fd_entry *e = find_fd(cookie); if (!e) return invalidRefNum; if (!(e->access & writeEnable)) return invalidAccess; switch (e->type) { case file_directory: return badStoreType; } word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0); word32 data_buffer = 0; word32 request_count = 0; if (class) { data_buffer = get_memory24_c(pb + IORecGS_dataBuffer, 0); request_count = get_memory24_c(pb + IORecGS_requestCount, 0); // pre-zero transfer count set_memory32_c(pb + IORecGS_transferCount, 0, 0); } else { data_buffer = get_memory24_c(pb + FileIORec_dataBuffer, 0); request_count = get_memory24_c(pb + FileIORec_requestCount, 0); set_memory32_c(pb + FileIORec_transferCount, 0, 0); } if (request_count == 0) return 0; byte *data = host_gc_malloc(request_count); if (!data) return outOfMem; for (word32 i = 0; i < request_count; ++i) { data[i] = get_memory_c(data_buffer + i,0); } word32 rv = 0; ssize_t ok = safe_write(e, data, request_count); if (ok < 0) rv = host_map_errno(errno); if (ok > 0) { if (class) set_memory32_c(pb + IORecGS_transferCount, ok, 0); else set_memory32_c(pb + FileIORec_transferCount, ok, 0); } return rv; } static word32 fst_close(int class) { int cookie = engine.yreg; return remove_fd(cookie); } static word32 fst_flush(int class) { int cookie = engine.yreg; struct fd_entry *e = find_fd(cookie); if (!e) return invalidRefNum; switch (e->type) { case file_directory: return badStoreType; } int ok = fsync(e->fd); if (ok < 0) return host_map_errno(errno); return 0; } static off_t get_offset(int fd, word16 base, word32 displacement) { off_t pos = lseek(fd, 0, SEEK_CUR); off_t eof = lseek(fd, 0, SEEK_END); lseek(fd, pos, SEEK_SET); switch (base) { case startPlus: return displacement; break; case eofMinus: return eof - displacement; break; case markPlus: return pos + displacement; break; case markMinus: return pos - displacement; break; default: return -1; } } static word32 fst_set_mark(int class) { int cookie = engine.yreg; struct fd_entry *e = find_fd(cookie); if (!e) return invalidRefNum; switch (e->type) { case file_directory: return badStoreType; } word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0); word16 base = 0; word32 displacement = 0; if (class) { base = get_memory16_c(pb + SetPositionRecGS_base, 0); displacement = get_memory32_c(pb + SetPositionRecGS_displacement, 0); } else { displacement = get_memory32_c(pb + MarkRec_position, 0); } if (base > markMinus) return paramRangeErr; off_t offset = get_offset(e->fd, base, displacement); if (offset < 0) return outOfRange; off_t ok = lseek(e->fd, offset, SEEK_SET); if (ok < 0) return host_map_errno(errno); return 0; } static word32 fst_set_eof(int class) { int cookie = engine.yreg; struct fd_entry *e = find_fd(cookie); if (!e) return invalidRefNum; switch (e->type) { case file_directory: return badStoreType; } word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0); word16 base = 0; word32 displacement = 0; if (class) { base = get_memory16_c(pb + SetPositionRecGS_base, 0); displacement = get_memory32_c(pb + SetPositionRecGS_displacement, 0); } else { displacement = get_memory32_c(pb + EOFRec_eofPosition, 0); } if (base > markMinus) return paramRangeErr; off_t offset = get_offset(e->fd, base, displacement); if (offset < 0) return outOfRange; int ok = ftruncate(e->fd, offset); if (ok < 0) return host_map_errno(errno); return 0; } static word32 fst_get_mark(int class) { int cookie = engine.yreg; struct fd_entry *e = find_fd(cookie); if (!e) return invalidRefNum; word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0); off_t pos = 0; switch (e->type) { case file_directory: return badStoreType; } pos = lseek(e->fd, 0, SEEK_CUR); if (pos < 0) return host_map_errno(errno); if (class) { set_memory32_c(pb + PositionRecGS_position, pos, 0); } else { set_memory32_c(pb + MarkRec_position, pos, 0); } return 0; } static word32 fst_get_eof(int class) { int cookie = engine.yreg; struct fd_entry *e = find_fd(cookie); if (!e) return invalidRefNum; word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0); switch (e->type) { case file_directory: return badStoreType; } off_t eof = 0; off_t pos = 0; pos = lseek(e->fd, 0, SEEK_CUR); eof = lseek(e->fd, 0, SEEK_END); if (eof < 0) return host_map_errno(errno); lseek(e->fd, pos, SEEK_SET); if (class) { set_memory32_c(pb + PositionRecGS_position, eof, 0); } else { set_memory32_c(pb + MarkRec_position, eof, 0); } return 0; } static void free_directory(struct directory *dd) { if (!dd) return; for (int i = 0; i < dd->num_entries; ++i) { free(dd->entries[i]); } free(dd); } static int qsort_callback(const void *a, const void *b) { return strcmp(*(const char **)a, *(const char **)b); } /* * exclude files from get_dir_entries. * */ static int filter_directory_entry(const char *name) { if (!name[0]) return 1; if (name[0] == '.') { return 1; /* if (!strcmp(name, ".")) return 1; if (!strcmp(name, "..")) return 1; if (!strncmp(name, "._", 2)) return 1; // ._ resource fork if (!strcmp(name, ".fseventsd")) return 1; */ } return 0; } static struct directory *read_directory(const char *path, word16 *error) { DIR *dirp; struct directory *dd; int capacity = 100; int size = sizeof(struct directory) + capacity * sizeof(char *); dirp = opendir(path); if (!dirp) { *error = host_map_errno_path(errno, path); return NULL; } dd = (struct directory *)malloc(size); if (!dd) { *error = outOfMem; closedir(dirp); return NULL; } memset(dd, 0, size); for(;;) { struct dirent *d = readdir(dirp); if (!d) break; if (filter_directory_entry(d->d_name)) continue; if (dd->num_entries >= capacity) { capacity += capacity; size = sizeof(struct directory) + capacity * sizeof(char *); struct directory * tmp = realloc(dd, size); if (!tmp) { *error = host_map_errno(errno); free_directory(dd); closedir(dirp); return NULL; } dd = tmp; } dd->entries[dd->num_entries++] = strdup(d->d_name); }; closedir(dirp); // sort them.... qsort(dd->entries, dd->num_entries, sizeof(char *), qsort_callback); return dd; } static word32 fst_get_dir_entry(int class) { int cookie = engine.yreg; struct fd_entry *e = find_fd(cookie); if (!e) return invalidRefNum; if (e->type != file_directory) return badFileFormat; word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0); word16 base = 0; word16 pcount = 0; word32 displacement = 0; word32 name = 0; if (class) { pcount = get_memory16_c(pb, 0); base = get_memory16_c(pb + DirEntryRecGS_base, 0); displacement = get_memory16_c(pb + DirEntryRecGS_displacement, 0); name = get_memory24_c(pb + DirEntryRecGS_name, 0); } else { base = get_memory16_c(pb + DirEntryRec_base, 0); displacement = get_memory16_c(pb + DirEntryRec_displacement, 0); name = get_memory24_c(pb + DirEntryRec_nameBuffer, 0); } if (base == 0 && displacement == 0) { // count them up. int count = e->dir->num_entries; e->dir->displacement = 0; if (class) { if (pcount >= 6) set_memory16_c(pb + DirEntryRecGS_entryNum, count, 0); } else { set_memory16_c(pb + DirEntryRec_entryNum, count, 0); } return 0; } int dir_displacement = e->dir->displacement; switch (base) { case 0: // displacement is absolute entry number. break; case 1: // displacement is added to the current displacement. displacement = dir_displacement + displacement; break; case 2: // displacement is substracted from current displacement. displacement = dir_displacement - displacement; break; default: return paramRangeErr; } //if (displacement) --displacement; --displacement; if (displacement < 0) return endOfDir; if (displacement >= e->dir->num_entries) return endOfDir; word32 rv = 0; const char *dname = e->dir->entries[displacement++]; e->dir->displacement = displacement; char *fullpath = host_gc_append_path(e->path, dname); struct file_info fi; rv = host_get_file_info(fullpath, &fi); if (dname) fprintf(stderr, " - %s", dname); // p16 and gs/os both use truncating c1 output string. rv = set_gsstr_truncate(name, dname); if (class) { if (pcount > 2) set_memory16_c(pb + DirEntryRecGS_flags, fi.storage_type == 0x05 ? 0x8000 : 0, 0); if (pcount >= 6) set_memory16_c(pb + DirEntryRecGS_entryNum, displacement, 0); if (pcount >= 7) set_memory16_c(pb + DirEntryRecGS_fileType, fi.file_type, 0); if (pcount >= 8) set_memory32_c(pb + DirEntryRecGS_eof, fi.eof, 0); if (pcount >= 9) set_memory32_c(pb + DirEntryRecGS_blockCount, fi.blocks, 0); if (pcount >= 10) host_set_date_time_rec(pb + DirEntryRecGS_createDateTime, fi.create_date); if (pcount >= 11) host_set_date_time_rec(pb + DirEntryRecGS_modDateTime, fi.modified_date); if (pcount >= 12) set_memory16_c(pb + DirEntryRecGS_access, fi.access, 0); if (pcount >= 13) set_memory32_c(pb + DirEntryRecGS_auxType, fi.aux_type, 0); if (pcount >= 14) set_memory16_c(pb + DirEntryRecGS_fileSysID, mfsFSID, 0); if (pcount >= 15) { word16 fst_id = hfsFSID; //if (fi.storage_type == 0x0f) fst_id = mfsFSID; word32 option_list = get_memory24_c(pb + DirEntryRecGS_optionList, 0); word32 tmp = set_option_list(option_list, fst_id, fi.finder_info, fi.has_fi ? 32 : 0); if (!rv) rv = tmp; } if (pcount >= 16) set_memory32_c(pb + DirEntryRecGS_resourceEOF, fi.resource_eof, 0); if (pcount >= 17) set_memory32_c(pb + DirEntryRecGS_resourceBlocks, fi.resource_blocks, 0); } else { set_memory16_c(pb + DirEntryRec_flags, fi.storage_type == 0x05 ? 0x8000 : 0, 0); set_memory16_c(pb + DirEntryRec_entryNum, displacement, 0); set_memory16_c(pb + DirEntryRec_fileType, fi.file_type, 0); set_memory32_c(pb + DirEntryRec_endOfFile, fi.eof, 0); set_memory32_c(pb + DirEntryRec_blockCount, fi.blocks, 0); host_set_date_time_rec(pb + DirEntryRec_createTime, fi.create_date); host_set_date_time_rec(pb + DirEntryRec_modTime, fi.modified_date); set_memory16_c(pb + DirEntryRec_access, fi.access, 0); set_memory32_c(pb + DirEntryRec_auxType, fi.aux_type, 0); set_memory16_c(pb + DirEntryRec_fileSysID, mfsFSID, 0); } return rv; } static word32 fst_change_path(int class, const char *path1, const char *path2) { /* make sure they're not trying to rename the volume... */ struct stat st; if (stat(path1, &st) < 0) return host_map_errno_path(errno, path1); if (host_is_root(&st)) return invalidAccess; // rename will delete any previous file. ChangePath should return an error. if (stat(path2, &st) == 0) return dupPathname; if (rename(path1, path2) < 0) return host_map_errno_path(errno, path2); return 0; } static word32 fst_format(int class) { return notBlockDev; } static word32 fst_erase(int class) { return notBlockDev; } static const char *call_name(word16 call) { static char* class1[] = { // 0x00 "", "CreateGS", "DestroyGS", "", "ChangePathGS", "SetFileInfoGS", "GetFileInfoGS", "JudgeNameGS", "VolumeGS", "", "", "ClearBackupGS", "", "", "", "", // 0x10 "OpenGS", "", "ReadGS", "WriteGS", "CloseGS", "FlushGS", "SetMarkGS", "GetMarkGS", "SetEOFGS", "GetEOFGS", "", "", "GetDirEntryGS", "", "", "", // 0x20 "", "", "", "", "FormatGS", "EraseDiskGS", }; static char* class0[] = { // 0x00 "", "CREATE", "DESTROY", "", "CHANGE_PATH", "SET_FILE_INFO", "GET_FILE_INFO", "", "VOLUME", "", "", "CLEAR_BACKUP_BIT", "", "", "", "", // 0X10 "OPEN", "", "READ", "WRITE", "CLOSE", "FLUSH", "SET_MARK", "GET_MARK", "SET_EOF", "GET_EOF", "", "", "GET_DIR_ENTRY", "", "", "", // 0X20 "", "", "", "", "FORMAT", "ERASE_DISK", }; if (call & 0x8000) { static char *sys[] = { "", "fst_startup", "fst_shutdown", "fst_remove_vcr", "fst_deferred_flush" }; call &= ~0x8000; if (call < sizeof(sys) / sizeof(sys[0])) return sys[call]; return ""; } int class = call >> 13; call &= 0x1fff; switch(class) { case 0: if (call < sizeof(class0) / sizeof(class0[0])) return class0[call]; break; case 1: if (call < sizeof(class1) / sizeof(class1[0])) return class1[call]; break; } return ""; } void host_fst(void) { /* * input: * c = set * a = default error code * x = gs/os callnum * y = [varies] * * output: * c = set/clear * a = error code/0 * x = varies * y = varies */ word32 acc = 0; word16 call = engine.xreg; fprintf(stderr, "Host FST: %04x %s", call, call_name(call)); if (call & 0x8000) { fputs("\n", stderr); // system level. switch(call) { case 0x8001: acc = fst_startup(); break; case 0x8002: acc = fst_shutdown(); break; default: acc = badSystemCall; break; } } else { if (!host_root) { acc = networkError; engine.acc = acc; SEC(); fprintf(stderr, " %02x %s\n", acc, host_error_name(acc)); return; } int class = call >> 13; call &= 0x1fff; if (class > 1) { acc = invalidClass; engine.acc = acc; SEC(); fprintf(stderr, " %02x %s\n", acc, host_error_name(acc)); return; } char *path1 = NULL; char *path2 = NULL; char *path3 = NULL; char *path4 = NULL; const char *cp; switch(call & 0xff) { case 0x01: case 0x02: case 0x05: case 0x06: case 0x0b: case 0x10: path1 = get_path1(); break; case 0x04: path1 = get_path1(); path2 = get_path2(); break; } if (path1) fprintf(stderr, " - %s", path1); if (path2) fprintf(stderr, " - %s", path2); switch(call & 0xff) { case 0x01: cp = check_path(path1, &acc); if (acc) break; path3 = host_gc_append_path(host_root, cp); acc = fst_create(class, path3); break; case 0x02: cp = check_path(path1, &acc); if (acc) break; path3 = host_gc_append_path(host_root, cp); acc = fst_destroy(class, path3); break; case 0x04: cp = check_path(path1, &acc); if (acc) break; path3 = host_gc_append_path(host_root, cp); cp = check_path(path2, &acc); if (acc) break; path4 = host_gc_append_path(host_root, cp); acc = fst_change_path(class, path3, path4); break; case 0x05: cp = check_path(path1, &acc); if (acc) break; path3 = host_gc_append_path(host_root, cp); acc = fst_set_file_info(class, path3); break; case 0x06: cp = check_path(path1, &acc); if (acc) break; path3 = host_gc_append_path(host_root, cp); acc = fst_get_file_info(class, path3); break; case 0x07: acc = fst_judge_name(class, path1); break; case 0x08: acc = fst_volume(class); break; case 0x0b: cp = check_path(path1, &acc); if (acc) break; path3 = host_gc_append_path(host_root, cp); acc = fst_clear_backup(class, path3); break; case 0x10: cp = check_path(path1, &acc); if (acc) break; path3 = host_gc_append_path(host_root, cp); acc = fst_open(class, path3); break; case 0x012: acc = fst_read(class); break; case 0x013: acc = fst_write(class); break; case 0x14: acc = fst_close(class); break; case 0x15: acc = fst_flush(class); break; case 0x16: acc = fst_set_mark(class); break; case 0x17: acc = fst_get_mark(class); break; case 0x18: acc = fst_set_eof(class); break; case 0x19: acc = fst_get_eof(class); break; case 0x1c: acc = fst_get_dir_entry(class); break; case 0x24: acc = fst_format(class); break; case 0x25: acc = fst_erase(class); break; default: acc = invalidFSTop; break; } fputs("\n", stderr); } if (acc) fprintf(stderr, " %02x %s\n", acc, host_error_name(acc)); host_gc_free(); engine.acc = acc; if (acc) SEC(); else CLC(); }