tenox7/disktrim

Disk size detected as 0.0GB and only partition table is deleted, not data

Dwedit opened this issue · 14 comments

It appears that this program only deletes the partition table, and not the actual contents of the disk. I can still find data on the hard drive using a hex editor.

Before the Yes/No prompt, it displays the size of the disk drive (238.5GB for my ssd) correctly.

But after the Yes/No prompt, it displays this:

\\.\PhysicalDrive0 LBA: 0, Block: 0, Size: 0.0GB

It appears to be multiplying 0 by 0 to calculate a size of 0, and overwrites only that much.

Yes so looks like size detection problem. Can you send the whole output? Thanks!

This line seems to receive the incorrect information:

    if (!DeviceIoControl(hDisk, IOCTL_SCSI_PASS_THROUGH, Buffer, BufLen, Buffer, BufLen, &BytesRet, NULL))

Then there are Zeroes sitting in the buffer where the variables are read:

    DiskLbaCount = REVERSE_BYTES_LONG64(pReadCapacity->LBA);
    DiskBlockSize = REVERSE_BYTES_LONG(pReadCapacity->BlockLength);

I examined the ScsiPass struct, and it appears that TargetId became 0, SenseInfoLength became 0, and other data changed at all. I also checked within 256 bytes after the Cdb field, no changes there either.

Can you tell me more about the environment so I can try to reproduce it? Disk/machine make/model? Windows version? Thanks.

Sure...

I reproduced this on two machines.

One is a Dell laptop, with an M.2 SATA hard drive, 1TB from Crucial. Running on Windows 10 x64 ("2004" version). Made sure all erase code was commented out, so I could try debugging the IOCTRL call.

Other is an Acer laptop that I'm trying to return (want to blank out the SSD first), NVMe HFM256GDJTNG-831. Running Windows 10 PE. For some reason, none of the bootable linux USB sticks could see the drive, so I'm using Windows PE instead.

Perhaps it should try to do IOCTL_STORAGE_READ_CAPACITY instead of SCSI_PASSTHROUGH?

Edit: Tried IOCTL_STORAGE_READ_CAPACITY, and it returned 1953525168 sectors, each 512 bytes, for a size of 931.5GB. Don't know why SCSI_PASSTHROUGH is failing.

Edit 2: Looks like the SCSI_PASSTHROUGH trim command just doesn't work. "ERROR: TRIM didn't seem to work". Perhaps Windows 10 is blocking it?

Edit 3: Looks like IOCTL_STORAGE_READ_CAPACITY can verify if SCSI_PASSTHROUGH works or not. I wonder if ATA_PASSTHROUGH or windows nvme pasthrough would work?

NVME is not ATA. But it may be worth trying. Perhaps there is another class for NVME all together? Also could try to use IOCTL_STORAGE_READ_CAPACITY and see if it works?

I just did a few tests in VirtualBox using normal disk operations (no SCSI bypass stuff here). Actually getting VirtualBox to support the TRIM command took some command line trickery**, but you can do it.

  • Deleting a volume or deleting the partition table will not trigger a trim.
  • Creating a blank NTFS file system using Quick Format will trigger a trim.
  • Deleting a file will trigger a trim.
  • Creating a huge file using SetEndOfFile and SetFileValidData* will expose the prior contents of the disk, and the huge file can be deleted to trigger a trim.
  • Performing FSCTL_FILE_LEVEL_TRIM on a huge file only works on SSDs which ensure that data read back after a Trim is 00 bytes, otherwise the operation will fail.

So it seems the best way to actually trim a disk may be this:

  • Delete all paritions
  • Create a max-size NTFS parition, quick format it
  • Optional: create a huge dummy file using SetEndOfFile and SetFileValidData*, then delete the file
  • Make sure all IO operations have finished, and the disk is synced
  • Delete all partitions

I only tested this on VirtualBox. When VirtualBox sees a trim command, it will shrink the size of the virtual hard disk file, then future reads from those areas of the disk become 00 bytes.

