blob: bc1d2e261d12b5289c7e196b09d4cbf399a38db0 [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 <inttypes.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include <fbl/atomic.h>
#include <fbl/ref_ptr.h>
#include <fbl/unique_ptr.h>
#include <lib/fdio/vfs.h>
#include <fs/vfs.h>
#include <lib/memfs/cpp/vnode.h>
#include <zircon/device/vfs.h>
#include "dnode.h"
namespace memfs {
// Artificially cap the maximum in-memory file size to 512MB.
constexpr size_t kMemfsMaxFileSize = 512 * 1024 * 1024;
VnodeFile::VnodeFile(Vfs* vfs)
: VnodeMemfs(vfs), vmo_(ZX_HANDLE_INVALID), length_(0) {}
VnodeFile::VnodeFile(Vfs* vfs, zx_handle_t vmo, zx_off_t length)
: VnodeMemfs(vfs), vmo_(vmo), length_(length) {}
VnodeFile::~VnodeFile() {
if (vmo_ != ZX_HANDLE_INVALID) {
zx_handle_close(vmo_);
}
}
zx_status_t VnodeFile::ValidateFlags(uint32_t flags) {
if (flags & ZX_FS_FLAG_DIRECTORY) {
return ZX_ERR_NOT_DIR;
}
return ZX_OK;
}
zx_status_t VnodeFile::Read(void* data, size_t len, size_t off, size_t* out_actual) {
if ((off >= length_) || (vmo_ == ZX_HANDLE_INVALID)) {
*out_actual = 0;
return ZX_OK;
} else if (len > length_ - off) {
len = length_ - off;
}
zx_status_t status = zx_vmo_read(vmo_, data, off, len);
if (status == ZX_OK) {
*out_actual = len;
}
return status;
}
zx_status_t VnodeFile::Write(const void* data, size_t len, size_t offset,
size_t* out_actual) {
zx_status_t status;
size_t newlen = offset + len;
newlen = newlen > kMemfsMaxFileSize ? kMemfsMaxFileSize : newlen;
size_t alignedlen = fbl::round_up(newlen, static_cast<size_t>(PAGE_SIZE));
if (vmo_ == ZX_HANDLE_INVALID) {
// First access to the file? Allocate it.
if ((status = zx_vmo_create(alignedlen, 0, &vmo_)) != ZX_OK) {
return status;
}
} else if (alignedlen > fbl::round_up(length_, static_cast<size_t>(PAGE_SIZE))) {
// Accessing beyond the end of the file? Extend it.
if ((status = zx_vmo_set_size(vmo_, alignedlen)) != ZX_OK) {
return status;
}
}
size_t writelen = newlen - offset;
if ((status = zx_vmo_write(vmo_, data, offset, writelen)) != ZX_OK) {
return status;
}
*out_actual = writelen;
if (newlen > length_) {
length_ = newlen;
}
if (writelen < len) {
// short write because we're beyond the end of the permissible length
return ZX_ERR_FILE_BIG;
}
UpdateModified();
return ZX_OK;
}
zx_status_t VnodeFile::Append(const void* data, size_t len, size_t* out_end,
size_t* out_actual) {
zx_status_t status = Write(data, len, length_, out_actual);
*out_end = length_;
return status;
}
zx_status_t VnodeFile::GetVmo(int flags, zx_handle_t* out) {
zx_status_t status;
if (vmo_ == ZX_HANDLE_INVALID) {
// First access to the file? Allocate it.
if ((status = zx_vmo_create(0, 0, &vmo_)) != ZX_OK) {
return status;
}
}
// Let clients map and set the names of their VMOs.
zx_rights_t rights = ZX_RIGHTS_BASIC | ZX_RIGHT_MAP | ZX_RIGHTS_PROPERTY;
rights |= (flags & FDIO_MMAP_FLAG_READ) ? ZX_RIGHT_READ : 0;
rights |= (flags & FDIO_MMAP_FLAG_WRITE) ? ZX_RIGHT_WRITE : 0;
rights |= (flags & FDIO_MMAP_FLAG_EXEC) ? ZX_RIGHT_EXECUTE : 0;
if (flags & FDIO_MMAP_FLAG_PRIVATE) {
if ((status = zx_vmo_clone(vmo_, ZX_VMO_CLONE_COPY_ON_WRITE, 0, length_, out)) != ZX_OK) {
return status;
}
return zx_handle_replace(*out, rights, out);
}
return zx_handle_duplicate(vmo_, rights, out);
}
zx_status_t VnodeFile::Getattr(vnattr_t* attr) {
memset(attr, 0, sizeof(vnattr_t));
attr->inode = ino_;
attr->mode = V_TYPE_FILE | V_IRUSR | V_IWUSR | V_IRGRP | V_IROTH;
attr->size = length_;
attr->blksize = kMemfsBlksize;
attr->blkcount = fbl::round_up(attr->size, kMemfsBlksize) / VNATTR_BLKSIZE;
attr->nlink = link_count_;
attr->create_time = create_time_;
attr->modify_time = modify_time_;
return ZX_OK;
}
zx_status_t VnodeFile::Truncate(size_t len) {
zx_status_t status;
if (len > kMemfsMaxFileSize) {
return ZX_ERR_INVALID_ARGS;
}
size_t alignedLen = fbl::round_up(len, static_cast<size_t>(PAGE_SIZE));
if (vmo_ == ZX_HANDLE_INVALID) {
// First access to the file? Allocate it.
if ((status = zx_vmo_create(alignedLen, 0, &vmo_)) != ZX_OK) {
return status;
}
} else if ((len < length_) && (len % PAGE_SIZE != 0)) {
// Currently, if the file is truncated to a 'partial page', an later re-expanded, then the
// partial page is *not necessarily* filled with zeroes. As a consequence, we manually must
// fill the portion between "len" and the next highest page (or vn->length, whichever
// is smaller) with zeroes.
char buf[PAGE_SIZE];
size_t ppage_size = PAGE_SIZE - (len % PAGE_SIZE);
ppage_size = len + ppage_size < length_ ? ppage_size : length_ - len;
memset(buf, 0, ppage_size);
if (zx_vmo_write(vmo_, buf, len, ppage_size) != ZX_OK) {
return ZX_ERR_IO;
}
if ((status = zx_vmo_set_size(vmo_, alignedLen)) != ZX_OK) {
return status;
}
} else if ((status = zx_vmo_set_size(vmo_, alignedLen)) != ZX_OK) {
return status;
}
length_ = len;
UpdateModified();
return ZX_OK;
}
} // namespace memfs