mirror of
https://github.com/ish-app/ish.git
synced 2026-01-25 14:06:40 +00:00
524 lines
15 KiB
C
524 lines
15 KiB
C
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <dirent.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/ioctl.h>
|
|
#include <termios.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/xattr.h>
|
|
#include <sys/file.h>
|
|
#include <sys/statvfs.h>
|
|
#include <poll.h>
|
|
|
|
#include "debug.h"
|
|
#include "kernel/errno.h"
|
|
#include "kernel/calls.h"
|
|
#include "kernel/fs.h"
|
|
#include "fs/dev.h"
|
|
#include "fs/tty.h"
|
|
|
|
static int getpath(int fd, char *buf) {
|
|
#if defined(__linux__)
|
|
char proc_fd[20];
|
|
sprintf(proc_fd, "/proc/self/fd/%d", fd);
|
|
ssize_t size = readlink(proc_fd, buf, MAX_PATH - 1);
|
|
if (size >= 0)
|
|
buf[size] = '\0';
|
|
return size;
|
|
#elif defined(__APPLE__)
|
|
return fcntl(fd, F_GETPATH, buf);
|
|
#endif
|
|
}
|
|
|
|
const char *fix_path(const char *path) {
|
|
if (strcmp(path, "") == 0)
|
|
return ".";
|
|
if (path[0] == '/')
|
|
path++;
|
|
return path;
|
|
}
|
|
|
|
// temporarily change directory and block other threads from doing so
|
|
// useful for simulating mknodat on ios, dealing with long unix socket paths, etc
|
|
lock_t fchdir_lock;
|
|
static void lock_fchdir(int dirfd) {
|
|
lock(&fchdir_lock);
|
|
fchdir(dirfd);
|
|
}
|
|
static void unlock_fchdir() {
|
|
unlock(&fchdir_lock);
|
|
}
|
|
|
|
static int open_flags_real_from_fake(int flags) {
|
|
int real_flags = 0;
|
|
if (flags & O_RDONLY_) real_flags |= O_RDONLY;
|
|
if (flags & O_WRONLY_) real_flags |= O_WRONLY;
|
|
if (flags & O_RDWR_) real_flags |= O_RDWR;
|
|
if (flags & O_CREAT_) real_flags |= O_CREAT;
|
|
if (flags & O_TRUNC_) real_flags |= O_TRUNC;
|
|
if (flags & O_APPEND_) real_flags |= O_APPEND;
|
|
if (flags & O_NONBLOCK_) real_flags |= O_NONBLOCK;
|
|
return real_flags;
|
|
}
|
|
|
|
static int open_flags_fake_from_real(int flags) {
|
|
int fake_flags = 0;
|
|
if (flags & O_RDONLY) fake_flags |= O_RDONLY_;
|
|
if (flags & O_WRONLY) fake_flags |= O_WRONLY_;
|
|
if (flags & O_RDWR) fake_flags |= O_RDWR_;
|
|
if (flags & O_CREAT) fake_flags |= O_CREAT_;
|
|
if (flags & O_TRUNC) fake_flags |= O_TRUNC_;
|
|
if (flags & O_APPEND) fake_flags |= O_APPEND_;
|
|
if (flags & O_NONBLOCK) fake_flags |= O_NONBLOCK_;
|
|
return fake_flags;
|
|
}
|
|
|
|
static struct fd *realfs_open(struct mount *mount, const char *path, int flags, int mode) {
|
|
int real_flags = open_flags_real_from_fake(flags);
|
|
int fd_no = openat(mount->root_fd, fix_path(path), real_flags, mode);
|
|
if (fd_no < 0)
|
|
return ERR_PTR(errno_map());
|
|
struct fd *fd = fd_create(&realfs_fdops);
|
|
fd->real_fd = fd_no;
|
|
fd->dir = NULL;
|
|
return fd;
|
|
}
|
|
|
|
int realfs_close(struct fd *fd) {
|
|
if (fd->dir != NULL)
|
|
closedir(fd->dir);
|
|
int err = close(fd->real_fd);
|
|
if (err < 0)
|
|
return errno_map();
|
|
return 0;
|
|
}
|
|
|
|
static void copy_stat(struct statbuf *fake_stat, struct stat *real_stat) {
|
|
fake_stat->dev = dev_fake_from_real(real_stat->st_dev);
|
|
fake_stat->inode = real_stat->st_ino;
|
|
fake_stat->mode = real_stat->st_mode;
|
|
fake_stat->nlink = real_stat->st_nlink;
|
|
fake_stat->uid = real_stat->st_uid;
|
|
fake_stat->gid = real_stat->st_gid;
|
|
fake_stat->rdev = dev_fake_from_real(real_stat->st_rdev);
|
|
fake_stat->size = real_stat->st_size;
|
|
fake_stat->blksize = real_stat->st_blksize;
|
|
fake_stat->blocks = real_stat->st_blocks;
|
|
fake_stat->atime = real_stat->st_atime;
|
|
fake_stat->mtime = real_stat->st_mtime;
|
|
fake_stat->ctime = real_stat->st_ctime;
|
|
#if __APPLE__
|
|
#define TIMESPEC(x) st_##x##timespec
|
|
#elif __linux__
|
|
#define TIMESPEC(x) st_##x##tim
|
|
#endif
|
|
fake_stat->atime_nsec = real_stat->TIMESPEC(a).tv_nsec;
|
|
fake_stat->mtime_nsec = real_stat->TIMESPEC(m).tv_nsec;
|
|
fake_stat->ctime_nsec = real_stat->TIMESPEC(c).tv_nsec;
|
|
#undef TIMESPEC
|
|
}
|
|
|
|
static int realfs_stat(struct mount *mount, const char *path, struct statbuf *fake_stat) {
|
|
struct stat real_stat;
|
|
if (fstatat(mount->root_fd, fix_path(path), &real_stat, AT_SYMLINK_NOFOLLOW) < 0)
|
|
return errno_map();
|
|
copy_stat(fake_stat, &real_stat);
|
|
return 0;
|
|
}
|
|
|
|
static int realfs_fstat(struct fd *fd, struct statbuf *fake_stat) {
|
|
struct stat real_stat;
|
|
if (fstat(fd->real_fd, &real_stat) < 0)
|
|
return errno_map();
|
|
copy_stat(fake_stat, &real_stat);
|
|
return 0;
|
|
}
|
|
|
|
ssize_t realfs_read(struct fd *fd, void *buf, size_t bufsize) {
|
|
ssize_t res = read(fd->real_fd, buf, bufsize);
|
|
if (res < 0)
|
|
return errno_map();
|
|
return res;
|
|
}
|
|
|
|
ssize_t realfs_write(struct fd *fd, const void *buf, size_t bufsize) {
|
|
ssize_t res = write(fd->real_fd, buf, bufsize);
|
|
if (res < 0)
|
|
return errno_map();
|
|
return res;
|
|
}
|
|
|
|
ssize_t realfs_pread(struct fd *fd, void *buf, size_t bufsize, off_t off) {
|
|
ssize_t res = pread(fd->real_fd, buf, bufsize, off);
|
|
if (res < 0)
|
|
return errno_map();
|
|
return res;
|
|
}
|
|
|
|
ssize_t realfs_pwrite(struct fd *fd, const void *buf, size_t bufsize, off_t off) {
|
|
ssize_t res = pwrite(fd->real_fd, buf, bufsize, off);
|
|
if (res < 0)
|
|
return errno_map();
|
|
return res;
|
|
}
|
|
|
|
static void realfs_opendir(struct fd *fd) {
|
|
if (fd->dir == NULL) {
|
|
int dirfd = dup(fd->real_fd);
|
|
fd->dir = fdopendir(dirfd);
|
|
// this should never get called on a non-directory
|
|
assert(fd->dir != NULL);
|
|
}
|
|
}
|
|
|
|
int realfs_readdir(struct fd *fd, struct dir_entry *entry) {
|
|
realfs_opendir(fd);
|
|
errno = 0;
|
|
struct dirent *dirent = readdir(fd->dir);
|
|
if (dirent == NULL) {
|
|
if (errno != 0)
|
|
return errno_map();
|
|
else
|
|
return 0;
|
|
}
|
|
entry->inode = dirent->d_ino;
|
|
strcpy(entry->name, dirent->d_name);
|
|
return 1;
|
|
}
|
|
|
|
unsigned long realfs_telldir(struct fd *fd) {
|
|
realfs_opendir(fd);
|
|
return telldir(fd->dir);
|
|
}
|
|
|
|
void realfs_seekdir(struct fd *fd, unsigned long ptr) {
|
|
realfs_opendir(fd);
|
|
seekdir(fd->dir, ptr);
|
|
}
|
|
|
|
off_t realfs_lseek(struct fd *fd, off_t offset, int whence) {
|
|
if (fd->dir != NULL && whence == LSEEK_SET) {
|
|
realfs_seekdir(fd, offset);
|
|
return offset;
|
|
}
|
|
|
|
if (whence == LSEEK_SET)
|
|
whence = SEEK_SET;
|
|
else if (whence == LSEEK_CUR)
|
|
whence = SEEK_CUR;
|
|
else if (whence == LSEEK_END)
|
|
whence = SEEK_END;
|
|
else
|
|
return _EINVAL;
|
|
off_t res = lseek(fd->real_fd, offset, whence);
|
|
if (res < 0)
|
|
return errno_map();
|
|
return res;
|
|
}
|
|
|
|
int realfs_poll(struct fd *fd) {
|
|
struct pollfd p = {.fd = fd->real_fd, .events = POLLPRI};
|
|
// prevent POLLNVAL
|
|
int flags = fcntl(fd->real_fd, F_GETFL, 0);
|
|
if ((flags & O_ACCMODE) != O_WRONLY)
|
|
p.events |= POLLIN;
|
|
if ((flags & O_ACCMODE) != O_RDONLY)
|
|
p.events |= POLLOUT;
|
|
if (poll(&p, 1, 0) <= 0)
|
|
return 0;
|
|
if (p.revents & POLLNVAL) {
|
|
printk("pollnval %d flags %d events %d revents %d\n", fd->real_fd, flags, p.events, p.revents);
|
|
// Seriously, fuck Darwin. I just want to poll on POLLIN|POLLOUT|POLLPRI.
|
|
// But if there's almost any kind of error, you just get POLLNVAL back,
|
|
// and no information about the bits that are in fact set. So ask for each
|
|
// separately and ignore a POLLNVAL.
|
|
// This is no longer atomic but I don't really know what to do about that.
|
|
int events = 0;
|
|
static const int pollbits[] = {POLLIN, POLLOUT, POLLPRI};
|
|
for (unsigned i = 0; i < sizeof(pollbits)/sizeof(pollbits[0]); i++) {
|
|
p.events = pollbits[i];
|
|
if (poll(&p, 1, 0) > 0 && !(p.revents & POLLNVAL))
|
|
events |= p.revents;
|
|
}
|
|
return events;
|
|
}
|
|
return p.revents;
|
|
}
|
|
|
|
int realfs_mmap(struct fd *fd, struct mem *mem, page_t start, pages_t pages, off_t offset, int prot, int flags) {
|
|
if (pages == 0)
|
|
return 0;
|
|
|
|
int mmap_flags = 0;
|
|
if (flags & MMAP_PRIVATE) mmap_flags |= MAP_PRIVATE;
|
|
if (flags & MMAP_SHARED) mmap_flags |= MAP_SHARED;
|
|
int mmap_prot = PROT_READ;
|
|
if (prot & P_WRITE) mmap_prot |= PROT_WRITE;
|
|
|
|
off_t real_offset = (offset / real_page_size) * real_page_size;
|
|
off_t correction = offset - real_offset;
|
|
char *memory = mmap(NULL, (pages * PAGE_SIZE) + correction,
|
|
mmap_prot, mmap_flags, fd->real_fd, real_offset);
|
|
return pt_map(mem, start, pages, memory, correction, prot);
|
|
}
|
|
|
|
static ssize_t realfs_readlink(struct mount *mount, const char *path, char *buf, size_t bufsize) {
|
|
ssize_t size = readlinkat(mount->root_fd, fix_path(path), buf, bufsize);
|
|
if (size < 0)
|
|
return errno_map();
|
|
return size;
|
|
}
|
|
|
|
int realfs_getpath(struct fd *fd, char *buf) {
|
|
int err = getpath(fd->real_fd, buf);
|
|
if (err < 0)
|
|
return err;
|
|
if (strcmp(fd->mount->source, "/") != 0 || strcmp(buf, "/") == 0) {
|
|
size_t source_len = strlen(fd->mount->source);
|
|
memmove(buf, buf + source_len, MAX_PATH - source_len);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int realfs_link(struct mount *mount, const char *src, const char *dst) {
|
|
int res = linkat(mount->root_fd, fix_path(src), mount->root_fd, fix_path(dst), 0);
|
|
if (res < 0)
|
|
return errno_map();
|
|
return res;
|
|
}
|
|
|
|
static int realfs_unlink(struct mount *mount, const char *path) {
|
|
int res = unlinkat(mount->root_fd, fix_path(path), 0);
|
|
if (res < 0)
|
|
return errno_map();
|
|
return res;
|
|
}
|
|
|
|
static int realfs_rmdir(struct mount *mount, const char *path) {
|
|
int err = unlinkat(mount->root_fd, fix_path(path), AT_REMOVEDIR);
|
|
if (err < 0)
|
|
return errno_map();
|
|
return 0;
|
|
}
|
|
|
|
static int realfs_rename(struct mount *mount, const char *src, const char *dst) {
|
|
int err = renameat(mount->root_fd, fix_path(src), mount->root_fd, fix_path(dst));
|
|
if (err < 0)
|
|
return errno_map();
|
|
return err;
|
|
}
|
|
|
|
static int realfs_symlink(struct mount *mount, const char *target, const char *link) {
|
|
int err = symlinkat(target, mount->root_fd, link);
|
|
if (err < 0)
|
|
return errno_map();
|
|
return err;
|
|
}
|
|
|
|
static int realfs_mknod(struct mount *mount, const char *path, mode_t_ mode, dev_t_ UNUSED(dev)) {
|
|
int err;
|
|
if (S_ISFIFO(mode)) {
|
|
lock_fchdir(mount->root_fd);
|
|
err = mkfifo(fix_path(path), mode & ~S_IFMT);
|
|
unlock_fchdir();
|
|
} else if (S_ISREG(mode)) {
|
|
err = openat(mount->root_fd, fix_path(path), O_CREAT|O_EXCL|O_RDONLY, mode & ~S_IFMT);
|
|
if (err >= 0)
|
|
err = close(err);
|
|
} else {
|
|
return _EPERM;
|
|
}
|
|
if (err < 0)
|
|
return errno_map();
|
|
return err;
|
|
}
|
|
|
|
int realfs_truncate(struct mount *mount, const char *path, off_t_ size) {
|
|
int fd = openat(mount->root_fd, fix_path(path), O_RDWR);
|
|
if (fd < 0)
|
|
return errno_map();
|
|
int err = 0;
|
|
if (ftruncate(fd, size) < 0)
|
|
err = errno_map();
|
|
close(fd);
|
|
return err;
|
|
}
|
|
|
|
static int realfs_setattr(struct mount *mount, const char *path, struct attr attr) {
|
|
path = fix_path(path);
|
|
int root = mount->root_fd;
|
|
int err;
|
|
switch (attr.type) {
|
|
case attr_uid:
|
|
err = fchownat(root, path, attr.uid, -1, 0);
|
|
break;
|
|
case attr_gid:
|
|
err = fchownat(root, path, attr.gid, -1, 0);
|
|
break;
|
|
case attr_mode:
|
|
err = fchmodat(root, path, attr.mode, 0);
|
|
break;
|
|
case attr_size:
|
|
return realfs_truncate(mount, path, attr.size);
|
|
default:
|
|
TODO("other attrs");
|
|
}
|
|
if (err < 0)
|
|
return errno_map();
|
|
return err;
|
|
}
|
|
|
|
static int realfs_fsetattr(struct fd *fd, struct attr attr) {
|
|
int real_fd = fd->real_fd;
|
|
int err;
|
|
switch (attr.type) {
|
|
case attr_uid:
|
|
err = fchown(real_fd, attr.uid, -1);
|
|
break;
|
|
case attr_gid:
|
|
err = fchown(real_fd, attr.gid, -1);
|
|
break;
|
|
case attr_mode:
|
|
err = fchmod(real_fd, attr.mode);
|
|
break;
|
|
case attr_size:
|
|
err = ftruncate(real_fd, attr.size);
|
|
break;
|
|
}
|
|
if (err < 0)
|
|
return errno_map();
|
|
return err;
|
|
}
|
|
|
|
int realfs_utime(struct mount *mount, const char *path, struct timespec atime, struct timespec mtime) {
|
|
struct timespec times[2] = {atime, mtime};
|
|
int err = utimensat(mount->root_fd, fix_path(path), times, 0);
|
|
if (err < 0)
|
|
return errno_map();
|
|
return 0;
|
|
}
|
|
|
|
static int realfs_mkdir(struct mount *mount, const char *path, mode_t_ mode) {
|
|
int err = mkdirat(mount->root_fd, fix_path(path), mode);
|
|
if (err < 0)
|
|
return errno_map();
|
|
return 0;
|
|
}
|
|
|
|
int realfs_flock(struct fd *fd, int operation) {
|
|
int real_op = 0;
|
|
if (operation & LOCK_SH_) real_op |= LOCK_SH;
|
|
if (operation & LOCK_EX_) real_op |= LOCK_EX;
|
|
if (operation & LOCK_UN_) real_op |= LOCK_UN;
|
|
if (operation & LOCK_NB_) real_op |= LOCK_NB;
|
|
return flock(fd->real_fd, real_op);
|
|
}
|
|
|
|
int realfs_statfs(struct mount *mount, struct statfsbuf *stat) {
|
|
struct statvfs vfs = {};
|
|
fstatvfs(mount->root_fd, &vfs);
|
|
stat->bsize = vfs.f_bsize;
|
|
stat->blocks = vfs.f_blocks;
|
|
stat->bfree = vfs.f_bfree;
|
|
stat->bavail = vfs.f_bavail;
|
|
stat->files = vfs.f_files;
|
|
stat->ffree = vfs.f_ffree;
|
|
stat->namelen = vfs.f_namemax;
|
|
stat->frsize = vfs.f_frsize;
|
|
return 0;
|
|
}
|
|
|
|
int realfs_mount(struct mount *mount) {
|
|
mount->root_fd = open(mount->source, O_DIRECTORY);
|
|
if (mount->root_fd < 0)
|
|
return errno_map();
|
|
return 0;
|
|
}
|
|
|
|
int realfs_fsync(struct fd *fd) {
|
|
int err = fsync(fd->real_fd);
|
|
if (err < 0)
|
|
return errno_map();
|
|
return 0;
|
|
}
|
|
|
|
int realfs_getflags(struct fd *fd) {
|
|
int flags = fcntl(fd->real_fd, F_GETFL);
|
|
if (flags < 0)
|
|
return errno_map();
|
|
return open_flags_fake_from_real(flags);
|
|
}
|
|
|
|
int realfs_setflags(struct fd *fd, dword_t flags) {
|
|
int ret = fcntl(fd->real_fd, F_SETFL, open_flags_real_from_fake(flags));
|
|
if (ret < 0)
|
|
return errno_map();
|
|
return 0;
|
|
}
|
|
|
|
ssize_t realfs_ioctl_size(int cmd) {
|
|
if (cmd == FIONREAD_)
|
|
return sizeof(dword_t);
|
|
return -1;
|
|
}
|
|
|
|
int realfs_ioctl(struct fd *fd, int cmd, void *arg) {
|
|
int err;
|
|
size_t nread;
|
|
switch (cmd) {
|
|
case FIONREAD_:
|
|
err = ioctl(fd->real_fd, FIONREAD, &nread);
|
|
if (err < 0)
|
|
return errno_map();
|
|
*(dword_t *) arg = nread;
|
|
return 0;
|
|
}
|
|
return _ENOTTY;
|
|
}
|
|
|
|
const struct fs_ops realfs = {
|
|
.name = "real", .magic = 0x7265616c,
|
|
.mount = realfs_mount,
|
|
.statfs = realfs_statfs,
|
|
|
|
.open = realfs_open,
|
|
.readlink = realfs_readlink,
|
|
.link = realfs_link,
|
|
.unlink = realfs_unlink,
|
|
.rmdir = realfs_rmdir,
|
|
.rename = realfs_rename,
|
|
.symlink = realfs_symlink,
|
|
.mknod = realfs_mknod,
|
|
|
|
.close = realfs_close,
|
|
.stat = realfs_stat,
|
|
.fstat = realfs_fstat,
|
|
.setattr = realfs_setattr,
|
|
.fsetattr = realfs_fsetattr,
|
|
.utime = realfs_utime,
|
|
.getpath = realfs_getpath,
|
|
.flock = realfs_flock,
|
|
|
|
.mkdir = realfs_mkdir,
|
|
};
|
|
|
|
const struct fd_ops realfs_fdops = {
|
|
.read = realfs_read,
|
|
.write = realfs_write,
|
|
.pread = realfs_pread,
|
|
.pwrite = realfs_pwrite,
|
|
.readdir = realfs_readdir,
|
|
.telldir = realfs_telldir,
|
|
.seekdir = realfs_seekdir,
|
|
.lseek = realfs_lseek,
|
|
.mmap = realfs_mmap,
|
|
.poll = realfs_poll,
|
|
.ioctl_size = realfs_ioctl_size,
|
|
.ioctl = realfs_ioctl,
|
|
.fsync = realfs_fsync,
|
|
.close = realfs_close,
|
|
.getflags = realfs_getflags,
|
|
.setflags = realfs_setflags,
|
|
};
|