Skip to main content

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;