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