| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // for S_IF* |
| #define _XOPEN_SOURCE |
| #include "host.h" |
| |
| #include <dirent.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include <fs/vfs.h> |
| |
| #include <fdio/vfs.h> |
| #include "minfs.h" |
| #include "minfs-private.h" |
| |
| static zx_status_t do_stat(fbl::RefPtr<fs::Vnode> vn, struct stat* s) { |
| vnattr_t a; |
| zx_status_t status = vn->Getattr(&a); |
| if (status == ZX_OK) { |
| memset(s, 0, sizeof(struct stat)); |
| s->st_mode = a.mode; |
| s->st_size = a.size; |
| s->st_ino = a.inode; |
| s->st_ctime = a.create_time; |
| s->st_mtime = a.modify_time; |
| } |
| return status; |
| } |
| |
| typedef struct { |
| fbl::RefPtr<fs::Vnode> vn; |
| uint64_t off; |
| vdircookie_t dircookie; |
| } file_t; |
| |
| #define MAXFD 64 |
| |
| static file_t fdtab[MAXFD]; |
| |
| #define FD_MAGIC 0x45AB0000 |
| |
| static file_t* file_get(int fd) { |
| if (((fd)&0xFFFF0000) != FD_MAGIC) { |
| return nullptr; |
| } |
| fd &= 0x0000FFFF; |
| if ((fd < 0) || (fd >= MAXFD)) { |
| return nullptr; |
| } |
| if (fdtab[fd].vn == nullptr) { |
| return nullptr; |
| } |
| return fdtab + fd; |
| } |
| |
| int status_to_errno(zx_status_t status) { |
| switch (status) { |
| case ZX_OK: |
| return 0; |
| default: |
| return EIO; |
| } |
| } |
| |
| #define FAIL(err) \ |
| do { \ |
| errno = (err); \ |
| return errno ? -1 : 0; \ |
| } while (0) |
| #define STATUS(status) \ |
| FAIL(status_to_errno(status)) |
| #define FILE_GET(f, fd) \ |
| do { \ |
| if ((f = file_get(fd)) == nullptr) { \ |
| FAIL(EBADF); \ |
| } \ |
| } while (0) |
| #define FILE_WRAP(f, fd, name, args...) \ |
| do { \ |
| if ((f = file_get(fd)) == nullptr) \ |
| return name(args); \ |
| } while (0) |
| #define PATH_WRAP(path, name, args...) \ |
| do { \ |
| if (check_path(path)) \ |
| return name(args); \ |
| } while (0) |
| |
| fbl::RefPtr<fs::Vnode> fake_root; |
| |
| static inline int check_path(const char* path) { |
| if (strncmp(path, PATH_PREFIX, PREFIX_SIZE) || (fake_root == nullptr)) { |
| return -1; |
| } |
| return 0; |
| } |
| |
| int emu_open(const char* path, int flags, mode_t mode) { |
| //TODO: fdtab lock |
| PATH_WRAP(path, open, path, flags, mode); |
| int fd; |
| for (fd = 0; fd < MAXFD; fd++) { |
| if (fdtab[fd].vn == nullptr) { |
| const char* pathout = nullptr; |
| fbl::RefPtr<fs::Vnode> vn_fs; |
| zx_status_t status = minfs::vfs.Open(fake_root, &vn_fs, path + PREFIX_SIZE, &pathout, flags, mode); |
| if (status < 0) { |
| STATUS(status); |
| } |
| fdtab[fd].vn = fbl::RefPtr<fs::Vnode>::Downcast(vn_fs); |
| return fd | FD_MAGIC; |
| } |
| } |
| FAIL(EMFILE); |
| } |
| |
| int emu_close(int fd) { |
| //TODO: fdtab lock |
| file_t* f; |
| FILE_WRAP(f, fd, close, fd); |
| f->vn->Close(); |
| memset(f, 0, sizeof(file_t)); |
| return 0; |
| } |
| |
| int emu_mkdir(const char* path, mode_t mode) { |
| PATH_WRAP(path, mkdir, path, mode); |
| mode = S_IFDIR; |
| int fd = emu_open(path, O_CREAT | O_EXCL, S_IFDIR | (mode & 0777)); |
| if (fd >= 0) { |
| emu_close(fd); |
| return 0; |
| } else { |
| return fd; |
| } |
| } |
| |
| ssize_t emu_read(int fd, void* buf, size_t count) { |
| file_t* f; |
| FILE_WRAP(f, fd, read, fd, buf, count); |
| ssize_t r = f->vn->Read(buf, count, f->off); |
| if (r > 0) { |
| f->off += r; |
| } |
| return r; |
| } |
| |
| ssize_t emu_write(int fd, const void* buf, size_t count) { |
| file_t* f; |
| FILE_WRAP(f, fd, write, fd, buf, count); |
| ssize_t r = f->vn->Write(buf, count, f->off); |
| if (r > 0) { |
| f->off += r; |
| } |
| return r; |
| } |
| |
| off_t emu_lseek(int fd, off_t offset, int whence) { |
| file_t* f; |
| FILE_WRAP(f, fd, lseek, fd, offset, whence); |
| |
| uint64_t old = f->off; |
| uint64_t n; |
| vnattr_t a; |
| |
| switch (whence) { |
| case SEEK_SET: |
| if (offset < 0) { |
| FAIL(EINVAL); |
| } |
| f->off = offset; |
| break; |
| case SEEK_END: |
| if (f->vn->Getattr(&a)) { |
| FAIL(EINVAL); |
| } |
| old = a.size; |
| // fall through |
| case SEEK_CUR: |
| n = old + offset; |
| if (offset < 0) { |
| if (n >= old) { |
| FAIL(EINVAL); |
| } |
| } else { |
| if (n < old) { |
| FAIL(EINVAL); |
| } |
| } |
| f->off = n; |
| break; |
| default: |
| FAIL(EINVAL); |
| } |
| return f->off; |
| } |
| |
| int emu_fstat(int fd, struct stat* s) { |
| file_t* f; |
| FILE_WRAP(f, fd, fstat, fd, s); |
| STATUS(do_stat(f->vn, s)); |
| } |
| |
| int emu_unlink(const char* path) { |
| PATH_WRAP(path, unlink, path); |
| fbl::RefPtr<fs::Vnode> vn; |
| zx_status_t status = minfs::vfs.Walk(fake_root, &vn, path + PREFIX_SIZE, &path); |
| if (status == ZX_OK) { |
| status = vn->Unlink(path, strlen(path), false); |
| vn->Close(); |
| } |
| STATUS(status); |
| } |
| |
| int emu_rename(const char* oldpath, const char* newpath) { |
| STATUS(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| int emu_stat(const char* fn, struct stat* s) { |
| PATH_WRAP(fn, stat, fn, s); |
| fbl::RefPtr<fs::Vnode> vn = fake_root; |
| fbl::RefPtr<fs::Vnode> cur = fake_root; |
| zx_status_t status; |
| const char* nextpath = nullptr; |
| size_t len; |
| |
| fn += PREFIX_SIZE; |
| do { |
| while (fn[0] == '/') { |
| fn++; |
| } |
| if (fn[0] == 0) { |
| fn = "."; |
| } |
| len = strlen(fn); |
| nextpath = strchr(fn, '/'); |
| if (nextpath != nullptr) { |
| len = nextpath - fn; |
| nextpath++; |
| } |
| fbl::RefPtr<fs::Vnode> vn_fs; |
| status = cur->Lookup(&vn_fs, fn, len); |
| if (status != ZX_OK) { |
| return -ENOENT; |
| } |
| vn = fbl::RefPtr<fs::Vnode>::Downcast(vn_fs); |
| if (cur != fake_root) { |
| cur->Close(); |
| } |
| cur = vn; |
| fn = nextpath; |
| } while (nextpath != nullptr); |
| |
| status = do_stat(vn, s); |
| if (vn != fake_root) { |
| vn->Close(); |
| } |
| STATUS(status); |
| } |
| |
| #define DIR_BUFSIZE 2048 |
| |
| typedef struct MINDIR { |
| uint64_t magic; |
| fbl::RefPtr<fs::Vnode> vn; |
| vdircookie_t cookie; |
| uint8_t* ptr; |
| uint8_t data[DIR_BUFSIZE]; |
| size_t size; |
| struct dirent de; |
| } MINDIR; |
| |
| DIR* emu_opendir(const char* name) { |
| PATH_WRAP(name, opendir, name); |
| fbl::RefPtr<fs::Vnode> vn; |
| zx_status_t status = minfs::vfs.Open(fake_root, &vn, name + PREFIX_SIZE, &name, O_RDONLY, 0); |
| if (status != ZX_OK) { |
| return nullptr; |
| } |
| MINDIR* dir = (MINDIR*)calloc(1, sizeof(MINDIR)); |
| dir->magic = minfs::kMinfsMagic0; |
| dir->vn = fbl::RefPtr<fs::Vnode>::Downcast(vn); |
| return (DIR*) dir; |
| } |
| |
| struct dirent* emu_readdir(DIR* dirp) { |
| MINDIR* dir = (MINDIR*)dirp; |
| for (;;) { |
| if (dir->size >= sizeof(vdirent_t)) { |
| vdirent_t* vde = (vdirent_t*)dir->ptr; |
| if (dir->size >= vde->size) { |
| struct dirent* ent = &dir->de; |
| strcpy(ent->d_name, vde->name); |
| dir->ptr += vde->size; |
| dir->size -= vde->size; |
| return ent; |
| } |
| dir->size = 0; |
| } |
| zx_status_t status = dir->vn->Readdir(&dir->cookie, &dir->data, DIR_BUFSIZE); |
| if (status <= 0) { |
| break; |
| } |
| dir->ptr = dir->data; |
| dir->size = status; |
| } |
| return nullptr; |
| } |
| |
| int emu_closedir(DIR* dirp) { |
| if (((uint64_t*)dirp)[0] != minfs::kMinfsMagic0) { |
| return closedir(dirp); |
| } |
| |
| MINDIR* dir = (MINDIR*)dirp; |
| dir->vn->Close(); |
| free(dirp); |
| |
| return 0; |
| } |