/* * Copyright (c) 2020, Alexey Melnikov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include "../../hardware.h" #include "../../user_io.h" #include "../../file_io.h" #include "../../cfg.h" #include "../../shmem.h" #define SHMEM_ADDR 0x300CE000 #define SHMEM_SIZE 0x2000 static uint8_t *shmem = 0; #define REQUEST_FLG 0 #define REQUEST_BUFFER 4 #define HDRLEN 8 #define DATA_BUFFER (REQUEST_BUFFER+66) //#define DEBUG #ifdef DEBUG #define dbg_print printf #define dbg_hexdump hexdump #else #define dbg_print(x,...) void() #define dbg_hexdump(x,...) void() #endif enum AL_SUBFUNCTIONS { AL_RMDIR = 0x01, AL_MKDIR = 0x03, AL_CHDIR = 0x05, AL_CLOSE = 0x06, AL_READ = 0x08, AL_WRITE = 0x09, AL_LOCK = 0x0A, AL_DISKSPACE = 0x0C, AL_SETATTR = 0x0E, AL_GETATTR = 0x0F, AL_RENAME = 0x11, AL_DELETE = 0x13, AL_OPEN = 0x16, AL_CREATE = 0x17, AL_FINDFIRST = 0x1B, AL_FINDNEXT = 0x1C, AL_SKFMEND = 0x21, AL_QUALIFY = 0x23, AL_SPOPEN = 0x2E, AL_UNKNOWN = 0xFF }; #define FAT_RO 1 #define FAT_HID 2 #define FAT_SYS 4 #define FAT_VOL 8 #define FAT_DIR 16 #define FAT_ARC 32 #define FAT_DEV 64 static char basepath[1024] = {}; static int baselen = 0; struct dir_item_t { dirent64 de; stat64 st; }; struct lock { uint16_t token; std::vector dir_items; }; static std::map locks; static short next_key = 0; static short get_key() { short key; do { next_key++; if (!next_key) next_key++; key = next_key; } while (locks.find(key) != locks.end()); return key; } static short get_lock(const uint16_t token) { for (const auto &pair : locks) { if (pair.second.token == token) { dbg_print("! token %u has lock: %d\n", token, pair.first); return pair.first; } } return 0; } static short add_lock(const uint16_t token) { short key = get_lock(token); if (key) { locks[key].dir_items.clear(); } else { key = get_key(); locks[key] = { token, {} }; dbg_print("+ add lock: %d, %u\n", key, token); } return key; } static std::map open_file_handles; static short next_fp = 1; static short get_fp() { short fp; do { next_fp++; if (!next_fp) next_fp++; fp = next_fp; } while (open_file_handles.find(fp) != open_file_handles.end()); return fp; } static char* find_path(const char *name) { dbg_print("find_path(%s)\n", name); static char str[1024] = {}; const char* p = strchr(name, ':'); if (p) { name = p + 1; } strcpy(str, basepath); if (strlen(name)) { strcat(str, "/"); strcat(str, name); } char *bsl; while ((bsl = strchr(str, '\\'))) *bsl = '/'; dbg_print("Requested path: %s\n", str); if (strncmp(basepath, str, baselen)) { dbg_print("Not belonging to shared folder\n"); str[0] = 0; } else if (str[baselen] && str[baselen] != '/') { dbg_print("No / after root\n"); str[0] = 0; } else if (str[baselen]) { char *cur = str + baselen; while (*cur) { cur++; char *next = strchr(cur, '/'); if (!next) next = cur + strlen(cur); int len = next - cur; if (!len && !*next) break; if (!len || !strncmp(cur, ".", len)) { strcpy(cur, next + 1); cur--; continue; } if (!strncmp(cur, "..", len)) { cur -= 2; while (*cur != '/') cur--; if (cur < str + baselen) { printf("Going above root\n"); str[0] = 0; break; } // collapse the component strcpy(cur, next); continue; } cur = next; } } // remove trailing / int len = strlen(str); if (len && str[len - 1] == '/') str[len - 1] = 0; dbg_print("Converted path: %s\n", str); if (str[0]) { char *p = strrchr(str, '/'); if (!p) str[0] = 0; else { *p = 0; if (!PathIsDir(str, 0)) str[0] = 0; else *p = '/'; } } dbg_print("returned path: %s\n", str); return str; } static void __attribute__((noinline)) memcpyb(void *dst, const void *src, int len) { char *d = (char*)dst; char *s = (char*)src; while (len--) *d++ = *s++; } // | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Directory Attribute Flags // | | | | | | | \-- 1 = read only // | | | | | | \--- 1 = hidden // | | | | | \---- 1 = system // | | | | \----- 1 = volume label(exclusive) // | | | \------ 1 = subdirectory // | | \------- 1 = archive // \---+-------unused static uint32_t get_attr(char *path, uint16_t *time, uint16_t *date, uint32_t *size) { if (time) *time = 0; if (date) *date = 0; if (size) *size = 0; stat64 *st = getPathStat(path); if (!st) return 0; tm *t = localtime(&st->st_mtime); if (time) *time = (t->tm_sec / 2) | (t->tm_min << 5) | (t->tm_hour << 11); if (date) *date = t->tm_mday | ((t->tm_mon + 1) << 5) | ((t->tm_year - 80) << 9); if (size) *size = st->st_size; return st->st_mode; } static void name83(const char *src, char *dst) { int namelen = 0; int extlen = 0; const char *p = strrchr(src, '/'); if (p) src = p + 1; if (!strcmp(src, ".") || !strcmp(src, "..")) { namelen = strlen(src); } else { p = strrchr(src, '.'); if (!p) namelen = strlen(src); else { namelen = p - src; extlen = strlen(src) - namelen - 1; } } char ext[4] = { ' ', ' ', ' ', 0 }; if (p) memcpy(ext, p + 1, extlen); for (int i = 0; i < namelen; i++) dst[i] = toupper(src[i]); while (namelen < 8) dst[namelen++] = ' '; for (int i = 0; i < 3; i++) dst[8 + i] = toupper(ext[i]); } static int cmp_name(const char *name, const char *flt) { int namelen = 0; int extlen = 0; const char *ext = strrchr(name, '.'); if (!ext) { namelen = strlen(name); ext = name + namelen; } else { namelen = ext - name; ext++; extlen = strlen(ext); } if (namelen > 8 || extlen > 3) return 0; char testname[16]; char fltname[16]; name83(name, testname); name83(flt, fltname); testname[11] = 0; fltname[11] = 0; char *cmpname = fltname; char *cmpend = fltname + 8; char *cur = testname; while (cmpname < cmpend) { if (*cmpname == '?') { cmpname++; cur++; } else if (*cmpname == '*') { break; } else if (*cmpname++ != *cur++) { return 0; } } cmpname = fltname + 8; cmpend = fltname + 11; cur = testname + 8; while (cmpname < cmpend) { if (*cmpname == '?') { cmpname++; cur++; } else if (*cmpname == '*') { break; } else if (*cmpname++ != *cur++) { return 0; } } return 1; } static int process_request(void *reqres_buffer) { static char str[1024]; int len = *(unsigned short*)reqres_buffer; char func = ((char*)reqres_buffer)[4]; dbg_hexdump(reqres_buffer, 8, 0); if(func != AL_WRITE) dbg_hexdump((char*)reqres_buffer+8, len, 8); short res = -1; short reslen = 0; unsigned short idx = 0; short key = 0; if (!baselen) { if (strlen(cfg.shared_folder)) { if (cfg.shared_folder[0] == '/') strcpy(basepath, cfg.shared_folder); else { strcpy(basepath, HomeDir()); strcat(basepath, "/"); strcat(basepath, cfg.shared_folder); } } else { strcpy(basepath, HomeDir()); strcat(basepath, "/shared"); } baselen = strlen(basepath); if (baselen && basepath[baselen - 1] == '/') { basepath[baselen - 1] = 0; baselen--; } if (baselen) FileCreatePath(basepath); } char *buf = ((char*)reqres_buffer) + 8; buf[len] = 0; switch (func) { case AL_RMDIR: { dbg_print("> AL_RMDIR\n"); char *path = find_path(buf); if (!*path) { res = 3; break; } if(!DirDelete(path)) { dbg_print("Cannot delete dir %s\n", path); res = 29; break; } res = 0; } break; case AL_MKDIR: { dbg_print("> AL_MKDIR\n"); char *path = find_path(buf); if (!*path) { res = 3; break; } if (!FileCreatePath(path)) { res = 29; break; } res = 0; } break; case AL_CHDIR: { dbg_print("> AL_CHDIR\n"); char *path = find_path(buf); if (!*path || !PathIsDir(path)) { res = 3; break; } res = 0; } break; case AL_OPEN: { dbg_print("> AL_OPEN\n"); uint16_t attr = *(uint16_t *)buf; char *path = find_path(buf + 6); if (!*path) { res = 3; break; } if (!FileExists(path, 0)) { res = 2; break; } int mode = attr & 3; short key = get_fp(); open_file_handles[key] = {}; if (!FileOpenEx(&open_file_handles[key], path, mode, 0, 0)) { open_file_handles.erase(key); res = 5; break; } dbg_print("opened handle: %d\n", key); *buf++ = 0; name83(path, buf); buf += 11; get_attr(path, (uint16_t*)buf, (uint16_t*)(buf + 2), (uint32_t*)(buf + 4)); buf += 8; *buf++ = key; *buf++ = key >> 8; *buf++ = 0; *buf++ = 0; *buf++ = mode; /* Bit 0-2 access mode 000=read 001=write 010=read/write 4-6 sharing mode 000=compatibility 001=deny read/write 010=deny write 011=deny read 100=deny none 13 critical error handling 0=execute INT 24 1=return error code 14 buffering 0=buffer writes 1=don't buffer writes 15 1=FCB SFT */ reslen = 25; res = 0; } break; case AL_CREATE: { dbg_print("> AL_CREATE\n"); char *path = find_path(buf + 6); if (!*path) { res = 3; break; } int mode = O_RDWR | O_CREAT | O_TRUNC; short key = get_fp(); open_file_handles[key] = {}; if (!FileOpenEx(&open_file_handles[key], path, mode, 0, 0)) { open_file_handles.erase(key); res = 5; break; } dbg_print("opened handle: %d\n", key); *buf++ = 0; name83(path, buf); buf += 11; get_attr(path, (uint16_t*)buf, (uint16_t*)(buf + 2), (uint32_t*)(buf + 4)); buf += 8; *buf++ = key; *buf++ = key >> 8; *buf++ = 0; *buf++ = 0; *buf++ = 2; reslen = 25; res = 0; } break; case AL_SPOPEN: { dbg_print("> AL_SPOPEN\n"); /* actioncode contains instructions about how to behave... * high nibble = action if file does NOT exist: * 0000 fail * 0001 create * low nibble = action if file DOES exist * 0000 fail * 0001 open * 0010 truncate/open */ uint16_t attr = *(uint16_t *)buf; uint16_t actioncode = *(uint16_t *)(buf + 2); uint16_t openmode = *(uint16_t *)(buf + 4); char *path = find_path(buf + 6); if (!*path) { res = 3; break; } int mode = openmode & 0x3; uint16_t spopres = 0; if (FileExists(path, 0)) { if ((actioncode & 0xF) == 1) { spopres = 1; } else if ((actioncode & 0xF) == 2) { mode = O_RDWR | O_TRUNC; spopres = 3; } else { res = 5; break; } } else { // some copiers create an empty file with hidden attribute and then fail if found non-hidden file // so prohibit to create a hidden file. if ((actioncode & 0xF0) == 0x10 && !(attr & FAT_HID)) { mode = O_RDWR | O_CREAT; spopres = 2; } else { res = 2; break; } } key = get_fp(); open_file_handles[key] = {}; if (!FileOpenEx(&open_file_handles[key], path, mode, 0, 0)) { open_file_handles.erase(key); res = 5; break; } dbg_print("opened handle: %d\n", key); *buf++ = 0; name83(path, buf); buf += 11; get_attr(path, (uint16_t*)buf, (uint16_t*)(buf + 2), (uint32_t*)(buf + 4)); // 12 14 16 buf += 8; *buf++ = key; *buf++ = key >> 8; *buf++ = spopres; *buf++ = spopres >> 8; *buf++ = openmode & 0x7f; reslen = 25; res = 0; } break; case AL_CLOSE: { dbg_print("> AL_CLOSE\n"); key = *(short *)buf; if (open_file_handles.find(key) != open_file_handles.end()) { FileClose(&open_file_handles[key]); open_file_handles.erase(key); dbg_print("closed handle: %d\n", key); } reslen = 0; res = 0; } break; case AL_READ: { dbg_print("> AL_READ\n"); key = buf[4] | (buf[5] << 8); if (open_file_handles.find(key) == open_file_handles.end()) { res = 5; break; } uint32_t off; memcpyb(&off, buf, 4); uint16_t sz = buf[6] | (buf[7] << 8); dbg_print(" read %d bytes at %d\n", sz, off); FileSeek(&open_file_handles[key], off, SEEK_SET); int read = FileReadAdv(&open_file_handles[key], buf, sz, -1); if (read < 0) { res = 5; break; } dbg_print(" was read %d\n", read); reslen = read; res = 0; } break; case AL_WRITE: { dbg_print("> AL_WRITE\n"); key = buf[4] | (buf[5] << 8); if (open_file_handles.find(key) == open_file_handles.end()) { res = 5; break; } uint32_t off; memcpyb(&off, buf, 4); uint16_t sz = buf[6] | (buf[7] << 8); dbg_print(" write %d bytes at %d\n", sz, off); FileSeek(&open_file_handles[key], off, SEEK_SET); int written = 0; if (sz) { written = FileWriteAdv(&open_file_handles[key], buf + 8, sz); if (!written) { res = 5; break; } } dbg_print(" written %d\n", written); *buf++ = written; *buf++ = written >> 8; reslen = 2; res = 0; } break; case AL_LOCK: { dbg_print("> AL_LOCK\n"); reslen = 0; res = 0; } break; case AL_DISKSPACE: { dbg_print("> AL_DISKSPACE\n"); uint32_t total = 10; uint32_t avail = 1; // Set the sector size to the maximum size supported by FAT16: // 1024 MB - 2047 MB: 64 Sectors/Cluster, 32K Cluster Size // // This allows for MS-DOS version that only support up to 64 // Sectors/Cluster to display a non-zero value for disk space remaning. uint32_t spc = 64; // Sectors/Cluster (normally based on partition size) uint32_t bps = 512; // Bytes/Sector (this value is not adjusted) struct statvfs st; if (!statvfs(getFullPath(basepath), &st)) { // Calculate the size available and remaining as reported from Linux uint64_t sz = st.f_bsize * st.f_blocks; uint64_t av = st.f_bsize * st.f_bavail; // Convert those values into cluster sizes. total = sz / (bps * spc); avail = av / (bps * spc); } // Limit the total and available sizes to the maximum supported size // based on the cluster size. (In our case 2GB at 32KB clusters) if (total > UINT16_MAX) total = UINT16_MAX; if (avail > UINT16_MAX) avail = UINT16_MAX; *buf++ = total; *buf++ = total >> 8; *buf++ = bps; *buf++ = bps >> 8; *buf++ = avail; *buf++ = avail >> 8; reslen = 6; res = spc; } break; case AL_SETATTR: { dbg_print("> AL_SETATTR\n"); reslen = 0; res = 0; } break; case AL_GETATTR: { dbg_print("> AL_GETATTR\n"); char *path = find_path(buf); if (!*path) { res = 2; break; } uint16_t time, date; uint32_t sz; uint32_t mode = get_attr(path, &time, &date, &sz); if (!mode) { res = 2; break; } *buf++ = time; *buf++ = time >> 8; *buf++ = date; *buf++ = date >> 8; *buf++ = sz; *buf++ = sz >> 8; *buf++ = sz >> 16; *buf++ = sz >> 24; *buf++ = (mode & S_IFDIR) ? FAT_DIR : 0; *buf++ = 0; res = 0; reslen = 10; } break; case AL_RENAME: { dbg_print("> AL_RENAME\n"); int srclen = (*buf++) & 0xFF; char *path = find_path(buf + srclen); if (!*path) { res = 3; break; } strcpy(str, getFullPath(path)); buf[srclen] = 0; path = find_path(buf); if (!*path) { res = 2; break; } if (rename(getFullPath(path), str)) { res = 5; break; } res = 0; } break; case AL_DELETE: { dbg_print("> AL_DELETE\n"); char *path = find_path(buf); if (!*path) { res = 3; break; } if(strchr(path, '?') || strchr(path, '*')) { res = 2; break; } if (!FileDelete(path)) { res = 2; break; } res = 0; } break; case AL_FINDFIRST: { dbg_print("> AL_FINDFIRST\n"); const uint16_t token = ((uint16_t *)buf)[0]; char attr = buf[2]; char *path = find_path(buf+3); if (!*path) { res = 0x12; break; } char *flt = strrchr(path, '/'); if (!*flt) { res = 0x12; break; } *flt++ = 0; key = add_lock(token); const char* full_path = getFullPath(path); DIR *d = opendir(full_path); if (!d) { locks.erase(key); printf("Couldn't open dir: %s\n", full_path); res = 0x12; break; } if (attr == 8) { struct dirent64 de = {}; strcpy(de.d_name, "MiSTer"); locks[key].dir_items.push_back({ de, {} }); *buf++ = 8; memcpyb(buf, "MiSTer ", 11); buf += 11; *buf++ = 0; *buf++ = 0; // time; *buf++ = 0; *buf++ = 0; // date; *buf++ = 0; *buf++ = 0; *buf++ = 0; *buf++ = 0; // size; *buf++ = key; *buf++ = key >> 8; *buf++ = 0; *buf++ = 0; res = 0; reslen = 24; break; } else { struct dirent64 de, *de2; while ((de2 = readdir64(d))) { if (de2->d_type == DT_REG || (attr & FAT_DIR)) { memcpy(&de, de2, sizeof(dirent64)); sprintf(str, "%s/%s", path, de.d_name); stat64 *st = getPathStat(str); if (st && cmp_name(de.d_name, flt)) { name83(de2->d_name, de.d_name); de.d_name[11] = 0; locks[key].dir_items.push_back({ de, *st }); } } } closedir(d); } } // fall through case AL_FINDNEXT: { if (func == AL_FINDNEXT) { dbg_print("> AL_FINDNEXT\n"); key = *(short *)buf; idx = *(short *)(buf+2); idx++; if (locks.find(key) == locks.end()) { dbg_print("Key %d not found\n", key); res = 0x12; break; } } if (idx >= locks[key].dir_items.size()) { if (locks.find(key) != locks.end()) locks.erase(key); dbg_print("No more items\n"); res = 0x12; break; } *buf++ = (locks[key].dir_items[idx].de.d_type == DT_DIR) ? FAT_DIR : 0; memcpyb(buf, locks[key].dir_items[idx].de.d_name, 11); buf += 11; tm *t = localtime(&locks[key].dir_items[idx].st.st_mtime); uint16_t time = (t->tm_sec / 2) | (t->tm_min << 5) | (t->tm_hour << 11); uint16_t date = t->tm_mday | ((t->tm_mon + 1) << 5) | ((t->tm_year - 80) << 9); *buf++ = time; *buf++ = time >> 8; *buf++ = date; *buf++ = date >> 8; memcpyb(buf, &locks[key].dir_items[idx].st.st_size, 4); buf += 4; *buf++ = key; *buf++ = key >> 8; *buf++ = idx; *buf++ = idx >> 8; res = 0; reslen = 24; } break; case AL_SKFMEND: { dbg_print("> AL_SKFMEND\n"); key = buf[4] | (buf[5] << 8); if (open_file_handles.find(key) == open_file_handles.end()) { res = 2; break; } int32_t off; memcpyb(&off, buf, 4); int32_t sz = open_file_handles[key].size; off += sz; if (off < 0) off = 0; memcpyb(buf, &off, 4); res = 0; reslen = 4; } break; case AL_QUALIFY: { dbg_print("> AL_QUALIFY\n"); res = 0; reslen = 128; } break; } ((short *)reqres_buffer)[0] = reslen; ((short *)reqres_buffer)[2] = res; dbg_print("result %d, %d:\n", reslen, res); dbg_hexdump(reqres_buffer, 8, 0); if (reslen > 0 && func != AL_READ) { dbg_hexdump((char*)reqres_buffer + 8, reslen, 8); } return reslen; } void x86_share_poll() { if (!shmem) { shmem = (uint8_t *)shmem_map(SHMEM_ADDR, SHMEM_SIZE); if (!shmem) shmem = (uint8_t *)-1; } else if (shmem != (uint8_t *)-1) { static uint32_t old_req_id = 0; uint32_t req_id = *(uint32_t*)(shmem + REQUEST_FLG); if ((uint16_t)old_req_id != (uint16_t)req_id) { dbg_print("\nnew req: %08X\n", req_id); old_req_id = req_id; if (((req_id >> 16) & 0xFFFF) == 0xA55A && ((req_id + 77) & 0xFF) == ((req_id >> 8) & 0xFF)) { process_request(shmem + REQUEST_BUFFER); *(uint16_t*)(shmem + REQUEST_FLG + 2) = (uint16_t)req_id; } } } } void x86_share_reset() { open_file_handles.clear(); locks.clear(); next_fp = 1; next_key = 1; }