Answered: How to get all drive letters on Windows

I was reading through this topic and realized it was never fully answered:

This post is intended to show how you can get the drive letters on Windows using the windows-rs crate.

First, we need to set up the project dependencies. We will be using the windows and bitvec crates to make our lives easier.

[dependencies]
bitvec = '1'

[dependencies.windows]
version = '0.57'
features = [
    'Win32_Storage_FileSystem',
]

Next, we need to set up our imports from these crates. We're following the advice from the bitvec docs to pull in the prelude, and we're bringing in the specific Windows APIs we need to access drives and errors. Our function for fetching the drive letters is going to be a HashSet<char> since it cannot contain duplicates.

use std::{
    collections::HashSet,
    error::Error,
    fmt::{Debug, Display, Formatter},
};
use bitvec::prelude::*;
use windows::Win32::{
    Storage::FileSystem::GetLogicalDrives,
    Foundation::GetLastError,
};

We grab the various std types to implement our own error types for the function we are going to write. There are two potential failure modes for the API, since a u32 is too wide for valid drive letters (A-Z is 26 bits), it could return a value with the most-significant 6 bits set. It's also possible for the API to return 0 to indicate a Windows error.

pub enum GetLogicalDrivesError {
    TooManyDrivesError,
    ApiError(u32),
}

impl Display for GetLogicalDrivesError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{self:?}")
    }
}

impl Debug for GetLogicalDrivesError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            GetLogicalDrivesError::TooManyDrivesError => write!(f, "TooManyDrives"),
            GetLogicalDrivesError::ApiError(code) => write!(f, "ApiError({code})"),
        }
    }
}

impl Error for GetLogicalDrivesError {}

And finally, the function itself:

const INVALID_DRIVE_LETTER_BITMASK: u32 = 0b11111100_00000000_00000000_00000000;

/// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getlogicaldrives
pub fn get_drives() -> Result<HashSet<char>, GetLogicalDrivesError> {
    let drives_bitmap = unsafe { GetLogicalDrives() };

    // If the function fails, the return value is zero. To get extended error information, call GetLastError.
    if drives_bitmap == 0 {
        let err = unsafe { GetLastError() };
        Err(GetLogicalDrivesError::ApiError(err.0))
    } else if drives_bitmap & INVALID_DRIVE_LETTER_BITMASK != 0 {
        Err(GetLogicalDrivesError::TooManyDrivesError)
    } else {
        Ok(drives_bitmap
            .view_bits::<Lsb0>()
            .iter()
            .zip('A'..='Z')
            .filter_map(|(bit, drive_letter)| {
                // a bit derefs into a bool
                if *bit {
                    Some(drive_letter)
                } else {
                    None
                }
            })
            .collect())
    }
}

Since drive letters are enumerated by least-significant (Lsb0, smallest, or right-most first) bits, we need to specify this detail to the bitvec type.

Getting the actual Windows error string is left as an exercise to the user. Here is the documentation for the API, however:

To obtain an error string for system error codes, use the FormatMessage function.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.