/* CPMREDIR: CP/M filesystem redirector Copyright (C) 1998, John Elliott 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