// // iOSFS.m // iSH // // Created by Noah Peeters on 26.10.19. // #import #import #include #include "SceneDelegate.h" #include "iOSFS.h" #include "kernel/fs.h" #include "kernel/errno.h" #include "fs/path.h" #include "fs/real.h" const NSFileCoordinatorWritingOptions NSFileCoordinatorWritingForCreating = NSFileCoordinatorWritingForMerging; @interface DirectoryPicker : NSObject @property NSArray *urls; @property lock_t lock; @property cond_t cond; @end @implementation DirectoryPicker - (instancetype)init { if (self = [super init]) { lock_init(&_lock); cond_init(&_cond); } return self; } - (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller { [self documentPicker:controller didPickDocumentsAtURLs:@[]]; } - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray *)urls { lock(&_lock); self.urls = urls; notify(&_cond); unlock(&_lock); } - (int)askForURL:(NSURL **)url { TerminalViewController *terminalViewController = currentTerminalViewController; if (!terminalViewController) return _ENODEV; dispatch_async(dispatch_get_main_queue(), ^(void) { UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[ @"public.folder" ] inMode:UIDocumentPickerModeOpen]; picker.delegate = self; [terminalViewController presentViewController:picker animated:true completion:nil]; }); lock(&_lock); while (_urls == nil) { int err = wait_for(&_cond, &_lock, NULL); if (err < 0) { unlock(&_lock); return err; } } NSArray *urls = _urls; _urls = nil; unlock(&_lock); assert(urls.count <= 1); if (urls.count == 0) return _ECANCELED; *url = urls[0]; return 0; } - (void)dealloc { cond_destroy(&_cond); } @end NSURL *url_for_mount(struct mount *mount) { return (__bridge NSURL *) mount->data; } NSURL *url_for_path_in_mount(struct mount *mount, const char *path) { if (path[0] == '/') path++; return [url_for_mount(mount) URLByAppendingPathComponent:[NSString stringWithUTF8String:path] isDirectory:NO]; } const char *path_for_url_in_mount(struct mount *mount, NSURL *url, const char *fallback) { NSString *mount_path = url_for_mount(mount).path; NSString *url_path = url.path; // The `/private` prefix is a special case as described in the documentation of `URLByStandardizingPath`. if ([mount_path hasPrefix:@"/private/"]) mount_path = [mount_path substringFromIndex:8]; if ([url_path hasPrefix:@"/private/"]) url_path = [url_path substringFromIndex:8]; if (![url_path hasPrefix:mount_path]) return fallback; return [url_path substringFromIndex:[mount_path length]].UTF8String; } static int iosfs_stat(struct mount *mount, const char *path, struct statbuf *fake_stat); const struct fd_ops iosfs_fdops; static int posixErrorFromNSError(NSError *error) { while (error != nil) { if (error.domain == NSPOSIXErrorDomain) { return err_map((int) error.code); } } return 0; } static int combine_error(NSError *coordinatorError, int err) { int posix_error = posixErrorFromNSError(coordinatorError); return posix_error ? posix_error : err; } static struct fd *iosfs_open(struct mount *mount, const char *path, int flags, int mode) { NSURL *url = url_for_path_in_mount(mount, path); // FIXME: this does a redundant file coordination operation struct statbuf stats; int err = iosfs_stat(mount, path, &stats); if (err == 0 && S_ISREG(stats.mode)) { NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil]; __block NSError *error; __block struct fd *fd; __block dispatch_semaphore_t file_opened = dispatch_semaphore_create(0); dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){ void (^operation)(NSURL *url) = ^(NSURL *url) { fd = realfs_open(mount, path_for_url_in_mount(mount, url, path), flags, mode); fd->ops = &iosfs_fdops; if (IS_ERR(fd)) { dispatch_semaphore_signal(file_opened); } else { dispatch_semaphore_t file_closed = dispatch_semaphore_create(0); fd->data = (__bridge void *) file_closed; dispatch_semaphore_signal(file_opened); dispatch_semaphore_wait(file_closed, DISPATCH_TIME_FOREVER); } }; if (!(flags & O_WRONLY_) && !(flags & O_RDWR_)) { [coordinator coordinateReadingItemAtURL:url options:NSFileCoordinatorReadingWithoutChanges error:&error byAccessor:operation]; } else if (flags & O_CREAT_) { [coordinator coordinateWritingItemAtURL:url options:NSFileCoordinatorWritingForCreating error:&error byAccessor:operation]; } else { [coordinator coordinateWritingItemAtURL:url options:NSFileCoordinatorWritingForMerging error:&error byAccessor:operation]; } }); dispatch_semaphore_wait(file_opened, DISPATCH_TIME_FOREVER); int posix_error = posixErrorFromNSError(error); return posix_error ? ERR_PTR(posix_error) : fd; } struct fd *fd = realfs_open(mount, path, flags, mode); fd->ops = &iosfs_fdops; return fd; } int iosfs_close(struct fd *fd) { int err = realfs.close(fd); if (fd->data != NULL) { dispatch_semaphore_t file_closed = (__bridge dispatch_semaphore_t) fd->data; dispatch_semaphore_signal(file_closed); } return err; } static int iosfs_mount(struct mount *mount) { NSURL *url; DirectoryPicker *picker = [DirectoryPicker new]; int err = [picker askForURL:&url]; if (err) return err; // Overwrite url & base path mount->data = (void *)CFBridgingRetain(url); free((void *) mount->source); mount->source = strdup([[url path] UTF8String]); if ([url startAccessingSecurityScopedResource] == NO) { CFBridgingRelease(mount->data); return _EPERM; } if (mount_param_flag(mount->info, "unsafe")) { mount->fs = &iosfs_unsafe; } return realfs.mount(mount); } static int iosfs_umount(struct mount *mount) { NSURL *url = url_for_mount(mount); [url stopAccessingSecurityScopedResource]; CFBridgingRelease(mount->data); return 0; } static int iosfs_rename(struct mount *mount, const char *src, const char *dst) { NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil]; NSURL *src_url = url_for_path_in_mount(mount, src); NSURL *dst_url = url_for_path_in_mount(mount, dst); NSError *error; __block int err; [coordinator coordinateWritingItemAtURL:src_url options:NSFileCoordinatorWritingForMoving error:&error byAccessor:^(NSURL *url) { [coordinator itemAtURL:url willMoveToURL:dst_url]; err = realfs.rename(mount, path_for_url_in_mount(mount, url, src), dst); [coordinator itemAtURL:url didMoveToURL:dst_url]; }]; return combine_error(error, err); } static int iosfs_symlink(struct mount *mount, const char *target, const char *link) { NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil]; NSURL *dst_url = url_for_path_in_mount(mount, link); NSError *error; __block int err; [coordinator coordinateWritingItemAtURL:dst_url options:NSFileCoordinatorWritingForCreating error:&error byAccessor:^(NSURL *url) { err = realfs.symlink(mount, path_for_url_in_mount(mount, url, target), link); }]; return combine_error(error, err); } static int iosfs_mknod(struct mount *mount, const char *path, mode_t_ mode, dev_t_ dev) { NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil]; NSURL *in_url = url_for_path_in_mount(mount, path); NSError *error; __block int err; [coordinator coordinateWritingItemAtURL:in_url options:NSFileCoordinatorWritingForCreating error:&error byAccessor:^(NSURL *url) { err = realfs.mknod(mount, path_for_url_in_mount(mount, url, path), mode, dev); }]; return combine_error(error, err); } static int iosfs_setattr(struct mount *mount, const char *path, struct attr attr) { NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil]; NSURL *in_url = url_for_path_in_mount(mount, path); NSError *error; __block int err; [coordinator coordinateWritingItemAtURL:in_url options:NSFileCoordinatorWritingContentIndependentMetadataOnly error:&error byAccessor:^(NSURL *url) { err = realfs.setattr(mount, path_for_url_in_mount(mount, url, path), attr); }]; return combine_error(error, err); } static int iosfs_fsetattr(struct fd *fd, struct attr attr) { return realfs.fsetattr(fd, attr); } static ssize_t iosfs_readlink(struct mount *mount, const char *path, char *buf, size_t bufsize) { NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil]; NSURL *in_url = url_for_path_in_mount(mount, path); NSError *error; __block ssize_t size; [coordinator coordinateReadingItemAtURL:in_url options:NSFileCoordinatorReadingWithoutChanges error:&error byAccessor:^(NSURL *url) { size = realfs.readlink(mount, path_for_url_in_mount(mount, url, path), buf, bufsize); }]; int posix_error = posixErrorFromNSError(error); return posix_error ? posix_error : size; } static int iosfs_getpath(struct fd *fd, char *buf) { return realfs.getpath(fd, buf); } static int iosfs_link(struct mount *mount, const char *src, const char *dst) { NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil]; NSURL *dst_url = url_for_path_in_mount(mount, dst); NSError *error; __block int err; [coordinator coordinateWritingItemAtURL:dst_url options:NSFileCoordinatorWritingForCreating error:&error byAccessor:^(NSURL *url) { err = realfs.link(mount, src, path_for_url_in_mount(mount, url, dst)); }]; return combine_error(error, err); } static int iosfs_unlink(struct mount *mount, const char *path) { NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil]; NSURL *in_url = url_for_path_in_mount(mount, path); NSError *error; __block int err; [coordinator coordinateWritingItemAtURL:in_url options:NSFileCoordinatorWritingForDeleting error:&error byAccessor:^(NSURL *url) { err = realfs.unlink(mount, path_for_url_in_mount(mount, url, path)); }]; return combine_error(error, err); } static int iosfs_rmdir(struct mount *mount, const char *path) { NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil]; NSURL *in_url = url_for_path_in_mount(mount, path); NSError *error; __block int err; [coordinator coordinateWritingItemAtURL:in_url options:NSFileCoordinatorWritingForDeleting error:&error byAccessor:^(NSURL *url) { err = realfs.rmdir(mount, path_for_url_in_mount(mount, url, path)); }]; return combine_error(error, err); } static int iosfs_stat(struct mount *mount, const char *path, struct statbuf *fake_stat) { NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil]; NSURL *in_url = url_for_path_in_mount(mount, path); NSError *error; __block int err; [coordinator coordinateReadingItemAtURL:in_url options:NSFileCoordinatorReadingWithoutChanges error:&error byAccessor:^(NSURL *url) { err = realfs.stat(mount, path_for_url_in_mount(mount, url, path), fake_stat); }]; return combine_error(error, err); } static int iosfs_fstat(struct fd *fd, struct statbuf *fake_stat) { int err = realfs.fstat(fd, fake_stat); return err; } static int iosfs_utime(struct mount *mount, const char *path, struct timespec atime, struct timespec mtime) { NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil]; NSURL *in_url = url_for_path_in_mount(mount, path); NSError *error; __block int err; [coordinator coordinateWritingItemAtURL:in_url options:NSFileCoordinatorWritingContentIndependentMetadataOnly error:&error byAccessor:^(NSURL *url) { err = realfs.utime(mount, path_for_url_in_mount(mount, url, path), atime, mtime); }]; return combine_error(error, err); } static int iosfs_mkdir(struct mount *mount, const char *path, mode_t_ mode) { NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil]; NSURL *in_url = url_for_path_in_mount(mount, path); NSError *error; __block int err; [coordinator coordinateWritingItemAtURL:in_url options:NSFileCoordinatorWritingForCreating error:&error byAccessor:^(NSURL *url) { err = realfs.mkdir(mount, path_for_url_in_mount(mount, url, path), mode); }]; return combine_error(error, err); } static int iosfs_flock(struct fd *fd, int operation) { return realfs.flock(fd, operation); } const struct fs_ops iosfs = { .name = "ios", .magic = 0x694f5320, .mount = iosfs_mount, .umount = iosfs_umount, .statfs = realfs_statfs, .open = iosfs_open, .readlink = iosfs_readlink, .link = iosfs_link, .unlink = iosfs_unlink, .rmdir = iosfs_rmdir, .rename = iosfs_rename, .symlink = iosfs_symlink, .mknod = iosfs_mknod, .close = iosfs_close, .stat = iosfs_stat, .fstat = iosfs_fstat, .setattr = iosfs_setattr, .fsetattr = iosfs_fsetattr, .utime = iosfs_utime, .getpath = iosfs_getpath, .flock = iosfs_flock, .mkdir = iosfs_mkdir, }; const struct fs_ops iosfs_unsafe = { .name = "ios-unsafe", .magic = 0x694f5321, .mount = iosfs_mount, .umount = iosfs_umount, .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 iosfs_fdops = { .read = realfs_read, .write = realfs_write, .readdir = realfs_readdir, .telldir = realfs_telldir, .seekdir = realfs_seekdir, .lseek = realfs_lseek, .mmap = realfs_mmap, .poll = realfs_poll, .fsync = realfs_fsync, .close = iosfs_close, .getflags = realfs_getflags, .setflags = realfs_setflags, };