I am trying to write a CLI program using Rust that can flash USB sticks on Windows. However, when running it, I get a "Permission Denied" error. I'd be happy if someone could help me with this.
Here is the code:
use std::fs::OpenOptions;
use std::io::{self, Read, Write};
use std::path::PathBuf;
fn main() {
println!("Enter raw drive path:");
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
input = input.trim().to_string();
println!("Is \"{}\" correct?", input);
io::stdout().flush().unwrap();
let mut confirmation = String::new();
io::stdin().read_line(&mut confirmation).unwrap();
if confirmation.trim() != "y" {
println!("Aborting...");
return;
}
let mut iso_file = OpenOptions::new()
.read(true)
.open(PathBuf::from(
"C:\\path\\to\\my\\image.iso",
))
.unwrap();
let mut device_file = OpenOptions::new()
.write(true)
.read(true)
.open(input)
.unwrap();
let mut buffer = vec![0u8; 1024 * 1024];
let mut position = 0;
println!("Writing...");
loop {
position += 1;
println!("{}", position);
let bytes_read = iso_file.read(&mut buffer).unwrap();
if bytes_read == 0 {
break;
}
device_file.write_all(&buffer[..bytes_read]).unwrap();
}
println!("Syncing...");
device_file.sync_all().unwrap();
println!("Success!");
}
Here is the command line log:
Enter raw drive path:
\\.\PHYSICALDRIVE2
Is "\\.\PHYSICALDRIVE2" correct?
y
Writing...
1
thread 'main' panicked at src\main.rs:49:54:
called `Result::unwrap()` on an `Err` value: Os { code: 5, kind: PermissionDenied, message: "Zugriff verweigert" }
I ran the compiled EXE file in PowerShell as Administrator. I plugged the USB stick in shortly before executing the CLI app. To get the raw drive path, I used WMI (Get-WmiObject -Class Win32_DiskDrive
+ find volume with brand name of my USB stick and roughly matching size). Only the PowerShell window was open when I executed the application (I therefore doubt that another app tried to access the USB stick during the flash process). I also disabled every Windows Defender setting I could find.
In the Windows C++ documentation I also read about the following:
Dismounting a volume is useful when a volume needs to disappear for a while. For example, an application that changes a volume file system from the FAT file system to the NTFS file system might use the following procedure.
To change a volume file system
- Open a volume.
- Lock the volume.
- Format the volume.
- Dismount the volume.
- Unlock the volume.
- Close the volume handle.
Since writing a disk image also kind of changes the file system, I tried to apply these steps (with some help from AI). Here is the resulting code:
use std::ffi::{OsStr, c_void};
use std::fs::File;
use std::io::{self, ErrorKind, Read, Write};
use std::iter::once;
use std::os::windows::ffi::OsStrExt;
use std::ptr;
use winapi::shared::minwindef::{DWORD, LPVOID};
use winapi::um::fileapi::{
CreateFileW, INVALID_SET_FILE_POINTER, OPEN_EXISTING, SetFilePointer, WriteFile,
};
use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE};
use winapi::um::ioapiset::DeviceIoControl;
use winapi::um::winbase::FILE_BEGIN;
use winapi::um::winioctl::{FSCTL_DISMOUNT_VOLUME, FSCTL_LOCK_VOLUME, FSCTL_UNLOCK_VOLUME};
use winapi::um::winnt::{
FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE,
};
const FILE_FLAG_NO_BUFFERING: u32 = 0x20000000;
const FILE_FLAG_WRITE_THROUGH: u32 = 0x80000000;
fn to_wide_chars(s: &str) -> Vec<u16> {
OsStr::new(s).encode_wide().chain(once(0)).collect()
}
fn main() -> io::Result<()> {
println!("Enter raw drive path:");
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
input = input.trim().to_string();
println!("Is \"{}\" correct?", input);
io::stdout().flush()?;
let mut confirmation = String::new();
io::stdin().read_line(&mut confirmation)?;
if confirmation.trim().to_lowercase() != "y" {
println!("Aborting...");
return Ok(());
}
let iso_path = "C:\\path\\to\\image.iso";
println!("Opening ISO file: {}", iso_path);
let mut iso_file = match File::open(iso_path) {
Ok(file) => file,
Err(e) => {
println!("Failed to open ISO file: {:?}", e);
return Err(e);
}
};
let device_file = Device::new(&input)?;
device_file.lock()?;
//device_file.dismount()?; I get the Permission Denied error nevertheless
//device_file.set_pointer()?; I get the Permission Denied error nevertheless
let mut buffer = vec![0u8; 512];
println!("Starting to write ISO to device...");
loop {
let bytes_read = match iso_file.read(&mut buffer) {
Ok(0) => break,
Ok(n) => n,
Err(e) => {
println!("Error reading from ISO: {:?}", e);
return Err(e);
}
};
device_file.write(&buffer, bytes_read)?;
}
device_file.dismount()?;
// device_file.unlock()?; Done automatically when the handle is closed
Ok(())
}
struct Device {
handle: *mut c_void,
}
impl Device {
fn new(path: &str) -> io::Result<Self> {
let path_wide = to_wide_chars(&path);
let handle = unsafe {
CreateFileW(
path_wide.as_ptr(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
ptr::null_mut(),
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH,
ptr::null_mut(),
)
};
if handle == INVALID_HANDLE_VALUE {
let err = io::Error::last_os_error();
return Err(err);
}
Ok(Device { handle })
}
fn set_pointer(&self) -> io::Result<u32> {
let result = unsafe { SetFilePointer(self.handle, 0, ptr::null_mut(), FILE_BEGIN) };
if result == INVALID_SET_FILE_POINTER {
return Err(io::Error::new(
ErrorKind::Other,
"Could not set the pointer.",
));
}
Ok(result)
}
fn write(&self, buffer: &Vec<u8>, number_of_bytes_to_write: usize) -> io::Result<i32> {
let mut bytes_written: DWORD = 0;
let result = unsafe {
WriteFile(
self.handle,
buffer.as_ptr() as LPVOID,
number_of_bytes_to_write as DWORD,
&mut bytes_written,
ptr::null_mut(),
)
};
if result == 0 {
let err = io::Error::last_os_error();
return Err(err);
}
Ok(result)
}
fn lock(&self) -> io::Result<i32> {
let result = unsafe {
//TODO: What is this?
let mut bytes_returned = 0;
let res = DeviceIoControl(
self.handle,
FSCTL_LOCK_VOLUME,
ptr::null_mut(),
0,
ptr::null_mut(),
0,
&mut bytes_returned,
ptr::null_mut(),
);
println!("{}", bytes_returned);
res
};
if result == 0 {
let err = io::Error::last_os_error();
return Err(err);
}
Ok(result)
}
fn unlock(&self) -> io::Result<i32> {
let result = unsafe {
//TODO: What is this?
let mut bytes_returned = 0;
let res = DeviceIoControl(
self.handle,
FSCTL_UNLOCK_VOLUME,
ptr::null_mut(),
0,
ptr::null_mut(),
0,
&mut bytes_returned,
ptr::null_mut(),
);
println!("{}", bytes_returned);
res
};
if result == 0 {
let err = io::Error::last_os_error();
return Err(err);
}
Ok(result)
}
fn dismount(&self) -> io::Result<i32> {
let result = unsafe {
//TODO: What is this?
let mut bytes_returned = 0;
let res = DeviceIoControl(
self.handle,
FSCTL_DISMOUNT_VOLUME,
ptr::null_mut(),
0,
ptr::null_mut(),
0,
&mut bytes_returned,
ptr::null_mut(),
);
println!("{}", bytes_returned);
res
};
if result == 0 {
let err = io::Error::last_os_error();
return Err(err);
}
Ok(result)
}
}
impl Drop for Device {
fn drop(&mut self) {
let result = unsafe { CloseHandle(self.handle) };
if result == -1 {
let err = io::Error::last_os_error();
eprintln!("Failed to close device: {:?}", err);
}
}
}
Unfortunately, I get the exact same error for the write operation. I tried to dismount the drive after locking it and before writing to it but that doesn't change the error either.
The Windows documentation for CreateFileW
also notes:
- The caller must have administrative privileges. For more information, see
Running with Special Privileges
[Link removed due to forum limitation].- The dwCreationDisposition parameter must have the OPEN_EXISTING flag.
- When opening a volume or floppy disk, the dwShareMode parameter must have the FILE_SHARE_WRITE flag.
as requirements to write directly to a raw drive path, but I fulfilled all those requirements too.
I've completely run out of ideas on what could be causing this issue. I also searched for it a lot online, but none of the solutions there were able to get rid of this error.
What might be causing this permission issue and how can I fix it?