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 <stdlib.h>
6 #include <string.h>
7 #include <sys/stat.h>
8 #include <unistd.h>
9
10 #ifdef __Fuchsia__
11 #include <zircon/device/vfs.h>
12 #include <zircon/syscalls.h>
13 #include <fbl/auto_lock.h>
14 #endif
15
16 #include <fbl/alloc_checker.h>
17 #include <fs/vfs.h>
18 #include <fs/vnode.h>
19 #include <fs/watcher.h>
20 #include <lib/zx/channel.h>
21
22 #include <utility>
23
24 namespace fs {
25
26 WatcherContainer::WatcherContainer() = default;
27 WatcherContainer::~WatcherContainer() = default;
28
VnodeWatcher(zx::channel h,uint32_t mask)29 WatcherContainer::VnodeWatcher::VnodeWatcher(zx::channel h, uint32_t mask) : h(std::move(h)),
30 mask(mask & ~(fuchsia_io_WATCH_MASK_EXISTING | fuchsia_io_WATCH_MASK_IDLE)) {}
31
~VnodeWatcher()32 WatcherContainer::VnodeWatcher::~VnodeWatcher() {}
33
34 // Transmission buffer for sending directory watcher notifications to clients.
35 // Allows enqueueing multiple messages in a buffer before sending an IPC message
36 // to a client.
37 class WatchBuffer {
38 public:
39 DISALLOW_COPY_ASSIGN_AND_MOVE(WatchBuffer);
40 WatchBuffer() = default;
41
42 zx_status_t AddMsg(const zx::channel& c, unsigned event, fbl::StringPiece name);
43 zx_status_t Send(const zx::channel& c);
44
45 private:
46 size_t watch_buf_size_ = 0;
47 char watch_buf_[fuchsia_io_MAX_BUF]{};
48 };
49
AddMsg(const zx::channel & c,unsigned event,fbl::StringPiece name)50 zx_status_t WatchBuffer::AddMsg(const zx::channel& c, unsigned event, fbl::StringPiece name) {
51 size_t slen = name.length();
52 size_t mlen = sizeof(vfs_watch_msg_t) + slen;
53 if (mlen + watch_buf_size_ > sizeof(watch_buf_)) {
54 // This message won't fit in the watch_buf; transmit first.
55 zx_status_t status = Send(c);
56 if (status != ZX_OK) {
57 return status;
58 }
59 }
60 vfs_watch_msg_t* vmsg = reinterpret_cast<vfs_watch_msg_t*>((uintptr_t)watch_buf_ + watch_buf_size_);
61 vmsg->event = static_cast<uint8_t>(event);
62 vmsg->len = static_cast<uint8_t>(slen);
63 memcpy(vmsg->name, name.data(), slen);
64 watch_buf_size_ += mlen;
65 return ZX_OK;
66 }
67
Send(const zx::channel & c)68 zx_status_t WatchBuffer::Send(const zx::channel& c) {
69 if (watch_buf_size_ > 0) {
70 // Only write if we have something to write
71 zx_status_t status = c.write(0, watch_buf_, static_cast<uint32_t>(watch_buf_size_),
72 nullptr, 0);
73 watch_buf_size_ = 0;
74 if (status != ZX_OK) {
75 return status;
76 }
77 }
78 return ZX_OK;
79 }
80
WatchDir(Vfs * vfs,Vnode * vn,uint32_t mask,uint32_t options,zx::channel channel)81 zx_status_t WatcherContainer::WatchDir(Vfs* vfs, Vnode* vn, uint32_t mask, uint32_t options,
82 zx::channel channel) {
83 if ((mask & fuchsia_io_WATCH_MASK_ALL) == 0) {
84 // No events to watch
85 return ZX_ERR_INVALID_ARGS;
86 }
87
88 fbl::AllocChecker ac;
89 fbl::unique_ptr<VnodeWatcher> watcher(new (&ac) VnodeWatcher(std::move(channel), mask));
90 if (!ac.check()) {
91 return ZX_ERR_NO_MEMORY;
92 }
93
94 if (mask & fuchsia_io_WATCH_MASK_EXISTING) {
95 vdircookie_t dircookie;
96 memset(&dircookie, 0, sizeof(dircookie));
97 char readdir_buf[FDIO_CHUNK_SIZE];
98 WatchBuffer wb;
99 {
100 // Send "fuchsia_io_WATCH_EVENT_EXISTING" for all entries in readdir
101 while (true) {
102 size_t actual;
103 zx_status_t status = vfs->Readdir(vn, &dircookie, readdir_buf,
104 sizeof(readdir_buf), &actual);
105 if (status != ZX_OK || actual == 0) {
106 break;
107 }
108 void* ptr = readdir_buf;
109 while (actual >= sizeof(vdirent_t)) {
110 auto dirent = reinterpret_cast<vdirent_t*>(ptr);
111 if (dirent->name[0]) {
112 wb.AddMsg(watcher->h, fuchsia_io_WATCH_EVENT_EXISTING,
113 fbl::StringPiece(dirent->name, dirent->size));
114 }
115 size_t entry_len = dirent->size + sizeof(vdirent_t);
116 ZX_ASSERT(entry_len <= actual); // Prevent underflow
117 actual -= entry_len;
118 ptr = reinterpret_cast<void*>(
119 static_cast<uintptr_t>(entry_len) +
120 reinterpret_cast<uintptr_t>(ptr));
121 }
122 }
123 }
124
125 // Send fuchsia_io_WATCH_EVENT_IDLE to signify that readdir has completed
126 if (mask & fuchsia_io_WATCH_MASK_IDLE) {
127 wb.AddMsg(watcher->h, fuchsia_io_WATCH_EVENT_IDLE, "");
128 }
129
130 wb.Send(watcher->h);
131 }
132
133 fbl::AutoLock lock(&lock_);
134 watch_list_.push_back(std::move(watcher));
135 return ZX_OK;
136 }
137
Notify(fbl::StringPiece name,unsigned event)138 void WatcherContainer::Notify(fbl::StringPiece name, unsigned event) {
139 if (name.length() > fuchsia_io_MAX_FILENAME) {
140 return;
141 }
142
143 fbl::AutoLock lock(&lock_);
144
145 if (watch_list_.is_empty()) {
146 return;
147 }
148
149 uint8_t msg[sizeof(vfs_watch_msg_t) + name.length()];
150 vfs_watch_msg_t* vmsg = reinterpret_cast<vfs_watch_msg_t*>(msg);
151 vmsg->event = static_cast<uint8_t>(event);
152 vmsg->len = static_cast<uint8_t>(name.length());
153 memcpy(vmsg->name, name.data(), name.length());
154
155 for (auto it = watch_list_.begin(); it != watch_list_.end();) {
156 if (!(it->mask & (1 << event))) {
157 ++it;
158 continue;
159 }
160
161 zx_status_t status = it->h.write(0, msg, static_cast<uint32_t>(sizeof(msg)), nullptr, 0);
162 if (status < 0) {
163 // Lazily remove watchers when their handles cannot accept incoming
164 // watch messages.
165 auto to_remove = it;
166 ++it;
167 watch_list_.erase(to_remove);
168 } else {
169 ++it;
170 }
171 }
172 }
173
174 } // namespace fs
175