mirror of https://github.com/digarok/gsplus.git
570 lines
12 KiB
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);
|
|
}
|
|
|