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 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;