/* -*- 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_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
/* 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;
struct shim_dentry * dent = NULL;
int ret = 0;
if ((ret = path_lookupat(NULL, file, LOOKUP_OPEN, &dent)) < 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 (*pathname == '/')
return (flag & AT_REMOVEDIR) ? shim_do_rmdir(pathname) :
shim_do_unlink(pathname);
struct shim_dentry * dir = NULL, * dent = NULL;
int ret = 0;
if ((ret = path_startat(dfd, &dir)) < 0)
return ret;
if ((ret = path_lookupat(dir, pathname, LOOKUP_OPEN, &dent)) < 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 (*pathname == '/')
return shim_do_mkdir(pathname, mode);
struct shim_dentry * dir = NULL;
int ret = 0;
if ((ret = path_startat(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 ((ret = path_lookupat(NULL, pathname, LOOKUP_OPEN|LOOKUP_DIRECTORY,
&dent)) < 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 ((ret = path_lookupat(NULL, path, LOOKUP_OPEN, &dent)) < 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 (*filename == '/')
return shim_do_chmod(filename, mode);
struct shim_dentry * dir = NULL, * dent = NULL;
int ret = 0;
if ((ret = path_startat(dfd, &dir)) < 0)
return ret;
if ((ret = path_lookupat(dir, filename, LOOKUP_OPEN, &dent)) < 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;
if ((ret = path_lookupat(NULL, path, LOOKUP_OPEN, &dent)) < 0)
return ret;
/* do nothing*/
out:
put_dentry(dent);
return ret;
}
int shim_do_fchownat (int dfd, const char * filename, uid_t uid, gid_t gid,
int flags)
{
if (!filename)
return -EINVAL;
if (*filename == '/')
return shim_do_chown(filename, uid, gid);
struct shim_dentry * dir = NULL, * dent = NULL;
int ret = 0;
if ((ret = path_startat(dfd, &dir)) < 0)
return ret;
if ((ret = path_lookupat(dir, filename, LOOKUP_OPEN, &dent)) < 0)
goto out;
/* do nothing */
out_dent:
put_dentry(dent);
out:
put_dentry(dir);
return ret;
}
int shim_do_fchown (int fd, uid_t uid, gid_t gid)
{
struct shim_handle * hdl = get_fd_handle(fd, NULL, NULL);
if (!hdl)
return -EBADF;
struct shim_dentry * dent = hdl->dentry;
int ret = 0;
/* do nothing */
out:
put_handle(hdl);
return ret;
}
#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;
goto done_copy;
}
if (do_mapo) {
copysize = fsi->fs_ops->read(hdli, bufo + boffo, bufsize);
DkVirtualMemoryFree(bufo, ALIGN_UP(bufsize + boffo));
bufo = NULL;
if (copysize < 0)
break;
goto done_copy;
}
if (do_mapi) {
copysize = fso->fs_ops->write(hdlo, bufi + boffi, bufsize);
DkVirtualMemoryFree(bufi, ALIGN_UP(bufsize + boffi));
bufi = NULL;
if (copysize < 0)
break;
goto done_copy;
}
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;
done_copy:
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)
{
int ret = 0;
if (old_dent->fs && old_dent->fs->d_ops &&
old_dent->fs->d_ops->rename &&
old_dent->type == new_dent->type) {
ret = old_dent->fs->d_ops->rename(old_dent, new_dent);
if (!ret) {
old_dent->state |= DENTRY_NEGATIVE;
new_dent->state &= ~DENTRY_NEGATIVE;
goto out;
}
if (ret == -EACCES)
goto out;
}
if (!(new_dent->state & DENTRY_NEGATIVE)) {
if (!new_dent->parent ||
!new_dent->fs || !new_dent->fs->d_ops ||
!new_dent->fs->d_ops->unlink) {
ret = -EACCES;
goto out;
}
if ((ret = new_dent->fs->d_ops->unlink(new_dent->parent,
new_dent)) < 0)
goto out;
new_dent->state |= DENTRY_NEGATIVE;
}
/* TODO: we are not able to handle directory copy yet */
if (old_dent->state & DENTRY_ISDIRECTORY) {
ret = -ENOSYS;
goto out;
}
struct shim_handle * old_hdl = NULL, * new_hdl = NULL;
bool old_opened = false;
bool new_opened = false;
if (!(old_hdl = get_new_handle())) {
ret = -ENOMEM;
goto out_hdl;
}
if ((ret = dentry_open(old_hdl, old_dent, O_RDONLY)) < 0)
goto out_hdl;
old_opened = true;
if (!(new_hdl = get_new_handle())) {
ret = -ENOMEM;
goto out_hdl;
}
if ((ret = dentry_open(new_hdl, new_dent, O_WRONLY|O_CREAT)) < 0)
goto out_hdl;
new_opened = true;
off_t old_offset = 0, new_offset = 0;
if ((ret = handle_copy(old_hdl, &old_offset,
new_hdl, &new_offset, -1)) < 0) {
if (new_dent->fs && new_dent->fs->d_ops &&
new_dent->fs->d_ops->unlink) {
ret = new_dent->fs->d_ops->unlink(new_dent->parent,
new_dent);
}
goto out_hdl;
}
new_dent->state &= ~DENTRY_NEGATIVE;
if (old_dent->fs && old_dent->fs->d_ops &&
old_dent->fs->d_ops->unlink) {
if ((ret = old_dent->fs->d_ops->unlink(old_dent->parent,
old_dent)) < 0)
goto out_hdl;
}
old_dent->state |= DENTRY_NEGATIVE;
out_hdl:
if (old_hdl) {
if (old_opened)
close_handle(old_hdl);
else
put_handle(old_hdl);
}
if (new_hdl) {
if (new_opened)
close_handle(new_hdl);
else
put_handle(new_hdl);
}
out:
return ret;
}
int shim_do_rename (const char * oldname, const char * newname)
{
struct shim_dentry * old_dent = NULL, * new_dent = NULL;
int ret = 0;
if ((ret = path_lookupat(NULL, oldname, LOOKUP_OPEN, &old_dent)) < 0)
goto out;
if (old_dent->state & DENTRY_NEGATIVE) {
ret = -ENOENT;
goto out;
}
if ((ret = path_lookupat(NULL, newname, LOOKUP_OPEN|LOOKUP_CREATE,
&new_dent)) < 0)
goto out;
ret = do_rename(old_dent, new_dent);
out:
if (old_dent)
put_dentry(old_dent);
if (new_dent)
put_dentry(new_dent);
return ret;
}
int shim_do_renameat (int olddfd, const char * pathname, int newdfd,
const char * newname)
{
struct shim_dentry * old_dir = NULL, * old_dent = NULL;
struct shim_dentry * new_dir = NULL, * new_dent = NULL;
int ret = 0;
if ((ret = path_startat(olddfd, &old_dir)) < 0)
goto out;
if ((ret = path_lookupat(old_dir, pathname, LOOKUP_OPEN, &old_dent)) < 0)
goto out;
if (old_dent->state & DENTRY_NEGATIVE) {
ret = -ENOENT;
goto out;
}
if ((ret = path_startat(newdfd, &new_dir)) < 0)
goto out;
if ((ret = path_lookupat(new_dir, newname, LOOKUP_OPEN|LOOKUP_CREATE,
&new_dent)) < 0)
goto out;
ret = do_rename(old_dent, new_dent);
out:
if (old_dir)
put_dentry(old_dir);
if (old_dent)
put_dentry(old_dent);
if (new_dir)
put_dentry(new_dir);
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)) < 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;
}