/* * Copyright (C) 2009 Tobias Brunner * Copyright (C) 2007 Martin Willi * Hochschule fuer Technik Rapperswil * Copyright (C) 2001-2007 Miklos Szeredi * * Based on example shipped with FUSE. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #define FUSE_USE_VERSION 26 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "cowfs.h" #include #include #include #include #include /** define _XOPEN_SOURCE 500 fails when using libstrongswan, define popen */ extern ssize_t pread(int fd, void *buf, size_t count, off_t offset); extern ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset); typedef struct private_cowfs_t private_cowfs_t; struct private_cowfs_t { /** public cowfs interface */ cowfs_t public; /** fuse channel to mountpoint */ struct fuse_chan *chan; /** fuse handle */ struct fuse *fuse; /** mountpoint of cowfs FUSE */ char *mount; /** master filesystem path */ char *master; /** host filesystem path */ char *host; /** overlay filesystems */ linked_list_t *overlays; /** lock for overlays */ rwlock_t *lock; /** fd of read only master filesystem */ int master_fd; /** copy on write overlay to master */ int host_fd; /** thread processing FUSE */ thread_t *thread; }; typedef struct overlay_t overlay_t; /** * data for overlay filesystems */ struct overlay_t { /** path to overlay */ char *path; /** overlay fd */ int fd; }; /** * destroy an overlay */ static void overlay_destroy(overlay_t *this) { close(this->fd); free(this->path); free(this); } CALLBACK(overlay_equals, bool, overlay_t *this, va_list args) { overlay_t *other; VA_ARGS_VGET(args, other); return streq(this->path, other->path); } /** * remove and destroy the overlay with the given absolute path. * returns FALSE, if not found. */ static bool overlay_remove(private_cowfs_t *this, char *path) { overlay_t over, *current; over.path = path; if (!this->overlays->find_first(this->overlays, overlay_equals, (void**)¤t, &over)) { return FALSE; } this->overlays->remove(this->overlays, current, NULL); overlay_destroy(current); return TRUE; } /** * get this pointer stored in fuse context */ static private_cowfs_t *get_this() { return (fuse_get_context())->private_data; } /** * make a path relative */ static void rel(const char **path) { if (**path == '/') { (*path)++; } if (**path == '\0') { *path = "."; } } /** * get the highest overlay in which path exists */ static int get_rd(const char *path) { overlay_t *over; enumerator_t *enumerator; private_cowfs_t *this = get_this(); this->lock->read_lock(this->lock); enumerator = this->overlays->create_enumerator(this->overlays); while (enumerator->enumerate(enumerator, (void**)&over)) { if (faccessat(over->fd, path, F_OK, 0) == 0) { int fd = over->fd; enumerator->destroy(enumerator); this->lock->unlock(this->lock); return fd; } } enumerator->destroy(enumerator); this->lock->unlock(this->lock); if (faccessat(this->host_fd, path, F_OK, 0) == 0) { return this->host_fd; } return this->master_fd; } /** * get the highest overlay available, to write something */ static int get_wr(const char *path) { overlay_t *over; private_cowfs_t *this = get_this(); int fd = this->host_fd; this->lock->read_lock(this->lock); if (this->overlays->get_first(this->overlays, (void**)&over) == SUCCESS) { fd = over->fd; } this->lock->unlock(this->lock); return fd; } /** * create full "path" at "wr" the same way they exist at "rd" */ static bool clone_path(int rd, int wr, const char *path) { char *pos, *full; struct stat st; full = strdupa(path); pos = full; while ((pos = strchr(pos, '/'))) { *pos = '\0'; if (fstatat(wr, full, &st, 0) < 0) { /* TODO: handle symlinks!? */ if (fstatat(rd, full, &st, 0) < 0) { return FALSE; } if (mkdirat(wr, full, st.st_mode) < 0) { return FALSE; } } *pos = '/'; pos++; } return TRUE; } /** * copy a (special) file from a readonly to a read-write overlay */ static int copy(const char *path) { char *buf[4096]; int len; int rd, wr; int from, to; struct stat st; rd = get_rd(path); wr = get_wr(path); if (rd == wr) { /* already writeable */ return wr; } if (fstatat(rd, path, &st, 0) < 0) { return -1; } if (!clone_path(rd, wr, path)) { return -1; } if (mknodat(wr, path, st.st_mode, st.st_rdev) < 0) { return -1; } /* copy if no special file */ if (st.st_size) { from = openat(rd, path, O_RDONLY, st.st_mode); if (from < 0) { return -1; } to = openat(wr, path, O_WRONLY , st.st_mode); if (to < 0) { close(from); return -1; } while ((len = read(from, buf, sizeof(buf))) > 0) { if (write(to, buf, len) < len) { /* TODO: only on len < 0 ? */ close(from); close(to); return -1; } } close(from); close(to); if (len < 0) { return -1; } } return wr; } /** * FUSE getattr method */ static int cowfs_getattr(const char *path, struct stat *stbuf) { rel(&path); if (fstatat(get_rd(path), path, stbuf, AT_SYMLINK_NOFOLLOW) < 0) { return -errno; } return 0; } /** * FUSE access method */ static int cowfs_access(const char *path, int mask) { rel(&path); if (faccessat(get_rd(path), path, mask, 0) < 0) { return -errno; } return 0; } /** * FUSE readlink method */ static int cowfs_readlink(const char *path, char *buf, size_t size) { int res; rel(&path); res = readlinkat(get_rd(path), path, buf, size - 1); if (res < 0) { return -errno; } buf[res] = '\0'; return 0; } /** * get a directory stream of two concatenated paths */ static DIR* get_dir(char *dir, const char *subdir) { char *full; if (dir == NULL) { return NULL; } full = alloca(strlen(dir) + strlen(subdir) + 1); strcpy(full, dir); strcat(full, subdir); return opendir(full); } /** * check if a directory stream contains a directory */ static bool contains_dir(DIR *d, char *dirname) { struct dirent *ent; rewinddir(d); while ((ent = readdir(d))) { if (streq(ent->d_name, dirname)) { return TRUE; } } return FALSE; } /** * check if one of the higher overlays contains a directory */ static bool overlays_contain_dir(DIR **d, char *dirname) { for (; *d; ++d) { if (contains_dir(*d, dirname)) { return TRUE; } } return FALSE; } /** * FUSE readdir method */ static int cowfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { #define ADD_DIR(overlay, base, path) ({\ DIR *dir = get_dir(base, path);\ if (dir) { *(--overlay) = dir; }\ }) private_cowfs_t *this = get_this(); int count; DIR **d, **overlays; struct stat st; struct dirent *ent; overlay_t *over; enumerator_t *enumerator; memset(&st, 0, sizeof(st)); this->lock->read_lock(this->lock); /* create a null-terminated array of DIR objects for all overlays (including * the master and host layer). the order is from bottom to top */ count = this->overlays->get_count(this->overlays) + 2; overlays = calloc(count + 1, sizeof(DIR*)); d = &overlays[count]; enumerator = this->overlays->create_enumerator(this->overlays); while (enumerator->enumerate(enumerator, (void**)&over)) { ADD_DIR(d, over->path, path); } enumerator->destroy(enumerator); this->lock->unlock(this->lock); ADD_DIR(d, this->host, path); ADD_DIR(d, this->master, path); for (; *d; ++d) { rewinddir(*d); while((ent = readdir(*d))) { if (!overlays_contain_dir(d + 1, ent->d_name)) { st.st_ino = ent->d_ino; st.st_mode = ent->d_type << 12; filler(buf, ent->d_name, &st, 0); } } closedir(*d); } free(overlays); return 0; } /** * FUSE mknod method */ static int cowfs_mknod(const char *path, mode_t mode, dev_t rdev) { int fd; rel(&path); fd = get_wr(path); if (!clone_path(get_rd(path), fd, path)) { return -errno; } if (mknodat(fd, path, mode, rdev) < 0) { return -errno; } return 0; } /** * FUSE mkdir method */ static int cowfs_mkdir(const char *path, mode_t mode) { int fd; rel(&path); fd = get_wr(path); if (!clone_path(get_rd(path), fd, path)) { return -errno; } if (mkdirat(fd, path, mode) < 0) { return -errno; } return 0; } /** * FUSE unlink method */ static int cowfs_unlink(const char *path) { rel(&path); /* TODO: whiteout master */ if (unlinkat(get_wr(path), path, 0) < 0) { return -errno; } return 0; } /** * FUSE rmdir method */ static int cowfs_rmdir(const char *path) { rel(&path); /* TODO: whiteout master */ if (unlinkat(get_wr(path), path, AT_REMOVEDIR) < 0) { return -errno; } return 0; } /** * FUSE symlink method */ static int cowfs_symlink(const char *from, const char *to) { int fd; const char *fromrel = from; rel(&to); rel(&fromrel); fd = get_wr(to); if (!clone_path(get_rd(fromrel), fd, fromrel)) { return -errno; } if (symlinkat(from, fd, to) < 0) { return -errno; } return 0; } /** * FUSE rename method */ static int cowfs_rename(const char *from, const char *to) { int fd; rel(&from); rel(&to); fd = copy(from); if (fd < 0) { return -errno; } if (renameat(fd, from, get_wr(to), to) < 0) { return -errno; } return 0; } /** * FUSE link method */ static int cowfs_link(const char *from, const char *to) { int rd, wr; rel(&from); rel(&to); rd = get_rd(from); wr = get_wr(to); if (!clone_path(rd, wr, to)) { DBG1(DBG_LIB, "cloning path '%s' failed", to); return -errno; } if (linkat(rd, from, wr, to, 0) < 0) { DBG1(DBG_LIB, "linking '%s' to '%s' failed", from, to); return -errno; } return 0; } /** * FUSE chmod method */ static int cowfs_chmod(const char *path, mode_t mode) { int fd; struct stat st; rel(&path); fd = get_rd(path); if (fstatat(fd, path, &st, 0) < 0) { return -errno; } if (st.st_mode == mode) { return 0; } fd = copy(path); if (fd < 0) { return -errno; } if (fchmodat(fd, path, mode, 0) < 0) { return -errno; } return 0; } /** * FUSE chown method */ static int cowfs_chown(const char *path, uid_t uid, gid_t gid) { int fd; struct stat st; rel(&path); fd = get_rd(path); if (fstatat(fd, path, &st, 0) < 0) { return -errno; } if (st.st_uid == uid && st.st_gid == gid) { return 0; } fd = copy(path); if (fd < 0) { return -errno; } if (fchownat(fd, path, uid, gid, AT_SYMLINK_NOFOLLOW) < 0) { return -errno; } return 0; } /** * FUSE truncate method */ static int cowfs_truncate(const char *path, off_t size) { int fd; struct stat st; rel(&path); fd = get_rd(path); if (fstatat(fd, path, &st, 0) < 0) { return -errno; } if (st.st_size == size) { return 0; } fd = copy(path); if (fd < 0) { return -errno; } fd = openat(fd, path, O_WRONLY); if (fd < 0) { return -errno; } if (ftruncate(fd, size) < 0) { close(fd); return -errno; } close(fd); return 0; } /** * FUSE utimens method */ static int cowfs_utimens(const char *path, const struct timespec ts[2]) { struct timeval tv[2]; int fd; rel(&path); fd = copy(path); if (fd < 0) { return -errno; } tv[0].tv_sec = ts[0].tv_sec; tv[0].tv_usec = ts[0].tv_nsec / 1000; tv[1].tv_sec = ts[1].tv_sec; tv[1].tv_usec = ts[1].tv_nsec / 1000; if (futimesat(fd, path, tv) < 0) { return -errno; } return 0; } /** * FUSE open method */ static int cowfs_open(const char *path, struct fuse_file_info *fi) { int fd; rel(&path); fd = get_rd(path); fd = openat(fd, path, fi->flags); if (fd < 0) { return -errno; } close(fd); return 0; } /** * FUSE read method */ static int cowfs_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { int file, fd, res; rel(&path); fd = get_rd(path); file = openat(fd, path, O_RDONLY); if (file < 0) { return -errno; } res = pread(file, buf, size, offset); if (res < 0) { res = -errno; } close(file); return res; } /** * FUSE write method */ static int cowfs_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { int file, fd, res; rel(&path); fd = copy(path); if (fd < 0) { return -errno; } file = openat(fd, path, O_WRONLY); if (file < 0) { return -errno; } res = pwrite(file, buf, size, offset); if (res < 0) { res = -errno; } close(file); return res; } /** * FUSE statfs method */ static int cowfs_statfs(const char *path, struct statvfs *stbuf) { int fd; fd = get_rd(path); if (fstatvfs(fd, stbuf) < 0) { return -errno; } return 0; } /** * FUSE init method */ static void *cowfs_init(struct fuse_conn_info *conn) { struct fuse_context *ctx; ctx = fuse_get_context(); return ctx->private_data; } /** * FUSE method vectors */ static struct fuse_operations cowfs_operations = { .getattr = cowfs_getattr, .access = cowfs_access, .readlink = cowfs_readlink, .readdir = cowfs_readdir, .mknod = cowfs_mknod, .mkdir = cowfs_mkdir, .symlink = cowfs_symlink, .unlink = cowfs_unlink, .rmdir = cowfs_rmdir, .rename = cowfs_rename, .link = cowfs_link, .chmod = cowfs_chmod, .chown = cowfs_chown, .truncate = cowfs_truncate, .utimens = cowfs_utimens, .open = cowfs_open, .read = cowfs_read, .write = cowfs_write, .statfs = cowfs_statfs, .init = cowfs_init, }; METHOD(cowfs_t, add_overlay, bool, private_cowfs_t *this, char *path) { overlay_t *over = malloc_thing(overlay_t); over->fd = open(path, O_RDONLY | O_DIRECTORY); if (over->fd < 0) { DBG1(DBG_LIB, "failed to open overlay directory '%s': %m", path); free(over); return FALSE; } over->path = realpath(path, NULL); this->lock->write_lock(this->lock); overlay_remove(this, over->path); this->overlays->insert_first(this->overlays, over); this->lock->unlock(this->lock); return TRUE; } METHOD(cowfs_t, del_overlay, bool, private_cowfs_t *this, char *path) { bool removed; char real[PATH_MAX]; this->lock->write_lock(this->lock); removed = overlay_remove(this, realpath(path, real)); this->lock->unlock(this->lock); return removed; } METHOD(cowfs_t, pop_overlay, bool, private_cowfs_t *this) { overlay_t *over; this->lock->write_lock(this->lock); if (this->overlays->remove_first(this->overlays, (void**)&over) != SUCCESS) { this->lock->unlock(this->lock); return FALSE; } this->lock->unlock(this->lock); overlay_destroy(over); return TRUE; } METHOD(cowfs_t, destroy, void, private_cowfs_t *this) { fuse_exit(this->fuse); fuse_unmount(this->mount, this->chan); this->thread->join(this->thread); fuse_destroy(this->fuse); this->lock->destroy(this->lock); this->overlays->destroy_function(this->overlays, (void*)overlay_destroy); free(this->mount); free(this->master); free(this->host); close(this->master_fd); close(this->host_fd); free(this); } /** * creates a new cowfs fuse instance */ cowfs_t *cowfs_create(char *master, char *host, char *mount) { struct fuse_args args = {0, NULL, 0}; private_cowfs_t *this; INIT(this, .public = { .add_overlay = _add_overlay, .del_overlay = _del_overlay, .pop_overlay = _pop_overlay, .destroy = _destroy, } ); this->master_fd = open(master, O_RDONLY | O_DIRECTORY); if (this->master_fd < 0) { DBG1(DBG_LIB, "failed to open master filesystem '%s'", master); free(this); return NULL; } this->host_fd = open(host, O_RDONLY | O_DIRECTORY); if (this->host_fd < 0) { DBG1(DBG_LIB, "failed to open host filesystem '%s'", host); close(this->master_fd); free(this); return NULL; } this->chan = fuse_mount(mount, &args); if (this->chan == NULL) { DBG1(DBG_LIB, "mounting cowfs FUSE on '%s' failed", mount); close(this->master_fd); close(this->host_fd); free(this); return NULL; } this->fuse = fuse_new(this->chan, &args, &cowfs_operations, sizeof(cowfs_operations), this); if (this->fuse == NULL) { DBG1(DBG_LIB, "creating cowfs FUSE handle failed"); close(this->master_fd); close(this->host_fd); fuse_unmount(mount, this->chan); free(this); return NULL; } this->mount = strdup(mount); this->master = strdup(master); this->host = strdup(host); this->overlays = linked_list_create(); this->lock = rwlock_create(RWLOCK_TYPE_DEFAULT); this->thread = thread_create((thread_main_t)fuse_loop, this->fuse); if (!this->thread) { DBG1(DBG_LIB, "creating thread to handle FUSE failed"); fuse_unmount(mount, this->chan); this->lock->destroy(this->lock); this->overlays->destroy(this->overlays); free(this->mount); free(this->master); free(this->host); close(this->master_fd); close(this->host_fd); free(this); return NULL; } return &this->public; }