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 <dirent.h>
6 #include <fcntl.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <unistd.h>
10
11 #include <fuchsia/io/c/fidl.h>
12 #include <lib/fdio/unsafe.h>
13 #include <lib/fdio/watcher.h>
14 #include <zircon/syscalls.h>
15 #include <zircon/types.h>
16 #include <zircon/device/vfs.h>
17
18 typedef struct fdio_watcher {
19 zx_handle_t h;
20 watchdir_func_t func;
21 void* cookie;
22 int fd;
23 } fdio_watcher_t;
24
fdio_watcher_create(int dirfd,fdio_watcher_t ** out)25 static zx_status_t fdio_watcher_create(int dirfd, fdio_watcher_t** out) {
26 fdio_watcher_t* watcher;
27 if ((watcher = malloc(sizeof(fdio_watcher_t))) == NULL) {
28 return ZX_ERR_NO_MEMORY;
29 }
30
31 zx_handle_t client;
32 zx_status_t status, io_status;
33 if ((status = zx_channel_create(0, &client, &watcher->h)) != ZX_OK) {
34 goto fail_allocated;
35 }
36
37 fdio_t* io = fdio_unsafe_fd_to_io(dirfd);
38 zx_handle_t dir_channel = fdio_unsafe_borrow_channel(io);
39 if (dir_channel == ZX_HANDLE_INVALID) {
40 fdio_unsafe_release(io);
41 status = ZX_ERR_NOT_SUPPORTED;
42 goto fail_two_channels;
43 }
44
45 io_status = fuchsia_io_DirectoryWatch(dir_channel, fuchsia_io_WATCH_MASK_ALL, 0,
46 client, &status);
47 fdio_unsafe_release(io);
48 if (io_status != ZX_OK) {
49 status = io_status;
50 goto fail_one_channel;
51 } else if (status != ZX_OK) {
52 goto fail_one_channel;
53 }
54
55 *out = watcher;
56 return ZX_OK;
57
58 fail_two_channels:
59 zx_handle_close(client);
60 fail_one_channel:
61 zx_handle_close(watcher->h);
62 fail_allocated:
63 free(watcher);
64 return status;
65 }
66
67 // watcher process expects the msg buffer to be len + 1 in length
68 // as it drops temporary nuls in it while dispatching
fdio_watcher_process(fdio_watcher_t * w,uint8_t * msg,size_t len)69 static zx_status_t fdio_watcher_process(fdio_watcher_t* w, uint8_t* msg, size_t len) {
70 // Message Format: { OP, LEN, DATA[LEN] }
71 while (len >= 2) {
72 unsigned event = *msg++;
73 unsigned namelen = *msg++;
74
75 if (len < (namelen + 2u)) {
76 break;
77 }
78
79 switch (event) {
80 case fuchsia_io_WATCH_EVENT_ADDED:
81 case fuchsia_io_WATCH_EVENT_EXISTING:
82 event = WATCH_EVENT_ADD_FILE;
83 break;
84 case fuchsia_io_WATCH_EVENT_REMOVED:
85 event = WATCH_EVENT_REMOVE_FILE;
86 break;
87 case fuchsia_io_WATCH_EVENT_IDLE:
88 event = WATCH_EVENT_IDLE;
89 break;
90 default:
91 // unsupported event
92 continue;
93 }
94
95 uint8_t tmp = msg[namelen];
96 msg[namelen] = 0;
97
98 zx_status_t status;
99 if ((status = w->func(w->fd, event, (char*) msg, w->cookie)) != ZX_OK) {
100 return status;
101 }
102 msg[namelen] = tmp;
103 len -= (namelen + 2);
104 msg += namelen;
105 }
106
107 return ZX_OK;
108 }
109
fdio_watcher_loop(fdio_watcher_t * w,zx_time_t deadline)110 static zx_status_t fdio_watcher_loop(fdio_watcher_t* w, zx_time_t deadline) {
111 for (;;) {
112 // extra byte for watcher process use
113 uint8_t msg[fuchsia_io_MAX_BUF + 1];
114 uint32_t sz = fuchsia_io_MAX_BUF;
115 zx_status_t status;
116 if ((status = zx_channel_read(w->h, 0, msg, NULL, sz, 0, &sz, NULL)) < 0) {
117 if (status != ZX_ERR_SHOULD_WAIT) {
118 return status;
119 }
120 if ((status = zx_object_wait_one(w->h, ZX_CHANNEL_READABLE |
121 ZX_CHANNEL_PEER_CLOSED,
122 deadline, NULL)) < 0) {
123 return status;
124 }
125 continue;
126 }
127
128 if ((status = fdio_watcher_process(w, msg, sz)) != ZX_OK) {
129 return status;
130 }
131 }
132 }
133
fdio_watcher_destroy(fdio_watcher_t * watcher)134 static void fdio_watcher_destroy(fdio_watcher_t* watcher) {
135 zx_handle_close(watcher->h);
136 free(watcher);
137 }
138
139 __EXPORT
fdio_watch_directory(int dirfd,watchdir_func_t cb,zx_time_t deadline,void * cookie)140 zx_status_t fdio_watch_directory(int dirfd, watchdir_func_t cb, zx_time_t deadline, void *cookie) {
141 fdio_watcher_t* watcher = NULL;
142
143 zx_status_t status;
144 if ((status = fdio_watcher_create(dirfd, &watcher)) < 0) {
145 return status;
146 }
147
148 watcher->func = cb;
149 watcher->cookie = cookie;
150 watcher->fd = dirfd;
151 status = fdio_watcher_loop(watcher, deadline);
152
153 fdio_watcher_destroy(watcher);
154 return status;
155 }
156