Real SSDs may not necessarily read back 00 bytes after using TRIM, and Windows won't tell you if it actually sent any TRIM commands to the drive successfully.

*To get SetFileValidData to work: (also need to run the program as Admin)

	BOOL okay;
        TOKEN_PRIVILEGES priv = {}, prevPriv = {};
	HANDLE hToken = NULL;
	DWORD returnLength = 0;
	priv.PrivilegeCount = 1;
	priv.Privileges->Attributes = SE_PRIVILEGE_ENABLED;
	okay = LookupPrivilegeValueW(NULL, L"SeManageVolumePrivilege", &priv.Privileges->Luid);
	okay = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
	prevPriv = priv;
	okay = AdjustTokenPrivileges(hToken, false, &priv, sizeof(prevPriv), &prevPriv, &returnLength);

**To get VirtualBox to support the TRIM command:

  • Create your virtual Hard Drive, make sure it is set for Dynamically Allocated size.
  • Detach the virtual hard drive
  • Go to File > Virtual Media Manager
  • Click on the disk
  • Go to Information tab, copy the UUID to the clipboard
  • Go to a command prompt, change directory to "C:\Program Files\Oracle\VirtualBox"
  • Run VBoxManage storageattach "Name of VM HERE" --storagectl "SATA" --port 3 --type hdd --medium {UUID HERE} --discard on

VirtualBox is not really supported here because of unknown SCSI controller type. I also don't want to create/delete any files, as this would be filesystem based trim, where I want a real, SCSI controller UNMAP, hence SCSI passthrough stuff. To me the real issue is zero LBA count, which will need to be fixed.

Okay, to fix the zero LBA count thing, just use IOCTL_STORAGE_READ_CAPACITY, and compare against the current passthrough code. If they disagree, the passthrough doesn't work.

Do you want to send a PR?

Looks like simply checking if it got zeroes and erroring out at that point would be sufficient.

    if (DiskLbaCount == 0 && DiskBlockSize == 0)
    {
        error(1, L"Error: SCSI Passthrough is not supported on this drive.");
    }

Here is the code that uses IOCTL_DISK_GET_LENGTH_INFO instead of SCSI passthrough to get the size. I don't really see a point to including it though, because if scsi passthrough fails on something like getting the drive size, it probably won't succeed on a TRIM command either.

    STORAGE_READ_CAPACITY capacity;
    ZeroMemory(&capacity, sizeof(capacity));

    if (!DeviceIoControl(
        (HANDLE)hDisk,             // handle to device
        IOCTL_STORAGE_READ_CAPACITY,  // dwIoControlCode
        NULL,                         // lpInBuffer
        0,                            // nInBufferSize
        (LPVOID)&capacity,         // output buffer
        (DWORD)sizeof(capacity),       // size of output buffer
        (LPDWORD)&BytesRet,    // number of bytes returned
        NULL  // OVERLAPPED structure 
    ))
    {
        error(1, L"Error on DeviceIoControl IOCTL_DISK_GET_LENGTH_INFO");
    }

    DiskLbaCount = (ULONG64)capacity.NumberOfBlocks.QuadPart;
    DiskBlockSize = (ULONG)capacity.BlockLength;

OK but wait have you tried if passthrough for trim works? Perhaps it's just size info that doesn't work. Have you tried to use IOCTL_STORAGE_READ_CAPACITY and feed it to the UNMAP passthrough?

When I tried it with a correct LBA count and Block size, none of the DeviceIoControl calls returned false, but when it checked the test pattern, it displayed "TRIM didn't seem to work".

I think there may be some problem with the M.2 SATA bus. Perhaps it doesn't support SCSI passthrough? Maybe it comes up as some legacy ATA bus in Windows. Can you see how is it connected in Windows? How about wmic diskdrive for starters. Maybe we could also debug it with busTRACE.

Can you download busTRACE capture client from this page: http://www.bustrace.com/downloads/free_utilities.php then do capture an unmodified vanilla version of disktrim and send me the capture file? Thanks.