443 lines
		
	
	
		
			No EOL
		
	
	
		
			8.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			443 lines
		
	
	
		
			No EOL
		
	
	
		
			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)
 | 
						|
{
 | 
						|
	int i;
 | 
						|
	for (i=0; i<NUM_DRIVES; i++)
 | 
						|
		drives[i] = -1;
 | 
						|
	file_set_drive(0, ".");
 | 
						|
 | 
						|
	for (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)
 | 
						|
{
 | 
						|
	int i;
 | 
						|
	char* pin = cpmfilename->bytes;
 | 
						|
	char* pout = unixfilename;
 | 
						|
 | 
						|
	for (i=0; i<8; i++)
 | 
						|
	{
 | 
						|
		char c = *pin++;
 | 
						|
		if (c != ' ')
 | 
						|
			*pout++ = tolower(c);
 | 
						|
	}
 | 
						|
	*pout++ = '.';
 | 
						|
	for (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)
 | 
						|
{
 | 
						|
	int i;
 | 
						|
	if (pattern->drive != filename->drive)
 | 
						|
		return false;
 | 
						|
 | 
						|
	for (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(¤tpattern, 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);
 | 
						|
} |