1// Copyright 2016 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <stdlib.h>
6#include <string.h>
7#include <sys/stat.h>
8#include <threads.h>
9
10#include <fs/vfs.h>
11#include <fs/vnode.h>
12#include <fuchsia/io/c/fidl.h>
13#include <lib/fdio/debug.h>
14#include <lib/fdio/remoteio.h>
15#include <lib/fdio/vfs.h>
16#include <fbl/alloc_checker.h>
17#include <fbl/auto_lock.h>
18#include <fbl/intrusive_double_list.h>
19#include <fbl/ref_ptr.h>
20#include <fbl/type_support.h>
21#include <fbl/unique_ptr.h>
22
23namespace fs {
24
25constexpr Vfs::MountNode::MountNode() : vn_(nullptr) {}
26
27Vfs::MountNode::~MountNode() {
28    ZX_DEBUG_ASSERT(vn_ == nullptr);
29}
30
31void Vfs::MountNode::SetNode(fbl::RefPtr<Vnode> vn) {
32    ZX_DEBUG_ASSERT(vn_ == nullptr);
33    vn_ = vn;
34}
35
36zx::channel Vfs::MountNode::ReleaseRemote() {
37    ZX_DEBUG_ASSERT(vn_ != nullptr);
38    zx::channel h = vn_->DetachRemote();
39    vn_ = nullptr;
40    return h;
41}
42
43bool Vfs::MountNode::VnodeMatch(fbl::RefPtr<Vnode> vn) const {
44    ZX_DEBUG_ASSERT(vn_ != nullptr);
45    return vn == vn_;
46}
47
48// Installs a remote filesystem on vn and adds it to the remote_list_.
49zx_status_t Vfs::InstallRemote(fbl::RefPtr<Vnode> vn, MountChannel h) {
50    if (vn == nullptr) {
51        return ZX_ERR_ACCESS_DENIED;
52    }
53
54    // Allocate a node to track the remote handle
55    fbl::AllocChecker ac;
56    fbl::unique_ptr<MountNode> mount_point(new (&ac) MountNode());
57    if (!ac.check()) {
58        return ZX_ERR_NO_MEMORY;
59    }
60    zx_status_t status = vn->AttachRemote(fbl::move(h));
61    if (status != ZX_OK) {
62        return status;
63    }
64    // Save this node in the list of mounted vnodes
65    mount_point->SetNode(fbl::move(vn));
66    fbl::AutoLock lock(&vfs_lock_);
67    remote_list_.push_front(fbl::move(mount_point));
68    return ZX_OK;
69}
70
71// Installs a remote filesystem on vn and adds it to the remote_list_.
72zx_status_t Vfs::InstallRemoteLocked(fbl::RefPtr<Vnode> vn, MountChannel h) {
73    if (vn == nullptr) {
74        return ZX_ERR_ACCESS_DENIED;
75    }
76
77    // Allocate a node to track the remote handle
78    fbl::AllocChecker ac;
79    fbl::unique_ptr<MountNode> mount_point(new (&ac) MountNode());
80    if (!ac.check()) {
81        return ZX_ERR_NO_MEMORY;
82    }
83    zx_status_t status = vn->AttachRemote(fbl::move(h));
84    if (status != ZX_OK) {
85        return status;
86    }
87    // Save this node in the list of mounted vnodes
88    mount_point->SetNode(fbl::move(vn));
89    remote_list_.push_front(fbl::move(mount_point));
90    return ZX_OK;
91}
92
93zx_status_t Vfs::MountMkdir(fbl::RefPtr<Vnode> vn, fbl::StringPiece name, MountChannel h,
94                            uint32_t flags) {
95    fbl::AutoLock lock(&vfs_lock_);
96    zx_status_t r = OpenLocked(vn, &vn, name, &name, ZX_FS_FLAG_CREATE |
97                               ZX_FS_RIGHT_READABLE | ZX_FS_FLAG_DIRECTORY |
98                               ZX_FS_FLAG_NOREMOTE, S_IFDIR);
99    ZX_DEBUG_ASSERT(r <= ZX_OK); // Should not be accessing remote nodes
100    if (r < 0) {
101        return r;
102    }
103    if (vn->IsRemote()) {
104        if (flags & fuchsia_io_MOUNT_CREATE_FLAG_REPLACE) {
105            // There is an old remote handle on this vnode; shut it down and
106            // replace it with our own.
107            zx::channel old_remote;
108            Vfs::UninstallRemoteLocked(vn, &old_remote);
109            vfs_unmount_handle(old_remote.release(), 0);
110        } else {
111            return ZX_ERR_BAD_STATE;
112        }
113    }
114    return Vfs::InstallRemoteLocked(vn, fbl::move(h));
115}
116
117zx_status_t Vfs::UninstallRemote(fbl::RefPtr<Vnode> vn, zx::channel* h) {
118    fbl::AutoLock lock(&vfs_lock_);
119    return UninstallRemoteLocked(fbl::move(vn), h);
120}
121
122zx_status_t Vfs::ForwardOpenRemote(fbl::RefPtr<Vnode> vn, zx::channel channel,
123                                   fbl::StringPiece path, uint32_t flags, uint32_t mode) {
124    fbl::AutoLock lock(&vfs_lock_);
125    zx_handle_t h = vn->GetRemote();
126    if (h == ZX_HANDLE_INVALID) {
127        return ZX_ERR_NOT_FOUND;
128    }
129
130    zx_status_t r = fuchsia_io_DirectoryOpen(h, flags, mode, path.data(),
131                                             path.length(), channel.release());
132    if (r == ZX_ERR_PEER_CLOSED) {
133        zx::channel c;
134        UninstallRemoteLocked(fbl::move(vn), &c);
135    }
136    return r;
137}
138
139// Uninstall the remote filesystem mounted on vn. Removes vn from the
140// remote_list_, and sends its corresponding filesystem an 'unmount' signal.
141zx_status_t Vfs::UninstallRemoteLocked(fbl::RefPtr<Vnode> vn, zx::channel* h) {
142    fbl::unique_ptr<MountNode> mount_point;
143    {
144        mount_point = remote_list_.erase_if([&vn](const MountNode& node) {
145            return node.VnodeMatch(vn);
146        });
147        if (!mount_point) {
148            return ZX_ERR_NOT_FOUND;
149        }
150    }
151    *h = mount_point->ReleaseRemote();
152    return ZX_OK;
153}
154
155// Uninstall all remote filesystems. Acts like 'UninstallRemote' for all
156// known remotes.
157zx_status_t Vfs::UninstallAll(zx_time_t deadline) {
158    fbl::unique_ptr<MountNode> mount_point;
159    for (;;) {
160        {
161            fbl::AutoLock lock(&vfs_lock_);
162            mount_point = remote_list_.pop_front();
163        }
164        if (mount_point) {
165            vfs_unmount_handle(mount_point->ReleaseRemote().release(), deadline);
166        } else {
167            return ZX_OK;
168        }
169    }
170}
171
172} // namespace fs
173