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 <unistd.h>
9
10 #include <fbl/auto_call.h>
11 #include <fs/trace.h>
12 #include <fs/vfs.h>
13 #include <fs/vnode.h>
14 #include <lib/fdio/watcher.h>
15
16 #ifdef __Fuchsia__
17 #include <threads.h>
18
19 #include <fbl/auto_lock.h>
20 #include <fbl/ref_ptr.h>
21 #include <fs/connection.h>
22 #include <fs/remote.h>
23 #include <lib/zx/event.h>
24 #include <lib/zx/process.h>
25 #include <zircon/assert.h>
26
27 #include <utility>
28 #endif
29
30 namespace fs {
31 namespace {
32
33 // Trim a name before sending it to internal filesystem functions.
34 // Trailing '/' characters imply that the name must refer to a directory.
vfs_name_trim(fbl::StringPiece name,fbl::StringPiece * name_out,bool * dir_out)35 zx_status_t vfs_name_trim(fbl::StringPiece name, fbl::StringPiece* name_out,
36 bool* dir_out) {
37 size_t len = name.length();
38 bool is_dir = false;
39 while ((len > 0) && name[len - 1] == '/') {
40 len--;
41 is_dir = true;
42 }
43
44 // 'name' should not contain paths consisting of exclusively '/' characters.
45 if (len == 0) {
46 return ZX_ERR_INVALID_ARGS;
47 } else if (len > NAME_MAX) {
48 return ZX_ERR_BAD_PATH;
49 }
50
51 name_out->set(name.data(), len);
52 *dir_out = is_dir;
53 return ZX_OK;
54 }
55
vfs_lookup(fbl::RefPtr<Vnode> vn,fbl::RefPtr<Vnode> * out,fbl::StringPiece name)56 zx_status_t vfs_lookup(fbl::RefPtr<Vnode> vn, fbl::RefPtr<Vnode>* out,
57 fbl::StringPiece name) {
58 if (name == "..") {
59 return ZX_ERR_INVALID_ARGS;
60 } else if (name == ".") {
61 *out = std::move(vn);
62 return ZX_OK;
63 }
64 return vn->Lookup(out, name);
65 }
66
67 // Validate open flags as much as they can be validated
68 // independently of the target node.
vfs_prevalidate_flags(uint32_t flags)69 zx_status_t vfs_prevalidate_flags(uint32_t flags) {
70 if (!(flags & ZX_FS_RIGHT_WRITABLE)) {
71 if (flags & ZX_FS_FLAG_TRUNCATE) {
72 return ZX_ERR_INVALID_ARGS;
73 }
74 } else if (!(flags & ZX_FS_RIGHTS)) {
75 if (!IsPathOnly(flags)) {
76 return ZX_ERR_INVALID_ARGS;
77 }
78 }
79 return ZX_OK;
80 }
81
82 } // namespace
83
84 #ifdef __Fuchsia__
85
IsRemote() const86 bool RemoteContainer::IsRemote() const {
87 return remote_.is_valid();
88 }
89
DetachRemote()90 zx::channel RemoteContainer::DetachRemote() {
91 return std::move(remote_);
92 }
93
GetRemote() const94 zx_handle_t RemoteContainer::GetRemote() const {
95 return remote_.get();
96 }
97
SetRemote(zx::channel remote)98 void RemoteContainer::SetRemote(zx::channel remote) {
99 ZX_DEBUG_ASSERT(!remote_.is_valid());
100 remote_ = std::move(remote);
101 }
102
103 #endif
104
105 Vfs::Vfs() = default;
106 Vfs::~Vfs() = default;
107
108 #ifdef __Fuchsia__
Vfs(async_dispatcher_t * dispatcher)109 Vfs::Vfs(async_dispatcher_t* dispatcher)
110 : dispatcher_(dispatcher) {}
111 #endif
112
Open(fbl::RefPtr<Vnode> vndir,fbl::RefPtr<Vnode> * out,fbl::StringPiece path,fbl::StringPiece * out_path,uint32_t flags,uint32_t mode)113 zx_status_t Vfs::Open(fbl::RefPtr<Vnode> vndir, fbl::RefPtr<Vnode>* out,
114 fbl::StringPiece path, fbl::StringPiece* out_path, uint32_t flags,
115 uint32_t mode) {
116 #ifdef __Fuchsia__
117 fbl::AutoLock lock(&vfs_lock_);
118 #endif
119 return OpenLocked(std::move(vndir), out, path, out_path, flags, mode);
120 }
121
OpenLocked(fbl::RefPtr<Vnode> vndir,fbl::RefPtr<Vnode> * out,fbl::StringPiece path,fbl::StringPiece * out_path,uint32_t flags,uint32_t mode)122 zx_status_t Vfs::OpenLocked(fbl::RefPtr<Vnode> vndir, fbl::RefPtr<Vnode>* out,
123 fbl::StringPiece path, fbl::StringPiece* out_path,
124 uint32_t flags, uint32_t mode) {
125 FS_TRACE_DEBUG("VfsOpen: path='%s' flags=%d\n", path.begin(), flags);
126 zx_status_t r;
127 if ((r = vfs_prevalidate_flags(flags)) != ZX_OK) {
128 return r;
129 }
130 if ((r = Vfs::Walk(vndir, &vndir, path, &path)) < 0) {
131 return r;
132 }
133 #ifdef __Fuchsia__
134 if (vndir->IsRemote()) {
135 // remote filesystem, return handle and path through to caller
136 *out = std::move(vndir);
137 *out_path = path;
138 return ZX_OK;
139 }
140 #endif
141
142 fbl::RefPtr<Vnode> vn;
143
144 bool must_be_dir = false;
145 if ((r = vfs_name_trim(path, &path, &must_be_dir)) != ZX_OK) {
146 return r;
147 } else if (path == "..") {
148 return ZX_ERR_INVALID_ARGS;
149 }
150
151 if (flags & ZX_FS_FLAG_CREATE) {
152 if (must_be_dir && !S_ISDIR(mode)) {
153 return ZX_ERR_INVALID_ARGS;
154 } else if (path == ".") {
155 return ZX_ERR_INVALID_ARGS;
156 } else if (ReadonlyLocked()) {
157 return ZX_ERR_ACCESS_DENIED;
158 }
159 if ((r = vndir->Create(&vn, path, mode)) < 0) {
160 if ((r == ZX_ERR_ALREADY_EXISTS) && (!(flags & ZX_FS_FLAG_EXCLUSIVE))) {
161 goto try_open;
162 }
163 if (r == ZX_ERR_NOT_SUPPORTED) {
164 // filesystem may not support create (like devfs)
165 // in which case we should still try to open() the file
166 goto try_open;
167 }
168 return r;
169 }
170 #ifdef __Fuchsia__
171 vndir->Notify(path, fuchsia_io_WATCH_EVENT_ADDED);
172 #endif
173 } else {
174 try_open:
175 r = vfs_lookup(std::move(vndir), &vn, path);
176 if (r < 0) {
177 return r;
178 }
179 #ifdef __Fuchsia__
180 if (!(flags & ZX_FS_FLAG_NOREMOTE) && vn->IsRemote()) {
181 // Opening a mount point: Traverse across remote.
182 *out_path = ".";
183 *out = std::move(vn);
184 return ZX_OK;
185 }
186
187 flags |= (must_be_dir ? ZX_FS_FLAG_DIRECTORY : 0);
188 #endif
189 if (ReadonlyLocked() && IsWritable(flags)) {
190 return ZX_ERR_ACCESS_DENIED;
191 }
192 if ((r = vn->ValidateFlags(flags)) != ZX_OK) {
193 return r;
194 }
195 // VNODE_REF_ONLY requests that we don't actually open the underlying
196 // Vnode.
197 if (!IsPathOnly(flags)) {
198 if ((r = OpenVnode(flags, &vn)) != ZX_OK) {
199 return r;
200 }
201 if ((flags & ZX_FS_FLAG_TRUNCATE) && ((r = vn->Truncate(0)) < 0)) {
202 vn->Close();
203 return r;
204 }
205 }
206 }
207 FS_TRACE_DEBUG("VfsOpen: vn=%p\n", vn.get());
208 *out_path = "";
209 *out = vn;
210 return ZX_OK;
211 }
212
Unlink(fbl::RefPtr<Vnode> vndir,fbl::StringPiece path)213 zx_status_t Vfs::Unlink(fbl::RefPtr<Vnode> vndir, fbl::StringPiece path) {
214 bool must_be_dir;
215 zx_status_t r;
216 if ((r = vfs_name_trim(path, &path, &must_be_dir)) != ZX_OK) {
217 return r;
218 } else if (path == ".") {
219 return ZX_ERR_UNAVAILABLE;
220 } else if (path == "..") {
221 return ZX_ERR_INVALID_ARGS;
222 }
223
224 {
225 #ifdef __Fuchsia__
226 fbl::AutoLock lock(&vfs_lock_);
227 #endif
228 if (ReadonlyLocked()) {
229 r = ZX_ERR_ACCESS_DENIED;
230 } else {
231 r = vndir->Unlink(path, must_be_dir);
232 }
233 }
234 if (r != ZX_OK) {
235 return r;
236 }
237 #ifdef __Fuchsia__
238 vndir->Notify(path, fuchsia_io_WATCH_EVENT_REMOVED);
239 #endif
240 return ZX_OK;
241 }
242
243 #ifdef __Fuchsia__
244
245 #define TOKEN_RIGHTS (ZX_RIGHTS_BASIC)
246
TokenDiscard(zx::event ios_token)247 void Vfs::TokenDiscard(zx::event ios_token) {
248 fbl::AutoLock lock(&vfs_lock_);
249 if (ios_token) {
250 // The token is cleared here to prevent the following race condition:
251 // 1) Open
252 // 2) GetToken
253 // 3) Close + Release Vnode
254 // 4) Use token handle to access defunct vnode (or a different vnode,
255 // if the memory for it is reallocated).
256 //
257 // By cleared the token cookie, any remaining handles to the event will
258 // be ignored by the filesystem server.
259 ios_token.set_cookie(*zx::process::self(), 0);
260 }
261 }
262
VnodeToToken(fbl::RefPtr<Vnode> vn,zx::event * ios_token,zx::event * out)263 zx_status_t Vfs::VnodeToToken(fbl::RefPtr<Vnode> vn, zx::event* ios_token,
264 zx::event* out) {
265 uint64_t vnode_cookie = reinterpret_cast<uint64_t>(vn.get());
266 zx_status_t r;
267
268 fbl::AutoLock lock(&vfs_lock_);
269 if (ios_token->is_valid()) {
270 // Token has already been set for this iostate
271 if ((r = ios_token->duplicate(TOKEN_RIGHTS, out) != ZX_OK)) {
272 return r;
273 }
274 return ZX_OK;
275 }
276
277 zx::event new_token;
278 zx::event new_ios_token;
279 if ((r = zx::event::create(0, &new_ios_token)) != ZX_OK) {
280 return r;
281 } else if ((r = new_ios_token.duplicate(TOKEN_RIGHTS, &new_token) != ZX_OK)) {
282 return r;
283 } else if ((r = new_ios_token.set_cookie(*zx::process::self(), vnode_cookie)) != ZX_OK) {
284 return r;
285 }
286 *ios_token = std::move(new_ios_token);
287 *out = std::move(new_token);
288 return ZX_OK;
289 }
290
TokenToVnode(zx::event token,fbl::RefPtr<Vnode> * out)291 zx_status_t Vfs::TokenToVnode(zx::event token, fbl::RefPtr<Vnode>* out) {
292 uint64_t vcookie;
293 zx_status_t r;
294 if ((r = token.get_cookie(*zx::process::self(), &vcookie)) < 0) {
295 // TODO(smklein): Return a more specific error code for "token not from this server"
296 return ZX_ERR_INVALID_ARGS;
297 }
298
299 if (vcookie == 0) {
300 // Client closed the channel associated with the token
301 return ZX_ERR_INVALID_ARGS;
302 }
303
304 *out = fbl::RefPtr<fs::Vnode>(reinterpret_cast<fs::Vnode*>(vcookie));
305 return ZX_OK;
306 }
307
Rename(zx::event token,fbl::RefPtr<Vnode> oldparent,fbl::StringPiece oldStr,fbl::StringPiece newStr)308 zx_status_t Vfs::Rename(zx::event token, fbl::RefPtr<Vnode> oldparent,
309 fbl::StringPiece oldStr, fbl::StringPiece newStr) {
310 // Local filesystem
311 bool old_must_be_dir;
312 bool new_must_be_dir;
313 zx_status_t r;
314 if ((r = vfs_name_trim(oldStr, &oldStr, &old_must_be_dir)) != ZX_OK) {
315 return r;
316 } else if (oldStr == ".") {
317 return ZX_ERR_UNAVAILABLE;
318 } else if (oldStr == "..") {
319 return ZX_ERR_INVALID_ARGS;
320 }
321
322 if ((r = vfs_name_trim(newStr, &newStr, &new_must_be_dir)) != ZX_OK) {
323 return r;
324 } else if (newStr == "." || newStr == "..") {
325 return ZX_ERR_INVALID_ARGS;
326 }
327
328 fbl::RefPtr<fs::Vnode> newparent;
329 {
330 fbl::AutoLock lock(&vfs_lock_);
331 if (ReadonlyLocked()) {
332 return ZX_ERR_ACCESS_DENIED;
333 }
334 if ((r = TokenToVnode(std::move(token), &newparent)) != ZX_OK) {
335 return r;
336 }
337
338 r = oldparent->Rename(newparent, oldStr, newStr, old_must_be_dir,
339 new_must_be_dir);
340 }
341 if (r != ZX_OK) {
342 return r;
343 }
344 oldparent->Notify(oldStr, fuchsia_io_WATCH_EVENT_REMOVED);
345 newparent->Notify(newStr, fuchsia_io_WATCH_EVENT_ADDED);
346 return ZX_OK;
347 }
348
Readdir(Vnode * vn,vdircookie_t * cookie,void * dirents,size_t len,size_t * out_actual)349 zx_status_t Vfs::Readdir(Vnode* vn, vdircookie_t* cookie,
350 void* dirents, size_t len, size_t* out_actual) {
351 fbl::AutoLock lock(&vfs_lock_);
352 return vn->Readdir(cookie, dirents, len, out_actual);
353 }
354
Link(zx::event token,fbl::RefPtr<Vnode> oldparent,fbl::StringPiece oldStr,fbl::StringPiece newStr)355 zx_status_t Vfs::Link(zx::event token, fbl::RefPtr<Vnode> oldparent,
356 fbl::StringPiece oldStr, fbl::StringPiece newStr) {
357 fbl::AutoLock lock(&vfs_lock_);
358 fbl::RefPtr<fs::Vnode> newparent;
359 zx_status_t r;
360 if ((r = TokenToVnode(std::move(token), &newparent)) != ZX_OK) {
361 return r;
362 }
363 // Local filesystem
364 bool old_must_be_dir;
365 bool new_must_be_dir;
366 if (ReadonlyLocked()) {
367 return ZX_ERR_ACCESS_DENIED;
368 } else if ((r = vfs_name_trim(oldStr, &oldStr, &old_must_be_dir)) != ZX_OK) {
369 return r;
370 } else if (old_must_be_dir) {
371 return ZX_ERR_NOT_DIR;
372 } else if (oldStr == ".") {
373 return ZX_ERR_UNAVAILABLE;
374 } else if (oldStr == "..") {
375 return ZX_ERR_INVALID_ARGS;
376 }
377
378 if ((r = vfs_name_trim(newStr, &newStr, &new_must_be_dir)) != ZX_OK) {
379 return r;
380 } else if (new_must_be_dir) {
381 return ZX_ERR_NOT_DIR;
382 } else if (newStr == "." || newStr == "..") {
383 return ZX_ERR_INVALID_ARGS;
384 }
385
386 // Look up the target vnode
387 fbl::RefPtr<Vnode> target;
388 if ((r = oldparent->Lookup(&target, oldStr)) < 0) {
389 return r;
390 }
391 r = newparent->Link(newStr, target);
392 if (r != ZX_OK) {
393 return r;
394 }
395 newparent->Notify(newStr, fuchsia_io_WATCH_EVENT_ADDED);
396 return ZX_OK;
397 }
398
ServeConnection(fbl::unique_ptr<Connection> connection)399 zx_status_t Vfs::ServeConnection(fbl::unique_ptr<Connection> connection) {
400 ZX_DEBUG_ASSERT(connection);
401
402 zx_status_t status = connection->Serve();
403 if (status == ZX_OK) {
404 RegisterConnection(std::move(connection));
405 }
406 return status;
407 }
408
OnConnectionClosedRemotely(Connection * connection)409 void Vfs::OnConnectionClosedRemotely(Connection* connection) {
410 ZX_DEBUG_ASSERT(connection);
411
412 UnregisterConnection(connection);
413 }
414
ServeDirectory(fbl::RefPtr<fs::Vnode> vn,zx::channel channel)415 zx_status_t Vfs::ServeDirectory(fbl::RefPtr<fs::Vnode> vn, zx::channel channel) {
416 uint32_t flags = ZX_FS_FLAG_DIRECTORY;
417 zx_status_t r;
418 if ((r = vn->ValidateFlags(flags)) != ZX_OK) {
419 return r;
420 } else if ((r = OpenVnode(flags, &vn)) != ZX_OK) {
421 return r;
422 }
423
424 // Tell the calling process that we've mounted the directory.
425 r = channel.signal_peer(0, ZX_USER_SIGNAL_0);
426 // ZX_ERR_PEER_CLOSED is ok because the channel may still be readable.
427 if (r != ZX_OK && r != ZX_ERR_PEER_CLOSED) {
428 return r;
429 }
430
431 return vn->Serve(this, std::move(channel), ZX_FS_RIGHT_ADMIN);
432 }
433
434 #endif // ifdef __Fuchsia__
435
SetReadonly(bool value)436 void Vfs::SetReadonly(bool value) {
437 #ifdef __Fuchsia__
438 fbl::AutoLock lock(&vfs_lock_);
439 #endif
440 readonly_ = value;
441 }
442
Walk(fbl::RefPtr<Vnode> vn,fbl::RefPtr<Vnode> * out_vn,fbl::StringPiece path,fbl::StringPiece * out_path)443 zx_status_t Vfs::Walk(fbl::RefPtr<Vnode> vn, fbl::RefPtr<Vnode>* out_vn,
444 fbl::StringPiece path, fbl::StringPiece* out_path) {
445 zx_status_t r;
446 while (!path.empty() && path[path.length() - 1] == '/') {
447 // Discard extra trailing '/' characters.
448 path.set(path.data(), path.length() - 1);
449 }
450
451 for (;;) {
452 while (!path.empty() && path[0] == '/') {
453 // Discard extra leading '/' characters.
454 path.set(&path[1], path.length() - 1);
455 }
456 if (path.empty()) {
457 // Convert empty initial path of final path segment to ".".
458 path.set(".", 1);
459 }
460 #ifdef __Fuchsia__
461 if (vn->IsRemote()) {
462 // Remote filesystem mount, caller must resolve.
463 *out_vn = std::move(vn);
464 *out_path = std::move(path);
465 return ZX_OK;
466 }
467 #endif
468
469 // Look for the next '/' separated path component.
470 const char* next_path = reinterpret_cast<const char*>(
471 memchr(path.data(), '/', path.length()));
472 if (next_path == nullptr) {
473 // Final path segment.
474 *out_vn = vn;
475 *out_path = std::move(path);
476 return ZX_OK;
477 }
478
479 // Path has at least one additional segment.
480 fbl::StringPiece component(path.data(), next_path - path.data());
481 if ((r = vfs_lookup(std::move(vn), &vn, component)) != ZX_OK) {
482 return r;
483 }
484 // Traverse to the next segment.
485 path.set(next_path + 1, path.length() - (component.length() + 1));
486 }
487 }
488
489 } // namespace fs
490