// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "minfs-private.h" namespace { zx_status_t do_stat(fbl::RefPtr 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 vn; uint64_t off; fs::vdircookie_t dircookie; } file_t; #define MAXFD 64 static file_t fdtab[MAXFD]; #define FD_MAGIC 0x45AB0000 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; case ZX_ERR_FILE_BIG: return EFBIG; case ZX_ERR_NO_SPACE: return ENOSPC; case ZX_ERR_ALREADY_EXISTS: return EEXIST; default: return EIO; } } #define FAIL(err) \ do { \ errno = (err); \ return errno ? -1 : 0; \ } while (0) #define STATUS(status) \ FAIL(status_to_errno(status)) // Ensure the order of these global destructors are ordered. // TODO(planders): Host-side tools should avoid using globals. struct fakeFs { ~fakeFs() { fake_root = nullptr; fake_vfs = nullptr; } fbl::RefPtr fake_root = nullptr; fbl::unique_ptr fake_vfs = nullptr; } fakeFs; } // namespace anonymous int emu_mkfs(const char* path) { fbl::unique_fd fd(open(path, O_RDWR)); if (!fd) { fprintf(stderr, "error: could not open path %s\n", path); return -1; } struct stat s; if (fstat(fd.get(), &s) < 0) { fprintf(stderr, "error: minfs could not find end of file/device\n"); return -1; } off_t size = s.st_size / minfs::kMinfsBlockSize; fbl::unique_ptr bc; if (minfs::Bcache::Create(&bc, fbl::move(fd), (uint32_t) size) < 0) { fprintf(stderr, "error: cannot create block cache\n"); return -1; } return Mkfs(fbl::move(bc)); } int emu_mount(const char* path) { fbl::unique_fd fd(open(path, O_RDWR)); if (!fd) { fprintf(stderr, "error: could not open path %s\n", path); return -1; } struct stat s; if (fstat(fd.get(), &s) < 0) { fprintf(stderr, "error: minfs could not find end of file/device\n"); return 0; } off_t size = s.st_size / minfs::kMinfsBlockSize; fbl::unique_ptr bc; if (minfs::Bcache::Create(&bc, fbl::move(fd), (uint32_t) size) < 0) { fprintf(stderr, "error: cannot create block cache\n"); return -1; } int r = minfs::minfs_mount(fbl::move(bc), &fakeFs.fake_root); if (r == 0) { fakeFs.fake_vfs.reset(fakeFs.fake_root->fs_); } return r; } int emu_mount_bcache(fbl::unique_ptr bc) { int r = minfs::minfs_mount(fbl::move(bc), &fakeFs.fake_root) == ZX_OK ? 0 : -1; if (r == 0) { fakeFs.fake_vfs.reset(fakeFs.fake_root->fs_); } return r; } bool emu_is_mounted() { return fakeFs.fake_root != nullptr; } // Since this is a host-side tool, the client may be bringing // their own C library, and we do not have the guarantee that // our ZX_FS flags align with the O_* flags. uint32_t fdio_flags_to_zxio(uint32_t flags) { uint32_t result = 0; switch (flags & O_ACCMODE) { case O_RDONLY: result |= ZX_FS_RIGHT_READABLE; break; case O_WRONLY: result |= ZX_FS_RIGHT_WRITABLE; break; case O_RDWR: result |= ZX_FS_RIGHT_READABLE | ZX_FS_RIGHT_WRITABLE; break; } #ifdef O_PATH if (flags & O_PATH) { result |= ZX_FS_FLAG_VNODE_REF_ONLY; } #endif #ifdef O_DIRECTORY if (flags & O_DIRECTORY) { result |= ZX_FS_FLAG_DIRECTORY; } #endif if (flags & O_CREAT) { result |= ZX_FS_FLAG_CREATE; } if (flags & O_EXCL) { result |= ZX_FS_FLAG_EXCLUSIVE; } if (flags & O_TRUNC) { result |= ZX_FS_FLAG_TRUNCATE; } if (flags & O_APPEND) { result |= ZX_FS_FLAG_APPEND; } return result; } int emu_open(const char* path, int flags, mode_t mode) { //TODO: fdtab lock ZX_DEBUG_ASSERT_MSG(!host_path(path), "'emu_' functions can only operate on target paths"); int fd; if (flags & O_APPEND) { errno = ENOTSUP; return -1; } for (fd = 0; fd < MAXFD; fd++) { if (fdtab[fd].vn == nullptr) { fbl::RefPtr vn_fs; fbl::StringPiece str(path + PREFIX_SIZE); flags = fdio_flags_to_zxio(flags); zx_status_t status = fakeFs.fake_vfs->Open(fakeFs.fake_root, &vn_fs, str, &str, flags, mode); if (status < 0) { STATUS(status); } fdtab[fd].vn = fbl::RefPtr::Downcast(vn_fs); return fd | FD_MAGIC; } } FAIL(EMFILE); } int emu_close(int fd) { //TODO: fdtab lock file_t* f = file_get(fd); if (f == nullptr) { return -1; } f->vn->Close(); f->vn.reset(); f->off = 0; f->dircookie.Reset(); return 0; } ssize_t emu_write(int fd, const void* buf, size_t count) { file_t* f = file_get(fd); if (f == nullptr) { return -1; } size_t actual; zx_status_t status = f->vn->Write(buf, count, f->off, &actual); if (status == ZX_OK) { f->off += actual; ZX_DEBUG_ASSERT(actual <= fbl::numeric_limits::max()); return static_cast(actual); } ZX_DEBUG_ASSERT(status < 0); STATUS(status); } ssize_t emu_pwrite(int fd, const void* buf, size_t count, off_t off) { file_t* f = file_get(fd); if (f == nullptr) { return -1; } size_t actual; zx_status_t status = f->vn->Write(buf, count, off, &actual); if (status == ZX_OK) { ZX_DEBUG_ASSERT(actual <= fbl::numeric_limits::max()); return static_cast(actual); } ZX_DEBUG_ASSERT(status < 0); STATUS(status); } ssize_t emu_read(int fd, void* buf, size_t count) { file_t* f = file_get(fd); if (f == nullptr) { return -1; } size_t actual; zx_status_t status = f->vn->Read(buf, count, f->off, &actual); if (status == ZX_OK) { f->off += actual; ZX_DEBUG_ASSERT(actual <= fbl::numeric_limits::max()); return static_cast(actual); } ZX_DEBUG_ASSERT(status < 0); STATUS(status); } ssize_t emu_pread(int fd, void* buf, size_t count, off_t off) { file_t* f = file_get(fd); if (f == nullptr) { return -1; } size_t actual; zx_status_t status = f->vn->Read(buf, count, off, &actual); if (status == ZX_OK) { ZX_DEBUG_ASSERT(actual <= fbl::numeric_limits::max()); return static_cast(actual); } ZX_DEBUG_ASSERT(status < 0); STATUS(status); } int emu_ftruncate(int fd, off_t len) { file_t* f = file_get(fd); if (f == nullptr) { return -1; } int r = f->vn->Truncate(len); return r < 0 ? -1 : r; } off_t emu_lseek(int fd, off_t offset, int whence) { file_t* f = file_get(fd); if (f == nullptr) { return -1; } 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_get(fd); if (f == nullptr) { return -1; } STATUS(do_stat(f->vn, s)); } int emu_stat(const char* fn, struct stat* s) { ZX_DEBUG_ASSERT_MSG(!host_path(fn), "'emu_' functions can only operate on target paths"); fbl::RefPtr vn = fakeFs.fake_root; fbl::RefPtr cur = fakeFs.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 vn_fs; status = cur->Lookup(&vn_fs, fbl::StringPiece(fn, len)); if (status != ZX_OK) { return -ENOENT; } vn = fbl::RefPtr::Downcast(vn_fs); if (cur != fakeFs.fake_root) { cur->Close(); } cur = vn; fn = nextpath; } while (nextpath != nullptr); status = do_stat(vn, s); if (vn != fakeFs.fake_root) { vn->Close(); } STATUS(status); } #define DIR_BUFSIZE 2048 typedef struct MINDIR { uint64_t magic; fbl::RefPtr vn; fs::vdircookie_t cookie; uint8_t* ptr; uint8_t data[DIR_BUFSIZE]; size_t size; struct dirent de; } MINDIR; int emu_mkdir(const char* path, mode_t mode) { ZX_DEBUG_ASSERT_MSG(!host_path(path), "'emu_' functions can only operate on target paths"); 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; } } DIR* emu_opendir(const char* name) { ZX_DEBUG_ASSERT_MSG(!host_path(name), "'emu_' functions can only operate on target paths"); fbl::RefPtr vn; fbl::StringPiece path(name + PREFIX_SIZE); zx_status_t status = fakeFs.fake_vfs->Open(fakeFs.fake_root, &vn, path, &path, O_RDONLY, 0); if (status != ZX_OK) { return nullptr; } MINDIR* dir = (MINDIR*)calloc(1, sizeof(MINDIR)); dir->magic = minfs::kMinfsMagic0; dir->vn = fbl::RefPtr::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; struct dirent* ent = &dir->de; size_t name_len = vde->size; size_t entry_len = vde->size + sizeof(vdirent_t); ZX_DEBUG_ASSERT(dir->size >= entry_len); memcpy(ent->d_name, vde->name, name_len); ent->d_name[name_len] = '\0'; ent->d_type = vde->type; dir->ptr += entry_len; dir->size -= entry_len; return ent; } size_t actual; zx_status_t status = dir->vn->Readdir(&dir->cookie, &dir->data, DIR_BUFSIZE, &actual); if (status != ZX_OK || actual == 0) { break; } dir->ptr = dir->data; dir->size = actual; } return nullptr; } void emu_rewinddir(DIR* dirp) { MINDIR* dir = (MINDIR*)dirp; dir->size = 0; dir->ptr = NULL; dir->cookie.n = 0; } int emu_closedir(DIR* dirp) { if (((uint64_t*)dirp)[0] != minfs::kMinfsMagic0) { return closedir(dirp); } MINDIR* dir = (MINDIR*)dirp; dir->vn->Close(); dir->vn.reset(); free(dirp); return 0; }