Windows "Permission Denied" Error When Trying to Write to a PHYSICALDRIVE

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

  1. Open a volume.
  2. Lock the volume.
  3. Format the volume.
  4. Dismount the volume.
  5. Unlock the volume.
  6. 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?

I’ve been hesitant to try to reproduce this problem because I don’t want to lose my main disk, but I figured that if I started small then Startup Repair should sort things out.

I wrote a short program to read the MBR from a USB flash drive and write it back, and it’s not having the same problem you’re seeing. My first thought is that I’m only writing one 512 byte sector, while you’re writing up to 1 MiB, and maybe that’s too big. ISO image sectors are 2048 bytes, so it shouldn’t be an issue with not being a whole number of disk sectors. Have you tried writing a smaller amount of data each time?

My test program looks like this:

use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::path::Path;

fn main() -> anyhow::Result<()> {
    let path = Path::new(r"\\.\PhysicalDrive2");
    let mut f = File::open(path)?;
    let mut buf = [0u8; 512];
    f.read_exact(&mut buf)?;
    drop(f);

    hexdump::hexdump(&buf);

    let mut f = OpenOptions::new().write(true).open(path)?;
    f.write_all(&buf)?;
    f.sync_all()?;
    drop(f);

    Ok(())
}
2 Likes

I ran your code and I get the following output:


