/* Copyright (C) 2014 Stony Brook University This file is part of Graphene Library OS. Graphene Library OS is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Graphene Library OS 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ /* * shim_fs.c * * Implementation of system call "unlink", "unlinkat", "mkdir", "mkdirat", * "rmdir", "umask", "chmod", "fchmod", "fchmodat", "rename", "renameat" and * "sendfile". */ #define __KERNEL__ #include #include #include #include #include #include #include #include #include #include #include #include /* The kernel would look up the parent directory, and remove the child from the inode. But we are * working with the PAL, so we open the file, truncate and close it. */ int shim_do_unlink(const char* file) { if (!file) return -EINVAL; if (test_user_string(file)) return -EFAULT; struct shim_dentry* dent = NULL; int ret = 0; if ((ret = path_lookupat(NULL, file, LOOKUP_OPEN, &dent, NULL)) < 0) return ret; if (!dent->parent) return -EACCES; if (dent->state & DENTRY_ISDIRECTORY) return -EISDIR; if (dent->fs && dent->fs->d_ops && dent->fs->d_ops->unlink) { if ((ret = dent->fs->d_ops->unlink(dent->parent, dent)) < 0) return ret; } else { dent->state |= DENTRY_PERSIST; } dent->state |= DENTRY_NEGATIVE; put_dentry(dent); return 0; } int shim_do_unlinkat(int dfd, const char* pathname, int flag) { if (!pathname) return -EINVAL; if (test_user_string(pathname)) return -EFAULT; if (flag & ~AT_REMOVEDIR) return -EINVAL; struct shim_dentry* dir = NULL; struct shim_dentry* dent = NULL; int ret = 0; if ((ret = get_dirfd_dentry(dfd, &dir)) < 0) return ret; if ((ret = path_lookupat(dir, pathname, LOOKUP_OPEN, &dent, NULL)) < 0) goto out; if (!dent->parent) { ret = -EACCES; goto out_dent; } if (flag & AT_REMOVEDIR) { if (!(dent->state & DENTRY_ISDIRECTORY)) return -ENOTDIR; } else { if (dent->state & DENTRY_ISDIRECTORY) return -EISDIR; } if (dent->fs && dent->fs->d_ops && dent->fs->d_ops->unlink) { if ((ret = dent->fs->d_ops->unlink(dent->parent, dent)) < 0) return ret; } else { dent->state |= DENTRY_PERSIST; } if (flag & AT_REMOVEDIR) dent->state &= ~DENTRY_ISDIRECTORY; dent->state |= DENTRY_NEGATIVE; out_dent: put_dentry(dent); out: put_dentry(dir); return ret; } int shim_do_mkdir(const char* pathname, int mode) { return open_namei(NULL, NULL, pathname, O_CREAT | O_EXCL | O_DIRECTORY, mode, NULL); } int shim_do_mkdirat(int dfd, const char* pathname, int mode) { if (!pathname) return -EINVAL; if (test_user_string(pathname)) return -EFAULT; struct shim_dentry* dir = NULL; int ret = 0; if ((ret = get_dirfd_dentry(dfd, &dir)) < 0) return ret; ret = open_namei(NULL, dir, pathname, O_CREAT | O_EXCL | O_DIRECTORY, mode, NULL); put_dentry(dir); return ret; } int shim_do_rmdir(const char* pathname) { int ret = 0; struct shim_dentry* dent = NULL; if (!pathname) return -EINVAL; if (test_user_string(pathname)) return -EFAULT; if ((ret = path_lookupat(NULL, pathname, LOOKUP_OPEN | LOOKUP_DIRECTORY, &dent, NULL)) < 0) return ret; if (!dent->parent) { ret = -EACCES; goto out; } if (!(dent->state & DENTRY_ISDIRECTORY)) { ret = -ENOTDIR; goto out; } if (dent->fs && dent->fs->d_ops && dent->fs->d_ops->unlink) { if ((ret = dent->fs->d_ops->unlink(dent->parent, dent)) < 0) goto out; } else { dent->state |= DENTRY_PERSIST; } dent->state &= ~DENTRY_ISDIRECTORY; dent->state |= DENTRY_NEGATIVE; out: put_dentry(dent); return ret; } mode_t shim_do_umask(mode_t mask) { struct shim_thread* cur = get_cur_thread(); lock(&cur->lock); mode_t old = cur->umask; cur->umask = mask & 0777; unlock(&cur->lock); return old; } int shim_do_chmod(const char* path, mode_t mode) { struct shim_dentry* dent = NULL; int ret = 0; if (test_user_string(path)) return -EFAULT; if ((ret = path_lookupat(NULL, path, LOOKUP_OPEN, &dent, NULL)) < 0) return ret; if (dent->fs && dent->fs->d_ops && dent->fs->d_ops->chmod) { if ((ret = dent->fs->d_ops->chmod(dent, mode)) < 0) goto out; } else { dent->state |= DENTRY_PERSIST; } dent->mode = mode; out: put_dentry(dent); return ret; } int shim_do_fchmodat(int dfd, const char* filename, mode_t mode) { if (!filename) return -EINVAL; if (test_user_string(filename)) return -EFAULT; struct shim_dentry* dir = NULL; struct shim_dentry* dent = NULL; int ret = 0; if ((ret = get_dirfd_dentry(dfd, &dir)) < 0) return ret; if ((ret = path_lookupat(dir, filename, LOOKUP_OPEN, &dent, NULL)) < 0) goto out; if (dent->fs && dent->fs->d_ops && dent->fs->d_ops->chmod) { if ((ret = dent->fs->d_ops->chmod(dent, mode)) < 0) goto out_dent; } else { dent->state |= DENTRY_PERSIST; } dent->mode = mode; out_dent: put_dentry(dent); out: put_dentry(dir); return ret; } int shim_do_fchmod(int fd, mode_t mode) { struct shim_handle* hdl = get_fd_handle(fd, NULL, NULL); if (!hdl) return -EBADF; struct shim_dentry* dent = hdl->dentry; int ret = 0; if (dent->fs && dent->fs->d_ops && dent->fs->d_ops->chmod) { if ((ret = dent->fs->d_ops->chmod(dent, mode)) < 0) goto out; } else { dent->state |= DENTRY_PERSIST; } dent->mode = mode; out: put_handle(hdl); return ret; } int shim_do_chown(const char* path, uid_t uid, gid_t gid) { struct shim_dentry* dent = NULL; int ret = 0; __UNUSED(uid); __UNUSED(gid); if (!path) return -EINVAL; if (test_user_string(path)) return -EFAULT; if ((ret = path_lookupat(NULL, path, LOOKUP_OPEN, &dent, NULL)) < 0) return ret; /* XXX: do nothing now */ put_dentry(dent); return ret; } int shim_do_fchownat(int dfd, const char* filename, uid_t uid, gid_t gid, int flags) { __UNUSED(flags); __UNUSED(uid); __UNUSED(gid); if (!filename) return -EINVAL; if (test_user_string(filename)) return -EFAULT; struct shim_dentry* dir = NULL; struct shim_dentry* dent = NULL; int ret = 0; if ((ret = get_dirfd_dentry(dfd, &dir)) < 0) return ret; if ((ret = path_lookupat(dir, filename, LOOKUP_OPEN, &dent, NULL)) < 0) goto out; /* XXX: do nothing now */ put_dentry(dent); out: put_dentry(dir); return ret; } int shim_do_fchown(int fd, uid_t uid, gid_t gid) { __UNUSED(uid); __UNUSED(gid); struct shim_handle* hdl = get_fd_handle(fd, NULL, NULL); if (!hdl) return -EBADF; /* XXX: do nothing now */ return 0; } #define MAP_SIZE (g_pal_alloc_align * 4) #define BUF_SIZE 2048 static ssize_t handle_copy(struct shim_handle* hdli, off_t* offseti, struct shim_handle* hdlo, off_t* offseto, ssize_t count) { struct shim_mount* fsi = hdli->fs; struct shim_mount* fso = hdlo->fs; if (!count) return 0; if (!fsi || !fsi->fs_ops || !fso || !fso->fs_ops) return -EACCES; bool do_mapi = fsi->fs_ops->mmap != NULL; bool do_mapo = fso->fs_ops->mmap != NULL; bool do_marki = false; bool do_marko = false; int offi = 0, offo = 0; if (offseti) { if (!fsi->fs_ops->seek) return -EACCES; offi = *offseti; fsi->fs_ops->seek(hdli, offi, SEEK_SET); } else { if (!fsi->fs_ops->seek || (offi = fsi->fs_ops->seek(hdli, 0, SEEK_CUR)) < 0) do_mapi = false; } if (offseto) { if (!fso->fs_ops->seek) return -EACCES; offo = *offseto; fso->fs_ops->seek(hdlo, offo, SEEK_SET); } else { if (!fso->fs_ops->seek || (offo = fso->fs_ops->seek(hdlo, 0, SEEK_CUR)) < 0) do_mapo = false; } if (do_mapi) { int size; if (fsi->fs_ops->poll && (size = fsi->fs_ops->poll(hdli, FS_POLL_SZ)) >= 0) { if (count == -1 || count > size - offi) count = size - offi; if (!count) return 0; } else { do_mapi = false; } } if (do_mapo && count > 0) do { int size; if (!fso->fs_ops->poll || (size = fso->fs_ops->poll(hdlo, FS_POLL_SZ)) < 0) { do_mapo = false; break; } if (offo + count < size) break; if (!fso->fs_ops->truncate || fso->fs_ops->truncate(hdlo, offo + count) < 0) { do_mapo = false; break; } } while (0); void* bufi = NULL; void* bufo = NULL; int bytes = 0; int bufsize = MAP_SIZE; int copysize = 0; if (!do_mapi && (hdli->flags & O_NONBLOCK) && fsi->fs_ops->setflags) { int ret = fsi->fs_ops->setflags(hdli, 0); if (!ret) { debug("mark handle %s as blocking\n", qstrgetstr(&hdli->uri)); do_marki = true; } } if (!do_mapo && (hdlo->flags & O_NONBLOCK) && fso->fs_ops->setflags) { int ret = fso->fs_ops->setflags(hdlo, 0); if (!ret) { debug("mark handle %s as blocking\n", qstrgetstr(&hdlo->uri)); do_marko = true; } } assert(count); do { int boffi = 0, boffo = 0; int expectsize = bufsize; if (count > 0 && bufsize > count - bytes) expectsize = bufsize = count - bytes; if (do_mapi && !bufi) { boffi = offi - ALLOC_ALIGN_DOWN(offi); if (fsi->fs_ops->mmap(hdli, &bufi, ALLOC_ALIGN_UP(bufsize + boffi), PROT_READ, MAP_FILE, offi - boffi) < 0) { do_mapi = false; boffi = 0; if ((hdli->flags & O_NONBLOCK) && fsi->fs_ops->setflags) { int ret = fsi->fs_ops->setflags(hdli, 0); if (!ret) { debug("mark handle %s as blocking\n", qstrgetstr(&hdli->uri)); do_marki = true; } } if (fsi->fs_ops->seek) offi = fsi->fs_ops->seek(hdli, offi, SEEK_SET); } } if (do_mapo && !bufo) { boffo = offo - ALLOC_ALIGN_DOWN(offo); if (fso->fs_ops->mmap(hdlo, &bufo, ALLOC_ALIGN_UP(bufsize + boffo), PROT_WRITE, MAP_FILE, offo - boffo) < 0) { do_mapo = false; boffo = 0; if ((hdlo->flags & O_NONBLOCK) && fso->fs_ops->setflags) { int ret = fso->fs_ops->setflags(hdlo, 0); if (!ret) { debug("mark handle %s as blocking\n", qstrgetstr(&hdlo->uri)); do_marko = true; } } if (fso->fs_ops->seek) offo = fso->fs_ops->seek(hdlo, offo, SEEK_SET); } } if (do_mapi && do_mapo) { copysize = count - bytes > bufsize ? bufsize : count - bytes; memcpy(bufo + boffo, bufi + boffi, copysize); DkVirtualMemoryFree(bufi, ALLOC_ALIGN_UP(bufsize + boffi)); bufi = NULL; DkVirtualMemoryFree(bufo, ALLOC_ALIGN_UP(bufsize + boffo)); bufo = NULL; } else if (do_mapo) { copysize = fsi->fs_ops->read(hdli, bufo + boffo, bufsize); DkVirtualMemoryFree(bufo, ALLOC_ALIGN_UP(bufsize + boffo)); bufo = NULL; if (copysize < 0) break; } else if (do_mapi) { copysize = fso->fs_ops->write(hdlo, bufi + boffi, bufsize); DkVirtualMemoryFree(bufi, ALLOC_ALIGN_UP(bufsize + boffi)); bufi = NULL; if (copysize < 0) break; } else { if (!bufi) bufi = __alloca((bufsize = (bufsize > BUF_SIZE) ? BUF_SIZE : bufsize)); copysize = fsi->fs_ops->read(hdli, bufi, bufsize); if (copysize <= 0) break; expectsize = copysize; copysize = fso->fs_ops->write(hdlo, bufi, expectsize); if (copysize < 0) break; } debug("copy %d bytes\n", copysize); bytes += copysize; offi += copysize; offo += copysize; if (copysize < expectsize) break; } while (bytes < count); if (copysize < 0 || (count > 0 && bytes < count)) { int ret = copysize < 0 ? copysize : -EAGAIN; if (bytes) { if (fsi->fs_ops->seek) fsi->fs_ops->seek(hdli, offi - bytes, SEEK_SET); if (fso->fs_ops->seek) fso->fs_ops->seek(hdlo, offo - bytes, SEEK_SET); } return ret; } if (do_marki && (hdli->flags & O_NONBLOCK)) { debug("mark handle %s as nonblocking\n", qstrgetstr(&hdli->uri)); fsi->fs_ops->setflags(hdli, O_NONBLOCK); } if (do_marko && (hdlo->flags & O_NONBLOCK)) { debug("mark handle %s as nonblocking\n", qstrgetstr(&hdlo->uri)); fso->fs_ops->setflags(hdlo, O_NONBLOCK); } if (do_mapi) { if (fsi->fs_ops->seek) fsi->fs_ops->seek(hdli, offi, SEEK_SET); } if (offseti) *offseti = offi; if (do_mapo) { if (fso->fs_ops->seek) fso->fs_ops->seek(hdlo, offo, SEEK_SET); } if (offseto) *offseto = offo; return bytes; } static int do_rename(struct shim_dentry* old_dent, struct shim_dentry* new_dent) { if ((old_dent->type != S_IFREG) || (!(new_dent->state & DENTRY_NEGATIVE) && (new_dent->type != S_IFREG))) { /* Current implementation of fs does not allow for renaming anything but regular files */ return -ENOSYS; } if (old_dent->fs != new_dent->fs) { /* Disallow cross mount renames */ return -EXDEV; } if (!old_dent->fs || !old_dent->fs->d_ops || !old_dent->fs->d_ops->rename) { return -EPERM; } if (old_dent->state & DENTRY_ISDIRECTORY) { if (!(new_dent->state & DENTRY_NEGATIVE)) { if (!(new_dent->state & DENTRY_ISDIRECTORY)) { return -ENOTDIR; } if (new_dent->nchildren > 0) { return -ENOTEMPTY; } } else { /* destination is a negative dentry and needs to be marked as a directory, since source * is a directory */ new_dent->state |= DENTRY_ISDIRECTORY; } } else if (new_dent->state & DENTRY_ISDIRECTORY) { return -EISDIR; } if (dentry_is_ancestor(old_dent, new_dent) || dentry_is_ancestor(new_dent, old_dent)) { return -EINVAL; } /* TODO: Add appropriate checks for hardlinks once they get implemented. */ int ret = old_dent->fs->d_ops->rename(old_dent, new_dent); if (!ret) { old_dent->state |= DENTRY_NEGATIVE; new_dent->state &= ~DENTRY_NEGATIVE; } return ret; } int shim_do_rename(const char* oldpath, const char* newpath) { return shim_do_renameat(AT_FDCWD, oldpath, AT_FDCWD, newpath); } int shim_do_renameat(int olddirfd, const char* oldpath, int newdirfd, const char* newpath) { struct shim_dentry* old_dir_dent = NULL; struct shim_dentry* old_dent = NULL; struct shim_dentry* new_dir_dent = NULL; struct shim_dentry* new_dent = NULL; int ret = 0; if (!oldpath || test_user_string(oldpath) || !newpath || test_user_string(newpath)) { return -EFAULT; } if ((ret = get_dirfd_dentry(olddirfd, &old_dir_dent)) < 0) { goto out; } if ((ret = path_lookupat(old_dir_dent, oldpath, LOOKUP_OPEN, &old_dent, NULL)) < 0) { goto out; } if (old_dent->state & DENTRY_NEGATIVE) { ret = -ENOENT; goto out; } if ((ret = get_dirfd_dentry(newdirfd, &new_dir_dent)) < 0) { goto out; } ret = path_lookupat(new_dir_dent, newpath, LOOKUP_OPEN | LOOKUP_CREATE, &new_dent, NULL); if (ret < 0) { if (ret != -ENOENT || !new_dent || (new_dent->state & (DENTRY_NEGATIVE | DENTRY_VALID)) != (DENTRY_NEGATIVE | DENTRY_VALID)) { goto out; } } // Both dentries should have a ref count of at least 2 at this point assert(REF_GET(old_dent->ref_count) >= 2); assert(REF_GET(new_dent->ref_count) >= 2); ret = do_rename(old_dent, new_dent); out: if (old_dir_dent) put_dentry(old_dir_dent); if (old_dent) put_dentry(old_dent); if (new_dir_dent) put_dentry(new_dir_dent); if (new_dent) put_dentry(new_dent); return ret; } ssize_t shim_do_sendfile(int ofd, int ifd, off_t* offset, size_t count) { struct shim_handle* hdli = get_fd_handle(ifd, NULL, NULL); struct shim_handle* hdlo = get_fd_handle(ofd, NULL, NULL); if (!hdli || !hdlo) return -EBADF; off_t old_offset = 0; int ret = -EACCES; if (offset) { if (!hdli->fs || !hdli->fs->fs_ops || !hdli->fs->fs_ops->seek) goto out; old_offset = hdli->fs->fs_ops->seek(hdli, 0, SEEK_CUR); if (old_offset < 0) { ret = old_offset; goto out; } } ret = handle_copy(hdli, offset, hdlo, NULL, count); if (ret >= 0 && offset) hdli->fs->fs_ops->seek(hdli, old_offset, SEEK_SET); out: put_handle(hdli); put_handle(hdlo); return ret; } int shim_do_chroot(const char* filename) { int ret = 0; struct shim_dentry* dent = NULL; if ((ret = path_lookupat(NULL, filename, 0, &dent, NULL)) < 0) goto out; if (!dent) { ret = -ENOENT; goto out; } struct shim_thread* thread = get_cur_thread(); lock(&thread->lock); put_dentry(thread->root); thread->root = dent; unlock(&thread->lock); out: return ret; }