ack/plat/cpm/emu/fileio.c
2019-02-07 22:39:57 +01:00

440 lines
8.9 KiB
C

#define _XOPEN_SOURCE 500
#define _POSIX_C_SOURCE 200809
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <dirent.h>
#include "globals.h"
#define logf(args...) while (0)
//#define logf(args...) printf(args)
struct file
{
struct file* prev;
struct file* next;
cpm_filename_t filename;
int fd;
int flags;
};
#define NUM_FILES 16
static struct file files[NUM_FILES];
static struct file* firstfile;
#define NUM_DRIVES 16
static int drives[NUM_DRIVES];
static cpm_filename_t currentpattern;
static int currentsearchdrivefd;
static DIR* currentdir;
void files_init(void)
{
for (int i=0; i<NUM_DRIVES; i++)
drives[i] = -1;
file_set_drive(0, ".");
for (int i=0; i<NUM_FILES; i++)
{
struct file* f = &files[i];
if (i == 0)
f->prev = NULL;
else
f->prev = &files[i-1];
if (i == (NUM_FILES-1))
f->next = NULL;
else
f->next = &files[i+1];
memset(&f->filename.bytes, ' ', 11);
f->filename.drive = 0;
f->fd = -1;
f->flags = 0;
}
firstfile = &files[0];
}
void file_set_drive(int drive, const char* path)
{
if ((drive < 0) || (drive >= NUM_DRIVES))
fatal("bad drive letter");
if (drives[drive] != -1)
close(drives[drive]);
drives[drive] = open(path, O_RDONLY);
if (drives[drive] == -1)
fatal("could not open '%s': %s", path, strerror(errno));
struct stat st;
fstat(drives[drive], &st);
if (!S_ISDIR(st.st_mode))
fatal("could not open '%s': not a directory", path);
logf("[drive %c now pointing at %s (fd %d)]\n", drive+'A', path, drives[drive]);
}
static void bump(struct file* f)
{
// logf("[bumping file %d to front]\n", f-files);
if (f != firstfile)
{
/* Remove from list. */
if (f->prev)
f->prev->next = f->next;
if (f->next)
f->next->prev = f->prev;
/* Reinsert at head of list. */
firstfile->prev = f;
f->prev = NULL;
f->next = firstfile;
firstfile = f;
}
// logf("[first file is %d]\n", firstfile-files);
// for (int i=0; i<NUM_FILES; i++)
// {
// f = &files[i];
// logf("[file %02d: %c:%.11s, fd=%d, prev=%d next=%d]\n",
// i, 'A'-1+f->filename.drive, f->filename.bytes, f->fd,
// f->prev ? (f->prev - files) : -1,
// f->next ? (f->next - files) : -1);
// }
}
static void cpm_filename_to_unix(cpm_filename_t* cpmfilename, char* unixfilename)
{
char* pin = cpmfilename->bytes;
char* pout = unixfilename;
for (int i=0; i<8; i++)
{
char c = *pin++;
if (c != ' ')
*pout++ = tolower(c);
}
*pout++ = '.';
for (int i=0; i<3; i++)
{
char c = *pin++;
if (c != ' ')
*pout++ = tolower(c);
}
if (pout[-1] == '.')
pout--;
*pout = '\0';
}
static bool unix_filename_to_cpm(const char* unixfilename, cpm_filename_t* cpmfilename)
{
const char* pin = unixfilename;
memset(cpmfilename, ' ', sizeof(cpm_filename_t));
char* pout = &cpmfilename->bytes[0];
int count = 0;
int maxcount = 8;
for (;;)
{
char c = *pin++;
if ((c == '.') && (maxcount == 8))
{
maxcount = 3;
count = 0;
pout = &cpmfilename->bytes[8];
}
else if (c == '\0')
break;
else if (count == maxcount)
return false;
else if (isupper(c))
return false;
else
{
*pout++ = toupper(c);
count++;
}
}
return true;
}
static bool match_filenames(cpm_filename_t* pattern, cpm_filename_t* filename)
{
if (pattern->drive != filename->drive)
return false;
for (int i=0; i<sizeof(pattern->bytes); i++)
{
char p = pattern->bytes[i];
if (p == '?')
continue;
if (p != filename->bytes[i])
return false;
}
return true;
}
static int get_drive_fd(cpm_filename_t* filename)
{
int drive = filename->drive - 1;
if ((drive < 0) || (drive >= NUM_DRIVES))
{
logf("[reference to bad drive %c]\n", drive + 'A');
return -1;
}
int drivefd = drives[drive];
if (drivefd == -1)
{
logf("[reference to undefined drive %c]\n", drive + 'A');
return -1;
}
logf("[selecting drive %c on fd %d]\n", drive + 'A', drivefd);
return drivefd;
}
static void reopen(struct file* f, int flags)
{
if ((f->fd == -1) || ((f->flags == O_RDONLY) && (flags == O_RDWR)))
{
char unixfilename[13];
cpm_filename_to_unix(&f->filename, unixfilename);
if (f->fd != -1)
{
logf("[reopening actual file '%s' on %d with different flags]\n", unixfilename, f->fd);
close(f->fd);
}
int drivefd = get_drive_fd(&f->filename);
if (drivefd == -1)
return;
f->flags = flags & O_ACCMODE;
errno = 0;
f->fd = openat(drivefd, unixfilename, flags, 0666);
logf("[opened actual file '%s' to fd %d: %s]\n", unixfilename, f->fd, strerror(errno));
}
}
static struct file* find_file(cpm_filename_t* filename)
{
struct file* f = firstfile;
for (;;)
{
if (memcmp(filename, &f->filename, sizeof(cpm_filename_t)) == 0)
break;
if (f->next)
f = f->next;
else
{
logf("[allocating file %d for '%.11s']\n", f-files, filename->bytes);
bump(f);
if (f->fd != -1)
{
logf("[closing old file %d for '%.11s']\n", f-files, f->filename.bytes);
close(f->fd);
}
f->fd = -1;
f->filename = *filename;
f->flags = 0;
break;
}
}
return f;
}
struct file* file_open(cpm_filename_t* filename)
{
struct file* f = find_file(filename);
reopen(f, O_RDONLY);
if (f->fd == -1)
return NULL;
return f;
}
struct file* file_create(cpm_filename_t* filename)
{
struct file* f = find_file(filename);
logf("[creating file %d for '%.11s']\n", f-files, f->filename.bytes);
reopen(f, O_RDWR | O_CREAT);
if (f->fd == -1)
return NULL;
return f;
}
int file_close(cpm_filename_t* filename)
{
struct file* f = find_file(filename);
logf("[explicitly closing file %d for '%.11s']\n", f-files, f->filename.bytes);
if (f->fd != -1)
{
logf("[closing file descriptor %d]\n", f->fd);
close(f->fd);
}
memset(&f->filename.bytes, ' ', 11);
f->fd = -1;
f->flags = 0;
return 0;
}
int file_read(struct file* f, uint8_t* data, uint16_t record)
{
reopen(f, O_RDONLY);
logf("[read record %04x from file %d for '%.11s']\n", record, f-files, f->filename.bytes);
bump(f);
memset(data, '\0', 128);
return pread(f->fd, data, 128, record*128);
}
int file_write(struct file* f, uint8_t* data, uint16_t record)
{
reopen(f, O_RDWR);
logf("[write record %04x from file %d for '%.11s']\n", record, f-files, f->filename.bytes);
bump(f);
return pwrite(f->fd, data, 128, record*128);
}
int file_getrecordcount(struct file* f)
{
reopen(f, O_RDONLY);
struct stat st;
fstat(f->fd, &st);
return (st.st_size + 127) >> 7;
}
void file_setrecordcount(struct file* f, int count)
{
reopen(f, O_RDONLY);
if (count != file_getrecordcount(f))
{
logf("[truncating file %d to %d records]\n", f-files, count);
reopen(f, O_RDWR);
ftruncate(f->fd, count*128);
}
}
int file_findfirst(cpm_filename_t* pattern)
{
if (currentdir)
{
closedir(currentdir);
currentdir = NULL;
}
currentpattern = *pattern;
logf("[reset search; current find pattern is '%.11s']\n", currentpattern.bytes);
currentsearchdrivefd = get_drive_fd(pattern);
if (currentsearchdrivefd == -1)
return 0;
currentdir = fdopendir(dup(currentsearchdrivefd));
if (currentdir)
{
rewinddir(currentdir);
return 0;
}
return -1;
}
int file_findnext(cpm_filename_t* result)
{
for (;;)
{
if (!currentdir)
return -1;
struct dirent* de = readdir(currentdir);
if (!de)
{
closedir(currentdir);
currentdir = NULL;
logf("[finished search]\n");
return -1;
}
struct stat st;
if ((fstatat(currentsearchdrivefd, de->d_name, &st, 0) == 0)
&& S_ISREG(st.st_mode)
&& unix_filename_to_cpm(de->d_name, result))
{
result->drive = currentpattern.drive;
logf("[compare '%.11s' with pattern '%.11s']\n", result->bytes, currentpattern.bytes);
if (match_filenames(&currentpattern, result))
{
logf("[positive match]\n");
return 0;
}
}
}
}
int file_delete(cpm_filename_t* pattern)
{
logf("[attempting to delete pattern '%.11s' on drive %c]\n", pattern->bytes, '@'+pattern->drive);
int drivefd = get_drive_fd(pattern);
DIR* dir = fdopendir(dup(drivefd));
if (!dir)
return -1;
rewinddir(dir);
int result = -1;
for (;;)
{
struct dirent* de = readdir(dir);
if (!de)
break;
struct stat st;
cpm_filename_t candidate;
if ((fstatat(drivefd, de->d_name, &st, 0) == 0)
&& S_ISREG(st.st_mode)
&& unix_filename_to_cpm(de->d_name, &candidate))
{
candidate.drive = pattern->drive;
logf("[compare '%.11s' with pattern '%.11s']\n", candidate.bytes, pattern->bytes);
if (match_filenames(pattern, &candidate))
{
logf("[positive match, deleting]\n");
unlinkat(drivefd, de->d_name, 0);
result = 0;
}
}
}
closedir(dir);
return result;
}
int file_rename(cpm_filename_t* src, cpm_filename_t* dest)
{
logf("[renaming %.11s to %.11s on drive %c]\n",
src->bytes, dest->bytes, '@'+src->drive);
char srcunixfilename[13];
cpm_filename_to_unix(src, srcunixfilename);
char destunixfilename[13];
cpm_filename_to_unix(dest, destunixfilename);
int drivefd = get_drive_fd(src);
return renameat(drivefd, srcunixfilename, drivefd, destunixfilename);
}