/* 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".
*/
#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, * 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 0;
}
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, * 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, * 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 (allocsize * 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, 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, * 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 - ALIGN_DOWN(offi);
if (fsi->fs_ops->mmap(hdli, &bufi, 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 - ALIGN_DOWN(offo);
if (fso->fs_ops->mmap(hdlo, &bufo, 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(hdlo + boffo, hdli + boffi, copysize);
DkVirtualMemoryFree(bufi, ALIGN_UP(bufsize + boffi));
bufi = NULL;
DkVirtualMemoryFree(bufo, ALIGN_UP(bufsize + boffo));
bufo = NULL;
} else if (do_mapo) {
copysize = fsi->fs_ops->read(hdli, bufo + boffo, bufsize);
DkVirtualMemoryFree(bufo, 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, 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;
}