/* * JR-IDE Project * - (c) 2017 Alan Hightower * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "netpi.h" #include typedef struct _st_redirect { char *root; // Top host path for mount point char *cwsd; // Current working sub-directory redir_stat_t statinfo; int od_total; // Open directory total entries int od_alloc; // Open directory allocated entries int od_current; // Open directory next entry to serve redir_dentry_t *od_entry; // List of translated paths } redirect_t; static redirect_t redir; int init_redirector (void) { memset (&redir, 0, sizeof (redirect_t)); redir.root = strdup ("/mnt"); redir.cwsd = strdup (""); return 0; } static void _posix_to_dos_time (time_t tv_sec, uint16_t *dos_time, uint16_t *dos_date) { struct tm ltime; localtime_r(&tv_sec, <ime); *dos_time = ((ltime.tm_hour & 0x1f) << 11) | ((ltime.tm_min & 0x3f) << 5) | ((ltime.tm_sec / 2) & 0x1f); *dos_date = (((ltime.tm_year - 80) & 0x7f) << 9) | (((ltime.tm_mon + 1) & 0x0f) << 5) | (ltime.tm_mday & 0x1f); } void _redir_build_dirlist (char *path, char *mask, uint8_t attrib) { LOGDBG("Building directory list for '%s' (%s)/0x%02x\n", path, mask, attrib); redir.od_total = 0; redir.od_alloc = 128; redir.od_current = 0; redir.od_entry = (redir_dentry_t *) malloc (sizeof (redir_dentry_t) * redir.od_alloc); DIR *dir = opendir (path); if (!dir) return; struct dirent *entry; while ((entry = readdir (dir))) { if (entry->d_name[0] == '.') continue; if ((entry->d_type != DT_DIR) && (entry->d_type != DT_REG)) continue; char *ptr = strchr (entry->d_name, '.'); if (ptr && ((ptr - entry->d_name) > 8)) continue; if (ptr && (strlen (ptr + 1) > 3)) continue; if (ptr && strchr (ptr + 1, '.')) continue; char tmp[4096]; strcpy (tmp, path); strcat (tmp, "/"); strcat (tmp, entry->d_name); struct stat stinfo; if (stat (tmp, &stinfo) != 0) continue; int i = 0, j = 0, valid = 1; while ((i < 11) && entry->d_name[j] && valid) { switch (entry->d_name[j]) { case '-': case '_': case '0' ... '9': case 'A' ... 'Z': tmp[i++] = entry->d_name[j++]; break; case 'a' ... 'z': tmp[i++] = entry->d_name[j++] - 'a' + 'A'; break; case '.': while (i != 8) tmp[i++] = ' '; j++; break; default: valid = 0; break; } } if (!valid) continue; while (i != 11) tmp[i++] = ' '; tmp[i] = '\0'; for (i = 0; i < 11; i++) { if ((mask[i] != '?') && (mask[i] != tmp[i])) break; } if (i != 11) continue; for (i = 0; i < redir.od_total; i++) { if (memcmp (redir.od_entry[i].filename, tmp, 11) == 0) break; } if (i != redir.od_total) continue; if ((redir.od_total + 1) >= redir.od_alloc) { redir.od_alloc *= 2; redir.od_entry = (redir_dentry_t *) realloc (redir.od_entry, sizeof (redir_dentry_t) * redir.od_alloc); } memcpy (redir.od_entry[redir.od_total].filename, tmp, 11); redir.od_entry[redir.od_total].attrib = (entry->d_type == DT_DIR) ? 0x10 : 0x00; redir.od_entry[redir.od_total].size = stinfo.st_size; _posix_to_dos_time (stinfo.st_mtim.tv_sec, &redir.od_entry[redir.od_total].dos_time, &redir.od_entry[redir.od_total].dos_date); LOGDBG("Adding '%s'\n", tmp); redir.od_total++; } closedir(dir); // Add nul trailing entry memset (&redir.od_entry[redir.od_total], 0, sizeof (redir_dentry_t)); redir.od_total++; return; } static int _lookup_sub (char *path, char *filename) { LOGDBG("PATH %s\n", path); char *ptr = strrchr (filename, '/'); char *remain = NULL; if (ptr) { *ptr = '\0'; if (ptr[1]) remain = ptr + 1; } if (!remain && !filename[0]) return 0x10; DIR *dirp = opendir (path); if (!dirp) return -1; struct dirent *entry; while ((entry = readdir (dirp))) { LOGDBG("ITTER %s %s\n", entry->d_name, filename); if (strcasecmp (entry->d_name, filename) != 0) continue; LOGDBG("MATCH\n"); // match if ((entry->d_type == DT_DIR) && remain) { strcat (path, "/"); strcat (path, entry->d_name); closedir (dirp); return _lookup_sub (path, remain); } if (!remain) { strcat (path, "/"); strcat (path, entry->d_name); closedir (dirp); return (entry->d_type == DT_DIR) ? 0x10 : 0; } } closedir (dirp); return -1; } static int _lookup_fuzzy_path (char *path, char *filename) { strcpy (path, redir.root); strcat (path, redir.cwsd); int i, len = strlen (filename); for (i = 0; i < len; i++) { if (filename[i] == '\\') filename[i] = '/'; } if (filename[0] == '/') filename++; return _lookup_sub (path, filename); } static void _proc_opendir_request (void *data, int data_len) { redir_opendir_t *arg = (redir_opendir_t *) data; LOGDBG("Opendir: '%s' '%s' %d %d %d\n", arg->filename, arg->mask, arg->driveno, arg->attrib_a, arg->attrib_b); // Toss previous directory search if found if (redir.od_total) { free (redir.od_entry); redir.od_entry = NULL; redir.od_total = 0; redir.od_current = 0; redir.od_alloc = 0; } // Build new search char *ptr = strrchr (arg->filename, '\\'); if (ptr) { *ptr = '\0'; if (arg->driveno & 0x40) { char *fmask = ptr + 1; int i = 0, j = 0; while ((i < 11) && fmask[j]) { if (fmask[j] == '.') { while (i != 8) arg->mask[i++] = '?'; j++; } else { arg->mask[i++] = fmask[j++]; } } } } char path[4096]; int i, len = strlen (arg->filename); for (i = 0; i < len; i++) { if (arg->filename[i] == '\\') arg->filename[i] = '/'; } strcpy (path, redir.root); strcat (path, redir.cwsd); if (arg->filename[0] == '/') _lookup_sub (path, &arg->filename[1]); else _lookup_sub (path, &arg->filename[0]); _redir_build_dirlist (path, arg->mask, arg->attrib_b); return; } static void _proc_stat_request (void *data, int data_len) { redir_stat_t *arg = (redir_stat_t *) data; // Translate file a to fully qualified path char path_a[4096]; int sts_a = _lookup_fuzzy_path (path_a, arg->filename_a); struct stat stinfo; switch (arg->statop) { case REDIR_STAT_ATTR: { LOGDBG("Get attr: '%s' %d\n", arg->filename_a, arg->driveno); arg->found = 0; if ((sts_a >= 0) && stat (path_a, &stinfo) == 0) { arg->found = 1; arg->attrib = sts_a; arg->size = stinfo.st_size; _posix_to_dos_time (stinfo.st_mtim.tv_sec, &arg->dos_time, &arg->dos_date); } break; } default: break; } memcpy (&redir.statinfo, arg, sizeof (redir_stat_t)); return; } void _handle_redirect_complete (uint8_t sub, void *data, int len) { switch (sub) { case REDIR_FEAT_DIR_FIRST: _proc_opendir_request (data, len); break; case REDIR_FEAT_STAT_PUSH: _proc_stat_request (data, len); break; default: break; } return; } void _handle_redirect_command (void) { uint32_t words = 256; LOGDBG("DOS REDIRECTOR - "); switch (ide.feat) { case REDIR_FEAT_GET_STATUS: { LOGDBG("GET STATUS [PIO-IN]\n"); redir_info_t *info = (redir_info_t *) data_out_setup (words); memset (info, 0, sizeof (redir_info_t)); info->protocol_magic = REDIR_PROTOCOL_MAGIC; info->protocol_ver = REDIR_PROTOCOL_VERSION; info->server_major = SERVER_VER_MAJOR; info->server_minor = SERVER_VER_MINOR; info->cpld_major = fw_ver_major; info->cpld_minor = fw_ver_minor; info->bytes_per_sector = 512; info->sector_per_cluster = 128; // 64K clusters struct statfs64 sfs; if (statfs64 (redir.root, &sfs) == 0) { uint32_t tc = (sfs.f_bsize * sfs.f_blocks) >> 16; uint32_t ac = (sfs.f_bsize * sfs.f_bfree) >> 16; info->total_clusters = (tc > 0x7fff) ? 0x7fff : tc; info->avail_clusters = (ac > 0x7fff) ? 0x7fff : ac; } strncpy (info->mount_path, redir.root, sizeof (info->mount_path) - 1); // Fetch local time time_t now = time(NULL); struct tm ltime; localtime_r(&now, <ime); // in case we don't have a good network reference yet int sanity_year = ltime.tm_year + 1900; if (sanity_year >= 2015) { info->time_sec = ltime.tm_sec; info->time_min = ltime.tm_min; info->time_hour = ltime.tm_hour; info->date_mday = ltime.tm_mday; info->date_wday = ltime.tm_wday; info->date_mon = ltime.tm_mon + 1; info->date_year = ltime.tm_year + 1900; } data_out_commit (words); break; } case REDIR_FEAT_DIR_FIRST: case REDIR_FEAT_STAT_PUSH: { const char *tag = "UNKNOWN"; switch (ide.feat) { case REDIR_FEAT_DIR_FIRST: tag = "DIR FIRST"; break; case REDIR_FEAT_STAT_PUSH: tag = "STAT PUSH"; break; } LOGDBG("%s [PIO-OUT]\n", tag); data_in_setup (ide.cmd, ide.feat, words); ide.DRDY = 0; break; } case REDIR_FEAT_DIR_NEXT: { LOGDBG("DIR NEXT [PIO-IN]\n"); redir_nextdir_t *next = (redir_nextdir_t *) data_out_setup (words); memset (next, 0, sizeof (redir_nextdir_t)); next->total = redir.od_total; next->start = redir.od_current; next->count = redir.od_total - redir.od_current; next->index = 0; if (next->count > NEXT_DENTRIES) next->count = NEXT_DENTRIES; memcpy (next->entry, &redir.od_entry[redir.od_current], sizeof (redir_dentry_t) * next->count); redir.od_current += next->count; data_out_commit (words); break; } case REDIR_FEAT_STAT_PULL: { LOGDBG("STAT PULL [PIO-IN]\n"); memcpy (data_out_setup (words), &redir.statinfo, sizeof (redir_stat_t)); data_out_commit (words); break; } default: LOGDBG("UNKNOWN %02Xh\n", ide.feat); ide.ABRT = 1; // Command aborted break; } return; }