blob: c3fb86f973b7d6349ff156c395a588de693fb64c [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "nacl_io/kernel_handle.h"
#include <errno.h>
#include <pthread.h>
#include "nacl_io/filesystem.h"
#include "nacl_io/node.h"
#include "nacl_io/osunistd.h"
#include "nacl_io/socket/socket_node.h"
#include "sdk_util/auto_lock.h"
namespace nacl_io {
// It is only legal to construct a handle while the kernel lock is held.
KernelHandle::KernelHandle() : filesystem_(NULL), node_(NULL) {
}
KernelHandle::KernelHandle(const ScopedFilesystem& fs, const ScopedNode& node)
: filesystem_(fs), node_(node) {
}
KernelHandle::~KernelHandle() {
// Force release order for cases where filesystem_ is not ref'd by mounting.
node_.reset(NULL);
filesystem_.reset(NULL);
}
// Returns the SocketNode* if this node is a socket.
SocketNode* KernelHandle::socket_node() {
if (node_.get() && node_->IsaSock())
return reinterpret_cast<SocketNode*>(node_.get());
return NULL;
}
Error KernelHandle::Init(int open_flags) {
handle_attr_.flags = open_flags;
if ((open_flags & O_CREAT) == 0 && !node_->CanOpen(open_flags)) {
return EACCES;
}
// Directories can only be opened read-only.
if ((open_flags & 3) != O_RDONLY && node_->IsaDir()) {
return EISDIR;
}
if (open_flags & O_APPEND) {
Error error = node_->GetSize(&handle_attr_.offs);
if (error)
return error;
}
return 0;
}
Error KernelHandle::Seek(off_t offset, int whence, off_t* out_offset) {
// By default, don't move the offset.
*out_offset = offset;
off_t base;
off_t node_size;
AUTO_LOCK(handle_lock_);
if (!node_->IsSeekable())
return ESPIPE;
Error error = node_->GetSize(&node_size);
if (error)
return error;
switch (whence) {
case SEEK_SET:
base = 0;
break;
case SEEK_CUR:
base = handle_attr_.offs;
break;
case SEEK_END:
base = node_size;
break;
default:
return -1;
}
if (base + offset < 0)
return EINVAL;
off_t new_offset = base + offset;
// Seeking past the end of the file will zero out the space between the old
// end and the new end.
if (new_offset > node_size) {
error = node_->FTruncate(new_offset);
if (error)
return EINVAL;
}
*out_offset = handle_attr_.offs = new_offset;
return 0;
}
Error KernelHandle::Read(void* buf, size_t nbytes, int* cnt) {
sdk_util::AutoLock read_lock(handle_lock_);
if (OpenMode() == O_WRONLY)
return EACCES;
if (!node_->IsSeekable()){
read_lock.Unlock();
AUTO_LOCK(input_lock_);
return node_->Read(handle_attr_, buf, nbytes, cnt);
}
Error error = node_->Read(handle_attr_, buf, nbytes, cnt);
if (0 == error)
handle_attr_.offs += *cnt;
return error;
}
Error KernelHandle::Write(const void* buf, size_t nbytes, int* cnt) {
sdk_util::AutoLock write_lock(handle_lock_);
if (OpenMode() == O_RDONLY)
return EACCES;
if (!node_->IsSeekable()){
write_lock.Unlock();
AUTO_LOCK(output_lock_);
return node_->Write(handle_attr_, buf, nbytes, cnt);
}
Error error = node_->Write(handle_attr_, buf, nbytes, cnt);
if (0 == error)
handle_attr_.offs += *cnt;
return error;
}
Error KernelHandle::GetDents(struct dirent* pdir, size_t nbytes, int* cnt) {
AUTO_LOCK(handle_lock_);
Error error = node_->GetDents(handle_attr_.offs, pdir, nbytes, cnt);
if (0 == error)
handle_attr_.offs += *cnt;
return error;
}
Error KernelHandle::Fcntl(int request, int* result, ...) {
va_list ap;
va_start(ap, result);
Error rtn = VFcntl(request, result, ap);
va_end(ap);
return rtn;
}
Error KernelHandle::VFcntl(int request, int* result, va_list args) {
switch (request) {
case F_GETFL: {
// Should not block, but could if blocked on Connect or Accept. This is
// acceptable.
AUTO_LOCK(handle_lock_);
*result = handle_attr_.flags;
return 0;
}
case F_SETFL: {
AUTO_LOCK(handle_lock_);
int flags = va_arg(args, int);
if (!(flags & O_APPEND) && (handle_attr_.flags & O_APPEND)) {
// Attempt to clear O_APPEND.
return EPERM;
}
// Only certain flags are mutable
const int mutable_flags = O_ASYNC | O_NONBLOCK;
flags &= mutable_flags;
handle_attr_.flags &= ~mutable_flags;
handle_attr_.flags |= flags;
return 0;
}
default:
LOG_ERROR("Unsupported fcntl: %#x", request);
break;
}
return ENOSYS;
}
Error KernelHandle::Accept(PP_Resource* new_sock,
struct sockaddr* addr,
socklen_t* len) {
SocketNode* sock = socket_node();
if (!sock)
return ENOTSOCK;
AUTO_LOCK(handle_lock_);
return sock->Accept(handle_attr_, new_sock, addr, len);
}
Error KernelHandle::Connect(const struct sockaddr* addr, socklen_t len) {
SocketNode* sock = socket_node();
if (!sock)
return ENOTSOCK;
AUTO_LOCK(handle_lock_);
return sock->Connect(handle_attr_, addr, len);
}
Error KernelHandle::Recv(void* buf, size_t len, int flags, int* out_len) {
SocketNode* sock = socket_node();
if (!sock)
return ENOTSOCK;
if (OpenMode() == O_WRONLY)
return EACCES;
AUTO_LOCK(input_lock_);
return sock->Recv(handle_attr_, buf, len, flags, out_len);
}
Error KernelHandle::RecvFrom(void* buf,
size_t len,
int flags,
struct sockaddr* src_addr,
socklen_t* addrlen,
int* out_len) {
SocketNode* sock = socket_node();
if (!sock)
return ENOTSOCK;
if (OpenMode() == O_WRONLY)
return EACCES;
AUTO_LOCK(input_lock_);
return sock->RecvFrom(handle_attr_, buf, len, flags, src_addr, addrlen,
out_len);
}
Error KernelHandle::Send(const void* buf, size_t len, int flags, int* out_len) {
SocketNode* sock = socket_node();
if (!sock)
return ENOTSOCK;
if (OpenMode() == O_RDONLY)
return EACCES;
AUTO_LOCK(output_lock_);
return sock->Send(handle_attr_, buf, len, flags, out_len);
}
Error KernelHandle::SendTo(const void* buf,
size_t len,
int flags,
const struct sockaddr* dest_addr,
socklen_t addrlen,
int* out_len) {
SocketNode* sock = socket_node();
if (!sock)
return ENOTSOCK;
if (OpenMode() == O_RDONLY)
return EACCES;
AUTO_LOCK(output_lock_);
return sock->SendTo(handle_attr_, buf, len, flags, dest_addr, addrlen,
out_len);
}
} // namespace nacl_io