sortix-mirror/kernel/descriptor.cpp
Jonas 'Sortie' Termansen d3a64e1163 Fix chmod(2), chown(2), and utimens(2) opening the path for writing.
The change 9d29e96c3b "Fix open(2) allowing
opening directories invalidly and check O_TRUNC errors." broke the chmod(2),
chown(2), and utimens(2) system calls on directories, because they can no
longer be opened for writing.

This changes fixes the regression by opening such paths for reading. There
is currently no filesystem permission checks for those system calls. However,
those system calls should check the permissions at the time of the operation
rather than relying on the file having been opened for writing previously.
2018-09-01 17:24:40 +02:00

1183 lines
34 KiB
C++

/*
* Copyright (c) 2012, 2013, 2014, 2015, 2016, 2017 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* descriptor.cpp
* A file descriptor.
*/
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <assert.h>
#include <errno.h>
#include <fsmarshall-msg.h>
#include <limits.h>
#include <stdint.h>
#include <string.h>
#include <sortix/dirent.h>
#include <sortix/fcntl.h>
#ifndef IOV_MAX
#include <sortix/limits.h>
#endif
#include <sortix/mount.h>
#include <sortix/seek.h>
#include <sortix/stat.h>
#include <sortix/kernel/copy.h>
#include <sortix/kernel/descriptor.h>
#include <sortix/kernel/fsfunc.h>
#include <sortix/kernel/inode.h>
#include <sortix/kernel/ioctx.h>
#include <sortix/kernel/kernel.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/process.h>
#include <sortix/kernel/refcount.h>
#include <sortix/kernel/string.h>
#include <sortix/kernel/vnode.h>
namespace Sortix {
// Flags for the various base modes to open a file in.
static const int ACCESS_FLAGS = O_READ | O_WRITE | O_EXEC | O_SEARCH;
// Flags that only make sense at open time.
static const int OPEN_FLAGS = O_CREATE | O_DIRECTORY | O_EXCL | O_TRUNC |
O_NOFOLLOW | O_SYMLINK_NOFOLLOW | O_NOCTTY |
O_TTY_INIT;
// Flags that only make sense for descriptors.
static const int DESCRIPTOR_FLAGS = O_APPEND | O_NONBLOCK;
// Let the ioctx_t force bits like O_NONBLOCK and otherwise use the dflags of
// the current file descriptor. This allows the caller to do non-blocking reads
// without calls to fcntl that may be racy with respect to other threads.
static int ContextFlags(int ctx_dflags, int desc_dflags)
{
return desc_dflags | (ctx_dflags & DESCRIPTOR_FLAGS);
}
int LinkInodeInDir(ioctx_t* ctx,
Ref<Descriptor> dir,
const char* name,
Ref<Inode> inode)
{
Ref<Vnode> vnode(new Vnode(inode, Ref<Vnode>(), 0, 0));
if ( !vnode )
return -1;
Ref<Descriptor> desc(new Descriptor(Ref<Vnode>(vnode), 0));
if ( !desc )
return -1;
return dir->link(ctx, name, desc);
}
Ref<Descriptor> OpenDirContainingPath(ioctx_t* ctx,
Ref<Descriptor> from,
const char* path,
char** final_ptr)
{
if ( !path[0] )
return errno = EINVAL, Ref<Descriptor>();
char* dirpath;
char* final;
if ( !SplitFinalElem(path, &dirpath, &final) )
return Ref<Descriptor>();
// TODO: Removing trailing slashes in final may not the right thing.
size_t final_length = strlen(final);
while ( final_length && final[final_length-1] == '/' )
final[--final_length] = 0;
// There is room for a single character as final is not the empty string.
if ( !final_length )
{
final[0] = '.';
final[1] = '\0';
}
if ( !dirpath[0] )
{
delete[] dirpath;
*final_ptr = final;
return from;
}
Ref<Descriptor> ret = from->open(ctx, dirpath, O_READ | O_DIRECTORY, 0);
delete[] dirpath;
if ( !ret )
return delete[] final, Ref<Descriptor>();
*final_ptr = final;
return ret;
}
size_t TruncateIOVec(struct iovec* iov, int iovcnt, off_t limit)
{
assert(0 <= iovcnt);
assert(0 <= limit);
off_t left = limit;
if ( (uintmax_t) SSIZE_MAX <= (uintmax_t) left )
left = SSIZE_MAX;
size_t requested = 0;
for ( int i = 0; i < iovcnt; i++ )
{
size_t request = iov[i].iov_len;
if ( __builtin_add_overflow(requested, request, &requested) )
requested = SIZE_MAX;
if ( left == 0 )
iov[i].iov_len = 0;
else if ( (uintmax_t) left < (uintmax_t) request )
{
iov[i].iov_len = left;
left = 0;
}
else
left -= request;
}
return requested;
}
// TODO: Add security checks.
Descriptor::Descriptor()
{
current_offset_lock = KTHREAD_MUTEX_INITIALIZER;
this->vnode = Ref<Vnode>(NULL);
this->ino = 0;
this->dev = 0;
this->type = 0;
this->dflags = 0;
checked_seekable = false;
seekable = false /* unused */;
current_offset = 0;
}
Descriptor::Descriptor(Ref<Vnode> vnode, int dflags)
{
current_offset_lock = KTHREAD_MUTEX_INITIALIZER;
this->vnode = Ref<Vnode>(NULL);
this->ino = 0;
this->dev = 0;
this->type = 0;
this->dflags = 0;
checked_seekable = false;
seekable = false /* unused */;
current_offset = 0;
LateConstruct(vnode, dflags);
}
void Descriptor::LateConstruct(Ref<Vnode> vnode, int dflags)
{
this->vnode = vnode;
this->ino = vnode->ino;
this->dev = vnode->dev;
this->type = vnode->type;
this->dflags = dflags;
}
Descriptor::~Descriptor()
{
}
bool Descriptor::SetFlags(int new_dflags)
{
// TODO: Hmm, there is race condition between changing the flags here and
// the code that uses the flags below. We could add a lock, but that
// would kinda prevent concurrency on the same file descriptor. Since
// the chances of this becoming a problem is rather slim (but could
// happen!), we'll do the unsafe thing for now. (See below also)
dflags = (dflags & ~DESCRIPTOR_FLAGS) | (new_dflags & DESCRIPTOR_FLAGS);
return true;
}
int Descriptor::GetFlags()
{
// TODO: The race condition also applies here if the variable can change.
return dflags;
}
Ref<Descriptor> Descriptor::Fork()
{
ScopedLock lock(&current_offset_lock);
Ref<Descriptor> ret(new Descriptor(vnode, dflags));
if ( !ret )
return Ref<Descriptor>();
ret->current_offset = current_offset;
ret->checked_seekable = checked_seekable;
ret->seekable = seekable;
return ret;
}
bool Descriptor::IsSeekable()
{
if ( S_ISCHR(type) )
return false;
ScopedLock lock(&current_offset_lock);
if ( !checked_seekable )
{
int saved_errno = errno;
ioctx_t ctx; SetupKernelIOCtx(&ctx);
seekable = S_ISDIR(vnode->type) || 0 <= vnode->lseek(&ctx, 0, SEEK_END);
checked_seekable = true;
errno = saved_errno;
}
return seekable;
}
int Descriptor::sync(ioctx_t* ctx)
{
// TODO: Possible denial-of-service attack if someone opens the file without
// that much rights and just syncs it a whole lot and slows down the
// system as a whole.
return vnode->sync(ctx);
}
int Descriptor::stat(ioctx_t* ctx, struct stat* st)
{
// TODO: Possible information leak if not O_READ | O_WRITE and the caller
// is told about the file size.
return vnode->stat(ctx, st);
}
int Descriptor::statvfs(ioctx_t* ctx, struct statvfs* stvfs)
{
// TODO: Possible information leak if not O_READ | O_WRITE and the caller
// is told about the file size.
return vnode->statvfs(ctx, stvfs);
}
int Descriptor::chmod(ioctx_t* ctx, mode_t mode)
{
// TODO: Regardless of dflags, check if the user/group can chmod.
return vnode->chmod(ctx, mode);
}
int Descriptor::chown(ioctx_t* ctx, uid_t owner, gid_t group)
{
// TODO: Regardless of dflags, check if the user/group can chown.
return vnode->chown(ctx, owner, group);
}
int Descriptor::truncate(ioctx_t* ctx, off_t length)
{
if ( length < 0 )
return errno = EINVAL, -1;
if ( !(dflags & O_WRITE) )
return errno = EBADF, -1;
return vnode->truncate(ctx, length);
}
off_t Descriptor::lseek(ioctx_t* ctx, off_t offset, int whence)
{
if ( S_ISCHR(type) )
{
if ( whence != SEEK_SET && whence != SEEK_CUR && whence != SEEK_END )
return errno = EINVAL, -1;
return 0;
}
if ( !IsSeekable() )
return errno = ESPIPE, -1;
ScopedLock lock(&current_offset_lock);
// TODO: Possible information leak to let someone without O_READ | O_WRITE
// seek the file and get information about data holes.
off_t reloff;
if ( whence == SEEK_SET )
reloff = 0;
else if ( whence == SEEK_CUR )
reloff = current_offset;
else if ( whence == SEEK_END )
{
if ( (reloff = vnode->lseek(ctx, 0, SEEK_END)) < 0 )
return -1;
}
else
return errno = EINVAL, -1;
off_t new_offset;
if ( __builtin_add_overflow(reloff, offset, &new_offset) )
return errno = EOVERFLOW, -1;
if ( new_offset < 0 )
return errno = EINVAL, -1;
return current_offset = new_offset;
}
ssize_t Descriptor::read(ioctx_t* ctx, uint8_t* buf, size_t count)
{
if ( !(dflags & O_READ) )
return errno = EBADF, -1;
if ( SIZE_MAX < count )
count = SSIZE_MAX;
int old_ctx_dflags = ctx->dflags;
ctx->dflags = ContextFlags(old_ctx_dflags, dflags);
ssize_t result;
if ( !IsSeekable() )
result = vnode->read(ctx, buf, count);
else
{
ScopedLock lock(&current_offset_lock);
off_t available = OFF_MAX - current_offset;
if ( (uintmax_t) available < (uintmax_t) count )
count = available;
result = vnode->pread(ctx, buf, count, current_offset);
if ( 0 < result &&
__builtin_add_overflow(current_offset, result, &current_offset) )
current_offset = OFF_MAX;
}
ctx->dflags = old_ctx_dflags;
return result;
}
ssize_t Descriptor::readv(ioctx_t* ctx, const struct iovec* iov_ptr, int iovcnt)
{
if ( !(dflags & O_READ) )
return errno = EBADF, -1;
if ( iovcnt < 0 || IOV_MAX < iovcnt )
return errno = EINVAL, -1;
struct iovec* iov = new struct iovec[iovcnt];
if ( !iov )
return -1;
size_t iov_size = sizeof(struct iovec) * iovcnt;
if ( !ctx->copy_from_src(iov, iov_ptr, iov_size) )
return delete[] iov, -1;
int old_ctx_dflags = ctx->dflags;
ctx->dflags = ContextFlags(old_ctx_dflags, dflags);
ssize_t result = -1;
if ( !IsSeekable() )
{
if ( SSIZE_MAX < TruncateIOVec(iov, iovcnt, SSIZE_MAX) )
errno = EINVAL;
else
result = vnode->readv(ctx, iov, iovcnt);
}
else
{
ScopedLock lock(&current_offset_lock);
off_t available = OFF_MAX - current_offset;
if ( SSIZE_MAX < TruncateIOVec(iov, iovcnt, available) )
errno = EINVAL;
else
result = vnode->preadv(ctx, iov, iovcnt, current_offset);
if ( 0 < result &&
__builtin_add_overflow(current_offset, result, &current_offset) )
current_offset = OFF_MAX;
}
ctx->dflags = old_ctx_dflags;
delete[] iov;
return result;
}
ssize_t Descriptor::pread(ioctx_t* ctx, uint8_t* buf, size_t count, off_t off)
{
if ( S_ISCHR(type) )
return read(ctx, buf, count);
if ( !(dflags & O_READ) )
return errno = EBADF, -1;
if ( !IsSeekable() )
return errno = ESPIPE, -1;
if ( off < 0 )
return errno = EINVAL, -1;
if ( SSIZE_MAX < count )
count = SSIZE_MAX;
off_t available = OFF_MAX - off;
if ( (uintmax_t) available < (uintmax_t) count )
count = available;
int old_ctx_dflags = ctx->dflags;
ctx->dflags = ContextFlags(old_ctx_dflags, dflags);
ssize_t result = vnode->pread(ctx, buf, count, off);
ctx->dflags = old_ctx_dflags;
return result;
}
ssize_t Descriptor::preadv(ioctx_t* ctx, const struct iovec* iov_ptr,
int iovcnt, off_t off)
{
if ( S_ISCHR(type) )
return readv(ctx, iov_ptr, iovcnt);
if ( !(dflags & O_READ) )
return errno = EBADF, -1;
if ( !IsSeekable() )
return errno = ESPIPE, -1;
if ( off < 0 || iovcnt < 0 || IOV_MAX < iovcnt )
return errno = EINVAL, -1;
struct iovec* iov = new struct iovec[iovcnt];
if ( !iov )
return -1;
size_t iov_size = sizeof(struct iovec) * iovcnt;
if ( !ctx->copy_from_src(iov, iov_ptr, iov_size) )
return delete[] iov, -1;
off_t available = OFF_MAX - off;
if ( SSIZE_MAX < TruncateIOVec(iov, iovcnt, available) )
return delete[] iov, errno = EINVAL, -1;
int old_ctx_dflags = ctx->dflags;
ctx->dflags = ContextFlags(old_ctx_dflags, dflags);
ssize_t result = vnode->preadv(ctx, iov, iovcnt, off);
ctx->dflags = old_ctx_dflags;
delete[] iov;
return result;
}
ssize_t Descriptor::write(ioctx_t* ctx, const uint8_t* buf, size_t count)
{
if ( !(dflags & O_WRITE) )
return errno = EBADF, -1;
if ( SSIZE_MAX < count )
count = SSIZE_MAX;
int old_ctx_dflags = ctx->dflags;
ctx->dflags = ContextFlags(old_ctx_dflags, dflags);
ssize_t result;
if ( !IsSeekable() )
result = vnode->write(ctx, buf, count);
else
{
ScopedLock lock(&current_offset_lock);
if ( ctx->dflags & O_APPEND )
{
off_t end = vnode->lseek(ctx, 0, SEEK_END);
if ( 0 <= end )
current_offset = end;
}
if ( current_offset == OFF_MAX && count )
{
errno = EFBIG;
result = -1;
}
else
{
off_t available = OFF_MAX - current_offset;
if ( (uintmax_t) available < (uintmax_t) count )
count = available;
result = vnode->pwrite(ctx, buf, count, current_offset);
if ( 0 < result &&
__builtin_add_overflow(current_offset, result,
&current_offset) )
current_offset = OFF_MAX;
}
}
ctx->dflags = old_ctx_dflags;
return result;
}
ssize_t Descriptor::writev(ioctx_t* ctx, const struct iovec* iov_ptr,
int iovcnt)
{
if ( !(dflags & O_WRITE) )
return errno = EBADF, -1;
if ( iovcnt < 0 || IOV_MAX < iovcnt )
return errno = EINVAL, -1;
struct iovec* iov = new struct iovec[iovcnt];
if ( !iov )
return -1;
size_t iov_size = sizeof(struct iovec) * iovcnt;
if ( !ctx->copy_from_src(iov, iov_ptr, iov_size) )
return delete[] iov, -1;
int old_ctx_dflags = ctx->dflags;
ctx->dflags = ContextFlags(old_ctx_dflags, dflags);
ssize_t result = -1;
if ( !IsSeekable() )
{
if ( SSIZE_MAX < TruncateIOVec(iov, iovcnt, SSIZE_MAX) )
errno = EINVAL;
else
result = vnode->writev(ctx, iov, iovcnt);
}
else
{
ScopedLock lock(&current_offset_lock);
if ( ctx->dflags & O_APPEND )
{
off_t end = vnode->lseek(ctx, 0, SEEK_END);
if ( 0 <= end )
current_offset = end;
}
off_t available = OFF_MAX - current_offset;
size_t count = TruncateIOVec(iov, iovcnt, available);
if ( SSIZE_MAX < count )
errno = EINVAL;
else if ( current_offset == OFF_MAX && count )
errno = EFBIG;
else
{
result = vnode->pwritev(ctx, iov, iovcnt, current_offset);
if ( 0 < result &&
__builtin_add_overflow(current_offset, result,
&current_offset) )
current_offset = OFF_MAX;
}
}
ctx->dflags = old_ctx_dflags;
delete[] iov;
return result;
}
ssize_t Descriptor::pwrite(ioctx_t* ctx, const uint8_t* buf, size_t count,
off_t off)
{
if ( S_ISCHR(type) )
return write(ctx, buf, count);
if ( !(dflags & O_WRITE) )
return errno = EBADF, -1;
if ( !IsSeekable() )
return errno = ESPIPE, -1;
if ( off < 0 )
return errno = EINVAL, -1;
if ( off == OFF_MAX && count )
return errno = EFBIG, -1;
if ( SSIZE_MAX < count )
count = SSIZE_MAX;
off_t available = OFF_MAX - off;
if ( (uintmax_t) available < (uintmax_t) count )
count = available;
int old_ctx_dflags = ctx->dflags;
ctx->dflags = ContextFlags(old_ctx_dflags, dflags);
ssize_t result = vnode->pwrite(ctx, buf, count, off);
ctx->dflags = old_ctx_dflags;
return result;
}
ssize_t Descriptor::pwritev(ioctx_t* ctx, const struct iovec* iov_ptr,
int iovcnt, off_t off)
{
if ( S_ISCHR(type) )
return writev(ctx, iov_ptr, iovcnt);
if ( !(dflags & O_WRITE) )
return errno = EBADF, -1;
if ( !IsSeekable() )
return errno = ESPIPE, -1;
if ( off < 0 || iovcnt < 0 || IOV_MAX < iovcnt )
return errno = EINVAL, -1;
struct iovec* iov = new struct iovec[iovcnt];
if ( !iov )
return -1;
size_t iov_size = sizeof(struct iovec) * iovcnt;
if ( !ctx->copy_from_src(iov, iov_ptr, iov_size) )
return delete[] iov, -1;
off_t available = OFF_MAX - off;
size_t count = TruncateIOVec(iov, iovcnt, available);
if ( SSIZE_MAX < count )
return delete[] iov, errno = EINVAL, -1;
if ( off == OFF_MAX && count != 0 )
return delete[] iov, errno = EFBIG, -1;
int old_ctx_dflags = ctx->dflags;
ctx->dflags = ContextFlags(old_ctx_dflags, dflags);
ssize_t result = vnode->pwritev(ctx, iov, iovcnt, off);
ctx->dflags = old_ctx_dflags;
delete[] iov;
return result;
}
static inline bool valid_utimens_timespec(struct timespec ts)
{
return ts.tv_nsec < 1000000000 ||
ts.tv_nsec == UTIME_NOW ||
ts.tv_nsec == UTIME_OMIT;
}
int Descriptor::utimens(ioctx_t* ctx, const struct timespec* user_times)
{
struct timespec times[2];
if ( !user_times )
{
times[0].tv_sec = 0;
times[0].tv_nsec = UTIME_NOW;
times[1].tv_sec = 0;
times[1].tv_nsec = UTIME_NOW;
}
else if ( !ctx->copy_from_src(&times, user_times, sizeof(times)) )
return -1;
if ( !valid_utimens_timespec(times[0]) ||
!valid_utimens_timespec(times[1]) )
return errno = EINVAL;
// TODO: Regardless of dflags, check if the user/group can utimens.
return vnode->utimens(ctx, times);
}
int Descriptor::isatty(ioctx_t* ctx)
{
return vnode->isatty(ctx);
}
ssize_t Descriptor::readdirents(ioctx_t* ctx,
struct dirent* dirent,
size_t size)
{
// TODO: COMPATIBILITY HACK: Traditionally, you can open a directory with
// O_RDONLY and pass it to fdopendir and then use it, which doesn't
// set the needed O_SEARCH flag! I think some software even do it with
// write permissions! Currently, we just let you search the directory
// if you opened with any of the O_SEARCH, O_READ or O_WRITE flags.
// A better solution would be to make fdopendir try to add the
// O_SEARCH flag to the file descriptor. Or perhaps just recheck the
// permissions to search (execute) the directory manually every time,
// though that is less pure. Unfortunately, POSIX is pretty vague on
// how O_SEARCH should be interpreted and most existing Unix systems
// such as Linux doesn't even have that flag! And how about combining
// it with the O_EXEC flag - POSIX allows that and it makes sense
// because the execute bit on directories control search permission.
if ( !(dflags & (O_SEARCH | O_READ | O_WRITE)) )
return errno = EBADF, -1;
if ( SSIZE_MAX < size )
size = SSIZE_MAX;
if ( size < sizeof(*dirent) )
return errno = EINVAL, -1;
ScopedLock lock(&current_offset_lock);
if ( current_offset == OFF_MAX && size )
return 0;
ssize_t ret = vnode->readdirents(ctx, dirent, size, current_offset);
if ( 0 < ret &&
__builtin_add_overflow(current_offset, 1, &current_offset) )
current_offset = OFF_MAX;
return ret;
}
static bool IsSaneFlagModeCombination(int flags, mode_t /*mode*/)
{
// It doesn't make sense to pass O_CREATE or O_TRUNC when attempting to open
// a directory. We also reject O_TRUNC | O_DIRECTORY early to prevent
// opening a directory, attempting to truncate it, and then aborting with an
// error because a directory was opened.
if ( (flags & (O_CREATE | O_TRUNC)) && (flags & (O_DIRECTORY)) )
return false;
// POSIX: "The result of using O_TRUNC without either O_RDWR or
// O_WRONLY is undefined."
if ( (flags & O_TRUNC) && !(flags & O_WRITE) )
return false;
return true;
}
static bool IsLastPathElement(const char* elem)
{
while ( !(*elem == '/' || *elem == '\0') )
elem++;
while ( *elem == '/' )
elem++;
return *elem == '\0';
}
Ref<Descriptor> Descriptor::open(ioctx_t* ctx, const char* filename, int flags,
mode_t mode)
{
Process* process = CurrentProcess();
kthread_mutex_lock(&process->idlock);
mode &= ~process->umask;
kthread_mutex_unlock(&process->idlock);
if ( !filename[0] )
return errno = ENOENT, Ref<Descriptor>();
// Reject some non-sensical flag combinations early.
if ( !IsSaneFlagModeCombination(flags, mode) )
return errno = EINVAL, Ref<Descriptor>();
char* filename_mine = NULL;
size_t symlink_iteration = 0;
const size_t MAX_SYMLINK_ITERATION = 20;
Ref<Descriptor> desc(this);
while ( filename[0] )
{
// Reaching a slash in the path means that the caller intended what came
// before to be a directory, stop the open call if it isn't.
if ( filename[0] == '/' )
{
if ( !S_ISDIR(desc->type) )
return delete[] filename_mine, errno = ENOTDIR, Ref<Descriptor>();
filename++;
continue;
}
// Cut out the next path element from the input string.
size_t slashpos = strcspn(filename, "/");
char* elem = String::Substring(filename, 0, slashpos);
if ( !elem )
return delete[] filename_mine, Ref<Descriptor>();
// Decide how to open the next element in the path.
bool lastelem = IsLastPathElement(filename);
int open_flags = lastelem ? flags : O_READ | O_DIRECTORY;
mode_t open_mode = lastelem ? mode : 0;
// Open the next element in the path.
Ref<Descriptor> next = desc->open_elem(ctx, elem, open_flags, open_mode);
delete[] elem;
if ( !next )
return delete[] filename_mine, Ref<Descriptor>();
filename += slashpos;
bool want_the_symlink_itself = lastelem && (flags & O_SYMLINK_NOFOLLOW);
if ( S_ISLNK(next->type) && !want_the_symlink_itself )
{
if ( (flags & O_NOFOLLOW) && lastelem )
return delete[] filename_mine, errno = ELOOP, Ref<Descriptor>();
if ( symlink_iteration++ == MAX_SYMLINK_ITERATION )
return delete[] filename_mine, errno = ELOOP, Ref<Descriptor>();
ioctx_t kctx;
SetupKernelIOCtx(&kctx);
struct stat st;
if ( next->stat(&kctx, &st) < 0 )
return delete[] filename_mine, Ref<Descriptor>();
assert(0 <= st.st_size);
if ( (uintmax_t) SIZE_MAX <= (uintmax_t) st.st_size )
return delete[] filename_mine, Ref<Descriptor>();
size_t linkpath_length = (size_t) st.st_size;
char* linkpath = new char[linkpath_length + 1];
if ( !linkpath )
return delete[] filename_mine, Ref<Descriptor>();
ssize_t linkpath_ret = next->readlink(&kctx, linkpath, linkpath_length);
if ( linkpath_ret < 0 )
return delete[] linkpath, delete[] filename_mine, Ref<Descriptor>();
linkpath[linkpath_length] = '\0';
linkpath_length = strlen(linkpath);
if ( linkpath_length == 0 )
return delete[] linkpath, delete[] filename_mine,
errno = ENOENT, Ref<Descriptor>();
bool link_from_root = linkpath[0] == '/';
// Either filename is the empty string or starts with a slash.
size_t filename_length = strlen(filename);
// TODO: Avoid overflow here.
size_t new_filename_length = linkpath_length + filename_length;
char* new_filename = new char[new_filename_length + 1];
if ( !new_filename )
return delete[] linkpath, delete[] filename_mine,
errno = ENOENT, Ref<Descriptor>();
stpcpy(stpcpy(new_filename, linkpath), filename);
delete[] filename_mine;
filename = filename_mine = new_filename;
if ( link_from_root )
desc = CurrentProcess()->GetRoot();
continue;
}
desc = next;
}
delete[] filename_mine;
// Abort if the user tries to write to an existing directory.
if ( S_ISDIR(desc->type) )
{
if ( flags & (O_CREATE | O_TRUNC | O_WRITE) )
return errno = EISDIR, Ref<Descriptor>();
}
// Truncate the file if requested.
if ( (flags & O_TRUNC) && S_ISREG(desc->type) )
{
assert(flags & O_WRITE); // IsSaneFlagModeCombination
if ( desc->truncate(ctx, 0) < 0 )
return Ref<Descriptor>();
}
// Abort the open if the user wanted a directory but this wasn't.
if ( flags & O_DIRECTORY && !S_ISDIR(desc->type) )
return errno = ENOTDIR, Ref<Descriptor>();
// TODO: The new file descriptor may not be opened with the correct
// permissions in the below case!
// If the path only contains slashes, we'll get outselves back, be sure to
// get ourselves back.
return desc == this ? Fork() : desc;
}
Ref<Descriptor> Descriptor::open_elem(ioctx_t* ctx, const char* filename,
int flags, mode_t mode)
{
assert(!strchr(filename, '/'));
// Verify that at least one of the base access modes are being used.
if ( !(flags & ACCESS_FLAGS) )
return errno = EINVAL, Ref<Descriptor>();
// Filter away flags that only make sense for descriptors.
int retvnode_flags = flags & ~DESCRIPTOR_FLAGS;
Ref<Vnode> retvnode = vnode->open(ctx, filename, retvnode_flags, mode);
if ( !retvnode )
return Ref<Descriptor>();
// Filter away flags that only made sense at during the open call.
int ret_flags = flags & ~OPEN_FLAGS;
Ref<Descriptor> ret(new Descriptor(retvnode, ret_flags));
if ( !ret )
return Ref<Descriptor>();
return ret;
}
int Descriptor::mkdir(ioctx_t* ctx, const char* filename, mode_t mode)
{
Process* process = CurrentProcess();
kthread_mutex_lock(&process->idlock);
mode &= ~process->umask;
kthread_mutex_unlock(&process->idlock);
char* final;
Ref<Descriptor> dir = OpenDirContainingPath(ctx, Ref<Descriptor>(this),
filename, &final);
if ( !dir )
return -1;
int ret = dir->vnode->mkdir(ctx, final, mode);
delete[] final;
return ret;
}
int Descriptor::link(ioctx_t* ctx, const char* filename, Ref<Descriptor> node)
{
char* final;
Ref<Descriptor> dir = OpenDirContainingPath(ctx, Ref<Descriptor>(this),
filename, &final);
if ( !dir )
return -1;
int ret = dir->vnode->link(ctx, final, node->vnode);
delete[] final;
return ret;
}
int Descriptor::unlinkat(ioctx_t* ctx, const char* filename, int flags)
{
if ( flags & ~(AT_REMOVEFILE | AT_REMOVEDIR) )
return errno = EINVAL, -1;
if ( !(flags & (AT_REMOVEFILE | AT_REMOVEDIR)) )
flags |= AT_REMOVEFILE;
char* final;
Ref<Descriptor> dir = OpenDirContainingPath(ctx, Ref<Descriptor>(this),
filename, &final);
if ( !dir )
return -1;
int ret = -1;
if ( ret < 0 && (flags & AT_REMOVEFILE) )
ret = dir->vnode->unlink(ctx, final);
if ( ret < 0 && (flags & AT_REMOVEDIR) )
ret = dir->vnode->rmdir(ctx, final);
delete[] final;
return ret;
}
int Descriptor::symlink(ioctx_t* ctx, const char* oldname, const char* filename)
{
char* final;
Ref<Descriptor> dir = OpenDirContainingPath(ctx, Ref<Descriptor>(this),
filename, &final);
if ( !dir )
return -1;
int ret = dir->vnode->symlink(ctx, oldname, final);
delete[] final;
return ret;
}
int Descriptor::rename_here(ioctx_t* ctx, Ref<Descriptor> from,
const char* oldpath, const char* newpath)
{
char* olddir_elem;
char* newdir_elem;
Ref<Descriptor> olddir = OpenDirContainingPath(ctx, from, oldpath,
&olddir_elem);
if ( !olddir )
return -1;
Ref<Descriptor> newdir = OpenDirContainingPath(ctx, Ref<Descriptor>(this),
newpath, &newdir_elem);
if ( !newdir )
return delete[] olddir_elem, -1;
int ret = newdir->vnode->rename_here(ctx, olddir->vnode, olddir_elem,
newdir_elem);
delete[] newdir_elem;
delete[] olddir_elem;
return ret;
}
ssize_t Descriptor::readlink(ioctx_t* ctx, char* buf, size_t bufsize)
{
if ( !(dflags & O_READ) )
return errno = EBADF, -1;
if ( SSIZE_MAX < bufsize )
bufsize = SSIZE_MAX;
return vnode->readlink(ctx, buf, bufsize);
}
int Descriptor::tcgetwincurpos(ioctx_t* ctx, struct wincurpos* wcp)
{
return vnode->tcgetwincurpos(ctx, wcp);
}
int Descriptor::ioctl(ioctx_t* ctx, int cmd, uintptr_t arg)
{
int old_ctx_dflags = ctx->dflags;
ctx->dflags = ContextFlags(old_ctx_dflags, dflags);
int result = vnode->ioctl(ctx, cmd, arg);
ctx->dflags = old_ctx_dflags;
return result;
}
int Descriptor::tcsetpgrp(ioctx_t* ctx, pid_t pgid)
{
return vnode->tcsetpgrp(ctx, pgid);
}
pid_t Descriptor::tcgetpgrp(ioctx_t* ctx)
{
return vnode->tcgetpgrp(ctx);
}
int Descriptor::settermmode(ioctx_t* ctx, unsigned mode)
{
return vnode->settermmode(ctx, mode);
}
int Descriptor::gettermmode(ioctx_t* ctx, unsigned* mode)
{
return vnode->gettermmode(ctx, mode);
}
int Descriptor::poll(ioctx_t* ctx, PollNode* node)
{
// TODO: Perhaps deny polling against some kind of events if this
// descriptor's dflags would reject doing these operations?
return vnode->poll(ctx, node);
}
Ref<Descriptor> Descriptor::accept4(ioctx_t* ctx, uint8_t* addr,
size_t* addrlen, int flags)
{
int old_ctx_dflags = ctx->dflags;
ctx->dflags = ContextFlags(old_ctx_dflags, dflags);
Ref<Vnode> retvnode = vnode->accept4(ctx, addr, addrlen, flags);
if ( !retvnode )
return Ref<Descriptor>();
ctx->dflags = old_ctx_dflags;
return Ref<Descriptor>(new Descriptor(retvnode, O_READ | O_WRITE));
}
int Descriptor::bind(ioctx_t* ctx, const uint8_t* addr, size_t addrlen)
{
return vnode->bind(ctx, addr, addrlen);
}
int Descriptor::connect(ioctx_t* ctx, const uint8_t* addr, size_t addrlen)
{
return vnode->connect(ctx, addr, addrlen);
}
int Descriptor::listen(ioctx_t* ctx, int backlog)
{
return vnode->listen(ctx, backlog);
}
ssize_t Descriptor::recv(ioctx_t* ctx, uint8_t* buf, size_t count, int flags)
{
if ( SIZE_MAX < count )
count = SSIZE_MAX;
int old_ctx_dflags = ctx->dflags;
ctx->dflags = ContextFlags(old_ctx_dflags, dflags);
if ( flags & MSG_DONTWAIT )
ctx->dflags |= O_NONBLOCK;
flags &= ~MSG_DONTWAIT;
ssize_t result = vnode->recv(ctx, buf, count, flags);
ctx->dflags = old_ctx_dflags;
return result;
}
ssize_t Descriptor::recvmsg(ioctx_t* ctx, struct msghdr* msg, int flags)
{
int old_ctx_dflags = ctx->dflags;
ctx->dflags = ContextFlags(old_ctx_dflags, dflags);
if ( flags & MSG_DONTWAIT )
ctx->dflags |= O_NONBLOCK;
flags &= ~MSG_DONTWAIT;
ssize_t result = vnode->recvmsg(ctx, msg, flags);
ctx->dflags = old_ctx_dflags;
return result;
}
ssize_t Descriptor::send(ioctx_t* ctx, const uint8_t* buf, size_t count, int flags)
{
if ( SIZE_MAX < count )
count = SSIZE_MAX;
int old_ctx_dflags = ctx->dflags;
ctx->dflags = ContextFlags(old_ctx_dflags, dflags);
if ( flags & MSG_DONTWAIT )
ctx->dflags |= O_NONBLOCK;
flags &= ~MSG_DONTWAIT;
ssize_t result = vnode->send(ctx, buf, count, flags);
ctx->dflags = old_ctx_dflags;
return result;
}
ssize_t Descriptor::sendmsg(ioctx_t* ctx, const struct msghdr* msg, int flags)
{
int old_ctx_dflags = ctx->dflags;
ctx->dflags = ContextFlags(old_ctx_dflags, dflags);
if ( flags & MSG_DONTWAIT )
ctx->dflags |= O_NONBLOCK;
flags &= ~MSG_DONTWAIT;
ssize_t result = vnode->sendmsg(ctx, msg, flags);
ctx->dflags = old_ctx_dflags;
return result;
}
int Descriptor::getsockopt(ioctx_t* ctx, int level, int option_name,
void* option_value, size_t* option_size_ptr)
{
return vnode->getsockopt(ctx, level, option_name, option_value, option_size_ptr);
}
int Descriptor::setsockopt(ioctx_t* ctx, int level, int option_name,
const void* option_value, size_t option_size)
{
return vnode->setsockopt(ctx, level, option_name, option_value, option_size);
}
ssize_t Descriptor::tcgetblob(ioctx_t* ctx, const char* name, void* buffer, size_t count)
{
if ( name && !name[0] )
name = NULL;
if ( SSIZE_MAX < count )
count = SSIZE_MAX;
return vnode->tcgetblob(ctx, name, buffer, count);
}
ssize_t Descriptor::tcsetblob(ioctx_t* ctx, const char* name, const void* buffer, size_t count)
{
if ( name && !name[0] )
name = NULL;
if ( SSIZE_MAX < count )
return errno = EFBIG, -1;
return vnode->tcsetblob(ctx, name, buffer, count);
}
int Descriptor::unmount(ioctx_t* ctx, const char* filename, int flags)
{
if ( flags & ~(UNMOUNT_FORCE | UNMOUNT_DETACH | UNMOUNT_NOFOLLOW) )
return errno = EINVAL, -1;
int subflags = flags & ~(UNMOUNT_NOFOLLOW);
char* final;
// TODO: This may follow a symlink when not supposed to!
Ref<Descriptor> dir =
OpenDirContainingPath(ctx, Ref<Descriptor>(this), filename, &final);
if ( !dir )
return -1;
if ( !(flags & UNMOUNT_NOFOLLOW) )
{
// TODO: Potentially follow a symlink here!
}
int ret = dir->vnode->unmount(ctx, final, subflags);
delete[] final;
return ret;
}
int Descriptor::fsm_fsbind(ioctx_t* ctx, Ref<Descriptor> target, int flags)
{
return vnode->fsm_fsbind(ctx, target->vnode, flags);
}
Ref<Descriptor> Descriptor::fsm_mount(ioctx_t* ctx,
const char* filename,
const struct stat* rootst,
int flags)
{
if ( flags & ~(FSM_MOUNT_NOFOLLOW | FSM_MOUNT_NONBLOCK) )
return errno = EINVAL, Ref<Descriptor>(NULL);
int result_dflags = O_READ | O_WRITE;
if ( flags & FSM_MOUNT_NOFOLLOW ) result_dflags |= O_NONBLOCK;
int subflags = flags & ~(FSM_MOUNT_NOFOLLOW | FSM_MOUNT_NONBLOCK);
char* final;
// TODO: This may follow a symlink when not supposed to!
Ref<Descriptor> dir =
OpenDirContainingPath(ctx, Ref<Descriptor>(this), filename, &final);
if ( !dir )
return errno = EINVAL, Ref<Descriptor>(NULL);
if ( !(flags & FSM_MOUNT_NOFOLLOW) )
{
// TODO: Potentially follow a symlink here!
}
Ref<Descriptor> result(new Descriptor());
if ( !result )
return Ref<Descriptor>(NULL);
Ref<Vnode> result_vnode = dir->vnode->fsm_mount(ctx, final, rootst, subflags);
delete[] final;
if ( !result_vnode )
return Ref<Descriptor>(NULL);
result->LateConstruct(result_vnode, result_dflags);
return result;
}
int Descriptor::tcdrain(ioctx_t* ctx)
{
return vnode->tcdrain(ctx);
}
int Descriptor::tcflow(ioctx_t* ctx, int action)
{
return vnode->tcflow(ctx, action);
}
int Descriptor::tcflush(ioctx_t* ctx, int queue_selector)
{
return vnode->tcflush(ctx, queue_selector);
}
int Descriptor::tcgetattr(ioctx_t* ctx, struct termios* tio)
{
return vnode->tcgetattr(ctx, tio);
}
pid_t Descriptor::tcgetsid(ioctx_t* ctx)
{
return vnode->tcgetsid(ctx);
}
int Descriptor::tcsendbreak(ioctx_t* ctx, int duration)
{
return vnode->tcsendbreak(ctx, duration);
}
int Descriptor::tcsetattr(ioctx_t* ctx, int actions, const struct termios* tio)
{
return vnode->tcsetattr(ctx, actions, tio);
}
int Descriptor::shutdown(ioctx_t* ctx, int how)
{
if ( how & ~(SHUT_RD | SHUT_WR) )
return errno = EINVAL, -1;
return vnode->shutdown(ctx, how);
}
int Descriptor::getpeername(ioctx_t* ctx, uint8_t* addr, size_t* addrsize)
{
return vnode->getpeername(ctx, addr, addrsize);
}
int Descriptor::getsockname(ioctx_t* ctx, uint8_t* addr, size_t* addrsize)
{
return vnode->getsockname(ctx, addr, addrsize);
}
} // namespace Sortix