/* -*- mode:c; c-file-style:"k&r"; c-basic-offset: 4; tab-width:4; indent-tabs-mode:nil; mode:auto-fill; fill-column:78; -*- */ /* vim: set ts=4 sw=4 et tw=78 fo=cqt wm=0: */ /* Copyright (C) 2014 OSCAR lab, 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 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* * shim_open.c * * Implementation of system call "read", "write", "open", "creat", "openat", * "close", "lseek", "pread64", "pwrite64", "getdents", "getdents64", * "fsync", "truncate" and "ftruncate". */ #include #include #include #include #include #include #include #include #include #include #include #include #include int do_handle_read (struct shim_handle * hdl, void * buf, int count) { if (!(hdl->acc_mode & MAY_READ)) return -EACCES; struct shim_mount * fs = hdl->fs; assert (fs && fs->fs_ops); if (!fs->fs_ops->read) return -EBADF; if (hdl->type == TYPE_DIR) return -EISDIR; return fs->fs_ops->read(hdl, buf, count); } size_t shim_do_read (int fd, void * buf, size_t count) { struct shim_handle * hdl = get_fd_handle(fd, NULL, NULL); if (!hdl) return -EBADF; int ret = do_handle_read(hdl, buf, count); put_handle(hdl); return ret; } int do_handle_write (struct shim_handle * hdl, const void * buf, int count) { if (!(hdl->acc_mode & MAY_WRITE)) return -EACCES; struct shim_mount * fs = hdl->fs; assert (fs && fs->fs_ops); if (!fs->fs_ops->write) return -EBADF; if (hdl->type == TYPE_DIR) return -EISDIR; return fs->fs_ops->write(hdl, buf, count); } size_t shim_do_write (int fd, const void * buf, size_t count) { struct shim_handle * hdl = get_fd_handle(fd, NULL, NULL); if (!hdl) return -EBADF; int ret = do_handle_write(hdl, buf, count); put_handle(hdl); return ret; } int shim_do_open (const char * file, int flags, mode_t mode) { if (!file || !(*file)) return -EINVAL; struct shim_handle * hdl = get_new_handle(); if (!hdl) return -ENOMEM; int ret = 0; ret = open_namei(hdl, NULL, file, flags, mode, NULL); if (ret < 0) goto out; ret = set_new_fd_handle(hdl, flags & O_CLOEXEC ? FD_CLOEXEC : 0, NULL); out: put_handle(hdl); return ret; } int shim_do_creat (const char * path, mode_t mode) { return shim_do_open(path, O_CREAT|O_TRUNC|O_WRONLY, mode); } int shim_do_openat (int dfd, const char * filename, int flags, int mode) { if (!filename) return -EINVAL; if (*filename == '/') return shim_do_open(filename, flags, mode); struct shim_dentry * dir = NULL; int ret = 0; if ((ret = path_startat(dfd, &dir)) < 0) return ret; struct shim_handle * hdl = get_new_handle(); if (!hdl) { ret = -ENOMEM; goto out; } ret = open_namei(hdl, dir, filename, flags, mode, NULL); if (ret < 0) goto out_hdl; ret = set_new_fd_handle(hdl, flags & O_CLOEXEC ? FD_CLOEXEC : 0, NULL); out_hdl: put_handle(hdl); out: put_dentry(dir); return ret; } int shim_do_close (int fd) { struct shim_handle * handle = detach_fd_handle(fd, NULL, NULL); if (!handle) return -EBADF; close_handle(handle); return 0; } /* lseek is simply doing arithmetic on the offset, no PAL call here */ off_t shim_do_lseek (int fd, off_t offset, int origin) { if (origin != SEEK_SET && origin != SEEK_CUR && origin != SEEK_END) return -EINVAL; struct shim_handle * hdl = get_fd_handle(fd, NULL, NULL); if (!hdl) return -EBADF; int ret = 0; struct shim_mount * fs = hdl->fs; assert(fs && fs->fs_ops); if (!fs->fs_ops->seek) { ret = -ESPIPE; goto out; } if (hdl->type == TYPE_DIR) { /* TODO: handle lseek'ing of directories */ ret = -ENOSYS; goto out; } ret = fs->fs_ops->seek(hdl, offset, origin); out: put_handle(hdl); return ret; } size_t shim_do_pread64 (int fd, char * buf, size_t count, loff_t pos) { struct shim_handle * hdl = get_fd_handle(fd, NULL, NULL); if (!hdl) return -EBADF; struct shim_mount * fs = hdl->fs; size_t ret = -EACCES; if (!fs || !fs->fs_ops) goto out; if (!fs->fs_ops->seek) { ret = -ESPIPE; goto out; } if (!fs->fs_ops->read) goto out; if (hdl->type == TYPE_DIR) goto out; int offset = fs->fs_ops->seek(hdl, 0, SEEK_CUR); if (offset < 0) { ret = offset; goto out; } ret = fs->fs_ops->seek(hdl, pos, SEEK_SET); if (ret < 0) goto out; int bytes = fs->fs_ops->read(hdl, buf, count); ret = fs->fs_ops->seek(hdl, offset, SEEK_SET); if (ret < 0) goto out; ret = bytes; out: put_handle(hdl); return ret; } size_t shim_do_pwrite64 (int fd, char * buf, size_t count, loff_t pos) { struct shim_handle * hdl = get_fd_handle(fd, NULL, NULL); if (!hdl) return -EBADF; struct shim_mount * fs = hdl->fs; size_t ret = -EACCES; if (!fs || !fs->fs_ops) goto out; if (!fs->fs_ops->seek) { ret = -ESPIPE; goto out; } if (!fs->fs_ops->write) goto out; if (hdl->type == TYPE_DIR) goto out; int offset = fs->fs_ops->seek(hdl, 0, SEEK_CUR); if (offset < 0) { ret = offset; goto out; } ret = fs->fs_ops->seek(hdl, pos, SEEK_SET); if (ret < 0) goto out; int bytes = fs->fs_ops->write(hdl, buf, count); ret = fs->fs_ops->seek(hdl, offset, SEEK_SET); if (ret < 0) goto out; ret = bytes; out: put_handle(hdl); return ret; } static inline int get_dirent_type (mode_t type) { switch (type) { case S_IFLNK: return LINUX_DT_LNK; case S_IFREG: return LINUX_DT_REG; case S_IFDIR: return LINUX_DT_DIR; case S_IFCHR: return LINUX_DT_CHR; case S_IFBLK: return LINUX_DT_BLK; case S_IFIFO: return LINUX_DT_FIFO; case S_IFSOCK: return LINUX_DT_SOCK; default: return LINUX_DT_UNKNOWN; } } size_t shim_do_getdents (int fd, struct linux_dirent * buf, size_t count) { struct shim_handle * hdl = get_fd_handle(fd, NULL, NULL); if (!hdl) return -EBADF; int ret = -EACCES; if (hdl->type != TYPE_DIR) { ret = -ENOTDIR; goto out; } /* DEP 3/3/17: Properly handle an unlinked directory */ if (hdl->dentry->state & DENTRY_NEGATIVE) { ret = -ENOENT; goto out; } /* we are grabbing the lock because the handle content is actually updated */ lock(hdl->lock); struct shim_dir_handle * dirhdl = &hdl->info.dir; struct shim_dentry * dent = hdl->dentry; struct linux_dirent * b = buf; int bytes = 0; #define DIRENT_SIZE(len) (sizeof(struct linux_dirent) + \ sizeof(struct linux_dirent_tail) + (len) + 1) #define ASSIGN_DIRENT(dent, name, type) \ do { \ int len = strlen(name); \ if (bytes + DIRENT_SIZE(len) > count) \ goto done; \ \ struct linux_dirent_tail * bt \ = (void *) b + sizeof(struct linux_dirent) + len + 1; \ \ b->d_ino = dent->ino; \ b->d_off = ++dirhdl->offset; \ b->d_reclen = DIRENT_SIZE(len); \ \ memcpy(b->d_name, name, len + 1); \ \ bt->pad = 0; \ bt->d_type = type ? : get_dirent_type(dent->mode); \ \ b = (void *) bt + sizeof(struct linux_dirent_tail); \ bytes += DIRENT_SIZE(len); \ } while(0) if (dirhdl->dot) { ASSIGN_DIRENT(dirhdl->dot, ".", LINUX_DT_DIR); put_dentry(dirhdl->dot); dirhdl->dot = NULL; } if (dirhdl->dotdot) { ASSIGN_DIRENT(dirhdl->dotdot, "..", LINUX_DT_DIR); put_dentry(dirhdl->dotdot); dirhdl->dotdot = NULL; } while (dirhdl->ptr && *dirhdl->ptr) { dent = *dirhdl->ptr; /* DEP 3/3/17: We need to filter negative dentries */ if (!(dent->state & DENTRY_NEGATIVE)) ASSIGN_DIRENT(dent, dentry_get_name(dent), 0); put_dentry(dent); *(dirhdl->ptr++) = NULL; } #undef DIRENT_SIZE #undef ASSIGN_DIRENT done: ret = bytes; /* DEP 3/3/17: Properly detect EINVAL case, where buffer is too small to * hold anything */ if (bytes == 0 && ((dirhdl->ptr && *dirhdl->ptr) || dirhdl->dotdot || dirhdl->dot)) ret = -EINVAL; unlock(hdl->lock); out: put_handle(hdl); return ret; } size_t shim_do_getdents64 (int fd, struct linux_dirent64 * buf, size_t count) { struct shim_handle * hdl = get_fd_handle(fd, NULL, NULL); if (!hdl) return -EBADF; int ret = -EACCES; if (hdl->type != TYPE_DIR) { ret = -ENOTDIR; goto out; } /* DEP 3/3/17: Properly handle an unlinked directory */ if (hdl->dentry->state & DENTRY_NEGATIVE) { ret = -ENOENT; goto out; } lock(hdl->lock); struct shim_dir_handle * dirhdl = &hdl->info.dir; struct shim_dentry * dent = hdl->dentry; struct linux_dirent64 * b = buf; int bytes = 0; #define DIRENT_SIZE(len) (sizeof(struct linux_dirent64) + (len) + 1) #define ASSIGN_DIRENT(dent, name, type) \ do { \ int len = strlen(name); \ if (bytes + DIRENT_SIZE(len) > count) \ goto done; \ \ b->d_ino = dent->ino; \ b->d_off = ++dirhdl->offset; \ b->d_reclen = DIRENT_SIZE(len); \ b->d_type = type ? : get_dirent_type(dent->mode); \ \ memcpy(b->d_name, name, len + 1); \ \ b = (void *) b + DIRENT_SIZE(len); \ bytes += DIRENT_SIZE(len); \ } while(0) if (dirhdl->dot) { ASSIGN_DIRENT(dirhdl->dot, ".", LINUX_DT_DIR); put_dentry(dirhdl->dot); dirhdl->dot = NULL; } if (dirhdl->dotdot) { ASSIGN_DIRENT(dirhdl->dotdot, "..", LINUX_DT_DIR); put_dentry(dirhdl->dotdot); dirhdl->dotdot = NULL; } while (dirhdl->ptr && *dirhdl->ptr) { dent = *dirhdl->ptr; /* DEP 3/3/17: We need to filter negative dentries */ if (!(dent->state & DENTRY_NEGATIVE)) ASSIGN_DIRENT(dent, dentry_get_name(dent), 0); put_dentry(dent); *(dirhdl->ptr++) = NULL; } #undef DIRENT_SIZE #undef ASSIGN_DIRENT done: ret = bytes; /* DEP 3/3/17: Properly detect EINVAL case, where buffer is too small to * hold anything */ if (bytes == 0 && ((dirhdl->ptr && *dirhdl->ptr) || dirhdl->dotdot || dirhdl->dot)) ret = -EINVAL; unlock(hdl->lock); out: put_handle(hdl); return ret; } int shim_do_fsync (int fd) { struct shim_handle * hdl = get_fd_handle(fd, NULL, NULL); if (!hdl) return -EBADF; int ret = -EACCES; struct shim_mount * fs = hdl->fs; if (!fs || !fs->fs_ops) goto out; if (hdl->type == TYPE_DIR) goto out; if (!fs->fs_ops->flush) { ret = -EROFS; goto out; } ret = fs->fs_ops->flush(hdl); out: put_handle(hdl); return ret; } // DEP 10/20/16: Assuming fsync >> fdatasync for now // and no app depends on only syncing data for correctness. int shim_do_fdatasync (int fd) { return shim_do_fsync(fd); } int shim_do_truncate (const char * path, loff_t length) { struct shim_dentry * dent = NULL; int ret = 0; if ((ret = path_lookupat(NULL, path, 0, &dent)) < 0) return ret; struct shim_mount * fs = dent->fs; if (!fs || !fs->d_ops || !fs->d_ops->open) { ret = -EBADF; goto out; } if (!fs->fs_ops->truncate) { ret = -EROFS; goto out; } struct shim_handle * hdl = get_new_handle(); if (!hdl) { ret = -ENOMEM; goto out; } hdl->fs = fs; if ((ret = fs->d_ops->open(hdl, dent, O_WRONLY)) < 0) goto out_handle; ret = fs->fs_ops->truncate(hdl, length); flush_handle(hdl); out_handle: put_handle(hdl); out: return ret; } int shim_do_ftruncate (int fd, loff_t length) { struct shim_handle * hdl = get_fd_handle(fd, NULL, NULL); if (!hdl) return -EBADF; struct shim_mount * fs = hdl->fs; int ret = -EACCES; if (!fs || !fs->fs_ops) goto out; if (hdl->type == TYPE_DIR) goto out; if (!fs->fs_ops->truncate) { ret = -EROFS; goto out; } ret = fs->fs_ops->truncate(hdl, length); out: put_handle(hdl); return ret; }