In OpenBSD, _XOPEN_SOURCE 500 forces _POSIX_C_SOURCE down to 199506, which hides the declarations of openat() and fdopendir() and causes compiler warnings. Don't set _POSIX_C_SOURCE, because _XOPEN_SOURCE may set _POSIX_C_SOURCE to a different value.
		
			
				
	
	
		
			442 lines
		
	
	
	
		
			8.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			442 lines
		
	
	
	
		
			8.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #define _XOPEN_SOURCE 700
 | |
| #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);
 | |
| }
 |