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