/*
* 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;
}