"\\\\.\\PhysicalDrive2"
|45520800 00009090 00000000 00000000| ER.............. 00000000
|00000000 00000000 00000000 00000000| ................ 00000010
|33edfa8e d5bc007c fbfc6631 db6631c9| 3......|..f1.f1. 00000020
|66536651 06578edd 8ec552be 007cbf00| fSfQ.W....R..|.. 00000030
|06b90001 f3a5ea4b 06000052 b441bbaa| .......K...R.A.. 00000040
|5531c930 f6f9cd13 721681fb 55aa7510| U1.0....r...U.u. 00000050
|83e10174 0b66c706 f306b442 eb15eb02| ...t.f.....B.... 00000060
|31c95a51 b408cd13 5b0fb6c6 405083e1| 1.ZQ....[...@P.. 00000070
|3f51f7e1 535250bb 007cb904 0066a1b0| ?Q..SRP..|...f.. 00000080
|07e84400 0f828000 664080c7 02e2f266| ..D.....f@.....f 00000090
|813e407c fbc07870 7509fabc ec7bea44| .>@|..xpu....{.D 000000a0
|7c0000e8 83006973 6f6c696e 75782e62| |.....isolinux.b 000000b0
|696e206d 69737369 6e67206f 7220636f| in missing or co 000000c0
|72727570 742e0d0a 66606631 d2660306| rrupt...f`f1.f.. 000000d0
|f87b6613 16fc7b66 52665006 536a016a| .{f...{fRfP.Sj.j 000000e0
|1089e666 f736e87b c0e40688 e188c592| ...f.6.{........ 000000f0
|f636ee7b 88c608e1 41b80102 8a16f27b| .6.{....A......{ 00000100
|cd138d64 106661c3 e81e004f 70657261| ...d.fa....Opera 00000110
|74696e67 20737973 74656d20 6c6f6164| ting system load 00000120
|20657272 6f722e0d 0a5eacb4 0e8a3e62|  error...^....>b 00000130
|04b307cd 103c0a75 f1cd18f4 ebfd0000| .....<.u........ 00000140
|00000000 00000000 00000000 00000000| ................ 00000150
|00000000 00000000 00000000 00000000| ................ 00000160
|00000000 00000000 00000000 00000000| ................ 00000170
|00000000 00000000 00000000 00000000| ................ 00000180
|00000000 00000000 00000000 00000000| ................ 00000190
|00000000 00000000 00000000 00000000| ................ 000001a0
|0c490000 00000000 5a3c00b7 00008002| .I......Z<...... 000001b0
|010000b1 e0fd4000 000040d3 580000fe| ......@...@.X... 000001c0
|ffffeffe ffff0c21 00000028 00000000| .......!...(.... 000001d0
|00000000 00000000 00000000 00000000| ................ 000001e0
|00000000 00000000 00000000 000055aa| ..............U. 000001f0
|45464920 50415254 00000100 5c000000| EFI PART....\... 00000200
|a9a7714d 00000000 01000000 00000000| ..qM............ 00000210
|7fd35800 00000000 40000000 00000000| ..X.....@....... 00000220
|4ad35800 00000000 32303235 30313140| J.X.....2025011@ 00000230
|b1303036 31363231 0c000000 00000000| .0061621........ 00000240
|d0000000 80000000 612137aa 00000000| ........a!7..... 00000250

all zeros after that except for:

|504d0000 00000002 00000001 00000002| PM.............. 00000800
|4170706c 65000000 00000000 00000000| Apple........... 00000810
|00000000 00000000 00000000 00000000| ................ 00000820
|4170706c 655f7061 72746974 696f6e5f| Apple_partition_ 00000830
|6d617000 00000000 00000000 00000000| map............. 00000840

at some point. Note that I ran your script 1:1 first and then I increased the buffer size (by a multiple of 512).

The subsequent write operation from your code succeeded on one USB stick and failed on another one even though I can save data to both of them in the file explorer just fine. (I made sure to close all file explorer instances again before running your code)

To debug this further I asked AI for help again and the code it spit out actually worked on the one drive that had also worked with your code. Unfortunately, this success was short-lived because when trying to run the code for a second time on the same drive, I got a permission error. The second USB stick that didn’t work with your example didn’t work here either.

AI Code if your are interested
use std::ffi::{OsStr, c_void};
use std::fs::File;
use std::io::{self, Read, Write};
use std::iter::once;
use std::os::windows::ffi::OsStrExt;
use std::ptr;
use std::thread;
use std::time::Duration;
use winapi::shared::minwindef::{DWORD, LPVOID};
use winapi::shared::ntdef::LARGE_INTEGER;
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,
    IOCTL_DISK_GET_LENGTH_INFO
};
use winapi::um::winnt::{
    FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE,
    HANDLE
};

const FILE_FLAG_NO_BUFFERING: u32 = 0x20000000;
const FILE_FLAG_WRITE_THROUGH: u32 = 0x80000000;
const FSCTL_ALLOW_EXTENDED_DASD_IO: u32 = 0x00090083;

// Define the GET_LENGTH_INFORMATION structure correctly
#[repr(C)]
struct GET_LENGTH_INFORMATION {
    Length: LARGE_INTEGER,
}

fn to_wide_chars(s: &str) -> Vec<u16> {
    OsStr::new(s).encode_wide().chain(once(0)).collect()
}

fn main() -> io::Result<()> {
    // Run as admin check
    if !is_running_as_admin() {
        println!("This program needs to be run as Administrator!");
        println!("Please restart the program with admin privileges.");
        return Ok(());
    }

    println!("Enter raw drive path (e.g., \\\\.\\PhysicalDrive2):");
    io::stdout().flush()?;

    let mut input = String::new();
    io::stdin().read_line(&mut input)?;
    input = input.trim().to_string();

    println!("WARNING: This will ERASE ALL DATA on \"{}\"", input);
    println!("Type 'YES' (all caps) to confirm:");
    io::stdout().flush()?;

    let mut confirmation = String::new();
    io::stdin().read_line(&mut confirmation)?;
    if confirmation.trim() != "YES" {
        println!("Aborting...");
        return Ok(());
    }

    let iso_path = "C:\\path\\to\\my\\file.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 iso_size = iso_file.metadata()?.len();
    println!("ISO file size: {} bytes ({:.2} MB)", iso_size, iso_size as f64 / (1024.0 * 1024.0));

    // Try to open the device with different access modes
    println!("Attempting to open device: {}", input);
    let device = match Device::new(&input) {
        Ok(dev) => dev,
        Err(e) => {
            println!("Failed to open device with standard flags: {:?}", e);
            println!("Trying alternative approach...");
            
            match Device::new_alternative(&input) {
                Ok(dev) => dev,
                Err(e2) => {
                    println!("All attempts to open device failed: {:?}", e2);
                    println!("Please ensure:");
                    println!("1. You're running as Administrator");
                    println!("2. The drive isn't in use by any program");
                    println!("3. The drive letter is dismounted in Disk Management");
                    return Err(e2);
                }
            }
        }
    };

    // Get disk size to verify we're writing to the correct device
    match device.get_disk_size() {
        Ok(size) => {
            println!("Device size: {} bytes ({:.2} GB)", size, size as f64 / (1024.0 * 1024.0 * 1024.0));
            if size < iso_size {
                println!("ERROR: ISO file ({} bytes) is larger than the target device ({} bytes)!", 
                         iso_size, size);
                return Err(io::Error::new(io::ErrorKind::Other, "Target device too small"));
            }
        },
        Err(e) => {
            println!("Warning: Could not determine device size: {:?}", e);
            println!("Do you want to continue anyway? (y/n)");
            io::stdout().flush()?;
            
            let mut response = String::new();
            io::stdin().read_line(&mut response)?;
            if response.trim().to_lowercase() != "y" {
                println!("Aborting...");
                return Ok(());
            }
        }
    }

    // Try to lock the volume multiple times
    println!("Attempting to lock the volume...");
    let max_attempts = 5;
    let mut locked = false;
    
    for attempt in 1..=max_attempts {
        match device.lock() {
            Ok(_) => {
                locked = true;
                println!("Successfully locked the volume on attempt {}", attempt);
                break;
            },
            Err(e) => {
                println!("Lock attempt {} failed: {:?}", attempt, e);
                if attempt < max_attempts {
                    println!("Waiting before retry...");
                    thread::sleep(Duration::from_secs(2));
                }
            }
        }
    }
    
    if !locked {
        println!("Failed to lock the volume after {} attempts", max_attempts);
        println!("Trying to continue without locking...");
    }

    // Try to dismount the volume
    println!("Attempting to dismount the volume...");
    match device.dismount() {
        Ok(_) => println!("Successfully dismounted the volume"),
        Err(e) => println!("Warning: Failed to dismount volume: {:?}", e)
    }

    // Allow extended DASD I/O
    println!("Enabling extended DASD I/O...");
    match device.allow_extended_dasd_io() {
        Ok(_) => println!("Successfully enabled extended DASD I/O"),
        Err(e) => println!("Warning: Failed to enable extended DASD I/O: {:?}", e)
    }

    // Set file pointer to beginning
    println!("Setting file pointer to beginning of device...");
    match device.set_pointer() {
        Ok(_) => println!("File pointer set successfully"),
        Err(e) => println!("Warning: Failed to set file pointer: {:?}", e)
    }

    // Use a larger buffer for better performance, aligned to sector size
    let sector_size = 512;
    let buffer_size = 1024 * 1024; // 1MB
    let buffer_size = buffer_size - (buffer_size % sector_size);
    let mut buffer = vec![0u8; buffer_size];

    println!("Starting to write ISO to device...");
    let mut total_bytes_read = 0u64;
    let mut last_percent = 0;

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

        total_bytes_read += bytes_read as u64;
        let percent = ((total_bytes_read as f64 / iso_size as f64) * 100.0) as u32;
        
        if percent > last_percent {
            println!("Progress: {}% ({:.2} MB / {:.2} MB)", 
                     percent, 
                     total_bytes_read as f64 / (1024.0 * 1024.0),
                     iso_size as f64 / (1024.0 * 1024.0));
            last_percent = percent;
        }

        // If bytes_read is not a multiple of sector size, pad with zeros
        let write_size = if bytes_read % sector_size != 0 {
            let new_size = ((bytes_read / sector_size) + 1) * sector_size;
            for i in bytes_read..new_size {
                buffer[i] = 0;
            }
            new_size
        } else {
            bytes_read
        };

        match device.write(&buffer, write_size) {
            Ok(_) => {},
            Err(e) => {
                println!("Error writing to device at position {}: {:?}", total_bytes_read, e);
                return Err(e);
            }
        }
    }

    println!("Write completed successfully!");
    println!("Total bytes written: {} ({:.2} MB)", 
             total_bytes_read, 
             total_bytes_read as f64 / (1024.0 * 1024.0));

    // Unlock volume if we locked it
    if locked {
        println!("Unlocking volume...");
        match device.unlock() {
            Ok(_) => println!("Volume unlocked successfully"),
            Err(e) => println!("Warning: Failed to unlock volume: {:?}", e)
        }
    }

    println!("Operation completed successfully!");
    Ok(())
}

fn is_running_as_admin() -> bool {
    // This is a simplified check - in a real app you might want a more robust check
    // using IsUserAnAdmin() from shell32.dll
    true
}

struct Device {
    handle: HANDLE,
}

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 {
            return Err(io::Error::last_os_error());
        }

        Ok(Device { handle })
    }

    fn new_alternative(path: &str) -> io::Result<Self> {
        let path_wide = to_wide_chars(&path);
        // Try with different flags
        let handle = unsafe {
            CreateFileW(
                path_wide.as_ptr(),
                GENERIC_READ | GENERIC_WRITE,
                0, // No sharing
                ptr::null_mut(),
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                ptr::null_mut(),
            )
        };

        if handle == INVALID_HANDLE_VALUE {
            return Err(io::Error::last_os_error());
        }

        Ok(Device { handle })
    }

    fn get_disk_size(&self) -> io::Result<u64> {
        // Initialize with zeroed LARGE_INTEGER
        let mut length_info = GET_LENGTH_INFORMATION {
            Length: unsafe { std::mem::zeroed() },
        };
        let mut bytes_returned: DWORD = 0;

        let result = unsafe {
            DeviceIoControl(
                self.handle,
                IOCTL_DISK_GET_LENGTH_INFO,
                ptr::null_mut(),
                0,
                &mut length_info as *mut _ as *mut c_void,
                std::mem::size_of::<GET_LENGTH_INFORMATION>() as DWORD,
                &mut bytes_returned,
                ptr::null_mut(),
            )
        };

        if result == 0 {
            return Err(io::Error::last_os_error());
        }

        // LARGE_INTEGER is a union with a 64-bit integer field
        // Access it safely
        let size = unsafe {
            *(&length_info.Length as *const LARGE_INTEGER as *const i64)
        };

        Ok(size as u64)
    }

    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::last_os_error());
        }

        Ok(result)
    }

    fn write(&self, buffer: &[u8], number_of_bytes_to_write: usize) -> io::Result<()> {
        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 {
            return Err(io::Error::last_os_error());
        }

        if bytes_written as usize != number_of_bytes_to_write {
            return Err(io::Error::new(
                io::ErrorKind::Other,
                format!("Incomplete write: {} of {} bytes", bytes_written, number_of_bytes_to_write),
            ));
        }

        Ok(())
    }

    fn lock(&self) -> io::Result<()> {
        let mut bytes_returned: DWORD = 0;
        let result = unsafe {
            DeviceIoControl(
                self.handle,
                FSCTL_LOCK_VOLUME,
                ptr::null_mut(),
                0,
                ptr::null_mut(),
                0,
                &mut bytes_returned,
                ptr::null_mut(),
            )
        };

        if result == 0 {
            return Err(io::Error::last_os_error());
        }

        Ok(())
    }

    fn unlock(&self) -> io::Result<()> {
        let mut bytes_returned: DWORD = 0;
        let result = unsafe {
            DeviceIoControl(
                self.handle,
                FSCTL_UNLOCK_VOLUME,
                ptr::null_mut(),
                0,
                ptr::null_mut(),
                0,
                &mut bytes_returned,
                ptr::null_mut(),
            )
        };

        if result == 0 {
            return Err(io::Error::last_os_error());
        }

        Ok(())
    }

    fn dismount(&self) -> io::Result<()> {
        let mut bytes_returned: DWORD = 0;
        let result = unsafe {
            DeviceIoControl(
                self.handle,
                FSCTL_DISMOUNT_VOLUME,
                ptr::null_mut(),
                0,
                ptr::null_mut(),
                0,
                &mut bytes_returned,
                ptr::null_mut(),
            )
        };

        if result == 0 {
            return Err(io::Error::last_os_error());
        }

        Ok(())
    }

    fn allow_extended_dasd_io(&self) -> io::Result<()> {
        let mut bytes_returned: DWORD = 0;
        let result = unsafe {
            DeviceIoControl(
                self.handle,
                FSCTL_ALLOW_EXTENDED_DASD_IO,
                ptr::null_mut(),
                0,
                ptr::null_mut(),
                0,
                &mut bytes_returned,
                ptr::null_mut(),
            )
        };

        if result == 0 {
            return Err(io::Error::last_os_error());
        }

        Ok(())
    }
}

impl Drop for Device {
    fn drop(&mut self) {
        unsafe { CloseHandle(self.handle) };
    }
}
Successful Output
Enter raw drive path (e.g., \\.\PhysicalDrive2):
\\.\PhysicalDrive2
WARNING: This will ERASE ALL DATA on "\\.\PhysicalDrive2"
Type 'YES' (all caps) to confirm:
YES
Opening ISO file: C:\path\to\my\file.iso
ISO file size: 2980511744 bytes (2842.44 MB)
Attempting to open device: \\.\PhysicalDrive2
Device size: 31457280000 bytes (29.30 GB)
Attempting to lock the volume...
Successfully locked the volume on attempt 1
Attempting to dismount the volume...
Successfully dismounted the volume
Enabling extended DASD I/O...
Warning: Failed to enable extended DASD I/O: Os { code: 87, kind: InvalidInput, message: "Falscher Parameter." }
Setting file pointer to beginning of device...
File pointer set successfully
Starting to write ISO to device...
Progress: 1% (29.00 MB / 2842.44 MB)
Progress: 2% (57.00 MB / 2842.44 MB)
/* I cut this out to keep the message somewhat short */
Progress: 100% (2842.44 MB / 2842.44 MB)
Write completed successfully!
Total bytes written: 2980511744 (2842.44 MB)
Unlocking volume...
Volume unlocked successfully
Operation completed successfully!
Permission Error Output
Enter raw drive path (e.g., \\.\PhysicalDrive2):
\\.\PHYSICALDRIVE2
WARNING: This will ERASE ALL DATA on "\\.\PHYSICALDRIVE2"
Type 'YES' (all caps) to confirm:
YES
Opening ISO file: C:\path\to\my\file.iso
ISO file size: 2980511744 bytes (2842.44 MB)
Attempting to open device: \\.\PHYSICALDRIVE2
Device size: 31457280000 bytes (29.30 GB)
Attempting to lock the volume...
Successfully locked the volume on attempt 1
Attempting to dismount the volume...
Successfully dismounted the volume
Enabling extended DASD I/O...
Warning: Failed to enable extended DASD I/O: Os { code: 87, kind: InvalidInput, message: "Falscher Parameter." }
Setting file pointer to beginning of device...
File pointer set successfully
Starting to write ISO to device...
Error writing to device at position 4331520: Os { code: 5, kind: PermissionDenied, message: "Zugriff verweigert" }
Error: Os { code: 5, kind: PermissionDenied, message: "Zugriff verweigert" }

This was increadibly weird. Since this behavoiur was so inconsistent I figured that it must have something to do with the data on the drives so I decided to delete all partitions from them. After doing so, everything worked consistently and even with my old code.

My best guess is that Windows has some sort of write protection so users can’t accidently overwrite their data. But this wasn‘t mentioned anywhere in their C++ API documentation to my knowledge (or maybe I overread it). Might this be new to Windows 11 or am I just making this solution and explanation up and the true reason is something entirely different?

My explanation also doesn‘t explain why the AI generated code worked at first (and only once) because there the drive already had partitions on it.

Given that this is mainly a question about Windows API use, and Rust seems to be calling CreateFileW and so on as expected, I had a look at some bulletproof formatting and copying code here: rufus/src/format.c at 845d26e3b650361290f2e8d0ae83c8509a330ef9 · pbatard/rufus · GitHub

As you've seen with your testing, Rufus takes at least 100 lines of C code to unmount and delete all the partitions before starting the actual formatting and copying, with special cases depending on the kind of filesystem being copied.

If you have the patience to go through the Rufus code and remove the code for Windows XP, and for things other than ISO images, it's probably not quite as bad.

3 Likes