1#![deny(missing_docs)]
8
9use flex_client::{MessageBuf, ProxyHasDomain};
10use flex_fuchsia_io as fio;
11use futures::stream::{FusedStream, Stream};
12use std::ffi::OsStr;
13use std::os::unix::ffi::OsStrExt;
14use std::path::PathBuf;
15use std::pin::Pin;
16use std::task::{Context, Poll};
17use thiserror::Error;
18
19#[cfg(not(feature = "fdomain"))]
20use fuchsia_async as fasync;
21
22#[derive(Debug, Error, Clone)]
23#[allow(missing_docs)]
24pub enum WatcherCreateError {
25 #[error("while sending watch request: {0}")]
26 SendWatchRequest(#[source] fidl::Error),
27
28 #[error("watch failed with status: {0}")]
29 WatchError(#[source] zx_status::Status),
30
31 #[error("while converting client end to fasync channel: {0}")]
32 ChannelConversion(#[source] zx_status::Status),
33}
34
35#[derive(Debug, Error)]
36#[cfg_attr(not(feature = "fdomain"), derive(Eq, PartialEq))]
37#[allow(missing_docs)]
38pub enum WatcherStreamError {
39 #[cfg(not(feature = "fdomain"))]
40 #[error("read from watch channel failed with status: {0}")]
41 ChannelRead(#[from] zx_status::Status),
42 #[cfg(feature = "fdomain")]
43 #[error("read from watch channel failed: {0}")]
44 ChannelRead(#[from] flex_client::Error),
45}
46
47#[repr(C)]
49#[derive(Copy, Clone, Eq, PartialEq)]
50pub struct WatchEvent(fio::WatchEvent);
51
52impl WatchEvent {
53 pub const DELETED: Self = Self(fio::WatchEvent::Deleted);
56 pub const ADD_FILE: Self = Self(fio::WatchEvent::Added);
58 pub const REMOVE_FILE: Self = Self(fio::WatchEvent::Removed);
60 pub const EXISTING: Self = Self(fio::WatchEvent::Existing);
62 pub const IDLE: Self = Self(fio::WatchEvent::Idle);
64
65 const fn assoc_const_name(&self) -> &'static str {
66 match self.0 {
67 fio::WatchEvent::Deleted => "DELETED",
68 fio::WatchEvent::Added => "ADD_FILE",
69 fio::WatchEvent::Removed => "REMOVE_FILE",
70 fio::WatchEvent::Existing => "EXISTING",
71 fio::WatchEvent::Idle => "IDLE",
72 }
73 }
74}
75
76impl std::fmt::Debug for WatchEvent {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 write!(f, "WatchEvent({})", self.assoc_const_name())
79 }
80}
81
82#[derive(Debug, Eq, PartialEq)]
85pub struct WatchMessage {
86 pub event: WatchEvent,
88 pub filename: PathBuf,
90}
91
92#[derive(Debug, Eq, PartialEq)]
93enum WatcherState {
94 Watching,
95 TerminateOnNextPoll,
96 Terminated,
97}
98
99#[derive(Debug)]
103#[must_use = "futures/streams must be polled"]
104pub struct Watcher {
105 ch: flex_client::AsyncChannel,
106 buf: MessageBuf,
108 idx: usize,
109 state: WatcherState,
110}
111
112impl Unpin for Watcher {}
113
114impl Watcher {
115 pub async fn new(dir: &fio::DirectoryProxy) -> Result<Watcher, WatcherCreateError> {
117 Self::new_with_mask(dir, fio::WatchMask::all()).await
118 }
119
120 pub async fn new_with_mask(
123 dir: &fio::DirectoryProxy,
124 mask: fio::WatchMask,
125 ) -> Result<Watcher, WatcherCreateError> {
126 let (client_end, server_end) = dir.domain().create_endpoints();
127 let options = 0u32;
128 let status = dir
129 .watch(mask, options, server_end)
130 .await
131 .map_err(WatcherCreateError::SendWatchRequest)?;
132 zx_status::Status::ok(status).map_err(WatcherCreateError::WatchError)?;
133 let mut buf = MessageBuf::new();
134 buf.ensure_capacity_bytes(fio::MAX_BUF as usize);
135 Ok(Watcher {
136 #[cfg(not(feature = "fdomain"))]
137 ch: fasync::Channel::from_channel(client_end.into_channel()),
138 #[cfg(feature = "fdomain")]
139 ch: client_end.into_channel(),
140 buf,
141 idx: 0,
142 state: WatcherState::Watching,
143 })
144 }
145
146 fn reset_buf(&mut self) {
147 self.idx = 0;
148 self.buf.clear();
149 }
150
151 fn get_next_msg(&mut self) -> WatchMessage {
152 assert!(self.idx < self.buf.bytes().len());
153 let next_msg = VfsWatchMsg::from_raw(&self.buf.bytes()[self.idx..])
154 .expect("Invalid buffer received by Watcher!");
155 self.idx += next_msg.len();
156
157 let mut pathbuf = PathBuf::new();
158 pathbuf.push(OsStr::from_bytes(next_msg.name()));
159 let event = next_msg.event();
160 WatchMessage { event, filename: pathbuf }
161 }
162}
163
164impl FusedStream for Watcher {
165 fn is_terminated(&self) -> bool {
166 self.state == WatcherState::Terminated
167 }
168}
169
170impl Stream for Watcher {
171 type Item = Result<WatchMessage, WatcherStreamError>;
172
173 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
174 let this = &mut *self;
175 if this.state == WatcherState::TerminateOnNextPoll {
178 this.state = WatcherState::Terminated;
179 }
180 if this.state == WatcherState::Terminated {
181 return Poll::Ready(None);
182 }
183 if this.idx >= this.buf.bytes().len() {
184 this.reset_buf();
185 }
186 if this.idx == 0 {
187 match this.ch.recv_from(cx, &mut this.buf) {
188 Poll::Ready(Ok(())) => {}
189 Poll::Ready(Err(e)) => {
190 self.state = WatcherState::TerminateOnNextPoll;
191 return Poll::Ready(Some(Err(e.into())));
192 }
193 Poll::Pending => return Poll::Pending,
194 }
195 }
196 Poll::Ready(Some(Ok(this.get_next_msg())))
197 }
198}
199
200#[repr(C)]
201#[derive(Default)]
202struct IncompleteArrayField<T>(::std::marker::PhantomData<T>);
203impl<T> IncompleteArrayField<T> {
204 #[inline]
205 pub unsafe fn as_ptr(&self) -> *const T {
206 unsafe { ::std::mem::transmute(self) }
207 }
208 #[inline]
209 pub unsafe fn as_slice(&self, len: usize) -> &[T] {
210 unsafe { ::std::slice::from_raw_parts(self.as_ptr(), len) }
211 }
212}
213impl<T> ::std::fmt::Debug for IncompleteArrayField<T> {
214 fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
215 fmt.write_str("IncompleteArrayField")
216 }
217}
218
219#[repr(C)]
220#[derive(Debug)]
221struct vfs_watch_msg_t {
222 event: fio::WatchEvent,
223 len: u8,
224 name: IncompleteArrayField<u8>,
225}
226
227#[derive(Debug)]
228struct VfsWatchMsg<'a> {
229 inner: &'a vfs_watch_msg_t,
230}
231
232impl<'a> VfsWatchMsg<'a> {
233 fn from_raw(buf: &'a [u8]) -> Option<VfsWatchMsg<'a>> {
234 if buf.len() < ::std::mem::size_of::<vfs_watch_msg_t>() {
235 return None;
236 }
237 let m = unsafe { VfsWatchMsg { inner: &*(buf.as_ptr() as *const vfs_watch_msg_t) } };
241 if buf.len() < ::std::mem::size_of::<vfs_watch_msg_t>() + m.namelen() {
242 return None;
243 }
244 Some(m)
245 }
246
247 fn len(&self) -> usize {
248 ::std::mem::size_of::<vfs_watch_msg_t>() + self.namelen()
249 }
250
251 fn event(&self) -> WatchEvent {
252 WatchEvent(self.inner.event)
253 }
254
255 fn namelen(&self) -> usize {
256 self.inner.len as usize
257 }
258
259 fn name(&self) -> &'a [u8] {
260 unsafe { self.inner.name.as_slice(self.namelen()) }
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269 use assert_matches::assert_matches;
270 use fuchsia_async::{DurationExt, TimeoutExt};
271
272 use futures::prelude::*;
273 use std::fmt::Debug;
274 use std::fs::File;
275 use std::path::Path;
276 use std::sync::Arc;
277 use tempfile::tempdir;
278 use vfs::ObjectRequestRef;
279 use vfs::directory::dirents_sink;
280 use vfs::directory::entry::{EntryInfo, GetEntryInfo};
281 use vfs::directory::entry_container::{Directory, DirectoryWatcher};
282 use vfs::directory::immutable::connection::ImmutableConnection;
283 use vfs::directory::traversal_position::TraversalPosition;
284 use vfs::execution_scope::ExecutionScope;
285 use vfs::node::Node;
286
287 fn one_step<'a, S, OK, ERR>(s: &'a mut S) -> impl Future<Output = OK> + 'a
288 where
289 S: Stream<Item = Result<OK, ERR>> + Unpin,
290 ERR: Debug,
291 {
292 let f = s.next();
293 let f = f.on_timeout(zx::MonotonicDuration::from_millis(500).after_now(), || {
294 panic!("timeout waiting for watcher")
295 });
296 f.map(|next| {
297 next.expect("the stream yielded no next item")
298 .unwrap_or_else(|e| panic!("Error waiting for watcher: {:?}", e))
299 })
300 }
301
302 #[fuchsia::test]
303 async fn test_existing() {
304 let tmp_dir = tempdir().unwrap();
305 let _ = File::create(tmp_dir.path().join("file1")).unwrap();
306
307 let dir = crate::directory::open_in_namespace(
308 tmp_dir.path().to_str().unwrap(),
309 fio::PERM_READABLE,
310 )
311 .unwrap();
312 let mut w = Watcher::new(&dir).await.unwrap();
313
314 let msg = one_step(&mut w).await;
315 assert_eq!(WatchEvent::EXISTING, msg.event);
316 assert_eq!(Path::new("."), msg.filename);
317
318 let msg = one_step(&mut w).await;
319 assert_eq!(WatchEvent::EXISTING, msg.event);
320 assert_eq!(Path::new("file1"), msg.filename);
321
322 let msg = one_step(&mut w).await;
323 assert_eq!(WatchEvent::IDLE, msg.event);
324 }
325
326 #[fuchsia::test]
327 async fn test_add() {
328 let tmp_dir = tempdir().unwrap();
329
330 let dir = crate::directory::open_in_namespace(
331 tmp_dir.path().to_str().unwrap(),
332 fio::PERM_READABLE,
333 )
334 .unwrap();
335 let mut w = Watcher::new(&dir).await.unwrap();
336
337 loop {
338 let msg = one_step(&mut w).await;
339 match msg.event {
340 WatchEvent::EXISTING => continue,
341 WatchEvent::IDLE => break,
342 _ => panic!("Unexpected watch event!"),
343 }
344 }
345
346 let _ = File::create(tmp_dir.path().join("file1")).unwrap();
347 let msg = one_step(&mut w).await;
348 assert_eq!(WatchEvent::ADD_FILE, msg.event);
349 assert_eq!(Path::new("file1"), msg.filename);
350 }
351
352 #[fuchsia::test]
353 async fn test_remove() {
354 let tmp_dir = tempdir().unwrap();
355
356 let filename = "file1";
357 let filepath = tmp_dir.path().join(filename);
358 let _ = File::create(&filepath).unwrap();
359
360 let dir = crate::directory::open_in_namespace(
361 tmp_dir.path().to_str().unwrap(),
362 fio::PERM_READABLE,
363 )
364 .unwrap();
365 let mut w = Watcher::new(&dir).await.unwrap();
366
367 loop {
368 let msg = one_step(&mut w).await;
369 match msg.event {
370 WatchEvent::EXISTING => continue,
371 WatchEvent::IDLE => break,
372 _ => panic!("Unexpected watch event!"),
373 }
374 }
375
376 ::std::fs::remove_file(&filepath).unwrap();
377 let msg = one_step(&mut w).await;
378 assert_eq!(WatchEvent::REMOVE_FILE, msg.event);
379 assert_eq!(Path::new(filename), msg.filename);
380 }
381
382 struct MockDirectory;
383
384 impl MockDirectory {
385 fn new() -> Arc<Self> {
386 Arc::new(Self)
387 }
388 }
389
390 impl GetEntryInfo for MockDirectory {
391 fn entry_info(&self) -> EntryInfo {
392 EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
393 }
394 }
395
396 impl Node for MockDirectory {
397 async fn get_attributes(
398 &self,
399 _query: fio::NodeAttributesQuery,
400 ) -> Result<fio::NodeAttributes2, zx::Status> {
401 unimplemented!();
402 }
403
404 fn close(self: Arc<Self>) {}
405 }
406
407 impl Directory for MockDirectory {
408 fn open(
409 self: Arc<Self>,
410 scope: ExecutionScope,
411 _path: vfs::path::Path,
412 flags: fio::Flags,
413 object_request: ObjectRequestRef<'_>,
414 ) -> Result<(), zx::Status> {
415 object_request.take().create_connection_sync::<ImmutableConnection<_>, _>(
416 scope,
417 self.clone(),
418 flags,
419 );
420 Ok(())
421 }
422
423 async fn read_dirents(
424 &self,
425 _pos: &TraversalPosition,
426 _sink: Box<dyn dirents_sink::Sink>,
427 ) -> Result<(TraversalPosition, Box<dyn dirents_sink::Sealed>), zx::Status> {
428 unimplemented!("Not implemented");
429 }
430
431 fn register_watcher(
432 self: Arc<Self>,
433 _scope: ExecutionScope,
434 _mask: fio::WatchMask,
435 _watcher: DirectoryWatcher,
436 ) -> Result<(), zx::Status> {
437 Ok(())
440 }
441
442 fn unregister_watcher(self: Arc<Self>, _key: usize) {
443 unimplemented!("Not implemented");
444 }
445 }
446
447 #[fuchsia::test]
448 async fn test_error() {
449 let test_dir = MockDirectory::new();
450 let client = vfs::directory::serve_read_only(test_dir, ExecutionScope::new());
451 let mut w = Watcher::new(&client).await.unwrap();
452 let msg = w.next().await.expect("the stream yielded no next item");
453 assert!(!w.is_terminated());
454 assert_matches!(msg, Err(WatcherStreamError::ChannelRead(zx::Status::PEER_CLOSED)));
455 assert!(!w.is_terminated());
456 assert_matches!(w.next().await, None);
457 assert!(w.is_terminated());
458 }
459}