windows_namespace_request_sys/volume.rs
1// Copyright (c) Mike Grier.
2
3//! The `GetVolumeInformationByHandleW` entry.
4//!
5//! Entry 8 of the audited catalogue: the **handle-based** volume query.
6//!
7//! # Why the path-based call is not here
8//!
9//! `GetVolumeInformationW` takes a root path rather than a handle and is a
10//! different Win32 call, so it would be its own entry. No audited consumer
11//! calls it, so it is deliberately out of round one -- recorded so a later
12//! reader can tell a considered omission from an unexamined one.
13//!
14//! # Two buffers, one call
15//!
16//! The call fills a volume-label buffer and a filesystem-name buffer in the
17//! same invocation, alongside three scalar out-parameters. Both buffers have
18//! documented maximums, so unlike [`crate::final_path`] this entry needs no
19//! grow-and-retry: it allocates the maximum once and is done.
20
21use std::fmt;
22
23use windows_sys::Win32::Storage::FileSystem::GetVolumeInformationByHandleW;
24use wtf_string::Wtf16String;
25
26use crate::handle::{CapturedHandle, HandleCaptureError};
27use crate::outcome::{Outcome, perform_bool};
28
29/// The longest volume label Windows supports, plus room for the terminator.
30///
31/// `MAX_PATH + 1`, which is what the documentation specifies for this buffer.
32const LABEL_CAPACITY: usize = 261;
33
34/// The longest filesystem name buffer, plus room for the terminator.
35///
36/// The same bound; a filesystem name is far shorter in practice, but the call's
37/// contract is stated in these terms.
38const FILESYSTEM_NAME_CAPACITY: usize = 261;
39
40/// What a volume reported about itself.
41#[derive(Clone, Debug, PartialEq, Eq)]
42pub struct VolumeInformation {
43 label: Wtf16String,
44 serial_number: u32,
45 maximum_component_length: u32,
46 flags: u32,
47 filesystem_name: Wtf16String,
48}
49
50impl VolumeInformation {
51 /// The volume label, which is frequently empty and is not an identifier.
52 #[must_use]
53 pub fn label(&self) -> &Wtf16String {
54 &self.label
55 }
56
57 /// The volume serial number.
58 ///
59 /// Not stable across reformatting, and not unique across machines, so it
60 /// identifies a volume only in combination with something else.
61 #[must_use]
62 pub fn serial_number(&self) -> u32 {
63 self.serial_number
64 }
65
66 /// The longest single path component the filesystem accepts.
67 #[must_use]
68 pub fn maximum_component_length(&self) -> u32 {
69 self.maximum_component_length
70 }
71
72 /// The raw `FILE_*` capability flags, carried unaltered.
73 ///
74 /// A bitmask rather than an enum, so a capability Windows adds later still
75 /// reaches a consumer.
76 #[must_use]
77 pub fn flags(&self) -> u32 {
78 self.flags
79 }
80
81 /// The filesystem name, such as `NTFS` or `ReFS`.
82 #[must_use]
83 pub fn filesystem_name(&self) -> &Wtf16String {
84 &self.filesystem_name
85 }
86}
87
88impl fmt::Display for VolumeInformation {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 write!(
91 f,
92 "{} volume {:08X}",
93 self.filesystem_name.to_string_lossy(),
94 self.serial_number
95 )
96 }
97}
98
99/// An owned, marshalable parameter set for `GetVolumeInformationByHandleW`.
100///
101/// # Example
102///
103/// ```
104/// use std::fs;
105/// use std::os::windows::io::AsHandle;
106///
107/// use windows_namespace_request_sys::volume::QueryVolumeInformation;
108/// use windows_namespace_request_sys::CapturedHandle;
109///
110/// let path = std::env::temp_dir().join(format!("wnrs-vol-{}.tmp", std::process::id()));
111/// fs::write(&path, b"example")?;
112/// let file = fs::File::open(&path)?;
113///
114/// let volume = QueryVolumeInformation::new(CapturedHandle::capture(file.as_handle())?)
115/// .perform()?;
116///
117/// // A real volume names its filesystem and reports a component limit.
118/// assert!(!volume.filesystem_name().is_empty());
119/// assert!(volume.maximum_component_length() > 0);
120/// # drop(file);
121/// # fs::remove_file(&path)?;
122/// # Ok::<(), Box<dyn std::error::Error>>(())
123/// ```
124#[derive(Debug)]
125#[must_use = "an unperformed request queries nothing"]
126pub struct QueryVolumeInformation {
127 handle: CapturedHandle,
128}
129
130impl QueryVolumeInformation {
131 /// Begins a request against `handle`.
132 ///
133 /// The handle names any file or directory on the volume; the volume is what
134 /// gets reported.
135 pub fn new(handle: CapturedHandle) -> Self {
136 Self { handle }
137 }
138
139 /// The owned duplicate of the handle being queried.
140 pub fn handle(&self) -> &CapturedHandle {
141 &self.handle
142 }
143
144 /// Copies the request, duplicating the handle.
145 ///
146 /// # Errors
147 ///
148 /// Returns the handle-capture failure when the handle cannot be duplicated.
149 pub fn try_clone(&self) -> Result<Self, HandleCaptureError> {
150 Ok(Self {
151 handle: self.handle.try_clone()?,
152 })
153 }
154
155 /// Performs the query on the calling thread.
156 ///
157 /// # Errors
158 ///
159 /// Returns the raw Win32 code, unaltered.
160 pub fn perform(&self) -> Outcome<VolumeInformation> {
161 let mut label = Wtf16String::with_capacity(LABEL_CAPACITY);
162 let mut filesystem_name = Wtf16String::with_capacity(FILESYSTEM_NAME_CAPACITY);
163 let mut serial_number = 0_u32;
164 let mut maximum_component_length = 0_u32;
165 let mut flags = 0_u32;
166
167 perform_bool(|| {
168 // SAFETY: the handle is a duplicate this request owns and keeps
169 // open across the call; both buffers are writable for the
170 // capacities passed; the three scalar out-parameters point at
171 // writable storage. Each buffer's invariant is restored below
172 // before it is observed.
173 unsafe {
174 GetVolumeInformationByHandleW(
175 self.handle.raw(),
176 label.as_mut_ptr(),
177 u32::try_from(LABEL_CAPACITY).expect("a small constant fits a u32"),
178 &raw mut serial_number,
179 &raw mut maximum_component_length,
180 &raw mut flags,
181 filesystem_name.as_mut_ptr(),
182 u32::try_from(FILESYSTEM_NAME_CAPACITY).expect("a small constant fits a u32"),
183 )
184 }
185 })?;
186
187 // SAFETY: a successful call NUL-terminates both buffers within the
188 // capacities given, so the terminator search stays in bounds.
189 unsafe {
190 set_len_to_terminator(&mut label, LABEL_CAPACITY);
191 set_len_to_terminator(&mut filesystem_name, FILESYSTEM_NAME_CAPACITY);
192 }
193
194 Ok(VolumeInformation {
195 label,
196 serial_number,
197 maximum_component_length,
198 flags,
199 filesystem_name,
200 })
201 }
202}
203
204/// Restores a buffer's length from the NUL terminator Win32 wrote.
205///
206/// The call reports no length for either string buffer -- unlike
207/// [`crate::final_path`], which returns one -- so the terminator is the only
208/// signal available.
209///
210/// # Safety
211///
212/// `buffer` must have capacity for `capacity` characters, and Win32 must have
213/// written a NUL-terminated string within it.
214///
215/// Note what this deliberately does **not** require: that all `capacity`
216/// characters are initialised. Win32 writes only the string it produced plus a
217/// terminator, so most of the buffer is untouched -- which is why the scan
218/// below reads one element at a time through a raw pointer rather than forming
219/// a slice over the whole capacity. A `&[u16]` spanning uninitialised elements
220/// would be undefined behaviour the moment it was created, before `position`
221/// ever short-circuited at the terminator.
222unsafe fn set_len_to_terminator(buffer: &mut Wtf16String, capacity: usize) {
223 let base = buffer.as_mut_ptr();
224 let mut length = 0;
225
226 while length < capacity {
227 // SAFETY: `base` is valid for `capacity` characters of storage, and
228 // every element up to and including the terminator was written by
229 // Win32 per this function's contract, so each read is of an
230 // initialised element.
231 if unsafe { base.add(length).read() } == 0 {
232 break;
233 }
234 length += 1;
235 }
236
237 // A terminator at the very end would mean Win32 filled the buffer without
238 // room for one, which its contract forbids; treating that as an empty
239 // string keeps the invariant rather than reporting content that was never
240 // terminated.
241 if length == capacity {
242 length = 0;
243 }
244
245 // SAFETY: `length` is the terminator's index, so that many content
246 // characters were written and it is within capacity.
247 unsafe { buffer.set_len_from_ffi(length) };
248}
249
250impl crate::request::Request for QueryVolumeInformation {
251 type Error = crate::Win32Error;
252 type Output = VolumeInformation;
253
254 fn perform(&self) -> Outcome<VolumeInformation> {
255 Self::perform(self)
256 }
257}
258
259#[cfg(test)]
260mod tests;