安全移除存储介质的操作指南

在现代计算机系统中,经常需要安全地移除存储介质,如硬盘、USB驱动器、ZIP/JAZ驱动器、读卡器等。这些设备支持热插拔,但为了确保数据的完整性和系统的稳定性,需要按照一定的步骤来操作。本文将介绍如何安全地移除这些存储介质。

移除存储介质通常是通过调用DeviceIoControl函数并使用IOCTL_STORAGE_EJECT_MEDIA命令来实现的。然而,直接使用这种方法可能会造成数据丢失,因为它甚至在驱动器上有打开的文件时也会执行弹出操作。因此,更好的做法是在移除之前先清空文件缓存、锁定和卸载文件系统

示例工具

这里介绍的是一个简化版的命令行工具EjectMedia。它需要传入要移除的驱动器字母作为参数。对于非CD/DVD驱动器,第一步是通过FlushFileBuffers API调用来清空文件缓存。这需要一个具有写入权限的句柄。因此,为了清空整个存储卷,需要以写入权限打开它,这可能会因为受限用户而失败:

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

接下来,尝试锁定和卸载。这里只需要读取权限,即使是受限用户也可以获得。如果该卷上没有打开的文件句柄,锁定就会成功。即使锁定失败,卸载也可以成功,并且卸载后所有打开的文件句柄仍然打开但无效,任何尝试使用它们进行读写操作都会导致ERROR_NOT_READY错误。因此,即使锁定失败,也可以选择卸载,这就是'Force'布尔值的含义:

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

最后,执行弹出操作:

res = DeviceIoControl(hVolRead, IOCTL_STORAGE_EJECT_MEDIA, NULL, 0, NULL, 0, &dwRet, NULL);

由于采取友好的方式,如果获得了锁定,会释放它:

if (Locked) { DeviceIoControl(hVolRead, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL, 0, &dwRet, NULL); }

顺便说一句,弹出的相反操作是加载介质。这真的只对CD/DVD驱动器有效:

res = DeviceIoControl(hVolRead, IOCTL_STORAGE_LOAD_MEDIA, NULL, 0, NULL, 0, &dwRet, NULL);

为什么没有检查驱动器类型DRIVE_CDROMDRIVE_REMOVABLE

Windows在这里不关心驱动器类型。弹出请求被传递到驱动器的驱动程序,然后传递到硬件。最终是由硬件给出答案。有些USB硬盘支持弹出。见过安全移除失败但弹出成功的案例。

什么是卸载?

卸载意味着将文件系统从存储卷上分离。这保证了在另一个驱动程序(例如,在系统休眠时,从可启动CD启动)挂载它之前,文件系统不会被保持在不一致的状态,这很重要。

这和删除驱动器字母(mountvol X: /D)是一样的吗?

不,绝对不是!/D不代表卸载,它代表删除挂载点。它只做这个。没有清空,没有卸载,没有句柄被关闭或使无效。

如何重新挂载一个卷?

如果锁定被释放,那么尝试打开卷上的一个文件。Windows会自动挂载该卷。在卸载之前打开的句柄仍然无法用于读写,它们永久性地生成ERROR_NOT_READY,即使卷再次被挂载。

为什么锁定有时会花费一些时间?

锁定似乎也会清空文件缓存,这可能需要一点时间。此外,应用程序可以注册自定义通知(RegisterDeviceNotification),然后DBT_CUSTOMEVENT会带有GUID_IO_VOLUME_LOCKFSCTL_LOCK_VOLUME不会在所有注册了该卷自定义通知的应用程序处理完消息之前返回。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485