1// Copyright 2017 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 <fs/connection.h>
6
7#include <fcntl.h>
8#include <limits.h>
9#include <stdint.h>
10#include <stdlib.h>
11#include <string.h>
12#include <sys/stat.h>
13
14#include <fs/trace.h>
15#include <fs/vnode.h>
16#include <fuchsia/io/c/fidl.h>
17#include <lib/fdio/debug.h>
18#include <lib/fdio/io.h>
19#include <lib/fdio/remoteio.h>
20#include <lib/fdio/vfs.h>
21#include <lib/zx/handle.h>
22#include <zircon/assert.h>
23
24#define ZXDEBUG 0
25
26namespace fs {
27namespace {
28
29void WriteDescribeError(zx::channel channel, zx_status_t status) {
30    zxrio_describe_t msg;
31    memset(&msg, 0, sizeof(msg));
32    msg.hdr.ordinal = fuchsia_io_NodeOnOpenOrdinal;
33    msg.status = status;
34    channel.write(0, &msg, sizeof(zxrio_describe_t), nullptr, 0);
35}
36
37zx_status_t GetNodeInfo(const fbl::RefPtr<Vnode>& vn, uint32_t flags,
38                        zxrio_node_info_t* info) {
39    if (IsPathOnly(flags)) {
40        return vn->Vnode::GetHandles(flags, &info->handle, &info->tag, info);
41    } else {
42        return vn->GetHandles(flags, &info->handle, &info->tag, info);
43    }
44}
45
46void Describe(const fbl::RefPtr<Vnode>& vn, uint32_t flags,
47              zxrio_describe_t* response, zx_handle_t* handle) {
48    response->hdr.ordinal = fuchsia_io_NodeOnOpenOrdinal;
49    response->extra.handle = ZX_HANDLE_INVALID;
50    zx_status_t r = GetNodeInfo(vn, flags, &response->extra);
51    *handle = response->extra.handle;
52
53    // If a handle was returned, encode it.
54    if (*handle != ZX_HANDLE_INVALID) {
55        response->extra.handle = FIDL_HANDLE_PRESENT;
56    } else {
57        response->extra.handle = FIDL_HANDLE_ABSENT;
58    }
59
60    // If a valid response was returned, encode it.
61    response->status = r;
62    response->extra_ptr = reinterpret_cast<zxrio_node_info_t*>(r == ZX_OK ?
63                                                                 FIDL_ALLOC_PRESENT :
64                                                                 FIDL_ALLOC_ABSENT);
65}
66
67void FilterFlags(uint32_t flags, uint32_t* out_flags, bool* out_describe) {
68    // Filter out flags that are invalid when combined with REF_ONLY.
69    if (IsPathOnly(flags)) {
70        flags &= ZX_FS_FLAG_VNODE_REF_ONLY | ZX_FS_FLAG_DIRECTORY | ZX_FS_FLAG_DESCRIBE;
71    }
72
73    *out_describe = flags & ZX_FS_FLAG_DESCRIBE;
74    *out_flags = flags & (~ZX_FS_FLAG_DESCRIBE);
75}
76
77void VnodeServe(Vfs* vfs, fbl::RefPtr<Vnode> vnode, zx::channel channel, uint32_t open_flags) {
78    if (IsPathOnly(open_flags)) {
79        vnode->Vnode::Serve(vfs, fbl::move(channel), open_flags);
80    } else {
81        vnode->Serve(vfs, fbl::move(channel), open_flags);
82    }
83}
84
85// Performs a path walk and opens a connection to another node.
86void OpenAt(Vfs* vfs, fbl::RefPtr<Vnode> parent, zx::channel channel,
87            fbl::StringPiece path, uint32_t flags, uint32_t mode) {
88    bool describe;
89    uint32_t open_flags;
90    FilterFlags(flags, &open_flags, &describe);
91
92    fbl::RefPtr<Vnode> vnode;
93    zx_status_t r = vfs->Open(fbl::move(parent), &vnode, path, &path, open_flags, mode);
94
95    if (r != ZX_OK) {
96        xprintf("vfs: open: r=%d\n", r);
97    } else if (!(open_flags & ZX_FS_FLAG_NOREMOTE) && vnode->IsRemote()) {
98        // Remote handoff to a remote filesystem node.
99        vfs->ForwardOpenRemote(fbl::move(vnode), fbl::move(channel), fbl::move(path),
100                               flags, mode);
101        return;
102    }
103
104    if (describe) {
105        // Regardless of the error code, in the 'describe' case, we
106        // should respond to the client.
107        if (r != ZX_OK) {
108            WriteDescribeError(fbl::move(channel), r);
109            return;
110        }
111
112        zxrio_describe_t response;
113        memset(&response, 0, sizeof(response));
114        zx_handle_t extra = ZX_HANDLE_INVALID;
115        Describe(vnode, flags, &response, &extra);
116        uint32_t hcount = (extra != ZX_HANDLE_INVALID) ? 1 : 0;
117        channel.write(0, &response, sizeof(zxrio_describe_t), &extra, hcount);
118    } else if (r != ZX_OK) {
119        return;
120    }
121
122    VnodeServe(vfs, fbl::move(vnode), fbl::move(channel), open_flags);
123}
124
125// This template defines a mechanism to transform a member of Connection
126// into a FIDL-dispatch operation compatible format, independent of
127// FIDL arguments.
128//
129// For example:
130//
131//      ZXFIDL_OPERATION(Foo)
132//
133// Defines the following method:
134//
135//      zx_status_t FooOp(void* ctx, Args... args);
136//
137// That invokes:
138//
139//      zx_status_t Connection::Foo(Args... args);
140//
141// Such that FooOp may be used in the fuchsia_io_* ops table.
142#define ZXFIDL_OPERATION(Method)                                          \
143template <typename... Args>                                               \
144zx_status_t Method ## Op(void* ctx, Args... args) {                       \
145    TRACE_DURATION("vfs", #Method);                                       \
146    auto connection = reinterpret_cast<Connection*>(ctx);                 \
147    return (connection->Connection::Method)(fbl::forward<Args>(args)...); \
148}
149
150ZXFIDL_OPERATION(NodeClone)
151ZXFIDL_OPERATION(NodeClose)
152ZXFIDL_OPERATION(NodeDescribe)
153ZXFIDL_OPERATION(NodeSync)
154ZXFIDL_OPERATION(NodeGetAttr)
155ZXFIDL_OPERATION(NodeSetAttr)
156ZXFIDL_OPERATION(NodeIoctl)
157
158const fuchsia_io_Node_ops kNodeOps = {
159    .Clone = NodeCloneOp,
160    .Close = NodeCloseOp,
161    .Describe = NodeDescribeOp,
162    .Sync = NodeSyncOp,
163    .GetAttr = NodeGetAttrOp,
164    .SetAttr = NodeSetAttrOp,
165    .Ioctl = NodeIoctlOp,
166};
167
168ZXFIDL_OPERATION(FileRead)
169ZXFIDL_OPERATION(FileReadAt)
170ZXFIDL_OPERATION(FileWrite)
171ZXFIDL_OPERATION(FileWriteAt)
172ZXFIDL_OPERATION(FileSeek)
173ZXFIDL_OPERATION(FileTruncate)
174ZXFIDL_OPERATION(FileGetFlags)
175ZXFIDL_OPERATION(FileSetFlags)
176ZXFIDL_OPERATION(FileGetVmo)
177
178const fuchsia_io_File_ops kFileOps = {
179    .Clone = NodeCloneOp,
180    .Close = NodeCloseOp,
181    .Describe = NodeDescribeOp,
182    .Sync = NodeSyncOp,
183    .GetAttr = NodeGetAttrOp,
184    .SetAttr = NodeSetAttrOp,
185    .Ioctl = NodeIoctlOp,
186    .Read = FileReadOp,
187    .ReadAt = FileReadAtOp,
188    .Write = FileWriteOp,
189    .WriteAt = FileWriteAtOp,
190    .Seek = FileSeekOp,
191    .Truncate = FileTruncateOp,
192    .GetFlags = FileGetFlagsOp,
193    .SetFlags = FileSetFlagsOp,
194    .GetVmo = FileGetVmoOp,
195};
196
197ZXFIDL_OPERATION(DirectoryOpen)
198ZXFIDL_OPERATION(DirectoryUnlink)
199ZXFIDL_OPERATION(DirectoryReadDirents)
200ZXFIDL_OPERATION(DirectoryRewind)
201ZXFIDL_OPERATION(DirectoryGetToken)
202ZXFIDL_OPERATION(DirectoryRename)
203ZXFIDL_OPERATION(DirectoryLink)
204ZXFIDL_OPERATION(DirectoryWatch)
205
206const fuchsia_io_Directory_ops kDirectoryOps {
207    .Clone = NodeCloneOp,
208    .Close = NodeCloseOp,
209    .Describe = NodeDescribeOp,
210    .Sync = NodeSyncOp,
211    .GetAttr = NodeGetAttrOp,
212    .SetAttr = NodeSetAttrOp,
213    .Ioctl = NodeIoctlOp,
214    .Open = DirectoryOpenOp,
215    .Unlink = DirectoryUnlinkOp,
216    .ReadDirents = DirectoryReadDirentsOp,
217    .Rewind = DirectoryRewindOp,
218    .GetToken = DirectoryGetTokenOp,
219    .Rename = DirectoryRenameOp,
220    .Link = DirectoryLinkOp,
221    .Watch = DirectoryWatchOp,
222};
223
224ZXFIDL_OPERATION(DirectoryAdminMount)
225ZXFIDL_OPERATION(DirectoryAdminMountAndCreate)
226ZXFIDL_OPERATION(DirectoryAdminUnmount)
227ZXFIDL_OPERATION(DirectoryAdminUnmountNode)
228ZXFIDL_OPERATION(DirectoryAdminQueryFilesystem)
229ZXFIDL_OPERATION(DirectoryAdminGetDevicePath)
230
231const fuchsia_io_DirectoryAdmin_ops kDirectoryAdminOps {
232    .Clone = NodeCloneOp,
233    .Close = NodeCloseOp,
234    .Describe = NodeDescribeOp,
235    .Sync = NodeSyncOp,
236    .GetAttr = NodeGetAttrOp,
237    .SetAttr = NodeSetAttrOp,
238    .Ioctl = NodeIoctlOp,
239    .Open = DirectoryOpenOp,
240    .Unlink = DirectoryUnlinkOp,
241    .ReadDirents = DirectoryReadDirentsOp,
242    .Rewind = DirectoryRewindOp,
243    .GetToken = DirectoryGetTokenOp,
244    .Rename = DirectoryRenameOp,
245    .Link = DirectoryLinkOp,
246    .Watch = DirectoryWatchOp,
247    .Mount = DirectoryAdminMountOp,
248    .MountAndCreate = DirectoryAdminMountAndCreateOp,
249    .Unmount = DirectoryAdminUnmountOp,
250    .UnmountNode = DirectoryAdminUnmountNodeOp,
251    .QueryFilesystem = DirectoryAdminQueryFilesystemOp,
252    .GetDevicePath = DirectoryAdminGetDevicePathOp,
253};
254
255} // namespace
256
257constexpr zx_signals_t kWakeSignals = ZX_CHANNEL_READABLE |
258                                      ZX_CHANNEL_PEER_CLOSED | kLocalTeardownSignal;
259
260Connection::Connection(Vfs* vfs, fbl::RefPtr<Vnode> vnode,
261                       zx::channel channel, uint32_t flags)
262    : vfs_(vfs), vnode_(fbl::move(vnode)), channel_(fbl::move(channel)),
263      wait_(this, ZX_HANDLE_INVALID, kWakeSignals), flags_(flags) {
264    ZX_DEBUG_ASSERT(vfs);
265    ZX_DEBUG_ASSERT(vnode_);
266    ZX_DEBUG_ASSERT(channel_);
267}
268
269Connection::~Connection() {
270    // Stop waiting and clean up if still connected.
271    if (wait_.is_pending()) {
272        zx_status_t status = wait_.Cancel();
273        ZX_DEBUG_ASSERT_MSG(status == ZX_OK, "Could not cancel wait: status=%d", status);
274    }
275
276    // Invoke a "close" call to the underlying object if we haven't already.
277    if (is_open()) {
278        CallClose();
279    }
280
281    // Release the token associated with this connection's vnode since the connection
282    // will be releasing the vnode's reference once this function returns.
283    if (token_) {
284        vfs_->TokenDiscard(fbl::move(token_));
285    }
286}
287
288void Connection::AsyncTeardown() {
289    if (channel_) {
290        ZX_ASSERT(channel_.signal(0, kLocalTeardownSignal) == ZX_OK);
291    }
292}
293
294void Connection::SyncTeardown() {
295    if (wait_.Cancel() == ZX_OK) {
296        Terminate(/* call_close= */ true);
297    }
298}
299
300zx_status_t Connection::Serve() {
301    wait_.set_object(channel_.get());
302    return wait_.Begin(vfs_->dispatcher());
303}
304
305void Connection::HandleSignals(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status,
306                               const zx_packet_signal_t* signal) {
307    ZX_DEBUG_ASSERT(is_open());
308
309    if (status == ZX_OK) {
310        if (vfs_->IsTerminating()) {
311            // Short-circuit locally destroyed connections, rather than servicing
312            // requests on their behalf. This prevents new requests from being
313            // opened while filesystems are torn down.
314            status = ZX_ERR_PEER_CLOSED;
315        } else if (signal->observed & ZX_CHANNEL_READABLE) {
316            // Handle the message.
317            status = CallHandler();
318            switch (status) {
319            case ERR_DISPATCHER_ASYNC:
320                return;
321            case ZX_OK:
322                status = wait_.Begin(dispatcher);
323                if (status == ZX_OK) {
324                    return;
325                }
326                break;
327            }
328        }
329    }
330
331    bool call_close = (status != ERR_DISPATCHER_DONE);
332    Terminate(call_close);
333}
334
335void Connection::Terminate(bool call_close) {
336    if (call_close) {
337        // Give the dispatcher a chance to clean up.
338        CallClose();
339    } else {
340        // It's assumed that someone called the close handler
341        // prior to calling this function.
342        set_closed();
343    }
344
345    // Tell the VFS that the connection closed remotely.
346    // This might have the side-effect of destroying this object.
347    vfs_->OnConnectionClosedRemotely(this);
348}
349
350zx_status_t Connection::CallHandler() {
351    return zxfidl_handler(channel_.get(), &Connection::HandleMessageThunk, this);
352}
353
354void Connection::CallClose() {
355    channel_.reset();
356    CallHandler();
357    set_closed();
358}
359
360zx_status_t Connection::HandleMessageThunk(fidl_msg_t* msg, fidl_txn_t* txn, void* cookie) {
361    Connection* connection = static_cast<Connection*>(cookie);
362    return connection->HandleMessage(msg, txn);
363}
364
365// Flags which can be modified by SetFlags.
366constexpr uint32_t kSettableStatusFlags = ZX_FS_FLAG_APPEND;
367
368// All flags which indicate state of the
369// connection (excluding rights).
370constexpr uint32_t kStatusFlags = kSettableStatusFlags | ZX_FS_FLAG_VNODE_REF_ONLY;
371
372zx_status_t Connection::NodeClone(uint32_t flags, zx_handle_t object) {
373    zx::channel channel(object);
374
375    bool describe;
376    uint32_t open_flags;
377    FilterFlags(flags, &open_flags, &describe);
378    // TODO(smklein): Avoid automatically inheriting rights
379    // from the cloned file descriptor; allow de-scoping.
380    // Currently, this is difficult, since the remote IO interface
381    // to clone does not specify a reduced set of rights.
382    open_flags |= (flags_ & (ZX_FS_RIGHTS | kStatusFlags));
383
384    fbl::RefPtr<Vnode> vn(vnode_);
385    zx_status_t status = ZX_OK;
386    if (!IsPathOnly(open_flags)) {
387        status = OpenVnode(open_flags, &vn);
388    }
389    if (describe) {
390        zxrio_describe_t response;
391        memset(&response, 0, sizeof(response));
392        response.status = status;
393        zx_handle_t extra = ZX_HANDLE_INVALID;
394        if (status == ZX_OK) {
395            Describe(vnode_, open_flags, &response, &extra);
396        }
397        uint32_t hcount = (extra != ZX_HANDLE_INVALID) ? 1 : 0;
398        channel.write(0, &response, sizeof(zxrio_describe_t), &extra, hcount);
399    }
400
401    if (status == ZX_OK) {
402        VnodeServe(vfs_, fbl::move(vn), fbl::move(channel), open_flags);
403    }
404    return ZX_OK;
405}
406
407zx_status_t Connection::NodeClose(fidl_txn_t* txn) {
408    zx_status_t status;
409    if (IsPathOnly(flags_)) {
410        status = ZX_OK;
411    } else {
412        status = vnode_->Close();
413    }
414    fuchsia_io_NodeClose_reply(txn, status);
415
416    return ERR_DISPATCHER_DONE;
417}
418
419zx_status_t Connection::NodeDescribe(fidl_txn_t* txn) {
420    zxrio_node_info_t info;
421    memset(&info, 0, sizeof(info));
422    zx_status_t status = GetNodeInfo(vnode_, flags_, &info);
423    if (status != ZX_OK) {
424        return status;
425    }
426    // See static_asserts in zxrio_process_open_response which ensure that this
427    // cast makes sense.
428    auto fidl_info = reinterpret_cast<fuchsia_io_NodeInfo*>(&info);
429    return fuchsia_io_NodeDescribe_reply(txn, fidl_info);
430}
431
432zx_status_t Connection::NodeSync(fidl_txn_t* txn) {
433    if (IsPathOnly(flags_)) {
434        return fuchsia_io_NodeSync_reply(txn, ZX_ERR_BAD_HANDLE);
435    }
436    Vnode::SyncCallback closure([this, ctxn = zxfidl_txn_copy(txn)]
437                                (zx_status_t status) mutable {
438        fuchsia_io_NodeSync_reply(&ctxn.txn, status);
439
440        // Try to reset the wait object
441        ZX_ASSERT_MSG(wait_.Begin(vfs_->dispatcher()) == ZX_OK,
442                      "Dispatch loop unexpectedly ended");
443    });
444
445    vnode_->Sync(fbl::move(closure));
446    return ERR_DISPATCHER_ASYNC;
447}
448
449zx_status_t Connection::NodeGetAttr(fidl_txn_t* txn) {
450    fuchsia_io_NodeAttributes attributes;
451    memset(&attributes, 0, sizeof(attributes));
452
453    // TODO(smklein): Consider using "NodeAttributes" within
454    // ulib/fs, rather than vnattr_t.
455    // Alternatively modify vnattr_t to match "NodeAttributes"
456    vnattr_t attr;
457    zx_status_t r;
458    if ((r = vnode_->Getattr(&attr)) != ZX_OK) {
459        return fuchsia_io_NodeGetAttr_reply(txn, r, &attributes);
460    }
461
462    attributes.mode = attr.mode;
463    attributes.id = attr.inode;
464    attributes.content_size = attr.size;
465    attributes.storage_size = VNATTR_BLKSIZE * attr.blkcount;
466    attributes.link_count = attr.nlink;
467    attributes.creation_time = attr.create_time;
468    attributes.modification_time = attr.modify_time;
469
470    return fuchsia_io_NodeGetAttr_reply(txn, ZX_OK, &attributes);
471}
472
473zx_status_t Connection::NodeSetAttr(uint32_t flags,
474                                    const fuchsia_io_NodeAttributes* attributes,
475                                    fidl_txn_t* txn) {
476    // TODO(smklein): Prevent read-only files from setting attributes,
477    // but allow attribute-setting on mutable directories.
478    // For context: ZX-1262, ZX-1065
479    if (IsPathOnly(flags_)) {
480        return fuchsia_io_NodeSetAttr_reply(txn, ZX_ERR_BAD_HANDLE);
481    }
482
483    vnattr_t attr;
484    attr.valid = flags;
485    attr.create_time = attributes->creation_time;
486    attr.modify_time = attributes->modification_time;
487    zx_status_t status = vnode_->Setattr(&attr);
488    return fuchsia_io_NodeSetAttr_reply(txn, status);
489}
490
491zx_status_t Connection::NodeIoctl(uint32_t opcode, uint64_t max_out,
492                                  const zx_handle_t* handles, size_t handles_count,
493                                  const uint8_t* in_data, size_t in_count, fidl_txn_t* txn) {
494    zx_handle_close_many(handles, handles_count);
495    return fuchsia_io_NodeIoctl_reply(txn, ZX_ERR_NOT_SUPPORTED, nullptr, 0, nullptr, 0);
496}
497
498zx_status_t Connection::FileRead(uint64_t count, fidl_txn_t* txn) {
499    if (!IsReadable(flags_)) {
500        return fuchsia_io_FileRead_reply(txn, ZX_ERR_BAD_HANDLE, nullptr, 0);
501    } else if (count > ZXFIDL_MAX_MSG_BYTES) {
502        return fuchsia_io_FileRead_reply(txn, ZX_ERR_INVALID_ARGS, nullptr, 0);
503    }
504    uint8_t data[count];
505    size_t actual = 0;
506    zx_status_t status = vnode_->Read(data, count, offset_, &actual);
507    if (status == ZX_OK) {
508        ZX_DEBUG_ASSERT(actual <= count);
509        offset_ += actual;
510    }
511    return fuchsia_io_FileRead_reply(txn, status, data, actual);
512}
513
514zx_status_t Connection::FileReadAt(uint64_t count, uint64_t offset, fidl_txn_t* txn) {
515    if (!IsReadable(flags_)) {
516        return fuchsia_io_FileReadAt_reply(txn, ZX_ERR_BAD_HANDLE, nullptr, 0);
517    } else if (count > ZXFIDL_MAX_MSG_BYTES) {
518        return fuchsia_io_FileReadAt_reply(txn, ZX_ERR_INVALID_ARGS, nullptr, 0);
519    }
520    uint8_t data[count];
521    size_t actual = 0;
522    zx_status_t status = vnode_->Read(data, count, offset, &actual);
523    if (status == ZX_OK) {
524        ZX_DEBUG_ASSERT(actual <= count);
525    }
526    return fuchsia_io_FileReadAt_reply(txn, status, data, actual);
527}
528
529zx_status_t Connection::FileWrite(const uint8_t* data_data, size_t data_count, fidl_txn_t* txn) {
530    if (!IsWritable(flags_)) {
531        return fuchsia_io_FileWrite_reply(txn, ZX_ERR_BAD_HANDLE, 0);
532    }
533
534    size_t actual = 0;
535    zx_status_t status;
536    if (flags_ & ZX_FS_FLAG_APPEND) {
537        size_t end;
538        status = vnode_->Append(data_data, data_count, &end, &actual);
539        if (status == ZX_OK) {
540            offset_ = end;
541        }
542    } else {
543        status = vnode_->Write(data_data, data_count, offset_, &actual);
544        if (status == ZX_OK) {
545            offset_ += actual;
546        }
547    }
548    ZX_DEBUG_ASSERT(actual <= data_count);
549    return fuchsia_io_FileWrite_reply(txn, status, actual);
550}
551
552zx_status_t Connection::FileWriteAt(const uint8_t* data_data, size_t data_count,
553                                    uint64_t offset, fidl_txn_t* txn) {
554    if (!IsWritable(flags_)) {
555        return fuchsia_io_FileWriteAt_reply(txn, ZX_ERR_BAD_HANDLE, 0);
556    }
557    size_t actual = 0;
558    zx_status_t status = vnode_->Write(data_data, data_count, offset, &actual);
559    ZX_DEBUG_ASSERT(actual <= data_count);
560    return fuchsia_io_FileWriteAt_reply(txn, status, actual);
561}
562
563zx_status_t Connection::FileSeek(int64_t offset, fuchsia_io_SeekOrigin start, fidl_txn_t* txn) {
564    static_assert(SEEK_SET == fuchsia_io_SeekOrigin_START, "");
565    static_assert(SEEK_CUR == fuchsia_io_SeekOrigin_CURRENT, "");
566    static_assert(SEEK_END == fuchsia_io_SeekOrigin_END, "");
567
568    if (IsPathOnly(flags_)) {
569        return fuchsia_io_FileSeek_reply(txn, ZX_ERR_BAD_HANDLE, offset_);
570    }
571    vnattr_t attr;
572    zx_status_t r;
573    if ((r = vnode_->Getattr(&attr)) < 0) {
574        return r;
575    }
576    size_t n;
577    switch (start) {
578    case SEEK_SET:
579        if (offset < 0) {
580            return fuchsia_io_FileSeek_reply(txn, ZX_ERR_INVALID_ARGS, offset_);
581        }
582        n = offset;
583        break;
584    case SEEK_CUR:
585        n = offset_ + offset;
586        if (offset < 0) {
587            // if negative seek
588            if (n > offset_) {
589                // wrapped around. attempt to seek before start
590                return fuchsia_io_FileSeek_reply(txn, ZX_ERR_INVALID_ARGS, offset_);
591            }
592        } else {
593            // positive seek
594            if (n < offset_) {
595                // wrapped around. overflow
596                return fuchsia_io_FileSeek_reply(txn, ZX_ERR_INVALID_ARGS, offset_);
597            }
598        }
599        break;
600    case SEEK_END:
601        n = attr.size + offset;
602        if (offset < 0) {
603            // if negative seek
604            if (n > attr.size) {
605                // wrapped around. attempt to seek before start
606                return fuchsia_io_FileSeek_reply(txn, ZX_ERR_INVALID_ARGS, offset_);
607            }
608        } else {
609            // positive seek
610            if (n < attr.size) {
611                // wrapped around
612                return fuchsia_io_FileSeek_reply(txn, ZX_ERR_INVALID_ARGS, offset_);
613            }
614        }
615        break;
616    default:
617        return fuchsia_io_FileSeek_reply(txn, ZX_ERR_INVALID_ARGS, offset_);
618    }
619    offset_ = n;
620    return fuchsia_io_FileSeek_reply(txn, ZX_OK, offset_);
621}
622
623zx_status_t Connection::FileTruncate(uint64_t length, fidl_txn_t* txn) {
624    if (!IsWritable(flags_)) {
625        return fuchsia_io_FileTruncate_reply(txn, ZX_ERR_BAD_HANDLE);
626    }
627
628    zx_status_t status = vnode_->Truncate(length);
629    return fuchsia_io_FileTruncate_reply(txn, status);
630}
631
632zx_status_t Connection::FileGetFlags(fidl_txn_t* txn) {
633    uint32_t flags = flags_ & (kStatusFlags | ZX_FS_RIGHTS);
634    return fuchsia_io_FileGetFlags_reply(txn, ZX_OK, flags);
635}
636
637zx_status_t Connection::FileSetFlags(uint32_t flags, fidl_txn_t* txn) {
638    flags_ = (flags_ & ~kSettableStatusFlags) | (flags & kSettableStatusFlags);
639    return fuchsia_io_FileSetFlags_reply(txn, ZX_OK);
640}
641
642zx_status_t Connection::FileGetVmo(uint32_t flags, fidl_txn_t* txn) {
643    if (IsPathOnly(flags_)) {
644        return fuchsia_io_FileGetVmo_reply(txn, ZX_ERR_BAD_HANDLE, ZX_HANDLE_INVALID);
645    }
646
647    if ((flags & FDIO_MMAP_FLAG_PRIVATE) && (flags & FDIO_MMAP_FLAG_EXACT)) {
648        return fuchsia_io_FileGetVmo_reply(txn, ZX_ERR_INVALID_ARGS, ZX_HANDLE_INVALID);
649    } else if ((flags_ & ZX_FS_FLAG_APPEND) && flags & FDIO_MMAP_FLAG_WRITE) {
650        return fuchsia_io_FileGetVmo_reply(txn, ZX_ERR_ACCESS_DENIED, ZX_HANDLE_INVALID);
651    } else if (!IsWritable(flags_) && (flags & FDIO_MMAP_FLAG_WRITE)) {
652        return fuchsia_io_FileGetVmo_reply(txn, ZX_ERR_ACCESS_DENIED, ZX_HANDLE_INVALID);
653    } else if (!IsReadable(flags_)) {
654        return fuchsia_io_FileGetVmo_reply(txn, ZX_ERR_ACCESS_DENIED, ZX_HANDLE_INVALID);
655    }
656
657    zx_handle_t handle = ZX_HANDLE_INVALID;
658    zx_status_t status = vnode_->GetVmo(flags, &handle);
659    return fuchsia_io_FileGetVmo_reply(txn, status, handle);
660}
661
662zx_status_t Connection::DirectoryOpen(uint32_t flags, uint32_t mode, const char* path_data,
663                                      size_t path_size, zx_handle_t object) {
664    zx::channel channel(object);
665    bool describe = flags & ZX_FS_FLAG_DESCRIBE;
666    if ((path_size < 1) || (path_size > PATH_MAX)) {
667        if (describe) {
668            WriteDescribeError(fbl::move(channel), ZX_ERR_INVALID_ARGS);
669        }
670    } else if ((flags & ZX_FS_RIGHT_ADMIN) && !(flags_ & ZX_FS_RIGHT_ADMIN)) {
671        if (describe) {
672            WriteDescribeError(fbl::move(channel), ZX_ERR_ACCESS_DENIED);
673        }
674    } else {
675        OpenAt(vfs_, vnode_, fbl::move(channel),
676               fbl::StringPiece(path_data, path_size), flags, mode);
677    }
678    return ZX_OK;
679}
680
681zx_status_t Connection::DirectoryUnlink(const char* path_data, size_t path_size, fidl_txn_t* txn) {
682    zx_status_t status = vfs_->Unlink(vnode_, fbl::StringPiece(path_data, path_size));
683    return fuchsia_io_DirectoryUnlink_reply(txn, status);
684}
685
686zx_status_t Connection::DirectoryReadDirents(uint64_t max_out, fidl_txn_t* txn) {
687    if (IsPathOnly(flags_)) {
688        return fuchsia_io_DirectoryReadDirents_reply(txn, ZX_ERR_BAD_HANDLE, nullptr, 0);
689    }
690    if (max_out > ZXFIDL_MAX_MSG_BYTES) {
691        return fuchsia_io_DirectoryReadDirents_reply(txn, ZX_ERR_INVALID_ARGS, nullptr, 0);
692    }
693    uint8_t data[max_out];
694    size_t actual = 0;
695    zx_status_t status = vfs_->Readdir(vnode_.get(), &dircookie_, data, max_out, &actual);
696    return fuchsia_io_DirectoryReadDirents_reply(txn, status, data, actual);
697}
698
699zx_status_t Connection::DirectoryRewind(fidl_txn_t* txn) {
700    if (IsPathOnly(flags_)) {
701        return fuchsia_io_DirectoryRewind_reply(txn, ZX_ERR_BAD_HANDLE);
702    }
703    dircookie_.Reset();
704    return fuchsia_io_DirectoryRewind_reply(txn, ZX_OK);
705}
706
707zx_status_t Connection::DirectoryGetToken(fidl_txn_t* txn) {
708    zx::event returned_token;
709    zx_status_t status = vfs_->VnodeToToken(vnode_, &token_, &returned_token);
710    return fuchsia_io_DirectoryGetToken_reply(txn, status, returned_token.release());
711}
712
713zx_status_t Connection::DirectoryRename(const char* src_data, size_t src_size,
714                                        zx_handle_t dst_parent_token, const char* dst_data,
715                                        size_t dst_size, fidl_txn_t* txn) {
716    zx::event token(dst_parent_token);
717    fbl::StringPiece oldStr(src_data, src_size);
718    fbl::StringPiece newStr(dst_data, dst_size);
719
720    if (src_size < 1 || dst_size < 1) {
721        return fuchsia_io_DirectoryRename_reply(txn, ZX_ERR_INVALID_ARGS);
722    }
723    zx_status_t status = vfs_->Rename(fbl::move(token), vnode_,
724                                      fbl::move(oldStr), fbl::move(newStr));
725    return fuchsia_io_DirectoryRename_reply(txn, status);
726}
727
728zx_status_t Connection::DirectoryLink(const char* src_data, size_t src_size,
729                                      zx_handle_t dst_parent_token, const char* dst_data,
730                                      size_t dst_size, fidl_txn_t* txn) {
731    zx::event token(dst_parent_token);
732    fbl::StringPiece oldStr(src_data, src_size);
733    fbl::StringPiece newStr(dst_data, dst_size);
734
735    if (src_size < 1 || dst_size < 1) {
736        return fuchsia_io_DirectoryLink_reply(txn, ZX_ERR_INVALID_ARGS);
737    }
738    zx_status_t status = vfs_->Link(fbl::move(token), vnode_, fbl::move(oldStr),
739                                    fbl::move(newStr));
740    return fuchsia_io_DirectoryLink_reply(txn, status);
741}
742
743zx_status_t Connection::DirectoryWatch(uint32_t mask, uint32_t options, zx_handle_t handle,
744                                       fidl_txn_t* txn) {
745    zx::channel watcher(handle);
746    zx_status_t status = vnode_->WatchDir(vfs_, mask, options, fbl::move(watcher));
747    return fuchsia_io_DirectoryWatch_reply(txn, status);
748}
749
750zx_status_t Connection::DirectoryAdminMount(zx_handle_t remote, fidl_txn_t* txn) {
751    if (!(flags_ & ZX_FS_RIGHT_ADMIN)) {
752        vfs_unmount_handle(remote, 0);
753        return fuchsia_io_DirectoryAdminMount_reply(txn, ZX_ERR_ACCESS_DENIED);
754    }
755    MountChannel c = MountChannel(remote);
756    zx_status_t status = vfs_->InstallRemote(vnode_, fbl::move(c));
757    return fuchsia_io_DirectoryAdminMount_reply(txn, status);;
758}
759
760zx_status_t Connection::DirectoryAdminMountAndCreate(zx_handle_t remote, const char* name,
761                                                     size_t name_size, uint32_t flags,
762                                                     fidl_txn_t* txn) {
763    if (!(flags_ & ZX_FS_RIGHT_ADMIN)) {
764        vfs_unmount_handle(remote, 0);
765        return fuchsia_io_DirectoryAdminMount_reply(txn, ZX_ERR_ACCESS_DENIED);
766    }
767    fbl::StringPiece str(name, name_size);
768    zx_status_t status = vfs_->MountMkdir(vnode_, fbl::move(str), MountChannel(remote), flags);
769    return fuchsia_io_DirectoryAdminMount_reply(txn, status);
770}
771
772zx_status_t Connection::DirectoryAdminUnmount(fidl_txn_t* txn) {
773    if (!(flags_ & ZX_FS_RIGHT_ADMIN)) {
774        return fuchsia_io_DirectoryAdminUnmount_reply(txn, ZX_ERR_ACCESS_DENIED);
775    }
776    vfs_->UninstallAll(ZX_TIME_INFINITE);
777
778    // Unmount is fatal to the requesting connections.
779    Vfs::ShutdownCallback closure([ch = fbl::move(channel_),
780                                   ctxn = zxfidl_txn_copy(txn)]
781                                  (zx_status_t status) mutable {
782        fuchsia_io_DirectoryAdminUnmount_reply(&ctxn.txn, status);
783    });
784    Vfs* vfs = vfs_;
785    Terminate(/* call_close= */ true);
786    vfs->Shutdown(fbl::move(closure));
787    return ERR_DISPATCHER_ASYNC;
788}
789
790zx_status_t Connection::DirectoryAdminUnmountNode(fidl_txn_t* txn) {
791    if (!(flags_ & ZX_FS_RIGHT_ADMIN)) {
792        return fuchsia_io_DirectoryAdminUnmountNode_reply(txn, ZX_ERR_ACCESS_DENIED, ZX_HANDLE_INVALID);
793    }
794    zx::channel c;
795    zx_status_t status = vfs_->UninstallRemote(vnode_, &c);
796    return fuchsia_io_DirectoryAdminUnmountNode_reply(txn, status, c.release());
797}
798
799zx_status_t Connection::DirectoryAdminQueryFilesystem(fidl_txn_t* txn) {
800    fuchsia_io_FilesystemInfo info;
801    zx_status_t status = vnode_->QueryFilesystem(&info);
802    return fuchsia_io_DirectoryAdminQueryFilesystem_reply(txn, status,
803                                                          status == ZX_OK ? &info : nullptr);
804}
805
806zx_status_t Connection::DirectoryAdminGetDevicePath(fidl_txn_t* txn) {
807    if (!(flags_ & ZX_FS_RIGHT_ADMIN)) {
808        return fuchsia_io_DirectoryAdminGetDevicePath_reply(txn, ZX_ERR_ACCESS_DENIED, nullptr, 0);
809    }
810
811    char name[fuchsia_io_MAX_PATH];
812    size_t actual = 0;
813    zx_status_t status = vnode_->GetDevicePath(sizeof(name), name, &actual);
814    return fuchsia_io_DirectoryAdminGetDevicePath_reply(txn, status, name, actual);
815}
816
817zx_status_t Connection::HandleFsSpecificMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
818    zx_handle_close_many(msg->handles, msg->num_handles);
819    return ZX_ERR_NOT_SUPPORTED;
820}
821
822zx_status_t Connection::HandleMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
823    fidl_message_header_t* hdr = reinterpret_cast<fidl_message_header_t*>(msg->bytes);
824    if (hdr->ordinal >= fuchsia_io_NodeCloneOrdinal &&
825        hdr->ordinal <= fuchsia_io_NodeIoctlOrdinal) {
826        return fuchsia_io_Node_dispatch(this, txn, msg, &kNodeOps);
827    } else if (hdr->ordinal >= fuchsia_io_FileReadOrdinal &&
828               hdr->ordinal <= fuchsia_io_FileGetVmoOrdinal) {
829        return fuchsia_io_File_dispatch(this, txn, msg, &kFileOps);
830    } else if (hdr->ordinal >= fuchsia_io_DirectoryOpenOrdinal &&
831               hdr->ordinal <= fuchsia_io_DirectoryWatchOrdinal) {
832        return fuchsia_io_Directory_dispatch(this, txn, msg, &kDirectoryOps);
833    } else if (hdr->ordinal >= fuchsia_io_DirectoryAdminMountOrdinal &&
834               hdr->ordinal <= fuchsia_io_DirectoryAdminGetDevicePathOrdinal) {
835        return fuchsia_io_DirectoryAdmin_dispatch(this, txn, msg, &kDirectoryAdminOps);
836    } else {
837        return HandleFsSpecificMessage(msg, txn);
838    }
839}
840
841} // namespace fs
842