Skip to content

Commit 94fa978

Browse files
committed
fix: use GetVolumeInformationW for NTFS detection (no admin required)
The previous approach used FSCTL_GET_NTFS_VOLUME_DATA which requires elevated privileges just to detect if a drive is NTFS. Now uses GetVolumeInformationW which works without admin rights. Admin is still required for actual MFT reading, but detection works for everyone. Also added is_elevated() export and better error message in CLI.
1 parent ed6f73e commit 94fa978

File tree

3 files changed

+37
-44
lines changed

3 files changed

+37
-44
lines changed

crates/uffs-cli/src/commands.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,18 @@ pub async fn search(
7979
// No drive specified - search ALL available NTFS drives
8080
#[cfg(windows)]
8181
{
82+
// Check for admin privileges first - MFT reading requires elevation
83+
if !uffs_mft::is_elevated() {
84+
bail!(
85+
"Administrator privileges required.\n\
86+
\n\
87+
UFFS reads the NTFS Master File Table directly, which requires elevated access.\n\
88+
\n\
89+
Solutions:\n\
90+
1. Run PowerShell/Terminal as Administrator\n\
91+
2. Use a pre-built index: uffs search --index <file.parquet> \"*.txt\""
92+
);
93+
}
8294
let all_drives = uffs_mft::detect_ntfs_drives();
8395
if all_drives.is_empty() {
8496
bail!("No NTFS drives found on this system");

crates/uffs-mft/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,9 @@ pub use ntfs::{
113113
};
114114
// Re-export platform types
115115
#[cfg(windows)]
116-
pub use platform::{MftBitmap, MftExtent, NtfsVolumeData, VolumeHandle, detect_ntfs_drives};
116+
pub use platform::{
117+
MftBitmap, MftExtent, NtfsVolumeData, VolumeHandle, detect_ntfs_drives, is_elevated,
118+
};
117119
pub use raw::{
118120
LoadRawOptions, RawMftData, RawMftHeader, SaveRawOptions, load_raw_mft, load_raw_mft_header,
119121
save_raw_mft,

crates/uffs-mft/src/platform.rs

Lines changed: 22 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -973,19 +973,19 @@ pub fn detect_ntfs_drives() -> Vec<char> {
973973

974974
/// Checks if a drive is an NTFS volume.
975975
///
976-
/// This attempts to get NTFS volume data from the drive. If successful,
977-
/// the drive is NTFS formatted.
978-
#[allow(unsafe_code)] // Required: Windows FFI (GetDriveTypeW, CreateFileW, DeviceIoControl)
976+
/// Uses `GetVolumeInformationW` to check the filesystem name, which doesn't
977+
/// require elevated privileges.
978+
#[allow(unsafe_code)] // Required: Windows FFI (GetDriveTypeW, GetVolumeInformationW)
979979
fn is_ntfs_volume(drive_letter: char) -> bool {
980-
use windows::Win32::Storage::FileSystem::GetDriveTypeW;
980+
use windows::Win32::Storage::FileSystem::{GetDriveTypeW, GetVolumeInformationW};
981981

982982
// First check if it's a fixed or removable drive (skip network, CD-ROM, etc.)
983-
let path: Vec<u16> = format!("{}:\\", drive_letter.to_ascii_uppercase())
983+
let root_path: Vec<u16> = format!("{}:\\", drive_letter.to_ascii_uppercase())
984984
.encode_utf16()
985985
.chain(std::iter::once(0))
986986
.collect();
987987

988-
let drive_type = unsafe { GetDriveTypeW(PCWSTR(path.as_ptr())) };
988+
let drive_type = unsafe { GetDriveTypeW(PCWSTR(root_path.as_ptr())) };
989989

990990
// DRIVE_FIXED = 3, DRIVE_REMOVABLE = 2
991991
// Skip DRIVE_UNKNOWN (0), DRIVE_NO_ROOT_DIR (1), DRIVE_REMOTE (4), DRIVE_CDROM
@@ -994,50 +994,29 @@ fn is_ntfs_volume(drive_letter: char) -> bool {
994994
return false;
995995
}
996996

997-
// Try to open the volume and get NTFS data
998-
let volume_path: Vec<u16> = format!("\\\\.\\{}:", drive_letter.to_ascii_uppercase())
999-
.encode_utf16()
1000-
.chain(std::iter::once(0))
1001-
.collect();
997+
// Get filesystem name using GetVolumeInformationW (no admin required)
998+
let mut fs_name_buffer: [u16; 32] = [0; 32];
1002999

1003-
let handle = unsafe {
1004-
CreateFileW(
1005-
PCWSTR(volume_path.as_ptr()),
1006-
FILE_READ_ATTRIBUTES.0,
1007-
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
1008-
None,
1009-
OPEN_EXISTING,
1010-
FILE_FLAG_BACKUP_SEMANTICS,
1011-
None,
1000+
let result = unsafe {
1001+
GetVolumeInformationW(
1002+
PCWSTR(root_path.as_ptr()),
1003+
None, // Volume name buffer (not needed)
1004+
None, // Volume serial number (not needed)
1005+
None, // Max component length (not needed)
1006+
None, // File system flags (not needed)
1007+
Some(&mut fs_name_buffer), // File system name buffer
10121008
)
10131009
};
10141010

1015-
let Ok(handle) = handle else {
1011+
if result.is_err() {
10161012
return false;
1017-
};
1018-
1019-
// Try to get NTFS volume data
1020-
use windows::Win32::System::IO::DeviceIoControl;
1021-
1022-
let mut buffer = NTFS_VOLUME_DATA_BUFFER::default();
1023-
let mut bytes_returned: u32 = 0;
1024-
1025-
let result = unsafe {
1026-
DeviceIoControl(
1027-
handle,
1028-
FSCTL_GET_NTFS_VOLUME_DATA,
1029-
None,
1030-
0,
1031-
Some(core::ptr::from_mut(&mut buffer).cast()),
1032-
size_of::<NTFS_VOLUME_DATA_BUFFER>() as u32,
1033-
Some(&mut bytes_returned),
1034-
None,
1035-
)
1036-
};
1013+
}
10371014

1038-
let _ = unsafe { CloseHandle(handle) };
1015+
// Convert filesystem name to string and check if it's NTFS
1016+
let fs_name = String::from_utf16_lossy(&fs_name_buffer);
1017+
let fs_name = fs_name.trim_end_matches('\0');
10391018

1040-
result.is_ok()
1019+
fs_name == "NTFS"
10411020
}
10421021

10431022
#[cfg(test)]

0 commit comments

Comments
 (0)