blob: d1c9409ea4cb22534e41a961d307188afae675c7 [file] [log] [blame]
// Copyright 2019 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 "src/storage/minfs/directory.h"
#include <fcntl.h>
#include <lib/syslog/cpp/macros.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <zircon/device/vfs.h>
#include <zircon/time.h>
#include <memory>
#include <utility>
#include <fbl/algorithm.h>
#include <fbl/auto_call.h>
#include <fbl/string_piece.h>
#include <fs/debug.h>
#include <fs/vfs_types.h>
#ifdef __Fuchsia__
#include <lib/fidl-utils/bind.h>
#include <zircon/syscalls.h>
#include <utility>
#include <fbl/auto_lock.h>
#endif
#include "src/storage/minfs/minfs_private.h"
#include "src/storage/minfs/unowned_vmo_buffer.h"
#include "src/storage/minfs/vnode.h"
namespace minfs {
namespace {
// Possible non-error return values for DirentCallback:
//
// Immediately stop iterating over the directory.
constexpr zx_status_t kDirIteratorDone = 0;
// Access the next direntry in the directory. Offsets updated.
constexpr zx_status_t kDirIteratorNext = 1;
// Identify that the direntry record was modified. Stop iterating.
constexpr zx_status_t kDirIteratorSaveSync = 2;
zx_status_t ValidateDirent(Dirent* de, size_t bytes_read, size_t off) {
uint32_t reclen = static_cast<uint32_t>(MinfsReclen(de, off));
if ((bytes_read < kMinfsDirentSize) || (reclen < kMinfsDirentSize)) {
FX_LOGS(ERROR) << "vn_dir: Could not read dirent at offset: " << off;
return ZX_ERR_IO;
}
if ((off + reclen > kMinfsMaxDirectorySize) || (reclen & kMinfsDirentAlignmentMask)) {
FX_LOGS(ERROR) << "vn_dir: bad reclen " << reclen << " > " << kMinfsMaxDirectorySize;
return ZX_ERR_IO;
}
if (de->ino != 0) {
if ((de->namelen == 0) || (de->namelen > (reclen - kMinfsDirentSize))) {
FX_LOGS(ERROR) << "vn_dir: bad namelen " << de->namelen << " / " << reclen;
return ZX_ERR_IO;
}
}
return ZX_OK;
}
// Updates offset information to move to the next direntry in the directory.
zx_status_t NextDirent(Dirent* de, DirectoryOffset* offs) {
offs->off_prev = offs->off;
offs->off += MinfsReclen(de, offs->off);
return kDirIteratorNext;
}
} // namespace
Directory::Directory(Minfs* fs) : VnodeMinfs(fs) {}
Directory::~Directory() = default;
blk_t Directory::GetBlockCount() const { return GetInode()->block_count; }
uint64_t Directory::GetSize() const { return GetInode()->size; }
void Directory::SetSize(uint32_t new_size) { GetMutableInode()->size = new_size; }
void Directory::AcquireWritableBlock(Transaction* transaction, blk_t local_bno, blk_t old_bno,
blk_t* out_bno) {
bool using_new_block = (old_bno == 0);
if (using_new_block) {
Vfs()->BlockNew(transaction, out_bno);
GetMutableInode()->block_count++;
}
}
void Directory::DeleteBlock(PendingWork* transaction, blk_t local_bno, blk_t old_bno,
bool indirect) {
// If we found a block that was previously allocated, delete it.
if (old_bno != 0) {
transaction->DeallocateBlock(old_bno);
GetMutableInode()->block_count--;
}
}
#ifdef __Fuchsia__
void Directory::IssueWriteback(Transaction* transaction, blk_t vmo_offset, blk_t dev_offset,
blk_t count) {
storage::Operation operation = {
.type = storage::OperationType::kWrite,
.vmo_offset = vmo_offset,
.dev_offset = dev_offset,
.length = count,
};
UnownedVmoBuffer buffer(vmo());
transaction->EnqueueMetadata(operation, &buffer);
}
bool Directory::HasPendingAllocation(blk_t vmo_offset) { return false; }
void Directory::CancelPendingWriteback() {}
#endif
zx_status_t Directory::DirentCallbackFind(fbl::RefPtr<Directory> vndir, Dirent* de, DirArgs* args) {
if ((de->ino != 0) && fbl::StringPiece(de->name, de->namelen) == args->name) {
args->ino = de->ino;
args->type = de->type;
return kDirIteratorDone;
}
return NextDirent(de, &args->offs);
}
zx_status_t Directory::CanUnlink() const {
// directories must be empty (dirent_count == 2)
if (GetInode()->dirent_count != 2) {
// if we have more than "." and "..", not empty, cannot unlink
return ZX_ERR_NOT_EMPTY;
#ifdef __Fuchsia__
} else if (IsRemote()) {
// we cannot unlink mount points
return ZX_ERR_UNAVAILABLE;
#endif
}
return ZX_OK;
}
zx_status_t Directory::UnlinkChild(Transaction* transaction, fbl::RefPtr<VnodeMinfs> childvn,
Dirent* de, DirectoryOffset* offs) {
// Coalesce the current dirent with the previous/next dirent, if they
// (1) exist and (2) are free.
size_t off_prev = offs->off_prev;
size_t off = offs->off;
size_t off_next = off + MinfsReclen(de, off);
zx_status_t status;
// Read the direntries we're considering merging with.
// Verify they are free and small enough to merge.
size_t coalesced_size = MinfsReclen(de, off);
// Coalesce with "next" first, so the kMinfsReclenLast bit can easily flow
// back to "de" and "de_prev".
if (!(de->reclen & kMinfsReclenLast)) {
Dirent de_next;
size_t len = kMinfsDirentSize;
if ((status = ReadExactInternal(transaction, &de_next, len, off_next)) != ZX_OK) {
FX_LOGS(ERROR) << "unlink: Failed to read next dirent";
return status;
}
if ((status = ValidateDirent(&de_next, len, off_next)) != ZX_OK) {
FX_LOGS(ERROR) << "unlink: Read invalid dirent";
return status;
}
if (de_next.ino == 0) {
coalesced_size += MinfsReclen(&de_next, off_next);
// If the next entry *was* last, then 'de' is now last.
de->reclen |= (de_next.reclen & kMinfsReclenLast);
}
}
if (off_prev != off) {
Dirent de_prev;
size_t len = kMinfsDirentSize;
if ((status = ReadExactInternal(transaction, &de_prev, len, off_prev)) != ZX_OK) {
FX_LOGS(ERROR) << "unlink: Failed to read previous dirent";
return status;
}
if ((status = ValidateDirent(&de_prev, len, off_prev)) != ZX_OK) {
FX_LOGS(ERROR) << "unlink: Read invalid dirent";
return status;
}
if (de_prev.ino == 0) {
coalesced_size += MinfsReclen(&de_prev, off_prev);
off = off_prev;
}
}
if (!(de->reclen & kMinfsReclenLast) && (coalesced_size >= kMinfsReclenMask)) {
// Should only be possible if the on-disk record format is corrupted
FX_LOGS(ERROR) << "unlink: Corrupted direntry with impossibly large size";
return ZX_ERR_IO;
}
de->ino = 0;
de->reclen =
static_cast<uint32_t>(coalesced_size & kMinfsReclenMask) | (de->reclen & kMinfsReclenLast);
// Erase dirent (replace with 'empty' dirent)
if ((status = WriteExactInternal(transaction, de, kMinfsDirentSize, off)) != ZX_OK) {
return status;
}
if (de->reclen & kMinfsReclenLast) {
// Truncating the directory merely removed unused space; if it fails,
// the directory contents are still valid.
TruncateInternal(transaction, off + kMinfsDirentSize);
}
GetMutableInode()->dirent_count--;
if (MinfsMagicType(childvn->GetInode()->magic) == kMinfsTypeDir) {
// Child directory had '..' which pointed to parent directory
GetMutableInode()->link_count--;
}
status = childvn->RemoveInodeLink(transaction);
if (status != ZX_OK) {
return status;
}
transaction->PinVnode(fbl::RefPtr(this));
transaction->PinVnode(childvn);
return kDirIteratorSaveSync;
}
// caller is expected to prevent unlink of "." or ".."
zx_status_t Directory::DirentCallbackUnlink(fbl::RefPtr<Directory> vndir, Dirent* de,
DirArgs* args) {
if ((de->ino == 0) || fbl::StringPiece(de->name, de->namelen) != args->name) {
return NextDirent(de, &args->offs);
}
fbl::RefPtr<VnodeMinfs> vn;
zx_status_t status;
if ((status = vndir->Vfs()->VnodeGet(&vn, de->ino)) < 0) {
return status;
}
// If a directory was requested, then only try unlinking a directory
if ((args->type == kMinfsTypeDir) && !vn->IsDirectory()) {
return ZX_ERR_NOT_DIR;
}
if ((status = vn->CanUnlink()) != ZX_OK) {
return status;
}
return vndir->UnlinkChild(args->transaction, std::move(vn), de, &args->offs);
}
// same as unlink, but do not validate vnode
zx_status_t Directory::DirentCallbackForceUnlink(fbl::RefPtr<Directory> vndir, Dirent* de,
DirArgs* args) {
if ((de->ino == 0) || fbl::StringPiece(de->name, de->namelen) != args->name) {
return NextDirent(de, &args->offs);
}
fbl::RefPtr<VnodeMinfs> vn;
zx_status_t status;
if ((status = vndir->Vfs()->VnodeGet(&vn, de->ino)) < 0) {
return status;
}
return vndir->UnlinkChild(args->transaction, std::move(vn), de, &args->offs);
}
// Given a (name, inode, type) combination:
// - If no corresponding 'name' is found, ZX_ERR_NOT_FOUND is returned
// - If the 'name' corresponds to a vnode, check that the target vnode:
// - Does not have the same inode as the argument inode
// - Is the same type as the argument 'type'
// - Is unlinkable
// - If the previous checks pass, then:
// - Remove the old vnode (decrement link count by one)
// - Replace the old vnode's position in the directory with the new inode
zx_status_t Directory::DirentCallbackAttemptRename(fbl::RefPtr<Directory> vndir, Dirent* de,
DirArgs* args) {
if ((de->ino == 0) || fbl::StringPiece(de->name, de->namelen) != args->name) {
return NextDirent(de, &args->offs);
}
fbl::RefPtr<VnodeMinfs> vn;
zx_status_t status;
if ((status = vndir->Vfs()->VnodeGet(&vn, de->ino)) < 0) {
return status;
}
if (args->ino == vn->GetIno()) {
// cannot rename node to itself
return ZX_ERR_BAD_STATE;
}
if (args->type != de->type) {
// cannot rename directory to file (or vice versa)
return args->type == kMinfsTypeDir ? ZX_ERR_NOT_DIR : ZX_ERR_NOT_FILE;
}
if ((status = vn->CanUnlink()) != ZX_OK) {
// if we cannot unlink the target, we cannot rename the target
return status;
}
// If we are renaming ON TOP of a directory, then we can skip
// updating the parent link count -- the old directory had a ".." entry to
// the parent (link count of 1), but the new directory will ALSO have a ".."
// entry, making the rename operation idempotent w.r.t. the parent link
// count.
status = vn->RemoveInodeLink(args->transaction);
if (status != ZX_OK) {
return status;
}
de->ino = args->ino;
status =
vndir->WriteExactInternal(args->transaction, de, DirentSize(de->namelen), args->offs.off);
if (status != ZX_OK) {
return status;
}
args->transaction->PinVnode(vn);
args->transaction->PinVnode(vndir);
return kDirIteratorSaveSync;
}
zx_status_t Directory::DirentCallbackUpdateInode(fbl::RefPtr<Directory> vndir, Dirent* de,
DirArgs* args) {
if ((de->ino == 0) || fbl::StringPiece(de->name, de->namelen) != args->name) {
return NextDirent(de, &args->offs);
}
de->ino = args->ino;
zx_status_t status =
vndir->WriteExactInternal(args->transaction, de, DirentSize(de->namelen), args->offs.off);
if (status != ZX_OK) {
return status;
}
args->transaction->PinVnode(vndir);
return kDirIteratorSaveSync;
}
zx_status_t Directory::DirentCallbackFindSpace(fbl::RefPtr<Directory> vndir, Dirent* de,
DirArgs* args) {
uint32_t reclen = static_cast<uint32_t>(MinfsReclen(de, args->offs.off));
if (de->ino == 0) {
// empty entry, do we fit?
if (args->reclen > reclen) {
return NextDirent(de, &args->offs);
}
return kDirIteratorDone;
}
// filled entry, can we sub-divide?
uint32_t size = static_cast<uint32_t>(DirentSize(de->namelen));
if (size > reclen) {
FX_LOGS(ERROR) << "bad reclen (smaller than dirent) " << reclen << " < " << size;
return ZX_ERR_IO;
}
uint32_t extra = reclen - size;
if (extra < args->reclen) {
return NextDirent(de, &args->offs);
}
return kDirIteratorDone;
}
zx_status_t Directory::AppendDirent(DirArgs* args) {
DirentBuffer dirent_buffer;
Dirent* de = &dirent_buffer.dirent;
size_t r;
zx_status_t status = ReadInternal(args->transaction, de, kMinfsMaxDirentSize, args->offs.off, &r);
if (status != ZX_OK) {
return status;
}
status = ValidateDirent(de, r, args->offs.off);
if (status != ZX_OK) {
return status;
}
uint32_t reclen = static_cast<uint32_t>(MinfsReclen(de, args->offs.off));
if (de->ino == 0) {
// empty entry, do we fit?
if (args->reclen > reclen) {
return ZX_ERR_NO_SPACE;
}
} else {
// filled entry, can we sub-divide?
uint32_t size = static_cast<uint32_t>(DirentSize(de->namelen));
if (size > reclen) {
FX_LOGS(ERROR) << "bad reclen (smaller than dirent) " << reclen << " < " << size;
return ZX_ERR_IO;
}
uint32_t extra = reclen - size;
if (extra < args->reclen) {
return ZX_ERR_NO_SPACE;
}
// shrink existing entry
bool was_last_record = de->reclen & kMinfsReclenLast;
de->reclen = size;
if ((status = WriteExactInternal(args->transaction, de, DirentSize(de->namelen),
args->offs.off)) != ZX_OK) {
return status;
}
args->offs.off += size;
// Overwrite dirent data to reflect the new dirent.
de->reclen = extra | (was_last_record ? kMinfsReclenLast : 0);
}
de->ino = args->ino;
de->type = static_cast<uint8_t>(args->type);
de->namelen = static_cast<uint8_t>(args->name.length());
memcpy(de->name, args->name.data(), de->namelen);
if ((status = WriteExactInternal(args->transaction, de, DirentSize(de->namelen),
args->offs.off)) != ZX_OK) {
return status;
}
if (args->type == kMinfsTypeDir) {
// Child directory has '..' which will point to parent directory
GetMutableInode()->link_count++;
}
GetMutableInode()->dirent_count++;
GetMutableInode()->seq_num++;
InodeSync(args->transaction, kMxFsSyncMtime);
args->transaction->PinVnode(fbl::RefPtr(this));
return ZX_OK;
}
// Calls a callback 'func' on all direntries in a directory 'vn' with the
// provided arguments, reacting to the return code of the callback.
//
// When 'func' is called, it receives a few arguments:
// 'vndir': The directory on which the callback is operating
// 'de': A pointer the start of a single dirent.
// Only DirentSize(de->namelen) bytes are guaranteed to exist in
// memory from this starting pointer.
// 'args': Additional arguments plumbed through ForEachDirent
// 'offs': Offset info about where in the directory this direntry is located.
// Since 'func' may create / remove surrounding dirents, it is responsible for
// updating the offset information to access the next dirent.
zx_status_t Directory::ForEachDirent(DirArgs* args, const DirentCallback func) {
DirentBuffer dirent_buffer;
Dirent* de = &dirent_buffer.dirent;
args->offs.off = 0;
args->offs.off_prev = 0;
while (args->offs.off + kMinfsDirentSize < kMinfsMaxDirectorySize) {
FX_LOGS(DEBUG) << "Reading dirent at offset " << args->offs.off;
size_t r;
zx_status_t status =
ReadInternal(args->transaction, de, kMinfsMaxDirentSize, args->offs.off, &r);
if (status != ZX_OK) {
return status;
}
status = ValidateDirent(de, r, args->offs.off);
if (status != ZX_OK) {
return status;
}
switch ((status = func(fbl::RefPtr<Directory>(this), de, args))) {
case kDirIteratorNext:
break;
case kDirIteratorSaveSync:
GetMutableInode()->seq_num++;
InodeSync(args->transaction, kMxFsSyncMtime);
args->transaction->PinVnode(fbl::RefPtr(this));
return ZX_OK;
case kDirIteratorDone:
return ZX_OK;
default:
// All errors. The callback should not be returning any other non-error (positive) values.
ZX_DEBUG_ASSERT(status < 0);
return status;
}
}
return ZX_ERR_NOT_FOUND;
}
fs::VnodeProtocolSet Directory::GetProtocols() const { return fs::VnodeProtocol::kDirectory; }
zx_status_t Directory::Read(void* data, size_t len, size_t off, size_t* out_actual) {
return ZX_ERR_NOT_FILE;
}
zx_status_t Directory::Write(const void* data, size_t len, size_t offset, size_t* out_actual) {
return ZX_ERR_NOT_FILE;
}
zx_status_t Directory::Append(const void* data, size_t len, size_t* out_end, size_t* out_actual) {
return ZX_ERR_NOT_FILE;
}
zx_status_t Directory::Lookup(fbl::StringPiece name, fbl::RefPtr<fs::Vnode>* out) {
TRACE_DURATION("minfs", "Directory::Lookup", "name", name);
ZX_DEBUG_ASSERT(fs::vfs_valid_name(name));
return LookupInternal(out, name);
}
zx_status_t Directory::LookupInternal(fbl::RefPtr<fs::Vnode>* out, fbl::StringPiece name) {
DirArgs args;
args.name = name;
bool success = false;
fs::Ticker ticker(Vfs()->StartTicker());
auto get_metrics = fbl::MakeAutoCall(
[&ticker, &success, this]() { Vfs()->UpdateLookupMetrics(success, ticker.End()); });
if (zx_status_t status = ForEachDirent(&args, DirentCallbackFind); status != ZX_OK) {
return status;
}
fbl::RefPtr<VnodeMinfs> vn;
if (zx_status_t status = Vfs()->VnodeGet(&vn, args.ino); status != ZX_OK) {
return status;
}
*out = std::move(vn);
return ZX_OK;
}
struct DirCookie {
size_t off; // Offset into directory
uint32_t reserved; // Unused
uint32_t seqno; // inode seq no
};
static_assert(sizeof(DirCookie) <= sizeof(fs::VdirCookie),
"MinFS DirCookie too large to fit in IO state");
zx_status_t Directory::Readdir(fs::VdirCookie* cookie, void* dirents, size_t len,
size_t* out_actual) {
TRACE_DURATION("minfs", "Directory::Readdir");
FX_LOGS(DEBUG) << "minfs_readdir() vn=" << this << "(#" << GetIno() << ") cookie=" << cookie
<< " len=" << len;
if (IsUnlinked()) {
*out_actual = 0;
return ZX_OK;
}
DirCookie* dc = reinterpret_cast<DirCookie*>(cookie);
fs::DirentFiller df(dirents, len);
size_t off = dc->off;
size_t r;
DirentBuffer dirent_buffer;
Dirent* de = &dirent_buffer.dirent;
if (off != 0 && dc->seqno != GetInode()->seq_num) {
// The offset *might* be invalid, if we called Readdir after a directory
// has been modified. In this case, we need to re-read the directory
// until we get to the direntry at or after the previously identified offset.
size_t off_recovered = 0;
while (off_recovered < off) {
if (off_recovered + kMinfsDirentSize >= kMinfsMaxDirectorySize) {
FX_LOGS(ERROR) << "Readdir: Corrupt dirent; dirent reclen too large";
goto fail;
}
zx_status_t status = ReadInternal(nullptr, de, kMinfsMaxDirentSize, off_recovered, &r);
if ((status != ZX_OK) || (ValidateDirent(de, r, off_recovered) != ZX_OK)) {
FX_LOGS(ERROR) << "Readdir: Corrupt dirent unreadable/failed validation";
goto fail;
}
off_recovered += MinfsReclen(de, off_recovered);
}
off = off_recovered;
}
while (off + kMinfsDirentSize < kMinfsMaxDirectorySize) {
zx_status_t status = ReadInternal(nullptr, de, kMinfsMaxDirentSize, off, &r);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Readdir: Unreadable dirent " << status;
goto fail;
} else if ((status = ValidateDirent(de, r, off)) != ZX_OK) {
FX_LOGS(ERROR) << "Readdir: Corrupt dirent failed validation " << status;
goto fail;
}
fbl::StringPiece name(de->name, de->namelen);
if (de->ino && name != "..") {
zx_status_t status;
if ((status = df.Next(name, de->type, de->ino)) != ZX_OK) {
// no more space
goto done;
}
}
off += MinfsReclen(de, off);
}
done:
// save our place in the DirCookie
dc->off = off;
dc->seqno = GetInode()->seq_num;
*out_actual = df.BytesFilled();
ZX_DEBUG_ASSERT(*out_actual <= len); // Otherwise, we're overflowing the input buffer.
return ZX_OK;
fail:
dc->off = 0;
return ZX_ERR_IO;
}
zx_status_t Directory::Create(fbl::StringPiece name, uint32_t mode, fbl::RefPtr<fs::Vnode>* out) {
TRACE_DURATION("minfs", "Directory::Create", "name", name);
if (!fs::vfs_valid_name(name)) {
return ZX_ERR_INVALID_ARGS;
}
bool success = false;
fs::Ticker ticker(Vfs()->StartTicker());
auto get_metrics = fbl::MakeAutoCall(
[&ticker, &success, this]() { Vfs()->UpdateCreateMetrics(success, ticker.End()); });
if (IsUnlinked()) {
return ZX_ERR_BAD_STATE;
}
DirArgs args;
args.name = name;
// Ensure file does not exist.
zx_status_t status;
if ((status = ForEachDirent(&args, DirentCallbackFind)) != ZX_ERR_NOT_FOUND) {
return ZX_ERR_ALREADY_EXISTS;
}
// Creating a directory?
uint32_t type = S_ISDIR(mode) ? kMinfsTypeDir : kMinfsTypeFile;
// Ensure that we have enough space to write the new vnode's direntry
// before updating any other metadata.
args.type = type;
args.reclen = static_cast<uint32_t>(DirentSize(static_cast<uint8_t>(name.length())));
status = ForEachDirent(&args, DirentCallbackFindSpace);
if (status == ZX_ERR_NOT_FOUND) {
return ZX_ERR_NO_SPACE;
}
if (status != ZX_OK) {
return status;
}
// Calculate maximum blocks to reserve for the current directory, based on the size and offset
// of the new direntry (Assuming that the offset is the current size of the directory).
auto reserve_blocks_or = GetRequiredBlockCount(GetSize(), args.reclen, Vfs()->BlockSize());
if (reserve_blocks_or.is_error()) {
return reserve_blocks_or.error_value();
}
// Reserve 1 additional block for the new directory's initial . and .. entries.
blk_t reserve_blocks = reserve_blocks_or.value() + 1;
ZX_DEBUG_ASSERT(reserve_blocks <= Vfs()->Limits().GetMaximumMetaDataBlocks());
// In addition to reserve_blocks, reserve 1 inode for the vnode to be created.
std::unique_ptr<Transaction> transaction;
if ((status = Vfs()->BeginTransaction(1, reserve_blocks, &transaction)) != ZX_OK) {
return status;
}
// mint a new inode and vnode for it
fbl::RefPtr<VnodeMinfs> vn;
if ((status = Vfs()->VnodeNew(transaction.get(), &vn, type)) < 0) {
return status;
}
// If the new node is a directory, fill it with '.' and '..'.
if (type == kMinfsTypeDir) {
char bdata[DirentSize(1) + DirentSize(2)];
InitializeDirectory(bdata, vn->GetIno(), GetIno());
size_t expected = DirentSize(1) + DirentSize(2);
if ((status = vn->WriteExactInternal(transaction.get(), bdata, expected, 0)) != ZX_OK) {
FX_LOGS(ERROR) << "Create: Failed to initialize empty directory: " << status;
return ZX_ERR_IO;
}
vn->InodeSync(transaction.get(), kMxFsSyncDefault);
}
// add directory entry for the new child node
args.ino = vn->GetIno();
args.transaction = transaction.get();
if ((status = AppendDirent(&args)) != ZX_OK) {
return status;
}
transaction->PinVnode(fbl::RefPtr(this));
transaction->PinVnode(vn);
Vfs()->CommitTransaction(std::move(transaction));
if ((status = vn->OpenValidating(fs::VnodeConnectionOptions(), nullptr)) != ZX_OK) {
return status;
}
*out = std::move(vn);
success = true;
return ZX_OK;
}
zx_status_t Directory::Unlink(fbl::StringPiece name, bool must_be_dir) {
TRACE_DURATION("minfs", "Directory::Unlink", "name", name);
ZX_DEBUG_ASSERT(fs::vfs_valid_name(name));
bool success = false;
fs::Ticker ticker(Vfs()->StartTicker());
auto get_metrics = fbl::MakeAutoCall(
[&ticker, &success, this]() { Vfs()->UpdateUnlinkMetrics(success, ticker.End()); });
zx_status_t status;
std::unique_ptr<Transaction> transaction;
if ((status = Vfs()->BeginTransaction(0, 0, &transaction)) != ZX_OK) {
return status;
}
DirArgs args;
args.name = name;
args.type = must_be_dir ? kMinfsTypeDir : 0;
args.transaction = transaction.get();
status = ForEachDirent(&args, DirentCallbackUnlink);
if (status != ZX_OK) {
return status;
}
transaction->PinVnode(fbl::RefPtr(this));
Vfs()->CommitTransaction(std::move(transaction));
success = true;
return ZX_OK;
}
zx_status_t Directory::Truncate(size_t len) { return ZX_ERR_NOT_FILE; }
// Verify that the 'newdir' inode is not a subdirectory of the source.
zx_status_t Directory::CheckNotSubdirectory(fbl::RefPtr<Directory> newdir) {
fbl::RefPtr<Directory> vn = std::move(newdir);
zx_status_t status = ZX_OK;
while (vn->GetIno() != kMinfsRootIno) {
if (vn->GetIno() == GetIno()) {
status = ZX_ERR_INVALID_ARGS;
break;
}
fbl::RefPtr<fs::Vnode> out = nullptr;
if ((status = vn->LookupInternal(&out, "..")) < 0) {
break;
}
vn = fbl::RefPtr<Directory>::Downcast(out);
}
return status;
}
zx_status_t Directory::Rename(fbl::RefPtr<fs::Vnode> _newdir, fbl::StringPiece oldname,
fbl::StringPiece newname, bool src_must_be_dir,
bool dst_must_be_dir) {
TRACE_DURATION("minfs", "Directory::Rename", "src", oldname, "dst", newname);
bool success = false;
fs::Ticker ticker(Vfs()->StartTicker());
auto get_metrics = fbl::MakeAutoCall(
[&ticker, &success, this]() { Vfs()->UpdateRenameMetrics(success, ticker.End()); });
ZX_DEBUG_ASSERT(fs::vfs_valid_name(oldname));
ZX_DEBUG_ASSERT(fs::vfs_valid_name(newname));
auto newdir_minfs = fbl::RefPtr<VnodeMinfs>::Downcast(_newdir);
// Ensure that the vnodes containing oldname and newname are directories.
if (!(newdir_minfs->IsDirectory())) {
return ZX_ERR_NOT_SUPPORTED;
}
if (newdir_minfs->IsUnlinked()) {
return ZX_ERR_NOT_FOUND;
}
auto newdir = fbl::RefPtr<Directory>::Downcast(newdir_minfs);
zx_status_t status;
fbl::RefPtr<VnodeMinfs> oldvn = nullptr;
// Acquire the 'oldname' node (it must exist).
DirArgs args;
args.name = oldname;
if ((status = ForEachDirent(&args, DirentCallbackFind)) < 0) {
return status;
}
if ((status = Vfs()->VnodeGet(&oldvn, args.ino)) < 0) {
return status;
}
if (oldvn->IsDirectory()) {
auto olddir = fbl::RefPtr<Directory>::Downcast(oldvn);
if ((status = olddir->CheckNotSubdirectory(newdir)) < 0) {
return status;
}
}
// If either the 'src' or 'dst' must be directories, BOTH of them must be directories.
if (!oldvn->IsDirectory() && (src_must_be_dir || dst_must_be_dir)) {
return ZX_ERR_NOT_DIR;
}
if ((newdir->GetIno() == GetIno()) && (oldname == newname)) {
// Renaming a file or directory to itself?
// Shortcut success case.
success = true;
return ZX_OK;
}
// Ensure that we have enough space to write the vnode's new direntry
// before updating any other metadata.
args.type = oldvn->IsDirectory() ? kMinfsTypeDir : kMinfsTypeFile;
args.reclen = static_cast<uint32_t>(DirentSize(static_cast<uint8_t>(newname.length())));
status = newdir->ForEachDirent(&args, DirentCallbackFindSpace);
if (status == ZX_ERR_NOT_FOUND) {
return ZX_ERR_NO_SPACE;
}
if (status != ZX_OK) {
return status;
}
DirectoryOffset append_offs = args.offs;
// Reserve potential blocks to add a new direntry to newdir.
auto reserved_blocks_or =
GetRequiredBlockCount(newdir->GetInode()->size, args.reclen, Vfs()->BlockSize());
if (reserved_blocks_or.is_error()) {
return reserved_blocks_or.error_value();
}
std::unique_ptr<Transaction> transaction;
if ((status = Vfs()->BeginTransaction(0, reserved_blocks_or.value(), &transaction)) != ZX_OK) {
return status;
}
// If the entry for 'newname' exists, make sure it can be replaced by
// the vnode behind 'oldname'.
args.transaction = transaction.get();
args.name = newname;
args.ino = oldvn->GetIno();
status = newdir->ForEachDirent(&args, DirentCallbackAttemptRename);
if (status == ZX_ERR_NOT_FOUND) {
// If 'newname' does not exist, create it.
args.offs = append_offs;
if ((status = newdir->AppendDirent(&args)) != ZX_OK) {
return status;
}
} else if (status != ZX_OK) {
return status;
}
// Update the oldvn's entry for '..' if (1) it was a directory, and (2) it moved to a new
// directory.
if ((args.type == kMinfsTypeDir) && (GetIno() != newdir->GetIno())) {
fbl::RefPtr<fs::Vnode> vn_fs;
if ((status = newdir->Lookup(newname, &vn_fs)) < 0) {
return status;
}
auto vn = fbl::RefPtr<Directory>::Downcast(vn_fs);
args.name = "..";
args.ino = newdir->GetIno();
if ((status = vn->ForEachDirent(&args, DirentCallbackUpdateInode)) < 0) {
return status;
}
}
// At this point, the oldvn exists with multiple names (or the same name in different
// directories).
oldvn->AddLink();
// finally, remove oldname from its original position
args.name = oldname;
if ((status = ForEachDirent(&args, DirentCallbackForceUnlink)) != ZX_OK) {
return status;
}
transaction->PinVnode(oldvn);
transaction->PinVnode(newdir);
Vfs()->CommitTransaction(std::move(transaction));
success = true;
return ZX_OK;
}
zx_status_t Directory::Link(fbl::StringPiece name, fbl::RefPtr<fs::Vnode> _target) {
TRACE_DURATION("minfs", "Directory::Link", "name", name);
ZX_DEBUG_ASSERT(fs::vfs_valid_name(name));
if (IsUnlinked()) {
return ZX_ERR_BAD_STATE;
}
auto target = fbl::RefPtr<VnodeMinfs>::Downcast(_target);
if (target->IsDirectory()) {
// The target must not be a directory
return ZX_ERR_NOT_FILE;
}
// The destination should not exist
DirArgs args;
args.name = name;
zx_status_t status;
if ((status = ForEachDirent(&args, DirentCallbackFind)) != ZX_ERR_NOT_FOUND) {
return (status == ZX_OK) ? ZX_ERR_ALREADY_EXISTS : status;
}
// Ensure that we have enough space to write the new vnode's direntry
// before updating any other metadata.
args.type = kMinfsTypeFile; // We can't hard link directories
args.reclen = static_cast<uint32_t>(DirentSize(static_cast<uint8_t>(name.length())));
status = ForEachDirent(&args, DirentCallbackFindSpace);
if (status == ZX_ERR_NOT_FOUND) {
return ZX_ERR_NO_SPACE;
}
if (status != ZX_OK) {
return status;
}
// Reserve potential blocks to write a new direntry.
auto reserved_blocks_or =
GetRequiredBlockCount(GetInode()->size, args.reclen, Vfs()->BlockSize());
if (reserved_blocks_or.is_error()) {
return reserved_blocks_or.error_value();
}
std::unique_ptr<Transaction> transaction;
if ((status = Vfs()->BeginTransaction(0, reserved_blocks_or.value(), &transaction)) != ZX_OK) {
return status;
}
args.ino = target->GetIno();
args.transaction = transaction.get();
if ((status = AppendDirent(&args)) != ZX_OK) {
return status;
}
// We have successfully added the vn to a new location. Increment the link count.
target->AddLink();
target->InodeSync(transaction.get(), kMxFsSyncDefault);
transaction->PinVnode(fbl::RefPtr(this));
transaction->PinVnode(target);
Vfs()->CommitTransaction(std::move(transaction));
return ZX_OK;
}
} // namespace minfs