Introduction
Ejecting a media is a well known task for CD/DVD drives. Here we get a physical ejection. But also ZIP/JAZ drives and card readers (drives with a removable media) support ejection and since they are not read only, it is required to dismount their file system before ejection to prevent loss of data.
Background
Ejecting a media is performed by the DeviceIoControl
call IOCTL_STORAGE_EJECT_MEDIA
. But it is quite reckless, it ejects even when there are open files on the drive! Therefore it's better to flush, lock, and dismount before doing it.
The Sample
This sample is a simplified version of my command-line tool EjectMedia.
It expects the drive letter to eject as a parameter. For non CD/DVD drives, the first step is to flush the file cache by means of the FlushFileBuffers
API call. It needs a handle with write access. So, for flushing a whole storage volume, we have to open it with write access, which will fail with ERROR_ACCESS_DENIED
for restricted users:
char szVolumeAccessPath[] = "\\\\.\\X:";
szVolumeAccessPath[4] = DriveLetter;
HANDLE hVolWrite = CreateFile(szVolumeAccessPath,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
if ( hVolWrite != INVALID_HANDLE_VALUE ) {
FlushFileBuffers(hVolWrite);
CloseHandle(hVolWrite);
}
Then we try to lock and dismount. Here read access is good enough which even restricted users can get. A lock succeeds only if there are no open handles to files on this volume. Dismount succeeds even when lock fails and there are open handles. After dismount, all open file handles on the volume are still open but invalid, any try to use it for read or write actions leads to ERROR_NOT_READY
. Therefore it is an option to dismount even when the lock fails, that is the meaning of the 'Force
' bool value.
bool Force = false;
HANDLE hVolRead = CreateFile(szVolumeAccessPath,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
if ( hVolRead != INVALID_HANDLE_VALUE ) {
int Locked = DeviceIoControl(hVolRead, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &dwRet, NULL);
if ( Locked || Force ) {
res = DeviceIoControl(hVolRead, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, &dwRet, NULL);
}
}
Finally we perform the ejection:
res = DeviceIoControl(hVolRead, IOCTL_STORAGE_EJECT_MEDIA, NULL, 0, NULL, 0, &dwRet, NULL);
Since we are friendly, we release the lock if we get it:
if ( Locked ) {
DeviceIoControl(hVolRead, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL, 0, &dwRet, NULL);
}
BTW: The opposite of ejection is loading a media. This really works on CD/DVD drives only:
res = DeviceIoControl(hVolRead, IOCTL_STORAGE_LOAD_MEDIA, NULL, 0, NULL, 0, &dwRet, NULL);
Discussion
Why is there no check for drivetypes DRIVE_CDROM and DRIVE_REMOVABLE?
Windows does not care about the drive type here. The eject request is passed down to the drive's driver which passes it to the hardware. Finally it is the hardware which gives the answer. There are USB hard drives which support the eject. I've seen safe removal failing but ejection succeeding.
What exactly is dismounting?
Dismounting means to detach the file system from the storage volume. This guarantees that the file system is not held in an inconsistent state which is important before mounting it by another driver, e.g., while the system is hibernated and you boot from a bootable CD.
Isn't it the same as removing the drive letter (mountvol X: /D)?
No, absolutely not! The /D does not stand for dismount, it stands for deleting the mount point. And that is all it does. No flushing, no dismounting, no handles are closed or invalidated.
How do we remount a volume?
If the lock is released, then just try to open a file on the volume. Windows automatically mounts the volume then. Handles which have been opened before the dismount was performed stay unusable for read/write, they permanently generate ERROR_NOT_READY
even when the volume is mounted again.
Why does locking take a while sometimes?
It seems that locking also flushes the file cache and this may take a little time. Furthermore, applications can register for custom notifications (RegisterDeviceNotification
) and then DBT_CUSTOMEVENT
comes with GUID_IO_VOLUME_LOCK
. FSCTL_LOCK_VOLUME
will not return before all applications registered for custom notifications of the volume in question have processed the message.
The Demo Project
The demo project is made with VS6. It requires some headers from a Microsoft Platform SDK. No special LIBs are required.
History
- 26 Sept 2011 - First release.
- 25 Oct 2011 - Corrections: The error after dismounting is
ERROR_NOT_READY
, not ERROR_INVALID_HANDLE
, and missing CloseHandle
in the demo project.