mirror of https://github.com/wwarthen/RomWBW.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
598 lines
14 KiB
598 lines
14 KiB
/*
|
|
|
|
CPMREDIR: CP/M filesystem redirector
|
|
Copyright (C) 1998, John Elliott <jce@seasip.demon.co.uk>
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
This library 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
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public
|
|
License along with this library; if not, write to the Free
|
|
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
This file implements those BDOS functions that use wildcard expansion.
|
|
*/
|
|
|
|
#include "cpmint.h"
|
|
#ifdef _MSC_VER
|
|
#define S_ISDIR(mode) (((mode) & _S_IFDIR) != 0)
|
|
#endif
|
|
|
|
static cpm_byte* find_fcb;
|
|
static int find_n;
|
|
static int find_ext = 0;
|
|
static int find_xfcb = 0;
|
|
static int entryno;
|
|
static cpm_byte lastdma[0x80];
|
|
static long lastsize;
|
|
static char target_name[CPM_MAXPATH];
|
|
|
|
static char upper(char c)
|
|
{
|
|
if (islower(c)) return toupper(c);
|
|
return c;
|
|
}
|
|
|
|
/*
|
|
* we need to handle case sensitive filesystems correctly.
|
|
* underlying files need to just work, irrespective if the files
|
|
* in the native filesystem are mixed, upper or lower.
|
|
* the naive code in the distributed zx will not work everywhere.
|
|
*/
|
|
|
|
/*
|
|
* Does the string "s" match the CP/M FCB?
|
|
* pattern[0-10] will become a CP/M name parsed from "s" if it matches.
|
|
* If 1st byte of FCB is '?' then anything matches.
|
|
*/
|
|
|
|
static int cpm_match(char* s, cpm_byte* fcb, cpm_byte* pattern)
|
|
{
|
|
int n;
|
|
size_t m;
|
|
char* dotpos;
|
|
|
|
m = strlen(s);
|
|
|
|
/*
|
|
* let's cook the filename into something that we can match against
|
|
* the fcb. we reject any that can't be valid cp/m filenames,
|
|
* normalizing case as we go. all this goes into 'pattern'
|
|
*/
|
|
|
|
for (n = 0; n < 11; n++) pattern[n] = ' ';
|
|
|
|
/* The name must have 1 or 0 dots */
|
|
dotpos = strchr(s, '.');
|
|
if (!dotpos) { /* No dot. The name must be at most 8 characters */
|
|
if (m > 8) return 0;
|
|
for (n = 0; n < m; n++) {
|
|
pattern[n] = upper(s[n]) & 0x7F;
|
|
}
|
|
}
|
|
else { /* at least one dot */
|
|
if (strchr(dotpos + 1, '.')) { /* More than 1 dot */
|
|
return 0;
|
|
}
|
|
if (dotpos == s) { /* Dot right at the beginning */
|
|
return 0;
|
|
}
|
|
if ((dotpos - s) > 8) { /* "name" > 8 characters */
|
|
return 0;
|
|
}
|
|
if (strlen(dotpos + 1) > 3) { /* "type" > 3 characters */
|
|
return 0;
|
|
}
|
|
for (n = 0; n < (dotpos - s); n++) { /* copy filename portion */
|
|
pattern[n] = upper(s[n]) & 0x7F;
|
|
}
|
|
m = strlen(dotpos + 1);
|
|
for (n = 0; n < m; n++) { /* copy extention portion */
|
|
pattern[n + 8] = upper(dotpos[n + 1]) & 0x7F;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* handle special case where fcb[0] == '?' or fcb[0] & 0x80
|
|
* this is used to return a full directory list on bdos's
|
|
*/
|
|
|
|
if (((fcb[0] & 0x7F) == '?') || (fcb[0] & 0x80)) {
|
|
return 1;
|
|
}
|
|
for (n = 0; n < 11; n++)
|
|
{
|
|
if (fcb[n + 1] == '?') continue;
|
|
if ((pattern[n] & 0x7F) != (fcb[n + 1] & 0x7F))
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
return 1; /* Success! */
|
|
}
|
|
|
|
/* Get the next entry from the host's directory matching "fcb" */
|
|
|
|
static struct dirent* next_entry(DIR* dir, cpm_byte* fcb, cpm_byte* pattern,
|
|
struct stat* st)
|
|
{
|
|
struct dirent* en;
|
|
int unsatisfied;
|
|
int drv = fcb[0] & 0x7F;
|
|
|
|
if (drv == '?') drv = 0;
|
|
if (!drv) drv = redir_cpmdrive;
|
|
else drv--;
|
|
|
|
for (unsatisfied = 1; unsatisfied; )
|
|
{
|
|
/* 1. Get the next entry */
|
|
en = readdir(dir);
|
|
if (!en) return NULL; /* No next entry */
|
|
++entryno; /* 0 for 1st, 1 for 2nd, etc. */
|
|
|
|
/* 2. See if it matches. We do this first (in preference to
|
|
* seeing if it's a subdirectory first) because it doesn't
|
|
* require disc access */
|
|
if (!cpm_match(en->d_name, fcb, pattern))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/* 3. Stat it, & reject it if it's a directory */
|
|
strcpy(target_name, redir_drive_prefix[drv]);
|
|
strcat(target_name, en->d_name);
|
|
|
|
if (stat(target_name, st))
|
|
{
|
|
DBGMSGV("Can't stat %s so omitting it.\n", target_name);
|
|
continue; /* Can't stat */
|
|
}
|
|
if (S_ISDIR(st->st_mode))
|
|
{
|
|
/* Searching for files only */
|
|
if (fcb[0] != '?' && fcb[0] < 0x80)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
unsatisfied = 0;
|
|
}
|
|
return en;
|
|
}
|
|
|
|
void volume_label(int drv, cpm_byte* dma)
|
|
{
|
|
struct stat st;
|
|
|
|
memset(dma, 0x20, 12); /* Volume label */
|
|
|
|
/* Get label name */
|
|
redir_get_label(drv, (char*)(dma + 1));
|
|
|
|
/* [0x0c] = label byte
|
|
* [0x0d] = password byte (=0)
|
|
* [0x10-0x17] = password
|
|
* [0x18] = label create date
|
|
* [0x1c] = label update date
|
|
*/
|
|
#ifdef __MSDOS__
|
|
dma[0x0c] = 0x21; /* Under DOS, only "update" */
|
|
if (redir_drdos) dma[0x0c] |= 0x80; /* Under DRDOS, passwords allowed */
|
|
#else
|
|
dma[0x0c] = 0x61; /* Label exists and time stamps allowed */
|
|
#endif /* (update & access) */
|
|
dma[0x0d] = 0; /* Label not passworded */
|
|
dma[0x0f] = 0x80; /* Non-CP/M media */
|
|
|
|
if (stat(redir_drive_prefix[drv], &st))
|
|
{
|
|
DBGMSGV("stat() fails on '%s'\n", redir_drive_prefix[drv]);
|
|
return;
|
|
}
|
|
|
|
redir_wr32(dma + 0x18, redir_cpmtime(st.st_atime));
|
|
redir_wr32(dma + 0x1C, redir_cpmtime(st.st_mtime));
|
|
}
|
|
|
|
cpm_word redir_find(int n, cpm_byte* fcb, cpm_byte* dma)
|
|
{
|
|
DIR* hostdir;
|
|
int drv, attrib;
|
|
long recs;
|
|
struct stat st;
|
|
struct dirent* de;
|
|
cpm_word rights;
|
|
|
|
drv = (fcb[0] & 0x7F);
|
|
if (!drv || drv == '?') drv = redir_cpmdrive;
|
|
else drv--;
|
|
|
|
if (find_xfcb) /* Return another extent */
|
|
{
|
|
memcpy(dma, lastdma, 0x80);
|
|
dma[0] |= 0x10; /* XFCB */
|
|
dma[0x0c] = dma[0x69]; /* Password mode */
|
|
dma[0x0d] = 0x0A; /* Password decode byte */
|
|
memset(dma + 0x10, '*', 7);
|
|
dma[0x17] = ' '; /* Encoded password */
|
|
memcpy(lastdma, dma, 0x80);
|
|
|
|
find_xfcb = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (find_ext) /* Return another extent */
|
|
{
|
|
memcpy(dma, lastdma, 0x80);
|
|
dma[0x0c]++;
|
|
if (dma[0x0c] == 0x20)
|
|
{
|
|
dma[0x0c] = 0;
|
|
dma[0x0e]++;
|
|
}
|
|
lastsize -= 0x4000;
|
|
recs = (lastsize + 127) / 128;
|
|
dma[0x0f] = (recs > 127) ? 0x80 : (recs & 0x7F);
|
|
|
|
if (lastsize <= 0x4000) find_ext = 0;
|
|
memcpy(lastdma, dma, 0x80);
|
|
|
|
return 0;
|
|
}
|
|
|
|
memset(dma, 0, 128); /* Zap the buffer */
|
|
|
|
/* If returning all entries, return a volume label. */
|
|
|
|
if ((fcb[0] & 0x7F) == '?')
|
|
{
|
|
if (!n)
|
|
{
|
|
volume_label(drv, dma);
|
|
return 0;
|
|
}
|
|
else --n;
|
|
}
|
|
|
|
/* Note: This implies that opendir() works on a filename with a
|
|
* trailing slash. It does under Linux, but that's the only assurance
|
|
* I can give. */
|
|
|
|
entryno = -1;
|
|
hostdir = opendir(redir_drive_prefix[drv]);
|
|
|
|
if (!hostdir)
|
|
{
|
|
DBGMSGV("opendir() fails on '%s'\n", redir_drive_prefix[drv]);
|
|
return 0xFF;
|
|
}
|
|
|
|
/* We have a handle to the directory. */
|
|
while (n >= 0)
|
|
{
|
|
de = next_entry(hostdir, fcb, dma + 1, &st);
|
|
if (!de)
|
|
{
|
|
closedir(hostdir);
|
|
return 0xFF;
|
|
}
|
|
--n;
|
|
}
|
|
/* Valid entry found & statted. dma+1 holds filename. */
|
|
|
|
dma[0] = redir_cpmuser; /* Uid always matches */
|
|
dma[0x0c] = 0; /* Extent counter, low */
|
|
dma[0x0d] = st.st_size & 0x7F; /* Last record byte count */
|
|
dma[0x0e] = 0; /* Extent counter, high */
|
|
|
|
#ifdef _WIN32
|
|
attrib = GetFileAttributesA(target_name);
|
|
rights = redir_drdos_get_rights(target_name);
|
|
if (rights && ((fcb[0] & 0x7F) == '?')) find_xfcb = 1;
|
|
#else
|
|
attrib = 0;
|
|
rights = 0;
|
|
#endif
|
|
if (attrib & 1) dma[9] |= 0x80; /* read only */
|
|
if (attrib & 4) dma[10] |= 0x80; /* system */
|
|
if (!(attrib & 0x20)) dma[11] |= 0x80; /* archive */
|
|
|
|
/* TODO: Under Unix, work out correct RO setting */
|
|
|
|
recs = (st.st_size + 127) / 128;
|
|
dma[0x0f] = (recs > 127) ? 0x80 : (recs & 0x7F);
|
|
dma[0x10] = 0x80;
|
|
if (S_ISDIR(st.st_mode)) dma[0x10] |= 0x40;
|
|
if (attrib & 2) dma[0x10] |= 0x20;
|
|
dma[0x10] |= ((entryno & 0x1FFF) >> 8);
|
|
dma[0x11] = dma[0x10];
|
|
dma[0x12] = entryno & 0xFF;
|
|
|
|
redir_wr32(dma + 0x16, (dword)st.st_mtime); /* Modification time. */
|
|
/* TODO: It should be in DOS format */
|
|
/* TODO: At 0x1A, 1st cluster */
|
|
redir_wr32(dma + 0x1C, st.st_size); /* True size */
|
|
|
|
if (rights) /* Store password mode. Don't return an XFCB. */
|
|
{
|
|
dma[0x69] = redir_cpm_pwmode(rights);
|
|
memcpy(lastdma, dma, 0x80);
|
|
}
|
|
|
|
dma[0x60] = 0x21; /* XFCB */
|
|
redir_wr32(dma + 0x61, redir_cpmtime(st.st_atime));
|
|
redir_wr32(dma + 0x65, redir_cpmtime(st.st_mtime));
|
|
|
|
closedir(hostdir);
|
|
|
|
if (st.st_size > 0x4000 && (fcb[0x0C] == '?')) /* All extents? */
|
|
{
|
|
lastsize = st.st_size;
|
|
find_ext = 1;
|
|
memcpy(lastdma, dma, 0x80);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
cpm_word fcb_find1(cpm_byte* fcb, cpm_byte* dma) /* 0x11 */
|
|
{
|
|
#ifdef DEBUG
|
|
int rv;
|
|
#endif
|
|
|
|
FCBENT(fcb);
|
|
|
|
redir_log_fcb(fcb);
|
|
|
|
find_n = 0;
|
|
find_fcb = fcb;
|
|
find_ext = 0;
|
|
find_xfcb = 0;
|
|
|
|
#ifdef DEBUG
|
|
rv = redir_find(find_n, fcb, dma);
|
|
|
|
if (rv < 4)
|
|
{
|
|
DBGMSGV("Ret: %-11.11s\n", dma + 1);
|
|
}
|
|
else DBGMSG("Ret: Fail\n");
|
|
FCBRET(rv);
|
|
#else
|
|
FCBRET(redir_find(find_n, find_fcb, dma));
|
|
#endif
|
|
}
|
|
|
|
/* We don't bother with the FCB parameter - it's undocmented, and
|
|
* programs that do know about it will just pass in the same parameter
|
|
* that they did to function 0x11 */
|
|
|
|
cpm_word fcb_find2(cpm_byte* fcb, cpm_byte* dma) /* 0x12 */
|
|
{
|
|
#ifdef DEBUG
|
|
int rv;
|
|
char fname[CPM_MAXPATH];
|
|
#endif
|
|
|
|
FCBENT(find_fcb);
|
|
|
|
#ifdef DEBUG
|
|
redir_fcb2unix(find_fcb, fname);
|
|
DBGMSGV("file number %d, '%s'\n", find_n, fname);
|
|
#endif
|
|
|
|
++find_n;
|
|
|
|
#ifdef DEBUG
|
|
rv = redir_find(find_n, find_fcb, dma);
|
|
|
|
if (rv < 4)
|
|
{
|
|
DBGMSGV("Ret: %-11.11s\n", dma + 1);
|
|
}
|
|
else DBGMSG("Ret: Fail\n");
|
|
FCBRET(rv);
|
|
#else
|
|
FCBRET(redir_find(find_n, find_fcb, dma));
|
|
#endif
|
|
}
|
|
|
|
/* Under CP/M, unlinking works with wildcards */
|
|
|
|
cpm_word fcb_unlink(cpm_byte* fcb, cpm_byte* dma)
|
|
{
|
|
DIR* hostdir;
|
|
int drv;
|
|
struct dirent* de;
|
|
struct stat st;
|
|
int handle = 0;
|
|
int unpasswd = 0;
|
|
char fname[CPM_MAXPATH];
|
|
int del_cnt = 0;
|
|
|
|
FCBENT(fcb);
|
|
|
|
if (fcb[5] & 0x80) unpasswd = 1; /* Remove password rather than file */
|
|
|
|
redir_log_fcb(fcb);
|
|
|
|
drv = (fcb[0] & 0x7F);
|
|
if (!drv || drv == '?') drv = redir_cpmdrive;
|
|
else drv--;
|
|
|
|
if (redir_ro_drv(drv))
|
|
{
|
|
/* Error: R/O drive */
|
|
DBGMSG("delete failed - R/O drive\n");
|
|
FCBRET(0x02FF);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
redir_fcb2unix(fcb, fname);
|
|
DBGMSGV("fcb_unlink('%s')\n", fname);
|
|
#endif
|
|
|
|
/* Note: This implies that opendir() works on a filename with a
|
|
* trailing slash. It does under Linux, but that's the only assurance
|
|
* I can give.*/
|
|
|
|
hostdir = opendir(redir_drive_prefix[drv]);
|
|
|
|
if (!hostdir)
|
|
{
|
|
DBGMSGV("opendir failed on '%s'\n", redir_drive_prefix[drv]);
|
|
FCBRET(0xFF);
|
|
}
|
|
|
|
/* We have a handle to the directory. */
|
|
do
|
|
{
|
|
de = next_entry(hostdir, fcb, (cpm_byte*)fname, &st);
|
|
if (de)
|
|
{
|
|
strcpy(target_name, redir_drive_prefix[drv]);
|
|
strcat(target_name, de->d_name);
|
|
DBGMSGV("deleting '%s'\n", de->d_name);
|
|
if (unpasswd)
|
|
{
|
|
#ifdef __MSDOS__
|
|
if (redir_drdos)
|
|
{
|
|
handle = redir_drdos_put_rights(target_name, dma, 0);
|
|
}
|
|
else handle = 0;
|
|
#endif
|
|
}
|
|
else if (fcb[0] & 0x80)
|
|
{
|
|
DBGMSGV("rmdir '%s'\n", target_name);
|
|
handle = rmdir(target_name);
|
|
if (handle && redir_password_error())
|
|
{
|
|
DBGMSGV("rmdir failed (errno=%lu): %s\n", errno, strerror(errno));
|
|
redir_password_append(target_name, dma);
|
|
DBGMSGV("rmdir '%s'\n", target_name);
|
|
handle = rmdir(target_name);
|
|
}
|
|
if (handle)
|
|
DBGMSGV("rmdir failed (errno=%lu): %s\n", errno, strerror(errno));
|
|
}
|
|
else
|
|
{
|
|
releaseFile(target_name);
|
|
DBGMSGV("unlink '%s'\n", target_name);
|
|
handle = unlink(target_name);
|
|
if (handle && redir_password_error())
|
|
{
|
|
DBGMSGV("unlink failed (errno=%lu): %s\n", errno, strerror(errno));
|
|
redir_password_append(target_name, dma);
|
|
releaseFile(target_name);
|
|
DBGMSGV("unlink '%s'\n", target_name);
|
|
handle = unlink(target_name);
|
|
}
|
|
if (handle)
|
|
DBGMSGV("unlink failed (errno=%lu): %s\n", errno, strerror(errno));
|
|
}
|
|
|
|
if (handle)
|
|
de = NULL; /* Delete failed */
|
|
else
|
|
del_cnt++;
|
|
}
|
|
} while (de != NULL);
|
|
|
|
if (!handle && !del_cnt)
|
|
DBGMSG("no matching directory entries\n");
|
|
else
|
|
DBGMSGV("deleted %i file(s)\n", del_cnt);
|
|
|
|
if (handle || !del_cnt)
|
|
{
|
|
DBGMSG("delete processing failed\n");
|
|
closedir(hostdir);
|
|
FCBRET(0xFF);
|
|
}
|
|
|
|
DBGMSG("delete processing succeeded\n");
|
|
closedir(hostdir);
|
|
FCBRET(0);
|
|
}
|
|
|
|
#ifdef __MSDOS__
|
|
cpm_word redir_get_label(cpm_byte drv, char* pattern)
|
|
{
|
|
char strs[10];
|
|
struct ffblk fblk;
|
|
int done;
|
|
char* s;
|
|
int n;
|
|
|
|
/* We need the drive prefix to be of the form "C:\etc..." */
|
|
|
|
memset(pattern, ' ', 11);
|
|
if (!redir_drive_prefix[drv][0] || redir_drive_prefix[drv][1] != ':')
|
|
return 0;
|
|
|
|
sprintf(strs, "%c:/*.*", redir_drive_prefix[drv][0]);
|
|
|
|
done = findfirst(strs, &fblk, FA_LABEL);
|
|
while (!done)
|
|
{
|
|
if ((fblk.ff_attrib & FA_LABEL) &&
|
|
!(fblk.ff_attrib & (FA_SYSTEM | FA_HIDDEN)))
|
|
{
|
|
s = strchr(fblk.ff_name, '/');
|
|
if (!s) s = strchr(fblk.ff_name, '\\');
|
|
if (!s) s = strchr(fblk.ff_name, ':');
|
|
if (!s) s = fblk.ff_name;
|
|
for (n = 0; n < 11; n++)
|
|
{
|
|
if (!(*s)) break;
|
|
if (*s == '.')
|
|
{
|
|
n = 7;
|
|
++s;
|
|
continue;
|
|
}
|
|
pattern[n] = upper(*s);
|
|
++s;
|
|
}
|
|
return 1;
|
|
}
|
|
done = findnext(&fblk);
|
|
}
|
|
return 0;
|
|
}
|
|
#else
|
|
cpm_word redir_get_label(cpm_byte drv, char* pattern)
|
|
{
|
|
char* dname;
|
|
size_t l;
|
|
int n;
|
|
|
|
memset(pattern, ' ', 11);
|
|
|
|
dname = strrchr(redir_drive_prefix[drv], '/');
|
|
if (dname)
|
|
{
|
|
++dname;
|
|
l = strlen(dname);
|
|
if (l > 11) l = 11;
|
|
for (n = 0; n < l; n++) pattern[n] = upper(dname[l]);
|
|
}
|
|
else
|
|
{
|
|
pattern[0] = '.';
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|