| // 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 <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include <fbl/limits.h> |
| #include <fbl/ref_ptr.h> |
| #include <fdio/vfs.h> |
| #include <fs/vfs.h> |
| #include <zircon/assert.h> |
| |
| #include <minfs/host.h> |
| #include <minfs/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; |
| fs::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; |
| case ZX_ERR_FILE_BIG: |
| return EFBIG; |
| case ZX_ERR_NO_SPACE: |
| return ENOSPC; |
| default: |
| return EIO; |
| } |
| } |
| |
| #define FAIL(err) \ |
| do { \ |
| errno = (err); \ |
| return errno ? -1 : 0; \ |
| } while (0) |
| #define STATUS(status) \ |
| FAIL(status_to_errno(status)) |
| |
| fbl::RefPtr<fs::Vnode> fake_root = nullptr; |
| fs::Vfs fake_vfs; |
| |
| 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<minfs::Bcache> bc; |
| if (minfs::Bcache::Create(&bc, fbl::move(fd), (uint32_t) size) < 0) { |
| fprintf(stderr, "error: cannot create block cache\n"); |
| return -1; |
| } |
| |
| return minfs_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<minfs::Bcache> bc; |
| if (minfs::Bcache::Create(&bc, fbl::move(fd), (uint32_t) size) < 0) { |
| fprintf(stderr, "error: cannot create block cache\n"); |
| return -1; |
| } |
| |
| fbl::RefPtr<minfs::VnodeMinfs> vn; |
| if (minfs_mount(&vn, fbl::move(bc)) < 0) { |
| return -1; |
| } |
| fake_root = vn; |
| return 0; |
| } |
| |
| 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); |
| zx_status_t status = fake_vfs.Open(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(); |
| memset(f, 0, sizeof(file_t)); |
| 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<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 <= fbl::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 <= fbl::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 <= fbl::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; |
| // 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<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, fbl::StringPiece(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; |
| 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 = fake_vfs.Open(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; |
| if (dir->size >= vde->size) { |
| struct dirent* ent = &dir->de; |
| strcpy(ent->d_name, vde->name); |
| ent->d_type = vde->type; |
| dir->ptr += vde->size; |
| dir->size -= vde->size; |
| return ent; |
| } |
| dir->size = 0; |
| } |
| 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(); |
| free(dirp); |
| |
| return 0; |
| } |