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