1// Copyright 2016 The Fuchsia Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5// for S_IF* 6#define _XOPEN_SOURCE 7#include <dirent.h> 8#include <errno.h> 9#include <fcntl.h> 10#include <stdio.h> 11#include <stdlib.h> 12#include <string.h> 13#include <sys/stat.h> 14#include <unistd.h> 15 16#include <fbl/limits.h> 17#include <fbl/ref_ptr.h> 18#include <fbl/unique_ptr.h> 19#include <lib/fdio/vfs.h> 20#include <fs/vfs.h> 21#include <minfs/format.h> 22#include <minfs/host.h> 23#include <minfs/minfs.h> 24#include <zircon/assert.h> 25 26#include "minfs-private.h" 27 28namespace { 29 30zx_status_t do_stat(fbl::RefPtr<fs::Vnode> vn, struct stat* s) { 31 vnattr_t a; 32 zx_status_t status = vn->Getattr(&a); 33 if (status == ZX_OK) { 34 memset(s, 0, sizeof(struct stat)); 35 s->st_mode = a.mode; 36 s->st_size = a.size; 37 s->st_ino = a.inode; 38 s->st_ctime = a.create_time; 39 s->st_mtime = a.modify_time; 40 } 41 return status; 42} 43 44typedef struct { 45 fbl::RefPtr<fs::Vnode> vn; 46 uint64_t off; 47 fs::vdircookie_t dircookie; 48} file_t; 49 50#define MAXFD 64 51 52static file_t fdtab[MAXFD]; 53 54#define FD_MAGIC 0x45AB0000 55 56file_t* file_get(int fd) { 57 if (((fd)&0xFFFF0000) != FD_MAGIC) { 58 return nullptr; 59 } 60 fd &= 0x0000FFFF; 61 if ((fd < 0) || (fd >= MAXFD)) { 62 return nullptr; 63 } 64 if (fdtab[fd].vn == nullptr) { 65 return nullptr; 66 } 67 return fdtab + fd; 68} 69 70int status_to_errno(zx_status_t status) { 71 switch (status) { 72 case ZX_OK: 73 return 0; 74 case ZX_ERR_FILE_BIG: 75 return EFBIG; 76 case ZX_ERR_NO_SPACE: 77 return ENOSPC; 78 case ZX_ERR_ALREADY_EXISTS: 79 return EEXIST; 80 default: 81 return EIO; 82 } 83} 84 85#define FAIL(err) \ 86 do { \ 87 errno = (err); \ 88 return errno ? -1 : 0; \ 89 } while (0) 90#define STATUS(status) \ 91 FAIL(status_to_errno(status)) 92 93// Ensure the order of these global destructors are ordered. 94// TODO(planders): Host-side tools should avoid using globals. 95struct fakeFs { 96 ~fakeFs() { 97 fake_root = nullptr; 98 fake_vfs = nullptr; 99 } 100 fbl::RefPtr<minfs::VnodeMinfs> fake_root = nullptr; 101 fbl::unique_ptr<fs::Vfs> fake_vfs = nullptr; 102} fakeFs; 103 104} // namespace anonymous 105 106int emu_mkfs(const char* path) { 107 fbl::unique_fd fd(open(path, O_RDWR)); 108 if (!fd) { 109 fprintf(stderr, "error: could not open path %s\n", path); 110 return -1; 111 } 112 113 struct stat s; 114 if (fstat(fd.get(), &s) < 0) { 115 fprintf(stderr, "error: minfs could not find end of file/device\n"); 116 return -1; 117 } 118 119 off_t size = s.st_size / minfs::kMinfsBlockSize; 120 121 fbl::unique_ptr<minfs::Bcache> bc; 122 if (minfs::Bcache::Create(&bc, fbl::move(fd), (uint32_t) size) < 0) { 123 fprintf(stderr, "error: cannot create block cache\n"); 124 return -1; 125 } 126 127 return Mkfs(fbl::move(bc)); 128} 129 130int emu_mount(const char* path) { 131 fbl::unique_fd fd(open(path, O_RDWR)); 132 if (!fd) { 133 fprintf(stderr, "error: could not open path %s\n", path); 134 return -1; 135 } 136 137 struct stat s; 138 if (fstat(fd.get(), &s) < 0) { 139 fprintf(stderr, "error: minfs could not find end of file/device\n"); 140 return 0; 141 } 142 143 off_t size = s.st_size / minfs::kMinfsBlockSize; 144 145 fbl::unique_ptr<minfs::Bcache> bc; 146 if (minfs::Bcache::Create(&bc, fbl::move(fd), (uint32_t) size) < 0) { 147 fprintf(stderr, "error: cannot create block cache\n"); 148 return -1; 149 } 150 151 int r = minfs::minfs_mount(fbl::move(bc), &fakeFs.fake_root); 152 if (r == 0) { 153 fakeFs.fake_vfs.reset(fakeFs.fake_root->fs_); 154 } 155 return r; 156} 157 158int emu_mount_bcache(fbl::unique_ptr<minfs::Bcache> bc) { 159 int r = minfs::minfs_mount(fbl::move(bc), &fakeFs.fake_root) == ZX_OK ? 0 : -1; 160 if (r == 0) { 161 fakeFs.fake_vfs.reset(fakeFs.fake_root->fs_); 162 } 163 return r; 164} 165 166bool emu_is_mounted() { 167 return fakeFs.fake_root != nullptr; 168} 169 170// Since this is a host-side tool, the client may be bringing 171// their own C library, and we do not have the guarantee that 172// our ZX_FS flags align with the O_* flags. 173uint32_t fdio_flags_to_zxio(uint32_t flags) { 174 uint32_t result = 0; 175 switch (flags & O_ACCMODE) { 176 case O_RDONLY: 177 result |= ZX_FS_RIGHT_READABLE; 178 break; 179 case O_WRONLY: 180 result |= ZX_FS_RIGHT_WRITABLE; 181 break; 182 case O_RDWR: 183 result |= ZX_FS_RIGHT_READABLE | ZX_FS_RIGHT_WRITABLE; 184 break; 185 } 186#ifdef O_PATH 187 if (flags & O_PATH) { 188 result |= ZX_FS_FLAG_VNODE_REF_ONLY; 189 } 190#endif 191#ifdef O_DIRECTORY 192 if (flags & O_DIRECTORY) { 193 result |= ZX_FS_FLAG_DIRECTORY; 194 } 195#endif 196 if (flags & O_CREAT) { 197 result |= ZX_FS_FLAG_CREATE; 198 } 199 if (flags & O_EXCL) { 200 result |= ZX_FS_FLAG_EXCLUSIVE; 201 } 202 if (flags & O_TRUNC) { 203 result |= ZX_FS_FLAG_TRUNCATE; 204 } 205 if (flags & O_APPEND) { 206 result |= ZX_FS_FLAG_APPEND; 207 } 208 209 return result; 210} 211 212int emu_open(const char* path, int flags, mode_t mode) { 213 //TODO: fdtab lock 214 ZX_DEBUG_ASSERT_MSG(!host_path(path), "'emu_' functions can only operate on target paths"); 215 int fd; 216 if (flags & O_APPEND) { 217 errno = ENOTSUP; 218 return -1; 219 } 220 for (fd = 0; fd < MAXFD; fd++) { 221 if (fdtab[fd].vn == nullptr) { 222 fbl::RefPtr<fs::Vnode> vn_fs; 223 fbl::StringPiece str(path + PREFIX_SIZE); 224 flags = fdio_flags_to_zxio(flags); 225 zx_status_t status = fakeFs.fake_vfs->Open(fakeFs.fake_root, &vn_fs, str, &str, flags, mode); 226 if (status < 0) { 227 STATUS(status); 228 } 229 fdtab[fd].vn = fbl::RefPtr<fs::Vnode>::Downcast(vn_fs); 230 return fd | FD_MAGIC; 231 } 232 } 233 FAIL(EMFILE); 234} 235 236int emu_close(int fd) { 237 //TODO: fdtab lock 238 file_t* f = file_get(fd); 239 if (f == nullptr) { 240 return -1; 241 } 242 f->vn->Close(); 243 f->vn.reset(); 244 f->off = 0; 245 f->dircookie.Reset(); 246 return 0; 247} 248 249ssize_t emu_write(int fd, const void* buf, size_t count) { 250 file_t* f = file_get(fd); 251 if (f == nullptr) { 252 return -1; 253 } 254 size_t actual; 255 zx_status_t status = f->vn->Write(buf, count, f->off, &actual); 256 if (status == ZX_OK) { 257 f->off += actual; 258 ZX_DEBUG_ASSERT(actual <= fbl::numeric_limits<ssize_t>::max()); 259 return static_cast<ssize_t>(actual); 260 } 261 262 ZX_DEBUG_ASSERT(status < 0); 263 STATUS(status); 264} 265 266ssize_t emu_pwrite(int fd, const void* buf, size_t count, off_t off) { 267 file_t* f = file_get(fd); 268 if (f == nullptr) { 269 return -1; 270 } 271 size_t actual; 272 zx_status_t status = f->vn->Write(buf, count, off, &actual); 273 if (status == ZX_OK) { 274 ZX_DEBUG_ASSERT(actual <= fbl::numeric_limits<ssize_t>::max()); 275 return static_cast<ssize_t>(actual); 276 } 277 278 ZX_DEBUG_ASSERT(status < 0); 279 STATUS(status); 280} 281 282ssize_t emu_read(int fd, void* buf, size_t count) { 283 file_t* f = file_get(fd); 284 if (f == nullptr) { 285 return -1; 286 } 287 size_t actual; 288 zx_status_t status = f->vn->Read(buf, count, f->off, &actual); 289 if (status == ZX_OK) { 290 f->off += actual; 291 ZX_DEBUG_ASSERT(actual <= fbl::numeric_limits<ssize_t>::max()); 292 return static_cast<ssize_t>(actual); 293 } 294 ZX_DEBUG_ASSERT(status < 0); 295 STATUS(status); 296} 297 298ssize_t emu_pread(int fd, void* buf, size_t count, off_t off) { 299 file_t* f = file_get(fd); 300 if (f == nullptr) { 301 return -1; 302 } 303 size_t actual; 304 zx_status_t status = f->vn->Read(buf, count, off, &actual); 305 if (status == ZX_OK) { 306 ZX_DEBUG_ASSERT(actual <= fbl::numeric_limits<ssize_t>::max()); 307 return static_cast<ssize_t>(actual); 308 } 309 ZX_DEBUG_ASSERT(status < 0); 310 STATUS(status); 311} 312 313int emu_ftruncate(int fd, off_t len) { 314 file_t* f = file_get(fd); 315 if (f == nullptr) { 316 return -1; 317 } 318 int r = f->vn->Truncate(len); 319 return r < 0 ? -1 : r; 320} 321 322off_t emu_lseek(int fd, off_t offset, int whence) { 323 file_t* f = file_get(fd); 324 if (f == nullptr) { 325 return -1; 326 } 327 328 uint64_t old = f->off; 329 uint64_t n; 330 vnattr_t a; 331 332 switch (whence) { 333 case SEEK_SET: 334 if (offset < 0) { 335 FAIL(EINVAL); 336 } 337 f->off = offset; 338 break; 339 case SEEK_END: 340 if (f->vn->Getattr(&a)) { 341 FAIL(EINVAL); 342 } 343 old = a.size; 344 // fall through 345 case SEEK_CUR: 346 n = old + offset; 347 if (offset < 0) { 348 if (n >= old) { 349 FAIL(EINVAL); 350 } 351 } else { 352 if (n < old) { 353 FAIL(EINVAL); 354 } 355 } 356 f->off = n; 357 break; 358 default: 359 FAIL(EINVAL); 360 } 361 return f->off; 362} 363 364int emu_fstat(int fd, struct stat* s) { 365 file_t* f = file_get(fd); 366 if (f == nullptr) { 367 return -1; 368 } 369 STATUS(do_stat(f->vn, s)); 370} 371 372int emu_stat(const char* fn, struct stat* s) { 373 ZX_DEBUG_ASSERT_MSG(!host_path(fn), "'emu_' functions can only operate on target paths"); 374 fbl::RefPtr<fs::Vnode> vn = fakeFs.fake_root; 375 fbl::RefPtr<fs::Vnode> cur = fakeFs.fake_root; 376 zx_status_t status; 377 const char* nextpath = nullptr; 378 size_t len; 379 380 fn += PREFIX_SIZE; 381 do { 382 while (fn[0] == '/') { 383 fn++; 384 } 385 if (fn[0] == 0) { 386 fn = "."; 387 } 388 len = strlen(fn); 389 nextpath = strchr(fn, '/'); 390 if (nextpath != nullptr) { 391 len = nextpath - fn; 392 nextpath++; 393 } 394 fbl::RefPtr<fs::Vnode> vn_fs; 395 status = cur->Lookup(&vn_fs, fbl::StringPiece(fn, len)); 396 if (status != ZX_OK) { 397 return -ENOENT; 398 } 399 vn = fbl::RefPtr<fs::Vnode>::Downcast(vn_fs); 400 if (cur != fakeFs.fake_root) { 401 cur->Close(); 402 } 403 cur = vn; 404 fn = nextpath; 405 } while (nextpath != nullptr); 406 407 status = do_stat(vn, s); 408 if (vn != fakeFs.fake_root) { 409 vn->Close(); 410 } 411 STATUS(status); 412} 413 414#define DIR_BUFSIZE 2048 415 416typedef struct MINDIR { 417 uint64_t magic; 418 fbl::RefPtr<fs::Vnode> vn; 419 fs::vdircookie_t cookie; 420 uint8_t* ptr; 421 uint8_t data[DIR_BUFSIZE]; 422 size_t size; 423 struct dirent de; 424} MINDIR; 425 426int emu_mkdir(const char* path, mode_t mode) { 427 ZX_DEBUG_ASSERT_MSG(!host_path(path), "'emu_' functions can only operate on target paths"); 428 mode = S_IFDIR; 429 int fd = emu_open(path, O_CREAT | O_EXCL, S_IFDIR | (mode & 0777)); 430 if (fd >= 0) { 431 emu_close(fd); 432 return 0; 433 } else { 434 return fd; 435 } 436} 437 438DIR* emu_opendir(const char* name) { 439 ZX_DEBUG_ASSERT_MSG(!host_path(name), "'emu_' functions can only operate on target paths"); 440 fbl::RefPtr<fs::Vnode> vn; 441 fbl::StringPiece path(name + PREFIX_SIZE); 442 zx_status_t status = fakeFs.fake_vfs->Open(fakeFs.fake_root, &vn, path, &path, O_RDONLY, 0); 443 if (status != ZX_OK) { 444 return nullptr; 445 } 446 MINDIR* dir = (MINDIR*)calloc(1, sizeof(MINDIR)); 447 dir->magic = minfs::kMinfsMagic0; 448 dir->vn = fbl::RefPtr<fs::Vnode>::Downcast(vn); 449 return (DIR*) dir; 450} 451 452struct dirent* emu_readdir(DIR* dirp) { 453 MINDIR* dir = (MINDIR*)dirp; 454 for (;;) { 455 if (dir->size >= sizeof(vdirent_t)) { 456 vdirent_t* vde = (vdirent_t*)dir->ptr; 457 struct dirent* ent = &dir->de; 458 size_t name_len = vde->size; 459 size_t entry_len = vde->size + sizeof(vdirent_t); 460 ZX_DEBUG_ASSERT(dir->size >= entry_len); 461 memcpy(ent->d_name, vde->name, name_len); 462 ent->d_name[name_len] = '\0'; 463 ent->d_type = vde->type; 464 dir->ptr += entry_len; 465 dir->size -= entry_len; 466 return ent; 467 } 468 size_t actual; 469 zx_status_t status = dir->vn->Readdir(&dir->cookie, &dir->data, DIR_BUFSIZE, &actual); 470 if (status != ZX_OK || actual == 0) { 471 break; 472 } 473 dir->ptr = dir->data; 474 dir->size = actual; 475 } 476 return nullptr; 477} 478 479void emu_rewinddir(DIR* dirp) { 480 MINDIR* dir = (MINDIR*)dirp; 481 dir->size = 0; 482 dir->ptr = NULL; 483 dir->cookie.n = 0; 484} 485 486int emu_closedir(DIR* dirp) { 487 if (((uint64_t*)dirp)[0] != minfs::kMinfsMagic0) { 488 return closedir(dirp); 489 } 490 491 MINDIR* dir = (MINDIR*)dirp; 492 dir->vn->Close(); 493 dir->vn.reset(); 494 free(dirp); 495 496 return 0; 497} 498