| 1 | use super::*; |
| 2 | use smb2::msg::query_directory::{ |
| 3 | FileInformationClass, QueryDirectoryFlags, QueryDirectoryRequest, QueryDirectoryResponse, |
| 4 | }; |
| 5 | use std::collections::BTreeMap; |
| 6 | |
| 7 | mod records; |
| 8 | pub use records::DirectoryEntry; |
| 9 | use records::decode; |
| 10 | |
| 11 | impl Client { |
| 12 | /// Enumerates a share-relative directory completely or returns an error without a partial list. |
| 13 | /// An empty path selects the share root. The limit excludes `.` and `..`. |
| 14 | /// Concurrent directory changes are not an atomic snapshot; repeated names return ResourceBusy. |
| 15 | pub fn read_dir(&self, path: &str, limit: usize) -> io::Result<Vec<DirectoryEntry>> { |
| 16 | if path.contains('\0') || path.encode_utf16().count() > 32767 { |
| 17 | return Err(io::ErrorKind::InvalidInput.into()); |
| 18 | } |
| 19 | let response: CreateResponse = self.request( |
| 20 | Command::Create, |
| 21 | CreateRequest { |
| 22 | requested_oplock_level: OplockLevel::None, |
| 23 | impersonation_level: ImpersonationLevel::Impersonation, |
| 24 | desired_access: FileAccessMask::new(0x80000000), |
| 25 | file_attributes: 0, |
| 26 | share_access: ShareAccess(7), |
| 27 | create_disposition: CreateDisposition::FileOpen, |
| 28 | create_options: 0x1, |
| 29 | name: smb2::encode_path(&path.replace('\\', "/")), |
| 30 | create_contexts: Vec::new(), |
| 31 | }, |
| 32 | )?; |
| 33 | let file = File::new(self, &response); |
| 34 | let mut entries = BTreeMap::new(); |
| 35 | loop { |
| 36 | let response: io::Result<QueryDirectoryResponse> = |
| 37 | self.request_with(Command::QueryDirectory, |connection| { |
| 38 | ( |
| 39 | QueryDirectoryRequest { |
| 40 | file_information_class: FileInformationClass::FileDirectoryInformation, |
| 41 | flags: QueryDirectoryFlags(if entries.is_empty() { 1 } else { 0 }), |
| 42 | file_index: 0, |
| 43 | file_id: file.id.unwrap(), |
| 44 | output_buffer_length: connection |
| 45 | .params() |
| 46 | .expect("connected SMB session is negotiated") |
| 47 | .max_transact_size |
| 48 | .min(65536), |
| 49 | file_name: "*".into(), |
| 50 | }, |
| 51 | CreditCharge(1), |
| 52 | ) |
| 53 | }); |
| 54 | let response = match response { |
| 55 | Ok(response) => response, |
| 56 | Err(error) |
| 57 | if matches!( |
| 58 | error.get_ref().and_then(|error| error.downcast_ref::<smb2::Error>()), |
| 59 | Some(smb2::Error::Protocol { status, command: Command::QueryDirectory }) |
| 60 | if status.0 == 0x80000006 || (entries.is_empty() && status.0 == 0xc000000f) |
| 61 | ) => |
| 62 | { |
| 63 | break; |
| 64 | } |
| 65 | Err(error) => return Err(error), |
| 66 | }; |
| 67 | for entry in decode(&response.output_buffer)? { |
| 68 | if entries |
| 69 | .insert(entry.name, (entry.size, entry.modified, entry.attributes)) |
| 70 | .is_some() |
| 71 | { |
| 72 | return Err(io::ErrorKind::ResourceBusy.into()); |
| 73 | } |
| 74 | if entries.len() |
| 75 | - usize::from(entries.contains_key(".")) |
| 76 | - usize::from(entries.contains_key("..")) |
| 77 | > limit |
| 78 | { |
| 79 | return Err(io::ErrorKind::FileTooLarge.into()); |
| 80 | } |
| 81 | } |
| 82 | } |
| 83 | file.close()?; |
| 84 | Ok(entries |
| 85 | .into_iter() |
| 86 | .filter(|(name, _)| name != "." && name != "..") |
| 87 | .map(|(name, (size, modified, attributes))| DirectoryEntry { |
| 88 | name, |
| 89 | size, |
| 90 | modified, |
| 91 | attributes, |
| 92 | }) |
| 93 | .collect()) |
| 94 | } |
| 95 | |
| 96 | /// Watches the directory tree at `path` as OneNote 2010 watches a notebook's folder: one |
| 97 | /// CHANGE_NOTIFY with WATCH_TREE, kept armed on the server, so an idle watch sends |
| 98 | /// nothing. `changed` runs on the client's runtime with each batch of changed paths, |
| 99 | /// relative to `path` and `/`-separated, or `""` when the server lost count of them, then |
| 100 | /// `Err(_)` once the watch ends with its connection or the client. |
| 101 | pub fn watch( |
| 102 | &self, |
| 103 | path: &str, |
| 104 | changed: impl FnMut(io::Result<Vec<String>>) + Send + 'static, |
| 105 | ) -> io::Result<()> { |
| 106 | if Handle::try_current().is_ok() { |
| 107 | return Err(io::ErrorKind::InvalidInput.into()); |
| 108 | } |
| 109 | let mut connection = self |
| 110 | .connection |
| 111 | .lock() |
| 112 | .map_err(|_| io::ErrorKind::Other)? |
| 113 | .clone() |
| 114 | .ok_or(io::ErrorKind::NotConnected)?; |
| 115 | // Probing a connection whose only request is the watch would end OneNote's silence. |
| 116 | connection.set_keepalive(None); |
| 117 | let runtime = self.runtime.lock().map_err(|_| io::ErrorKind::Other)?; |
| 118 | let runtime = runtime.as_ref().ok_or(io::ErrorKind::NotConnected)?; |
| 119 | let path = path.replace('\\', "/"); |
| 120 | let mut watcher = runtime |
| 121 | .block_on(async { |
| 122 | tokio::time::timeout(self.timeout, self.tree.watch(&mut connection, &path, true)) |
| 123 | .await |
| 124 | }) |
| 125 | .map_err(|_| io::Error::from(io::ErrorKind::TimedOut))? |
| 126 | .map_err(|error| match &error { |
| 127 | smb2::Error::Protocol { status, .. } if status.0 == 0xc0000034 => { |
| 128 | io::Error::new(io::ErrorKind::NotFound, error) |
| 129 | } |
| 130 | // STATUS_NOT_SUPPORTED, STATUS_INVALID_DEVICE_REQUEST |
| 131 | smb2::Error::Protocol { status, .. } |
| 132 | if matches!(status.0, 0xc00000bb | 0xc0000010) => |
| 133 | { |
| 134 | io::Error::new(io::ErrorKind::Unsupported, error) |
| 135 | } |
| 136 | _ => super::io_error(error), |
| 137 | })?; |
| 138 | // However the task ends, the connection failing or the client retiring its runtime, |
| 139 | // `changed` hears that the watch did. |
| 140 | struct Ends<F: FnMut(io::Result<Vec<String>>)>(F); |
| 141 | impl<F: FnMut(io::Result<Vec<String>>)> Drop for Ends<F> { |
| 142 | fn drop(&mut self) { |
| 143 | (self.0)(Err(io::ErrorKind::NotConnected.into())); |
| 144 | } |
| 145 | } |
| 146 | let mut ends = Ends(changed); |
| 147 | runtime.spawn(async move { |
| 148 | loop { |
| 149 | match watcher.next_events().await { |
| 150 | Ok(events) => { |
| 151 | (ends.0)(Ok(events.into_iter().map(|event| event.filename).collect())) |
| 152 | } |
| 153 | Err(smb2::Error::Protocol { status, .. }) if status.0 == 0x0000010c => { |
| 154 | (ends.0)(Ok(vec![String::new()])) |
| 155 | } |
| 156 | Err(_) => return, |
| 157 | } |
| 158 | } |
| 159 | }); |
| 160 | Ok(()) |
| 161 | } |
| 162 | } |