gsplus/src/unix_host_common.c

570 lines
12 KiB
C

#define _BSD_SOURCE
#include <ctype.h>
#include <dirent.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include <libgen.h>
#if defined(__APPLE__)
#include <sys/xattr.h>
#include <sys/attr.h>
#include <sys/paths.h>
#endif
#ifdef __linux__
#include <sys/xattr.h>
#endif
#if defined(__FreeBSD__)
#include <sys/types.h>
#include <sys/extattr.h>
#endif
#if defined(_AIX)
#include <sys/ea.h>
#endif
#ifndef XATTR_FINDERINFO_NAME
#define XATTR_FINDERINFO_NAME "com.apple.FinderInfo"
#endif
#ifndef XATTR_RESOURCEFORK_NAME
#define XATTR_RESOURCEFORK_NAME "com.apple.ResourceFork"
#endif
#include "defc.h"
#include "gsos.h"
#include "host_common.h"
static ino_t root_ino = 0;
static dev_t root_dev = 0;
unsigned host_startup(void) {
struct stat st;
if (!g_cfg_host_path) return invalidFSTop;
if (!*g_cfg_host_path) return invalidFSTop;
if (host_root) free(host_root);
host_root = strdup(g_cfg_host_path);
if (stat(host_root, &st) < 0) {
fprintf(stderr, "%s does not exist\n", host_root);
return invalidFSTop;
}
if (!S_ISDIR(st.st_mode)) {
fprintf(stderr, "%s is not a directory\n", host_root);
return invalidFSTop;
}
root_ino = st.st_ino;
root_dev = st.st_dev;
return 0;
}
void host_shutdown(void) {
if (host_root) free(host_root);
host_root = NULL;
root_ino = 0;
root_dev = 0;
}
int host_is_root(struct stat *st) {
return st->st_ino == root_ino && st->st_dev == root_dev;
}
/*
* Date/time conversion
*/
/*
* converts time_t to a gs/os readhextime date/time record.
*/
void host_set_date_time_rec(word32 ptr, time_t time) {
if (time == 0) {
for (int i = 0; i < 8; ++i) set_memory_c(ptr++, 0, 0);
return;
}
struct tm *tm = localtime(&time);
if (tm->tm_sec == 60) tm->tm_sec = 59; /* leap second */
set_memory_c(ptr++, tm->tm_sec, 0);
set_memory_c(ptr++, tm->tm_min, 0);
set_memory_c(ptr++, tm->tm_hour, 0);
set_memory_c(ptr++, tm->tm_year, 0);
set_memory_c(ptr++, tm->tm_mday - 1, 0);
set_memory_c(ptr++, tm->tm_mon, 0);
set_memory_c(ptr++, 0, 0);
set_memory_c(ptr++, tm->tm_wday + 1, 0);
}
/*
* converts time_t to a prodos16 date/time record.
*/
void host_set_date_time(word32 ptr, time_t time) {
if (time == 0) {
for (int i = 0; i < 4; ++i) set_memory_c(ptr++, 0, 0);
return;
}
struct tm *tm = localtime(&time);
word16 tmp = 0;
tmp |= (tm->tm_year % 100) << 9;
tmp |= (tm->tm_mon + 1) << 5;
tmp |= tm->tm_mday;
set_memory16_c(ptr, tmp, 0);
ptr += 2;
tmp = 0;
tmp |= tm->tm_hour << 8;
tmp |= tm->tm_min;
set_memory16_c(ptr, tmp, 0);
}
word32 host_convert_date_time(time_t time) {
if (time == 0) return 0;
struct tm *tm = localtime(&time);
word16 dd = 0;
dd |= (tm->tm_year % 100) << 9;
dd |= (tm->tm_mon + 1) << 5;
dd |= tm->tm_mday;
word16 tt = 0;
tt |= tm->tm_hour << 8;
tt |= tm->tm_min;
return (tt << 16) | dd;
}
time_t host_get_date_time(word32 ptr) {
word16 a = get_memory16_c(ptr + 0, 0);
word16 b = get_memory16_c(ptr + 2, 0);
if (!a && !b) return 0;
struct tm tm;
memset(&tm, 0, sizeof(tm));
tm.tm_year = (a >> 9) & 0x7f;
tm.tm_mon = ((a >> 5) & 0x0f) - 1;
tm.tm_mday = (a >> 0) & 0x1f;
tm.tm_hour = (b >> 8) & 0x1f;
tm.tm_min = (b >> 0) & 0x3f;
tm.tm_sec = 0;
tm.tm_isdst = -1;
// 00 - 39 => 2000-2039
// 40 - 99 => 1940-1999
if (tm.tm_year < 40) tm.tm_year += 100;
return mktime(&tm);
}
time_t host_get_date_time_rec(word32 ptr) {
byte buffer[8];
for (int i = 0; i < 8; ++i) buffer[i] = get_memory_c(ptr++, 0);
if (!memcmp(buffer, "\x00\x00\x00\x00\x00\x00\x00\x00", 8)) return 0;
struct tm tm;
memset(&tm, 0, sizeof(tm));
tm.tm_sec = buffer[0];
tm.tm_min = buffer[1];
tm.tm_hour = buffer[2];
tm.tm_year = buffer[3];
tm.tm_mday = buffer[4] + 1;
tm.tm_mon = buffer[5];
tm.tm_isdst = -1;
return mktime(&tm);
}
#if defined(__APPLE__)
void host_get_file_xinfo(const char *path, struct file_info *fi) {
ssize_t tmp;
tmp = getxattr(path, XATTR_RESOURCEFORK_NAME, NULL, 0, 0, 0);
if (tmp < 0) tmp = 0;
fi->resource_eof = tmp;
fi->resource_blocks = (tmp + 511) / 512;
tmp = getxattr(path, XATTR_FINDERINFO_NAME, fi->finder_info, 32, 0, 0);
if (tmp == 16 || tmp == 32) {
fi->has_fi = 1;
host_finder_info_to_filetype(fi->finder_info, &fi->file_type, &fi->aux_type);
}
}
#elif defined(__sun)
void host_get_file_xinfo(const char *path, struct file_info *fi) {
struct stat st;
// can't stat an xattr directly?
int fd;
fd = attropen(path, XATTR_RESOURCEFORK_NAME, O_RDONLY);
if (fd >= 0) {
if (fstat(fd, &st) == 0) {
fi->resource_eof = st.st_size;
fi->resource_blocks = st.st_blocks;
}
close(fd);
}
fd = attropen(path, XATTR_FINDERINFO_NAME, O_RDONLY);
if (fd >= 0) {
int tmp = read(fd, fi->finder_info, 32);
if (tmp == 16 || tmp == 32) {
fi->has_fi = 1;
host_finder_info_to_filetype(fi->finder_info, &fi->file_type, &fi->aux_type);
}
close(fd);
}
}
#elif defined(__linux__)
void host_get_file_xinfo(const char *path, struct file_info *fi) {
ssize_t tmp;
tmp = getxattr(path, "user.com.apple.ResourceFork", NULL, 0);
if (tmp < 0) tmp = 0;
fi->resource_eof = tmp;
fi->resource_blocks = (tmp + 511) / 512;
tmp = getxattr(path, "user.com.apple.FinderInfo", fi->finder_info, 32);
if (tmp == 16 || tmp == 32) {
fi->has_fi = 1;
host_finder_info_to_filetype(fi->finder_info, &fi->file_type, &fi->aux_type);
}
}
#else
void host_get_file_xinfo(const char *path, struct file_info *fi) {
}
#endif
word32 host_get_file_info(const char *path, struct file_info *fi) {
struct stat st;
memset(fi, 0, sizeof(*fi));
int ok = stat(path, &st);
if (ok < 0) return host_map_errno(errno);
fi->eof = st.st_size;
fi->blocks = st.st_blocks;
fi->create_date = st.st_ctime;
fi->modified_date = st.st_mtime;
#if defined(__APPLE__)
fi->create_date = st.st_birthtime;
#endif
if (S_ISDIR(st.st_mode)) {
fi->storage_type = directoryFile;
fi->file_type = 0x0f;
if (host_is_root(&st))
fi->storage_type = 0x0f;
} else if (S_ISREG(st.st_mode)) {
fi->file_type = 0x06;
if (st.st_size < 0x200) fi->storage_type = seedling;
else if (st.st_size < 0x20000) fi->storage_type = sapling;
else fi->storage_type = tree;
} else {
fi->storage_type = st.st_mode & S_IFMT;
fi->file_type = 0;
}
// 0x01 = read enable
// 0x02 = write enable
// 0x04 = invisible
// 0x08 = reserved
// 0x10 = reserved
// 0x20 = backup needed
// 0x40 = rename enable
// 0x80 = destroy enable
fi->access = 0xc3; // placeholder...
if (S_ISREG(st.st_mode)) {
host_get_file_xinfo(path, fi);
if (!fi->has_fi) {
host_synthesize_file_xinfo(path, fi);
}
}
// get file type/aux type
if (fi->resource_eof) fi->storage_type = extendedFile;
return 0;
}
#if defined(__APPLE__)
word32 host_set_file_info(const char *path, struct file_info *fi) {
int ok;
struct attrlist list;
unsigned i = 0;
struct timespec dates[2];
if (fi->has_fi && fi->storage_type != 0x0d) {
ok = setxattr(path, XATTR_FINDERINFO_NAME, fi->finder_info, 32, 0, 0);
if (ok < 0) return host_map_errno(errno);
}
memset(&list, 0, sizeof(list));
memset(dates, 0, sizeof(dates));
list.bitmapcount = ATTR_BIT_MAP_COUNT;
list.commonattr = 0;
if (fi->create_date)
{
dates[i++].tv_sec = fi->create_date;
list.commonattr |= ATTR_CMN_CRTIME;
}
if (fi->modified_date)
{
dates[i++].tv_sec = fi->modified_date;
list.commonattr |= ATTR_CMN_MODTIME;
}
ok = 0;
if (i) ok = setattrlist(path, &list, dates, i * sizeof(struct timespec), 0);
return 0;
}
#elif defined(__sun)
word32 host_set_file_info(const char *path, struct file_info *fi) {
if (fi->has_fi && fi->storage_type != 0x0d) {
int fd = attropen(path, XATTR_FINDERINFO_NAME, O_WRONLY | O_CREAT, 0666);
if (fd < 0) return host_map_errno(errno);
write(fd, fi->finder_info, 32);
close(fd);
}
if (fi->modified_date) {
struct timeval times[2];
memset(times, 0, sizeof(times));
//times[0] = 0; // access
times[1].tv_sec = fi.modified_date; // modified
int ok = utimes(path, times);
if (ok < 0) return host_map_errno(errno);
}
return 0;
}
#elif defined(__linux__)
word32 host_set_file_info(const char *path, struct file_info *fi) {
if (fi->has_fi && fi->storage_type != 0x0d) {
int ok = setxattr(path, "user.apple.FinderInfo", fi->finder_info, 32, 0);
if (ok < 0) return host_map_errno(errno);
}
if (fi->modified_date) {
struct timeval times[2];
memset(times, 0, sizeof(times));
//times[0] = 0; // access
times[1].tv_sec = fi->modified_date; // modified
int ok = utimes(path, times);
if (ok < 0) return host_map_errno(errno);
}
return 0;
}
#else
word32 host_set_file_info(const char *path, struct file_info *fi) {
if (fi->modified_date) {
struct timeval times[2];
memset(times, 0, sizeof(times));
times[0] = 0; // access
times[1].tv_sec = fi->modified_date; // modified
int ok = utimes(path, times);
if (ok < 0) return host_map_errno(errno);
}
return 0;
}
#endif
static int qsort_callback(const void *a, const void *b) {
return strcasecmp(*(const char **)a, *(const char **)b);
}
unsigned host_scan_directory(const char *path, char ***out, size_t *entries, unsigned p8) {
DIR *dp;
char **data = NULL;
size_t capacity = 0;
size_t count = 0;
dp = opendir(path);
if (!dp) return host_map_errno_path(errno, path);
for(;;) {
struct dirent *d = readdir(dp);
if (!d) break;
const char *name = d->d_name;
if (name[0] == 0) continue;
if (name[0] == '.') continue;
if (p8) {
int ok = 1;
int n = strlen(name);
if (n > 15) continue;
/* check for invalid characters? */
for (int i = 0; i < n; ++i) {
unsigned char c = name[i];
if (isalpha(c) || isdigit(c) || c == '.') continue;
ok = 0;
break;
}
if (!ok) continue;
}
if (count == capacity) {
char **tmp;
tmp = realloc(data, (capacity + 100) * sizeof(char *));
if (!tmp) {
closedir(dp);
host_free_directory(data, count);
return outOfMem;
}
data = tmp;
for (int i = count; i < capacity; ++i) data[i] = 0;
capacity += 100;
}
data[count++] = strdup(name);
}
closedir(dp);
qsort(data, count, sizeof(char *), qsort_callback);
*entries = count;
*out = data;
return 0;
}
unsigned host_storage_type(const char *path, word16 *error) {
struct stat st;
if (!path) {
*error = badPathSyntax;
return 0;
}
if (stat(path, &st) < 0) {
*error = host_map_errno_path(errno, path);
return 0;
}
if (S_ISDIR(st.st_mode)) {
return host_is_root(&st) ? 0x0f : directoryFile;
}
if (S_ISREG(st.st_mode)) return standardFile;
*error = badStoreType;
return 0;
}
/*
* error remapping.
* NOTE - GS/OS errors are a superset of P8 errors
*/
static word32 enoent(const char *path) {
/*
some op on path return ENOENT. check if it's
fileNotFound or pathNotFound
*/
char *p = (char *)path;
for(;;) {
struct stat st;
p = dirname(p);
if (p == NULL) break;
if (p[0] == '.' && p[1] == 0) break;
if (p[0] == '/' && p[1] == 0) break;
if (stat(p, &st) < 0) return pathNotFound;
}
return fileNotFound;
}
word32 host_map_errno(int xerrno) {
switch(xerrno) {
case 0: return 0;
case EBADF:
return invalidAccess;
#ifdef EDQUOT
case EDQUOT:
#endif
case EFBIG:
return volumeFull;
case ENOENT:
return fileNotFound;
case ENOTDIR:
return pathNotFound;
case ENOMEM:
return outOfMem;
case EEXIST:
return dupPathname;
case ENOTEMPTY:
return invalidAccess;
default:
return drvrIOError;
}
}
word32 host_map_errno_path(int xerrno, const char *path) {
if (xerrno == ENOENT) return enoent(path);
return host_map_errno(xerrno);
}