blob: da33cd8cc8afe25a03c79cb96e90260a5d769b08 [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/lib/vfs/cpp/vfs_types.h"
#include <fidl/fuchsia.io/cpp/natural_types.h>
#include <fidl/fuchsia.io/cpp/wire_types.h>
#include <lib/fit/function.h>
#include "src/storage/lib/vfs/cpp/vnode.h"
namespace fio = fuchsia_io;
namespace fs {
namespace {
constexpr VnodeConnectionOptions FlagsToConnectionOptions(fio::OpenFlags flags) {
VnodeConnectionOptions options;
// Filter out io1 OpenFlags.RIGHT_* flags, translated to io2 Rights below.
options.flags = flags & ~kAllIo1Rights;
// Using Open1 requires GET_ATTRIBUTES as this is not expressible via |fio::OpenFlags|.
// TODO(https://fxbug.dev/324080764): Restrict GET_ATTRIBUTES.
options.rights = fio::Rights::kGetAttributes;
// Approximate a set of io2 Rights corresponding to what is expected by |flags|.
if (!(options.flags & fio::OpenFlags::kNodeReference)) {
if (flags & fio::OpenFlags::kRightReadable) {
options.rights |= fio::kRStarDir;
}
if (flags & fio::OpenFlags::kRightWritable) {
options.rights |= fio::kWStarDir;
}
if (flags & fio::OpenFlags::kRightExecutable) {
options.rights |= fio::kXStarDir;
}
}
return options;
}
} // namespace
zx::result<VnodeConnectionOptions> VnodeConnectionOptions::FromOpen1Flags(fio::OpenFlags flags) {
if ((flags & fio::OpenFlags::kNodeReference) &&
(flags - fio::kOpenFlagsAllowedWithNodeReference)) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
if ((flags & fio::OpenFlags::kNotDirectory) && (flags & fio::OpenFlags::kDirectory)) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
if (flags & fio::OpenFlags::kCloneSameRights) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
if ((flags & fio::OpenFlags::kTruncate) && !(flags & fio::OpenFlags::kRightWritable)) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
return zx::ok(FlagsToConnectionOptions(flags));
}
zx::result<VnodeConnectionOptions> VnodeConnectionOptions::FromCloneFlags(fio::OpenFlags flags,
VnodeProtocol protocol) {
constexpr fio::OpenFlags kValidCloneFlags = kAllIo1Rights | fio::OpenFlags::kAppend |
fio::OpenFlags::kDescribe |
fio::OpenFlags::kCloneSameRights;
// Any flags not present in |kValidCloneFlags| should be ignored.
flags &= kValidCloneFlags;
// If CLONE_SAME_RIGHTS is specified, the client cannot request any specific rights.
if ((flags & fio::OpenFlags::kCloneSameRights) && (flags & kAllIo1Rights)) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
// Ensure we map the request to the correct flags based on the connection's protocol.
switch (protocol) {
case fs::VnodeProtocol::kNode: {
flags |= fio::OpenFlags::kNodeReference;
break;
}
case fs::VnodeProtocol::kDirectory: {
flags |= fio::OpenFlags::kDirectory;
break;
}
default: {
flags |= fio::OpenFlags::kNotDirectory;
break;
}
}
VnodeConnectionOptions options = FlagsToConnectionOptions(flags);
// Downscope the rights specified by |flags| to match those that were granted to this node
// based on |protocol|. io1 OpenFlags expand to a set of rights which may not be compatible
// with this protocol (e.g. OpenFlags::RIGHT_WRITABLE grants Rights::MODIFY_DIRECTORY, but this is
// not applicable to files which do not have this right).
options.rights = internal::DownscopeRights(options.rights, protocol);
return zx::ok(options);
}
fio::OpenFlags VnodeConnectionOptions::ToIoV1Flags() const {
return flags | RightsToOpenFlags(rights);
}
fio::OpenFlags RightsToOpenFlags(fio::Rights rights) {
fio::OpenFlags flags = {};
// Map io2 rights to io1 flags only if all constituent io2 rights are present.
if ((rights & fio::kRStarDir) == fio::kRStarDir) {
flags |= fio::OpenFlags::kRightReadable;
}
if ((rights & fio::kWStarDir) == fio::kWStarDir) {
flags |= fio::OpenFlags::kRightWritable;
}
if ((rights & fio::kXStarDir) == fio::kXStarDir) {
flags |= fio::OpenFlags::kRightExecutable;
}
return flags;
}
fio::wire::NodeAttributes VnodeAttributes::ToIoV1NodeAttributes(const fs::Vnode& vnode) const {
// Filesystems that don't support hard links typically report a value of 1 for the link count.
constexpr uint64_t kDefaultLinkCount = 1;
// TODO(https://fxbug.dev/324112857): Some filesystems now support POSIX attributes, which means
// that they won't have any hard-coded values. However, some tests still rely on the previous
// mode bits which were reported. For the time being, we pass in |vnode| to this function so that
// we can attempt to synthesize a valid set of mode bits for callers that rely on them in io1.
//
// This isn't an issue for the ongoing io2 migration as filesystems which do support the POSIX
// mode attributes will correctly handle them. In the long term, we should ensure that nothing
// load-bearing relies on these synthesized mode bits by migrating to io2's GetAttributes.
return fio::wire::NodeAttributes{
.mode = mode.has_value() ? *mode
: internal::GetPosixMode(vnode.GetProtocols(), vnode.GetAbilities()),
.id = id.value_or(fio::wire::kInoUnknown),
.content_size = content_size.value_or(0),
.storage_size = storage_size.value_or(0),
.link_count = link_count.value_or(kDefaultLinkCount),
.creation_time = creation_time.value_or(0),
.modification_time = modification_time.value_or(0)};
}
namespace internal {
fio::Rights DownscopeRights(fio::Rights rights, VnodeProtocol protocol) {
switch (protocol) {
case VnodeProtocol::kDirectory: {
// Directories support all rights.
return rights;
}
case VnodeProtocol::kFile: {
return rights & (fio::Rights::kReadBytes | fio::Rights::kWriteBytes | fio::Rights::kExecute |
fio::Rights::kGetAttributes | fio::Rights::kUpdateAttributes);
}
case VnodeProtocol::kNode: {
// Node connections only support GET_ATTRIBUTES.
return rights & fio::Rights::kGetAttributes;
}
default: {
// Remove all rights from unknown or unsupported node types.
return {};
}
}
}
zx::result<VnodeProtocol> NegotiateProtocol(fio::NodeProtocolKinds supported,
fio::NodeProtocolKinds requested) {
using fio::NodeProtocolKinds;
// Remove protocols that were not requested from the set of supported protocols.
supported = supported & requested;
// Attempt to negotiate a protocol for the connection based on the following order. The fuchsia.io
// protocol does not enforce a particular order for resolution, and when callers specify multiple
// protocols, they must be prepared to accept any that were set in the request.
if (supported & NodeProtocolKinds::kConnector) {
return zx::ok(VnodeProtocol::kService);
}
if (supported & NodeProtocolKinds::kDirectory) {
return zx::ok(VnodeProtocol::kDirectory);
}
if (supported & NodeProtocolKinds::kFile) {
return zx::ok(VnodeProtocol::kFile);
}
#if !defined(__Fuchsia__) || FUCHSIA_API_LEVEL_AT_LEAST(HEAD)
if (supported & NodeProtocolKinds::kSymlink) {
return zx::ok(VnodeProtocol::kSymlink);
}
#endif
// If we failed to resolve a protocol, we determine what error to return from a combination of the
// type of node and the protocols which were requested.
if ((requested & NodeProtocolKinds::kDirectory) && !(supported & NodeProtocolKinds::kDirectory)) {
return zx::error(ZX_ERR_NOT_DIR);
}
if ((requested & NodeProtocolKinds::kFile) && !(supported & NodeProtocolKinds::kFile)) {
return zx::error(ZX_ERR_NOT_FILE);
}
return zx::error(ZX_ERR_WRONG_TYPE);
}
fio::NodeProtocolKinds GetProtocols(const fio::wire::ConnectionProtocols& protocols) {
if (protocols.is_connector()) {
return fio::NodeProtocolKinds::kConnector;
}
if (!protocols.node().has_protocols()) {
return fio::NodeProtocolKinds::kMask ^ fio::NodeProtocolKinds::kConnector;
}
fio::NodeProtocolKinds allowed_protocols = {};
const fio::wire::NodeProtocols& node_protocols = protocols.node().protocols();
if (node_protocols.has_directory()) {
allowed_protocols |= fio::NodeProtocolKinds::kDirectory;
}
if (node_protocols.has_file()) {
allowed_protocols |= fio::NodeProtocolKinds::kFile;
}
#if FUCHSIA_API_LEVEL_AT_LEAST(HEAD)
if (node_protocols.has_symlink()) {
allowed_protocols |= fio::NodeProtocolKinds::kSymlink;
}
#endif
return allowed_protocols;
}
// Helper function to reduce verbosity in |NodeAttributes:Build| impl below. Returns an external
// (non-owning) |fidl::ObjectView| to |obj|.
template <typename T>
fidl::ObjectView<T> ExternalView(T& obj) {
return fidl::ObjectView<T>::FromExternal(&obj);
}
zx::result<fio::wire::NodeAttributes2> NodeAttributeBuilder::Build(const Vnode& vnode,
fio::NodeAttributesQuery query) {
zx::result vnode_attrs = vnode.GetAttributes();
if (vnode_attrs.is_error()) {
return vnode_attrs.take_error();
}
attributes = *vnode_attrs;
// Immutable attributes:
auto immutable_builder = ImmutableAttrs::ExternalBuilder(
fidl::ObjectView<fidl::WireTableFrame<ImmutableAttrs>>::FromExternal(&immutable_frame));
if (query & fio::NodeAttributesQuery::kProtocols) {
protocols = vnode.GetProtocols();
immutable_builder.protocols(ExternalView(protocols));
}
if (query & fio::NodeAttributesQuery::kAbilities) {
abilities = vnode.GetAbilities();
immutable_builder.abilities(ExternalView(abilities));
}
if (query & fio::NodeAttributesQuery::kContentSize && attributes.content_size) {
immutable_builder.content_size(ExternalView(*attributes.content_size));
}
if (query & fio::NodeAttributesQuery::kStorageSize && attributes.storage_size) {
immutable_builder.storage_size(ExternalView(*attributes.storage_size));
}
if (query & fio::NodeAttributesQuery::kLinkCount && attributes.link_count) {
immutable_builder.link_count(ExternalView(*attributes.link_count));
}
if (query & fio::NodeAttributesQuery::kId && attributes.id) {
immutable_builder.id(ExternalView(*attributes.id));
}
// Mutable attributes:
auto mutable_builder = MutableAttrs::ExternalBuilder(
fidl::ObjectView<fidl::WireTableFrame<MutableAttrs>>::FromExternal(&mutable_frame));
if (query & fio::NodeAttributesQuery::kCreationTime && attributes.creation_time) {
mutable_builder.creation_time(ExternalView(*attributes.creation_time));
}
if (query & fio::NodeAttributesQuery::kModificationTime && attributes.modification_time) {
mutable_builder.modification_time(ExternalView(*attributes.modification_time));
}
#if !defined(__Fuchsia__) || FUCHSIA_API_LEVEL_AT_LEAST(18)
if (query & fio::NodeAttributesQuery::kMode && attributes.mode) {
mutable_builder.mode(*attributes.mode);
}
if (query & fio::NodeAttributesQuery::kUid && attributes.uid) {
mutable_builder.uid(*attributes.uid);
}
if (query & fio::NodeAttributesQuery::kGid && attributes.gid) {
mutable_builder.gid(*attributes.gid);
}
#endif
// Build the wire table, which is now valid as long as this object remains in scope.
return zx::ok(NodeAttributes2{
.mutable_attributes = mutable_builder.Build(),
.immutable_attributes = immutable_builder.Build(),
});
}
uint32_t GetPosixMode(fio::NodeProtocolKinds protocols, fio::Abilities abilities) {
uint32_t mode = 0;
if (protocols & fio::NodeProtocolKinds::kDirectory) {
mode |= V_TYPE_DIR;
if (abilities & fio::Abilities::kEnumerate) {
mode |= V_IRUSR;
}
if (abilities & fio::Abilities::kModifyDirectory) {
mode |= V_IWUSR;
}
if (abilities & fio::Abilities::kTraverse) {
mode |= V_IXUSR;
}
} else {
mode |= V_TYPE_FILE;
if (abilities & fio::Abilities::kReadBytes) {
mode |= V_IRUSR;
}
if (abilities & fio::Abilities::kWriteBytes) {
mode |= V_IWUSR;
}
if (abilities & fio::Abilities::kExecute) {
mode |= V_IXUSR;
}
}
return mode;
}
} // namespace internal
} // namespace fs