forked from MirrorRepos/RomWBW
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.
579 lines
13 KiB
579 lines
13 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"
|
|
|
|
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, 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))
|
|
{
|
|
redir_Msg("Can't stat %s so omitting it.\n", target_name);
|
|
continue; /* Can't stat */
|
|
}
|
|
//if (S_ISDIR(st->st_mode))
|
|
if ((st->st_mode) & _S_IFDIR)
|
|
{
|
|
/* 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))
|
|
{
|
|
redir_Msg("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)
|
|
{
|
|
redir_Msg("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) 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 __MSDOS__
|
|
_dos_getfileattr(target_name, (unsigned int *)&attrib);
|
|
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;
|
|
if (attrib & 4) dma[10] |= 0x80;
|
|
if (!(attrib & 0x20)) dma[11] |= 0x80;
|
|
|
|
|
|
|
|
/* 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 ((st.st_mode) & _S_IFDIR) {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, 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;
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
#define SHOWNAME(func) \
|
|
{ \
|
|
char fname[CPM_MAXPATH]; \
|
|
redir_fcb2unix(fcb, fname); \
|
|
redir_Msg(func "(\"%s\")\n", fname); \
|
|
}
|
|
|
|
#else
|
|
#define SHOWNAME(func)
|
|
#endif
|
|
|
|
cpm_word fcb_find1 (cpm_byte *fcb, cpm_byte *dma) /* 0x11 */
|
|
{
|
|
#ifdef DEBUG
|
|
int rv;
|
|
#endif
|
|
SHOWNAME("fcb_find1")
|
|
|
|
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)
|
|
{
|
|
redir_Msg("Ret: %-11.11s\n", dma + 1);
|
|
}
|
|
else redir_Msg("Ret: Fail\n");
|
|
return rv;
|
|
#else
|
|
return 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];
|
|
redir_fcb2unix(find_fcb, fname);
|
|
redir_Msg("fcb_find2(\"%s\") no. %d\n", fname, find_n);
|
|
#endif
|
|
++find_n;
|
|
#ifdef DEBUG
|
|
rv = redir_find(find_n, find_fcb, dma);
|
|
|
|
if (rv < 4)
|
|
{
|
|
redir_Msg("Ret: %-11.11s\n", dma + 1);
|
|
}
|
|
else redir_Msg("Ret: Fail\n");
|
|
return rv;
|
|
#else
|
|
return 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];
|
|
|
|
SHOWNAME("fcb_unlink")
|
|
|
|
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)) return 0x02FF; /* Error: R/O drive */
|
|
|
|
#ifdef DEBUG
|
|
redir_fcb2unix(fcb, fname);
|
|
redir_Msg("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)
|
|
{
|
|
redir_Msg("opendir() fails on '%s'\n", redir_drive_prefix[drv]);
|
|
return 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);
|
|
redir_Msg("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)
|
|
{
|
|
handle = rmdir(target_name);
|
|
if (handle && redir_password_error())
|
|
{
|
|
redir_password_append(target_name, dma);
|
|
handle = rmdir(target_name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
handle = unlink(target_name);
|
|
if (handle && redir_password_error())
|
|
{
|
|
redir_password_append(target_name, dma);
|
|
handle = unlink(target_name);
|
|
}
|
|
}
|
|
|
|
if (handle) de = NULL; /* Delete failed */
|
|
}
|
|
}
|
|
while (de != NULL);
|
|
if (handle)
|
|
{
|
|
redir_Msg("Ret: -1\n");
|
|
closedir(hostdir);
|
|
return 0xFF;
|
|
}
|
|
redir_Msg("Ret: 0\n");
|
|
closedir(hostdir);
|
|
return 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;
|
|
int l, 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
|
|
|