| // 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 <dirent.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <limits> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include <fbl/ref_ptr.h> |
| #include <fbl/unique_ptr.h> |
| #include <fs/vfs.h> |
| #include <minfs/format.h> |
| #include <minfs/host.h> |
| #include <minfs/minfs.h> |
| #include <zircon/assert.h> |
| |
| #include <utility> |
| |
| #include "minfs-private.h" |
| |
| namespace { |
| |
| 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 = static_cast<mode_t>(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; |
| 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<minfs::VnodeMinfs> fake_root = nullptr; |
| fbl::unique_ptr<fs::Vfs> fake_vfs = nullptr; |
| } fakeFs; |
| |
| } // namespace anonymous |
| |
| int emu_mkfs(const char* path) { |
| fbl::unique_fd fd(open(path, O_RDWR)); |
| if (!fd) { |
| FS_TRACE_ERROR("error: could not open path %s\n", path); |
| return -1; |
| } |
| |
| struct stat s; |
| if (fstat(fd.get(), &s) < 0) { |
| FS_TRACE_ERROR("error: minfs could not find end of file/device\n"); |
| return -1; |
| } |
| |
| off_t size = s.st_size / minfs::kMinfsBlockSize; |
| |
| fbl::unique_ptr<minfs::Bcache> bc; |
| if (minfs::Bcache::Create(&bc, std::move(fd), (uint32_t) size) < 0) { |
| FS_TRACE_ERROR("error: cannot create block cache\n"); |
| return -1; |
| } |
| |
| return Mkfs(std::move(bc)); |
| } |
| |
| static const minfs::MountOptions kDefaultMountOptions = { |
| .readonly = false, |
| .metrics = false, |
| .verbose = false, |
| }; |
| |
| int emu_mount_bcache(fbl::unique_ptr<minfs::Bcache> bc) { |
| int r = minfs::Mount(std::move(bc), kDefaultMountOptions, &fakeFs.fake_root) == ZX_OK ? 0 : -1; |
| if (r == 0) { |
| fakeFs.fake_vfs.reset(fakeFs.fake_root->Vfs()); |
| } |
| return r; |
| } |
| |
| int emu_create_bcache(const char* path, fbl::unique_ptr<minfs::Bcache>* out_bc) { |
| fbl::unique_fd fd(open(path, O_RDWR)); |
| if (!fd) { |
| FS_TRACE_ERROR("error: could not open path %s\n", path); |
| return -1; |
| } |
| |
| struct stat s; |
| if (fstat(fd.get(), &s) < 0) { |
| FS_TRACE_ERROR("error: minfs could not find end of file/device\n"); |
| return 0; |
| } |
| |
| off_t size = s.st_size / minfs::kMinfsBlockSize; |
| |
| fbl::unique_ptr<minfs::Bcache> bc; |
| if (minfs::Bcache::Create(&bc, std::move(fd), (uint32_t) size) != ZX_OK) { |
| FS_TRACE_ERROR("error: cannot create block cache\n"); |
| return -1; |
| } |
| |
| *out_bc = std::move(bc); |
| return 0; |
| } |
| |
| int emu_mount(const char* path) { |
| fbl::unique_ptr<minfs::Bcache> bc; |
| if (emu_create_bcache(path, &bc) != 0) { |
| return -1; |
| } |
| return emu_mount_bcache(std::move(bc)); |
| } |
| |
| int emu_get_used_resources(const char* path, uint64_t* out_data_size, uint64_t* out_inodes, |
| uint64_t* out_used_size) { |
| fbl::unique_ptr<minfs::Bcache> bc; |
| if (emu_create_bcache(path, &bc) != 0) { |
| return -1; |
| } |
| if (minfs::UsedDataSize(bc, out_data_size) != ZX_OK) { |
| return -1; |
| } |
| |
| if (minfs::UsedInodes(bc, out_inodes) != ZX_OK) { |
| return -1; |
| } |
| |
| if (minfs::UsedSize(bc, out_used_size) != ZX_OK) { |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| 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<fs::Vnode> 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<fs::Vnode>::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 <= std::numeric_limits<ssize_t>::max()); |
| return static_cast<ssize_t>(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 <= std::numeric_limits<ssize_t>::max()); |
| return static_cast<ssize_t>(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 <= std::numeric_limits<ssize_t>::max()); |
| return static_cast<ssize_t>(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 <= std::numeric_limits<ssize_t>::max()); |
| return static_cast<ssize_t>(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; |
| __FALLTHROUGH; |
| 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<fs::Vnode> vn = fakeFs.fake_root; |
| fbl::RefPtr<fs::Vnode> 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) { |
| break; |
| } |
| len = strlen(fn); |
| nextpath = strchr(fn, '/'); |
| if (nextpath != nullptr) { |
| len = nextpath - fn; |
| nextpath++; |
| } |
| fbl::RefPtr<fs::Vnode> vn_fs; |
| status = cur->Lookup(&vn_fs, fbl::StringPiece(fn, len)); |
| if (status != ZX_OK) { |
| return -ENOENT; |
| } |
| vn = fbl::RefPtr<fs::Vnode>::Downcast(vn_fs); |
| cur = vn; |
| fn = nextpath; |
| } while (nextpath != nullptr); |
| |
| status = do_stat(vn, s); |
| STATUS(status); |
| } |
| |
| #define DIR_BUFSIZE 2048 |
| |
| typedef struct MINDIR { |
| uint64_t magic; |
| fbl::RefPtr<fs::Vnode> 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<fs::Vnode> 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<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; |
| 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; |
| } |