#include #include #include #include #include #include #include #include #include #include #include #include "../../hardware.h" #include "../../file_io.h" #include "../../user_io.h" #include "../../spi.h" #include "../../cfg.h" #include "../../shmem.h" #include "miminig_fs_messages.h" #define SHMEM_ADDR 0x27FF4000 #define SHMEM_SIZE 0x2000 static uint8_t *shmem = 0; #define REQUEST_FLG 0 // 4B #define REQUEST_BUFFER 4 // ~512B #define DATA_BUFFER 0x1000 // 4KB // Must match device name in MountList and volume name from MiSTerFileSystem #define DEVICE_NAME "SHARE" #define VOLUME_NAME "MiSTer" //#define DEBUG #ifdef DEBUG #define dbg_print printf #define dbg_hexdump hexdump #else #define dbg_print(x,...) void() #define dbg_hexdump(x,...) void() #endif #define SWAP_INT(a) ((((a)&0x000000ff)<<24)|(((a)&0x0000ff00)<<8)|(((a)&0x00ff0000)>>8)|(((a)&0xff000000)>>24)) static char basepath[1024] = {}; static int baselen = 0; struct lock { uint16_t mode; std::string path; std::vector dir_items; }; static std::map locks; static uint32_t next_key = 1; static uint32_t get_key() { uint32_t key; do { key = next_key; if (next_key == INT32_MAX) next_key = 1; else next_key++; } while (locks.find(key) != locks.end()); return key; } static uint32_t add_lock(uint16_t mode, const char* path) { uint32_t key = get_key(); locks[key] = { mode, path, {} }; dbg_print("+ add lock: %d, %s\n", key, path); return key; } static int has_locks(const char* path) { int has = 0; for (const auto &pair : locks) { if (pair.second.path == path) { if (!has) { dbg_print("! path %s has locks:", path); } has = 1; dbg_print(" %d", pair.first); } } if (has) { dbg_print("\n"); } return has; } static std::map open_file_handles; static uint32_t next_fp = 1; static uint32_t get_fp() { uint32_t fp; do { fp = next_fp; if (next_fp == INT32_MAX) next_fp = 1; else next_fp++; } while (open_file_handles.find(fp) != open_file_handles.end()); return fp; } static char* find_path(uint32_t key, const char *name) { dbg_print("find_path(%d, %s)\n", key, name); static char str[1024] = {}; const char* p = strchr(name, ':'); if (p) { size_t root_len = p - name; // Don't use lock for relative path if the name contains our root if (root_len == 0 || !strncasecmp(name, DEVICE_NAME, root_len) || !strncasecmp(name, VOLUME_NAME, root_len)) key = 0; name = p + 1; } strcpy(str, basepath); if (key) { auto it = locks.find(key); if (it != locks.end()) strcpy(str, it->second.path.c_str()); } if (strlen(name)) { strcat(str, "/"); strcat(str, name); } 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 && (!strncmp(cur, ".", len) || !strncmp(cur, "..", len))) { dbg_print("Illegal path\n"); str[0] = 0; break; } // end if (!*next) break; if (!len) { // / alone is used for parent cur -= 2; while (*cur != '/') cur--; if (cur < str + baselen) { dbg_print("Going above root\n"); str[0] = 0; break; } // collapse the component strcpy(cur, next); } else { 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 fill_date(time_t time, int date[3]) { time_t days = time / 86400; time_t left = time - (days * 86400); time_t mins = left / 60; time_t secs = left - (mins * 60); time_t ticks = secs * 50; days -= 2922; // Days between 1970 - 01 - 01 and 1978 - 01 - 01 if (days < 0) days = 0; date[0] = SWAP_INT(days); date[1] = SWAP_INT(mins); date[2] = SWAP_INT(ticks); } static int process_request(void *reqres_buffer) { static char buf[1024]; GenericRequestResponse *reqres = ( GenericRequestResponse *)reqres_buffer; int rtype = SWAP_INT(reqres->type); int ret = ERROR_ACTION_NOT_KNOWN; int sz = SWAP_INT(reqres->sz); ((uint8_t*)reqres_buffer)[sz] = 0; int sz_res = sizeof(GenericRequestResponse); dbg_print("request type: %d, struct size: %d\n", rtype, sz); dbg_hexdump(reqres_buffer, sz, 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); } // no base path => force fail if (!baselen) rtype = ACTION_NIL; switch (rtype) { case ACTION_LOCATE_OBJECT: { dbg_print("> ACTION_LOCATE_OBJECT\n"); LocateObjectRequest *req = (LocateObjectRequest*)reqres_buffer; LocateObjectResponse *res = (LocateObjectResponse*)reqres_buffer; sz_res = sizeof(LocateObjectResponse); char *str = find_path(SWAP_INT(req->key), req->name + 1); if (!str[0]) { ret = ERROR_OBJECT_NOT_FOUND; break; } if (!FileExists(str, 0) && !PathIsDir(str, 0)) { ret = ERROR_OBJECT_NOT_FOUND; break; } uint32_t key = add_lock(req->mode, str); res->key = SWAP_INT(key); ret = 0; } break; case ACTION_FREE_LOCK: { dbg_print("> ACTION_FREE_LOCK\n"); FreeLockRequest *req = (FreeLockRequest*)reqres_buffer; uint32_t key = SWAP_INT(req->key); locks.erase(key); dbg_print(" lock: %d\n", key); ret = 0; } break; case ACTION_COPY_LOCK: { dbg_print("> ACTION_COPY_LOCK\n"); CopyDirRequest *req = (CopyDirRequest*)reqres_buffer; CopyDirResponse *res = (CopyDirResponse*)reqres_buffer; sz_res = sizeof(CopyDirResponse); uint32_t key = SWAP_INT(req->key); if (locks.find(key) == locks.end()) { ret = ERROR_OBJECT_NOT_FOUND; break; } uint32_t new_key = add_lock(locks[key].mode, locks[key].path.c_str()); dbg_print("CopyDir: %s: %d -> %d\n", locks[new_key].path.c_str(), key, new_key); res->key = SWAP_INT(new_key); ret = 0; } break; case ACTION_PARENT: { dbg_print("> ACTION_PARENT\n"); ParentRequest *req = (ParentRequest*)reqres_buffer; ParentResponse *res = (ParentResponse*)reqres_buffer; sz_res = sizeof(ParentResponse); uint32_t key = SWAP_INT(req->key); dbg_print(" current key: %d\n", key); if (locks.find(key) == locks.end()) { ret = ERROR_OBJECT_NOT_FOUND; break; } ret = 0; res->key = 0; char *name = buf; strcpy(name, locks[key].path.c_str()); dbg_print(" current path: %s\n", name); if (!strncasecmp(basepath, name, baselen)) name += baselen; if (strlen(name)) { char* p = strrchr(name, '/'); if (p) { *p = 0; uint32_t key = add_lock(SHARED_LOCK, buf); res->key = SWAP_INT(key); dbg_print(" parent path: %s\n", buf); } } } break; case ACTION_EXAMINE_NEXT: case ACTION_EXAMINE_OBJECT: { dbg_print("> ACTION_EXAMINE\n"); ExamineObjectRequest *req = (ExamineObjectRequest*)reqres_buffer; ExamineObjectResponse *res = (ExamineObjectResponse*)reqres_buffer; sz_res = sizeof(ExamineObjectResponse); uint32_t key = SWAP_INT(req->key); dbg_print(" key: %d\n", key); char *name = buf; name[0] = 0; if (locks.find(key) != locks.end()) strcpy(name, locks[key].path.c_str()); if (!strlen(name)) strcpy(name, basepath); int disk_key = 666; static char fn[256]; if (rtype == ACTION_EXAMINE_OBJECT) { dbg_print(" examine first\n"); if (!strlen(name + baselen)) strcpy(fn, "MiSTer"); else { const char *p = strrchr(name, '/'); strcpy(fn, p ? p + 1 : name); } locks[key].dir_items.clear(); if (PathIsDir(name, 0)) { const char* full_path = getFullPath(name); DIR *d = opendir(full_path); if (!d) { printf("Couldn't open dir: %s\n", full_path); ret = ERROR_OBJECT_WRONG_TYPE; break; } struct dirent64 *de; while ((de = readdir64(d))) { if (!strcmp(de->d_name, "..") || !strcmp(de->d_name, ".")) continue; locks[key].dir_items.push_back(*de); } closedir(d); } } else { dbg_print(" examine next\n"); disk_key = SWAP_INT(req->disk_key); uint32_t listed = disk_key - 666; disk_key++; if (listed >= locks[key].dir_items.size()) { locks[key].dir_items.clear(); ret = ERROR_NO_MORE_ENTRIES; break; } strcat(name, "/"); strcat(name, locks[key].dir_items[listed].d_name); memcpy(fn, locks[key].dir_items[listed].d_name, sizeof(fn)); ret = 0; } dbg_print(" name: %s\n", name); dbg_print(" fn: %s\n", fn); int type = 0; if (FileExists(name, 0)) type = ST_FILE; else if (PathIsDir(name, 0)) type = ST_USERDIR; else { ret = ERROR_OBJECT_NOT_FOUND; break; } time_t time = 0; uint32_t size = 0; struct stat64 *st = getPathStat(name); if (st) { time = st->st_mtime; if (type == ST_FILE) { if (st->st_size > UINT32_MAX) size = UINT32_MAX; else size = (uint32_t)st->st_size; } } res->disk_key = SWAP_INT(disk_key); res->entry_type = SWAP_INT(type); res->size = SWAP_INT(size); res->protection = 0; fill_date(time, res->date); res->file_name[0] = strlen(fn); strcpy(res->file_name + 1, fn); sz_res = sizeof(ExamineObjectResponse) + strlen(fn); ret = 0; } break; case ACTION_EXAMINE_FH: { dbg_print("> ACTION_EXAMINE_FH\n"); ExamineFhRequest *req = (ExamineFhRequest*)reqres_buffer; ExamineFhResponse *res = (ExamineFhResponse*)reqres_buffer; sz_res = sizeof(ExamineFhResponse); uint32_t key = SWAP_INT(req->arg1); dbg_print(" key: %d\n", key); if (open_file_handles.find(key) == open_file_handles.end()) { ret = ERROR_OBJECT_NOT_FOUND; break; } const char *fn = open_file_handles[key].name; int disk_key = 666; int type = 0; time_t time = 0; uint32_t size = 0; struct stat64 st; if (fstat64(fileno(open_file_handles[key].filp), &st) == 0) { time = st.st_mtime; if (st.st_mode & S_IFDIR) type = ST_USERDIR; else { type = ST_FILE; if (st.st_size > UINT32_MAX) size = UINT32_MAX; else size = (uint32_t)st.st_size; } } else { dbg_print("Couldn't stat %s: %d\n", fn, errno); ret = ERROR_OBJECT_NOT_FOUND; break; } dbg_print(" fn: %s\n", fn); dbg_print(" size: %lld\n", open_file_handles[key].size); dbg_print(" type: %d\n", type); res->disk_key = SWAP_INT(disk_key); res->entry_type = SWAP_INT(type); res->size = SWAP_INT(size); res->protection = 0; fill_date(time, res->date); res->file_name[0] = strlen(fn); strcpy(res->file_name + 1, fn); sz_res = sizeof(ExamineFhResponse) + strlen(fn); ret = 0; } break; case ACTION_FINDINPUT: // MODE_OLDFILE case ACTION_FINDOUTPUT: // MODE_NEWFILE case ACTION_FINDUPDATE: // MODE_READWRITE { dbg_print("> ACTION_FIND\n"); FindXxxRequest *req = (FindXxxRequest*)reqres_buffer; FindXxxResponse *res = (FindXxxResponse*)reqres_buffer; sz_res = sizeof(FindXxxResponse); char *name = find_path(SWAP_INT(req->key), req->name + 1); if (!name[0]) { ret = ERROR_OBJECT_NOT_FOUND; break; } if (PathIsDir(name, 0)) { ret = ERROR_OBJECT_WRONG_TYPE; break; } uint32_t key = get_fp(); open_file_handles[key] = {}; int mode = O_RDWR; if (rtype == MODE_NEWFILE) mode = O_RDWR | O_CREAT | O_TRUNC; if (rtype == MODE_READWRITE) mode = O_RDWR | O_CREAT; ret = FileOpenEx(&open_file_handles[key], name, mode, 0, 0); if (!ret) { open_file_handles.erase(key); ret = ERROR_OBJECT_NOT_FOUND; break; } res->arg1 = SWAP_INT(key); ret = 0; } break; case ACTION_READ: { dbg_print("> ACTION_READ\n"); ReadRequest *req = (ReadRequest*)reqres_buffer; ReadResponse *res = (ReadResponse*)reqres_buffer; sz_res = sizeof(ReadResponse); uint32_t key = SWAP_INT(req->arg1); if (open_file_handles.find(key) == open_file_handles.end()) { ret = ERROR_OBJECT_NOT_FOUND; break; } DISKLED_ON; uint32_t length = SWAP_INT(req->length); length = FileReadAdv(&open_file_handles[key], shmem + DATA_BUFFER, length); res->actual = SWAP_INT(length); ret = 0; } break; case ACTION_WRITE: { dbg_print("> ACTION_WRITE\n"); WriteRequest *req = (WriteRequest*)reqres_buffer; WriteResponse *res = (WriteResponse*)reqres_buffer; sz_res = sizeof(WriteResponse); uint32_t key = SWAP_INT(req->arg1); if (open_file_handles.find(key) == open_file_handles.end()) { ret = ERROR_OBJECT_NOT_FOUND; break; } DISKLED_ON; uint32_t length = SWAP_INT(req->length); length = FileWriteAdv(&open_file_handles[key], shmem + DATA_BUFFER, length); res->actual = SWAP_INT(length); ret = 0; } break; case ACTION_SEEK: { dbg_print("> ACTION_SEEK\n"); SeekRequest *req = (SeekRequest*)reqres_buffer; SeekResponse *res = (SeekResponse*)reqres_buffer; sz_res = sizeof(SeekResponse); uint32_t key = SWAP_INT(req->arg1); if (open_file_handles.find(key) == open_file_handles.end()) { ret = ERROR_OBJECT_NOT_FOUND; break; } int old_pos = open_file_handles[key].offset; int new_pos = SWAP_INT(req->new_pos); int mode = SWAP_INT(req->mode); int origin = SEEK_SET; if (mode == OFFSET_CURRENT) origin = SEEK_CUR; if (mode == OFFSET_END) origin = SEEK_END; ret = FileSeek(&open_file_handles[key], new_pos, origin); dbg_print(" mode: %d\n", mode); dbg_print(" old_pos: %d\n", old_pos); dbg_print(" new_pos: %d\n", new_pos); if (!ret) { ret = ERROR_SEEK_ERROR; break; } res->old_pos = SWAP_INT(old_pos); ret = 0; } break; case ACTION_END: { dbg_print("> ACTION_END\n"); EndRequest *req = (EndRequest*)reqres_buffer; uint32_t key = SWAP_INT(req->arg1); if (open_file_handles.find(key) != open_file_handles.end()) { FileClose(&open_file_handles[key]); open_file_handles.erase(key); } ret = 0; } break; case ACTION_DELETE_OBJECT: { dbg_print("> ACTION_DELETE_OBJECT\n"); DeleteObjectRequest *req = (DeleteObjectRequest*)reqres_buffer; char *name = find_path(SWAP_INT(req->key), req->name + 1); if (name[0]) { if (has_locks(name)) { ret = ERROR_OBJECT_IN_USE; break; } DISKLED_ON; if (PathIsDir(name, 0)) { ret = DirDelete(name) ? 0 : ERROR_DIRECTORY_NOT_EMPTY; break; } if (FileExists(name, 0)) { ret = FileDelete(name) ? 0 : ERROR_OBJECT_NOT_FOUND; break; } } ret = ERROR_OBJECT_NOT_FOUND; } break; case ACTION_RENAME_OBJECT: { dbg_print("> ACTION_RENAME_OBJECT\n"); RenameObjectRequest *req = (RenameObjectRequest*)reqres_buffer; strncpy(buf, req->name, req->name_len); buf[req->name_len] = 0; uint32_t key = SWAP_INT(req->key); char *cp1 = find_path(key, buf); if (!cp1[0]) { ret = ERROR_OBJECT_NOT_FOUND; break; } if (!FileExists(cp1, 0) && !PathIsDir(cp1, 0)) { ret = ERROR_OBJECT_NOT_FOUND; break; } strcpy(buf, cp1); key = SWAP_INT(req->target_dir); char *cp2 = find_path(key, req->name + req->name_len); if (!cp2[0]) { ret = ERROR_OBJECT_NOT_FOUND; break; } strcpy(buf, getFullPath(buf)); const char *fp2 = getFullPath(cp2); // Identical match; do nothing if (!strcmp(buf, fp2)) { ret = 0; break; } if (FileExists(cp2, 0) || PathIsDir(cp2, 0)) { ret = ERROR_OBJECT_EXISTS; break; } DISKLED_ON; if (rename(buf, fp2)) { ret = ERROR_OBJECT_NOT_FOUND; break; } ret = 0; } break; case ACTION_CREATE_DIR: { dbg_print("> ACTION_CREATE_DIR\n"); CreateDirRequest *req = (CreateDirRequest*)reqres_buffer; CreateDirResponse *res = (CreateDirResponse*)reqres_buffer; sz_res = sizeof(CreateDirResponse); DISKLED_ON; char *name = find_path(SWAP_INT(req->key), req->name + 1); if (!FileCreatePath(name)) { ret = ERROR_OBJECT_NOT_FOUND; break; } uint32_t key = add_lock(SHARED_LOCK, name); res->key = SWAP_INT(key); ret = 0; } break; case ACTION_DISK_INFO: case ACTION_INFO: { dbg_print("> ACTION_INFO\n"); DiskInfoResponse *res = (DiskInfoResponse*)reqres_buffer; sz_res = sizeof(DiskInfoResponse); uint32_t total = 10; uint32_t used = 1; struct statvfs st; if (!statvfs(getFullPath(basepath), &st)) { uint64_t sz = st.f_bsize * st.f_blocks; uint64_t avail = st.f_bsize * st.f_bavail; total = sz / 512; used = (sz - avail) / 512; } res->total = SWAP_INT(total); res->used = SWAP_INT(used); ret = 0; } break; case ACTION_SET_PROTECT: { dbg_print("> ACTION_SET_PROTECT unimplemented\n"); ret = 0; } break; case ACTION_SET_COMMENT: { dbg_print("> ACTION_SET_COMMENT unimplemented\n"); ret = 0; } break; case ACTION_SAME_LOCK: { dbg_print("> ACTION_SAME_LOCK\n"); SameLockRequest *req = (SameLockRequest*)reqres_buffer; uint32_t key1 = SWAP_INT(req->key1); uint32_t key2 = SWAP_INT(req->key2); if ((locks.find(key1) == locks.end()) || (locks.find(key2) == locks.end())) { ret = LOCK_DIFFERENT; break; } if (locks[key1].path == locks[key2].path) { ret = LOCK_SAME; break; } ret = LOCK_SAME_VOLUME; } break; } int success = ret ? 0 : 1; reqres->success = SWAP_INT(success); reqres->error_code = SWAP_INT(ret); dbg_print("error: %d\n", ret); dbg_hexdump(shmem + REQUEST_BUFFER, sz_res, 0); dbg_print("\n"); return sz_res; } void minimig_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("new req: %08X\n", req_id); old_req_id = req_id; if (((req_id>>16) & 0xFFFF) == 0x5AA5 && ((req_id - 77) & 0xFF) == ((req_id >> 8) & 0xFF)) { process_request(shmem + REQUEST_BUFFER); *(uint16_t*)(shmem + REQUEST_FLG + 2) = (uint16_t)req_id; } } } } void minimig_share_reset() { open_file_handles.clear(); locks.clear(); next_fp = 1; next_key = 1; }