| /* |
| * Filesystem on disk |
| * |
| * Copyright (c) 2016 Fabrice Bellard |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to deal |
| * in the Software without restriction, including without limitation the rights |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| * copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| * THE SOFTWARE. |
| */ |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <inttypes.h> |
| #include <assert.h> |
| #include <stdarg.h> |
| #include <sys/statfs.h> |
| #include <sys/stat.h> |
| #include <sys/sysmacros.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <dirent.h> |
| #include <errno.h> |
| |
| #include "cutils.h" |
| #include "list.h" |
| #include "fs.h" |
| |
| typedef struct { |
| FSDevice common; |
| char *root_path; |
| } FSDeviceDisk; |
| |
| static void fs_close(FSDevice *fs, FSFile *f); |
| |
| struct FSFile { |
| uint32_t uid; |
| char *path; /* complete path */ |
| BOOL is_opened; |
| BOOL is_dir; |
| union { |
| int fd; |
| DIR *dirp; |
| } u; |
| }; |
| |
| static void fs_delete(FSDevice *fs, FSFile *f) |
| { |
| if (f->is_opened) |
| fs_close(fs, f); |
| free(f->path); |
| free(f); |
| } |
| |
| /* warning: path belong to fid_create() */ |
| static FSFile *fid_create(FSDevice *s1, char *path, uint32_t uid) |
| { |
| FSFile *f; |
| f = mallocz(sizeof(*f)); |
| f->path = path; |
| f->uid = uid; |
| return f; |
| } |
| |
| |
| static int errno_table[][2] = { |
| { P9_EPERM, EPERM }, |
| { P9_ENOENT, ENOENT }, |
| { P9_EIO, EIO }, |
| { P9_EEXIST, EEXIST }, |
| { P9_EINVAL, EINVAL }, |
| { P9_ENOSPC, ENOSPC }, |
| { P9_ENOTEMPTY, ENOTEMPTY }, |
| { P9_EPROTO, EPROTO }, |
| { P9_ENOTSUP, ENOTSUP }, |
| }; |
| |
| static int errno_to_p9(int err) |
| { |
| int i; |
| if (err == 0) |
| return 0; |
| for(i = 0; i < countof(errno_table); i++) { |
| if (err == errno_table[i][1]) |
| return errno_table[i][0]; |
| } |
| return P9_EINVAL; |
| } |
| |
| static int open_flags[][2] = { |
| { P9_O_CREAT, O_CREAT }, |
| { P9_O_EXCL, O_EXCL }, |
| // { P9_O_NOCTTY, O_NOCTTY }, |
| { P9_O_TRUNC, O_TRUNC }, |
| { P9_O_APPEND, O_APPEND }, |
| { P9_O_NONBLOCK, O_NONBLOCK }, |
| { P9_O_DSYNC, O_DSYNC }, |
| // { P9_O_FASYNC, O_FASYNC }, |
| // { P9_O_DIRECT, O_DIRECT }, |
| // { P9_O_LARGEFILE, O_LARGEFILE }, |
| // { P9_O_DIRECTORY, O_DIRECTORY }, |
| { P9_O_NOFOLLOW, O_NOFOLLOW }, |
| // { P9_O_NOATIME, O_NOATIME }, |
| // { P9_O_CLOEXEC, O_CLOEXEC }, |
| { P9_O_SYNC, O_SYNC }, |
| }; |
| |
| static int p9_flags_to_host(int flags) |
| { |
| int ret, i; |
| |
| ret = (flags & P9_O_NOACCESS); |
| for(i = 0; i < countof(open_flags); i++) { |
| if (flags & open_flags[i][0]) |
| ret |= open_flags[i][1]; |
| } |
| return ret; |
| } |
| |
| static void stat_to_qid(FSQID *qid, const struct stat *st) |
| { |
| if (S_ISDIR(st->st_mode)) |
| qid->type = P9_QTDIR; |
| else if (S_ISLNK(st->st_mode)) |
| qid->type = P9_QTSYMLINK; |
| else |
| qid->type = P9_QTFILE; |
| qid->version = 0; /* no caching on client */ |
| qid->path = st->st_ino; |
| } |
| |
| static void fs_statfs(FSDevice *fs1, FSStatFS *st) |
| { |
| FSDeviceDisk *fs = (FSDeviceDisk *)fs1; |
| struct statfs st1; |
| statfs(fs->root_path, &st1); |
| st->f_bsize = st1.f_bsize; |
| st->f_blocks = st1.f_blocks; |
| st->f_bfree = st1.f_bfree; |
| st->f_bavail = st1.f_bavail; |
| st->f_files = st1.f_files; |
| st->f_ffree = st1.f_ffree; |
| } |
| |
| static char *compose_path(const char *path, const char *name) |
| { |
| int path_len, name_len; |
| char *d; |
| |
| path_len = strlen(path); |
| name_len = strlen(name); |
| d = malloc(path_len + 1 + name_len + 1); |
| memcpy(d, path, path_len); |
| d[path_len] = '/'; |
| memcpy(d + path_len + 1, name, name_len + 1); |
| return d; |
| } |
| |
| static int fs_attach(FSDevice *fs1, FSFile **pf, |
| FSQID *qid, uint32_t uid, |
| const char *uname, const char *aname) |
| { |
| FSDeviceDisk *fs = (FSDeviceDisk *)fs1; |
| struct stat st; |
| FSFile *f; |
| |
| if (lstat(fs->root_path, &st) != 0) { |
| *pf = NULL; |
| return -errno_to_p9(errno); |
| } |
| f = fid_create(fs1, strdup(fs->root_path), uid); |
| stat_to_qid(qid, &st); |
| *pf = f; |
| return 0; |
| } |
| |
| static int fs_walk(FSDevice *fs, FSFile **pf, FSQID *qids, |
| FSFile *f, int n, char **names) |
| { |
| char *path, *path1; |
| struct stat st; |
| int i; |
| |
| path = strdup(f->path); |
| for(i = 0; i < n; i++) { |
| path1 = compose_path(path, names[i]); |
| if (lstat(path1, &st) != 0) { |
| free(path1); |
| break; |
| } |
| free(path); |
| path = path1; |
| stat_to_qid(&qids[i], &st); |
| } |
| *pf = fid_create(fs, path, f->uid); |
| return i; |
| } |
| |
| |
| static int fs_mkdir(FSDevice *fs, FSQID *qid, FSFile *f, |
| const char *name, uint32_t mode, uint32_t gid) |
| { |
| char *path; |
| struct stat st; |
| |
| path = compose_path(f->path, name); |
| if (mkdir(path, mode) < 0) { |
| free(path); |
| return -errno_to_p9(errno); |
| } |
| if (lstat(path, &st) != 0) { |
| free(path); |
| return -errno_to_p9(errno); |
| } |
| free(path); |
| stat_to_qid(qid, &st); |
| return 0; |
| } |
| |
| static int fs_open(FSDevice *fs, FSQID *qid, FSFile *f, uint32_t flags, |
| FSOpenCompletionFunc *cb, void *opaque) |
| { |
| struct stat st; |
| fs_close(fs, f); |
| |
| if (stat(f->path, &st) != 0) |
| return -errno_to_p9(errno); |
| stat_to_qid(qid, &st); |
| |
| if (flags & P9_O_DIRECTORY) { |
| DIR *dirp; |
| dirp = opendir(f->path); |
| if (!dirp) |
| return -errno_to_p9(errno); |
| f->is_opened = TRUE; |
| f->is_dir = TRUE; |
| f->u.dirp = dirp; |
| } else { |
| int fd; |
| fd = open(f->path, p9_flags_to_host(flags) & ~O_CREAT); |
| if (fd < 0) |
| return -errno_to_p9(errno); |
| f->is_opened = TRUE; |
| f->is_dir = FALSE; |
| f->u.fd = fd; |
| } |
| return 0; |
| } |
| |
| static int fs_create(FSDevice *fs, FSQID *qid, FSFile *f, const char *name, |
| uint32_t flags, uint32_t mode, uint32_t gid) |
| { |
| struct stat st; |
| char *path; |
| int ret, fd; |
| |
| fs_close(fs, f); |
| |
| path = compose_path(f->path, name); |
| fd = open(path, p9_flags_to_host(flags) | O_CREAT, mode); |
| if (fd < 0) { |
| free(path); |
| return -errno_to_p9(errno); |
| } |
| ret = lstat(path, &st); |
| if (ret != 0) { |
| free(path); |
| close(fd); |
| return -errno_to_p9(errno); |
| } |
| free(f->path); |
| f->path = path; |
| f->is_opened = TRUE; |
| f->is_dir = FALSE; |
| f->u.fd = fd; |
| stat_to_qid(qid, &st); |
| return 0; |
| } |
| |
| static int fs_readdir(FSDevice *fs, FSFile *f, uint64_t offset, |
| uint8_t *buf, int count) |
| { |
| struct dirent *de; |
| int len, pos, name_len, type, d_type; |
| |
| if (!f->is_opened || !f->is_dir) |
| return -P9_EPROTO; |
| if (offset == 0) |
| rewinddir(f->u.dirp); |
| else |
| seekdir(f->u.dirp, offset); |
| pos = 0; |
| for(;;) { |
| de = readdir(f->u.dirp); |
| if (de == NULL) |
| break; |
| name_len = strlen(de->d_name); |
| len = 13 + 8 + 1 + 2 + name_len; |
| if ((pos + len) > count) |
| break; |
| offset = telldir(f->u.dirp); |
| d_type = de->d_type; |
| if (d_type == DT_UNKNOWN) { |
| char *path; |
| struct stat st; |
| path = compose_path(f->path, de->d_name); |
| if (lstat(path, &st) == 0) { |
| d_type = st.st_mode >> 12; |
| } else { |
| d_type = DT_REG; /* default */ |
| } |
| free(path); |
| } |
| if (d_type == DT_DIR) |
| type = P9_QTDIR; |
| else if (d_type == DT_LNK) |
| type = P9_QTSYMLINK; |
| else |
| type = P9_QTFILE; |
| buf[pos++] = type; |
| put_le32(buf + pos, 0); /* version */ |
| pos += 4; |
| put_le64(buf + pos, de->d_ino); |
| pos += 8; |
| put_le64(buf + pos, offset); |
| pos += 8; |
| buf[pos++] = d_type; |
| put_le16(buf + pos, name_len); |
| pos += 2; |
| memcpy(buf + pos, de->d_name, name_len); |
| pos += name_len; |
| } |
| return pos; |
| } |
| |
| static int fs_read(FSDevice *fs, FSFile *f, uint64_t offset, |
| uint8_t *buf, int count) |
| { |
| int ret; |
| |
| if (!f->is_opened || f->is_dir) |
| return -P9_EPROTO; |
| ret = pread(f->u.fd, buf, count, offset); |
| if (ret < 0) |
| return -errno_to_p9(errno); |
| else |
| return ret; |
| } |
| |
| static int fs_write(FSDevice *fs, FSFile *f, uint64_t offset, |
| const uint8_t *buf, int count) |
| { |
| int ret; |
| |
| if (!f->is_opened || f->is_dir) |
| return -P9_EPROTO; |
| ret = pwrite(f->u.fd, buf, count, offset); |
| if (ret < 0) |
| return -errno_to_p9(errno); |
| else |
| return ret; |
| } |
| |
| static void fs_close(FSDevice *fs, FSFile *f) |
| { |
| if (!f->is_opened) |
| return; |
| if (f->is_dir) |
| closedir(f->u.dirp); |
| else |
| close(f->u.fd); |
| f->is_opened = FALSE; |
| } |
| |
| static int fs_stat(FSDevice *fs, FSFile *f, FSStat *st) |
| { |
| struct stat st1; |
| |
| if (lstat(f->path, &st1) != 0) |
| return -P9_ENOENT; |
| stat_to_qid(&st->qid, &st1); |
| st->st_mode = st1.st_mode; |
| st->st_uid = st1.st_uid; |
| st->st_gid = st1.st_gid; |
| st->st_nlink = st1.st_nlink; |
| st->st_rdev = st1.st_rdev; |
| st->st_size = st1.st_size; |
| st->st_blksize = st1.st_blksize; |
| st->st_blocks = st1.st_blocks; |
| st->st_atime_sec = st1.st_atim.tv_sec; |
| st->st_atime_nsec = st1.st_atim.tv_nsec; |
| st->st_mtime_sec = st1.st_mtim.tv_sec; |
| st->st_mtime_nsec = st1.st_mtim.tv_nsec; |
| st->st_ctime_sec = st1.st_ctim.tv_sec; |
| st->st_ctime_nsec = st1.st_ctim.tv_nsec; |
| return 0; |
| } |
| |
| static int fs_setattr(FSDevice *fs, FSFile *f, uint32_t mask, |
| uint32_t mode, uint32_t uid, uint32_t gid, |
| uint64_t size, uint64_t atime_sec, uint64_t atime_nsec, |
| uint64_t mtime_sec, uint64_t mtime_nsec) |
| { |
| BOOL ctime_updated = FALSE; |
| |
| if (mask & (P9_SETATTR_UID | P9_SETATTR_GID)) { |
| if (lchown(f->path, (mask & P9_SETATTR_UID) ? uid : -1, |
| (mask & P9_SETATTR_GID) ? gid : -1) < 0) |
| return -errno_to_p9(errno); |
| ctime_updated = TRUE; |
| } |
| /* must be done after uid change for suid */ |
| if (mask & P9_SETATTR_MODE) { |
| if (chmod(f->path, mode) < 0) |
| return -errno_to_p9(errno); |
| ctime_updated = TRUE; |
| } |
| if (mask & P9_SETATTR_SIZE) { |
| if (truncate(f->path, size) < 0) |
| return -errno_to_p9(errno); |
| ctime_updated = TRUE; |
| } |
| if (mask & (P9_SETATTR_ATIME | P9_SETATTR_MTIME)) { |
| struct timespec ts[2]; |
| if (mask & P9_SETATTR_ATIME) { |
| if (mask & P9_SETATTR_ATIME_SET) { |
| ts[0].tv_sec = atime_sec; |
| ts[0].tv_nsec = atime_nsec; |
| } else { |
| ts[0].tv_sec = 0; |
| ts[0].tv_nsec = UTIME_NOW; |
| } |
| } else { |
| ts[0].tv_sec = 0; |
| ts[0].tv_nsec = UTIME_OMIT; |
| } |
| if (mask & P9_SETATTR_MTIME) { |
| if (mask & P9_SETATTR_MTIME_SET) { |
| ts[1].tv_sec = mtime_sec; |
| ts[1].tv_nsec = mtime_nsec; |
| } else { |
| ts[1].tv_sec = 0; |
| ts[1].tv_nsec = UTIME_NOW; |
| } |
| } else { |
| ts[1].tv_sec = 0; |
| ts[1].tv_nsec = UTIME_OMIT; |
| } |
| if (utimensat(AT_FDCWD, f->path, ts, AT_SYMLINK_NOFOLLOW) < 0) |
| return -errno_to_p9(errno); |
| ctime_updated = TRUE; |
| } |
| if ((mask & P9_SETATTR_CTIME) && !ctime_updated) { |
| if (lchown(f->path, -1, -1) < 0) |
| return -errno_to_p9(errno); |
| } |
| return 0; |
| } |
| |
| static int fs_link(FSDevice *fs, FSFile *df, FSFile *f, const char *name) |
| { |
| char *path; |
| |
| path = compose_path(df->path, name); |
| if (link(f->path, path) < 0) { |
| free(path); |
| return -errno_to_p9(errno); |
| } |
| free(path); |
| return 0; |
| } |
| |
| static int fs_symlink(FSDevice *fs, FSQID *qid, |
| FSFile *f, const char *name, const char *symgt, uint32_t gid) |
| { |
| char *path; |
| struct stat st; |
| |
| path = compose_path(f->path, name); |
| if (symlink(symgt, path) < 0) { |
| free(path); |
| return -errno_to_p9(errno); |
| } |
| if (lstat(path, &st) != 0) { |
| free(path); |
| return -errno_to_p9(errno); |
| } |
| free(path); |
| stat_to_qid(qid, &st); |
| return 0; |
| } |
| |
| static int fs_mknod(FSDevice *fs, FSQID *qid, |
| FSFile *f, const char *name, uint32_t mode, uint32_t major, |
| uint32_t minor, uint32_t gid) |
| { |
| char *path; |
| struct stat st; |
| |
| path = compose_path(f->path, name); |
| if (mknod(path, mode, makedev(major, minor)) < 0) { |
| free(path); |
| return -errno_to_p9(errno); |
| } |
| if (lstat(path, &st) != 0) { |
| free(path); |
| return -errno_to_p9(errno); |
| } |
| free(path); |
| stat_to_qid(qid, &st); |
| return 0; |
| } |
| |
| static int fs_readlink(FSDevice *fs, char *buf, int buf_size, FSFile *f) |
| { |
| int ret; |
| ret = readlink(f->path, buf, buf_size - 1); |
| if (ret < 0) |
| return -errno_to_p9(errno); |
| buf[ret] = '\0'; |
| return 0; |
| } |
| |
| static int fs_renameat(FSDevice *fs, FSFile *f, const char *name, |
| FSFile *new_f, const char *new_name) |
| { |
| char *path, *new_path; |
| int ret; |
| |
| path = compose_path(f->path, name); |
| new_path = compose_path(new_f->path, new_name); |
| ret = rename(path, new_path); |
| free(path); |
| free(new_path); |
| if (ret < 0) |
| return -errno_to_p9(errno); |
| return 0; |
| } |
| |
| static int fs_unlinkat(FSDevice *fs, FSFile *f, const char *name) |
| { |
| char *path; |
| int ret; |
| |
| path = compose_path(f->path, name); |
| ret = remove(path); |
| free(path); |
| if (ret < 0) |
| return -errno_to_p9(errno); |
| return 0; |
| |
| } |
| |
| static int fs_lock(FSDevice *fs, FSFile *f, const FSLock *lock) |
| { |
| int ret; |
| struct flock fl; |
| |
| /* XXX: lock directories too */ |
| if (!f->is_opened || f->is_dir) |
| return -P9_EPROTO; |
| |
| fl.l_type = lock->type; |
| fl.l_whence = SEEK_SET; |
| fl.l_start = lock->start; |
| fl.l_len = lock->length; |
| |
| ret = fcntl(f->u.fd, F_SETLK, &fl); |
| if (ret == 0) { |
| ret = P9_LOCK_SUCCESS; |
| } else if (errno == EAGAIN || errno == EACCES) { |
| ret = P9_LOCK_BLOCKED; |
| } else { |
| ret = -errno_to_p9(errno); |
| } |
| return ret; |
| } |
| |
| static int fs_getlock(FSDevice *fs, FSFile *f, FSLock *lock) |
| { |
| int ret; |
| struct flock fl; |
| |
| /* XXX: lock directories too */ |
| if (!f->is_opened || f->is_dir) |
| return -P9_EPROTO; |
| |
| fl.l_type = lock->type; |
| fl.l_whence = SEEK_SET; |
| fl.l_start = lock->start; |
| fl.l_len = lock->length; |
| |
| ret = fcntl(f->u.fd, F_GETLK, &fl); |
| if (ret < 0) { |
| ret = -errno_to_p9(errno); |
| } else { |
| lock->type = fl.l_type; |
| lock->start = fl.l_start; |
| lock->length = fl.l_len; |
| } |
| return ret; |
| } |
| |
| static void fs_disk_end(FSDevice *fs1) |
| { |
| FSDeviceDisk *fs = (FSDeviceDisk *)fs1; |
| free(fs->root_path); |
| } |
| |
| FSDevice *fs_disk_init(const char *root_path) |
| { |
| FSDeviceDisk *fs; |
| struct stat st; |
| |
| lstat(root_path, &st); |
| if (!S_ISDIR(st.st_mode)) |
| return NULL; |
| |
| fs = mallocz(sizeof(*fs)); |
| |
| fs->common.fs_end = fs_disk_end; |
| fs->common.fs_delete = fs_delete; |
| fs->common.fs_statfs = fs_statfs; |
| fs->common.fs_attach = fs_attach; |
| fs->common.fs_walk = fs_walk; |
| fs->common.fs_mkdir = fs_mkdir; |
| fs->common.fs_open = fs_open; |
| fs->common.fs_create = fs_create; |
| fs->common.fs_stat = fs_stat; |
| fs->common.fs_setattr = fs_setattr; |
| fs->common.fs_close = fs_close; |
| fs->common.fs_readdir = fs_readdir; |
| fs->common.fs_read = fs_read; |
| fs->common.fs_write = fs_write; |
| fs->common.fs_link = fs_link; |
| fs->common.fs_symlink = fs_symlink; |
| fs->common.fs_mknod = fs_mknod; |
| fs->common.fs_readlink = fs_readlink; |
| fs->common.fs_renameat = fs_renameat; |
| fs->common.fs_unlinkat = fs_unlinkat; |
| fs->common.fs_lock = fs_lock; |
| fs->common.fs_getlock = fs_getlock; |
| |
| fs->root_path = strdup(root_path); |
| return (FSDevice *)fs; |
| } |