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