Безопасное извлечение USB-устройств — Архив WASM.RU
Цель данной статьи - описание метода безопасного извлечения USB-устройств. В данном случаи под USB-устройствами будут, подразумевается в основном устройства хранения информации (Flash, HDD), но все описанные ниже действия применимы и устройствам других классов. Существует мнение, что данная функция не нужна при обращении к USB-устройствам. Обычно нет ничего страшного в том, что устройство было извлечено небезопасно. В крайнем случае, может потеряться информация, которая еще не была записана на USB (например, если у вас включено кэширование записи на диск). Но все же осторожность не повредит. Следующий вопрос, который может возникнуть, это зачем писать то, что уже реализовано в Windows. Например, быстро вызвать окно безопасного отключения устройств, можно выполнив команду:
rundll32.exe shell32.dll,Control_RunDLL hotplug.dll
Здесь можно заметить, что реализация Microsoft не всем нравится. Тем более что можно развить мысль, например, вести поиск процессов, из-за которых невозможно безопасное извлечение. Т.е. процессов, которые имеют незакрытые дескрипторы файлов или папок, расположенных на съемном носителе.
Итак, начнем с того, что построим список всех устройств компьютера, с указанием, какие из них поддерживают безопасное извлечение. Все устройства в компьютере связаны “родственными узами”, т.е. могут быть представлены узлами некого дерева. Это выглядит примерно так:
Код (Text):
HTREE\ROOT\0 ROOT\ACPI_HAL\0000 'Однопроцессорный компьютер с ACPI' ACPI_HAL\PNP0C08\0 'Microsoft ACPI-совместимая система' ACPI\GENUINEINTEL_-_X86_FAMILY_15_MODEL_2\_0 'Intel(R) Celeron(R) CPU 2.40GHz' ACPI\PNP0A03\0 'Шина PCI' PCI\VEN_8086&DEV_2560&SUBSYS_0000&REV_03\3&267A616A&0&00 'Intel(R) 82845G/GL/GE/PE/GV Processor to I/O Controller - 2560 ' PCI\VEN_8086&DEV_2561&SUBSYS_0000&REV_03\3&267A616A&0&08 'Intel(R) 82845G/GL/GE/PE/GV Processor to AGP Controller - 2561 ' PCI\VEN_10DE&DEV_0181&SUBSYS_31021458&REV_C1\4&BCE2C57&0&0008 'NVIDIA GeForce4 MX 440 with AGP8X' DISPLAY\SAM01F9\5&EB43F14&1&22446688&01&00 'Модуль подключения монитора' [REMOVEABLE] PCI\VEN_8086&DEV_24C2&SUBSYS_53568086&REV_02\3&267A616A&0&E8 'Intel(R) 82801DB/DBM USB Universal Host Controller - 24C2 ' USB\ROOT_HUB20\4&3B39A3A1&0 'Корневой USB концентратор' USB\VID_04CF&PID_8818\100 'Запоминающее устройство для USB' [REMOVEABLE] USBSTOR\DISK&VEN_SAMSUNG&PROD_MP0402H&REV_UC10\100&0 'SAMSUNG MP0402H USB Device'Существуют несколько способов обхода графа (двоичного дерева) – рекурсивный и нерекурсивный методы [1],[2]. Для простоты применим рекурсивный метод.
Необходимые нам функции относятся к группе “PnP Configuration Manager Functions”, имеют префикс “CM_” и экспортируются библиотекой setupapi.dll.
Для использования этой библиотеки в MASM32 необходимо подключить setupapi.inc. Описание можно получить в MSDN, но на всякий случай я приведу здесь краткое описание основных используемых функций.
CM_Locate_DevNode – используется нами для получения корневого узла дерева устройств.
Код (Text):
CM_Locate_DevNode( OUT PDEVINST pdnDevInst, IN DEVINSTID pDeviceID, OPTIONAL IN ULONG ulFlags );Параметры
CM_Get_DevNode_Status – позволяет получить статус устройства по которому можно определить, можно ли извлечь данное устройство. Если в статусе флаг DN_REMOVABLE установлен, то устройство можно извлечь.
- pdnDevInst – указатель на переменную, куда будет записан дескриптор устройства
- pDeviceID – указатель на строку с идентификатором устройства. Если ноль, то возвращается корневой узел дерева устройств
- ulFlags – обычно функцию вызывают с флагом CM_LOCATE_DEVNODE_NORMAL
Код (Text):
CM_Get_DevNode_Status( OUT PULONG pulStatus, OUT PULONG pulProblemNumber, IN DEVINST dnDevInst, IN ULONG ulFlags );Параметры
- pulStatus – указатель на переменную типа DWORD со статусом устройства
- pulProblemNumber – указатель на переменную типа DWORD с номером ошибки
- dnDevInst – идентификатор устройства
- ulFlags – не используется. Должен быть нулем
CM_Get_Device_ID_Size – выдает размер строки идентификатора устройства
Код (Text):
CM_Get_Device_ID_Size( OUT PULONG pulLen, IN DEVINST dnDevInst, IN ULONG ulFlags );Параметры
CM_Get_Device_ID – позволяет узнать идентификатор устройства.
- pulLen – указатель на переменную для записи длины строки
- dnDevInst – идентификатор устройства
- ulFlags – не используется. Должен быть нулем
Код (Text):
CM_Get_Device_ID( IN DEVINST dnDevInst, OUT PTCHAR Buffer, IN ULONG BufferLen, IN ULONG ulFlags );Параметры
- dnDevInst – идентификатор устройства
- Buffer – указатель на буфер для записи строки идентификатора устройства
- BufferLen – длина строки идентификатора устройства
- ulFlags – не используется. Должен быть нулем
CM_Get_Child – необходима для получения потомка данного узла
Код (Text):
CM_Get_Child( OUT PDEVINST pdnDevInst, IN DEVINST dnDevInst, IN ULONG ulFlags );Параметры
- pdnDevInst – указатель на идентификатор устройства потомка
- dnDevInst – идентификатор устройства
- ulFlags – не используется. Должен быть нулем
CM_Get_Sibling – необходима для получения узла, находящегося следом за данным
Код (Text):
CM_Get_Sibling( OUT PDEVINST pdnDevInst, IN DEVINST DevInst, IN ULONG ulFlags );Параметры
- pdnDevInst – указатель на идентификатор смежного устройства
- dnDevInst – идентификатор устройства
- ulFlags – не используется. Должен быть нулем
CM_Locate_DevNode – позволяет получить дескриптор устройства по строке идентификатору
Код (Text):
CM_Locate_DevNode ( OUT PDEVINST pdnDevInst, IN DEVINSTID pDeviceID, OPTIONAL IN ULONG ulFlags, );Параметры
- pdnDevInst – указатель на дескриптор устройства
- pDeviceID – указатель на строку идентификатор устройства
- ulFlags – флаг мы будем использовать CM_LOCATE_DEVNODE_NORMAL
CM_Request_Device_Eject – как раз эта функция и позволяет безопасно извлечь устройство
Код (Text):
CM_Request_Device_Eject( IN DEVINST dnDevInst, OUT PPNP_VETO_TYPE pVetoType, OUT LPTSTR pszVetoName, IN ULONG ulNameLength, IN ULONG ulFlags );Параметры
- dnDevInst – идентификатор устройства
- pVetoType – в данном случае не нужно
- pszVetoName – в данном случае не нужно
- ulNameLength – в данном случае не нужно
- ulFlags – не используется. Должен быть нулем
CM_Get_DevNode_Registry_Property – извлекает спец. информацию об устройстве из соответствующих ключей реестра.
Код (Text):
CM_Get_DevNode_Registry_Property( IN DEVINST dnDevInst, IN ULONG ulProperty, OUT PULONG pulRegDataType, OPTIONAL OUT PVOID Buffer, OPTIONAL IN OUT PULONG pulLength, IN ULONG ulFlags );Параметры
- dnDevInst – идентификатор устройства
- ulProperty – константа с префиксом CM_DRP_ которая идентифицирует, какое из свойств устройство необходимо. Все эти константы объявлены в файле cfgmgr32.h.
- pulRegDataType – не обязательный параметр может быть NULL. Указатель на переменную, содержащую тип запрашиваемого значения.
- Buffer – адрес буфера для возвращаемого значения. Если этот параметр NULL, то функция вернет размер возвращаемой строки
- pulLength – указатель на переменную для записи размера полученной строки.
- ulFlags – не используется. Должен быть нулем
Теперь приступаем непосредственно к коду. Ниже приводится фрагмент, отвечающий за обход дерева устройств. Что бы не загромождать код, устройства будем перечислять в виде списка.
Код (Text):
EnumDevice proc dDevInst:DWORD LOCAL NewDevInst :DWORD LOCAL len :DWORD ;Получение статуса устройства invoke CM_Get_DevNode_Status,addr Status,addr ProblemNum,dDevInst,0 ;Получение идентификатора устройства invoke CM_Get_Device_ID_Size,addr DevLen,dDevInst,0 inc DevLen invoke CM_Get_Device_ID,dDevInst,addr aDeviceId,DevLen,0 ;Получение описания устройства mov len,sizeof aBuffer invoke CM_Get_DevNode_Registry_Property,dDevInst,CM_DRP_FRIENDLYNAME,0,\ addr aBuffer, addr len,0 .if eax!=CR_SUCCESS invoke CM_Get_DevNode_Registry_Property,dDevInst,CM_DRP_DEVICEDESC,0,\ addr aBuffer, addr len,0 .endif .if eax!=CR_SUCCESS mov aBuffer[0],0 .endif ;выводим информацию .if fFiltering==1 .if (Status & DN_REMOVABLE) invoke AddItemDev .endif .else invoke AddItemDev .endif ;ищем потомков invoke CM_Get_Child,addr NewDevInst,dDevInst,0 .if eax == CR_SUCCESS invoke EnumDevice,NewDevInst .endif ;ищем соседей invoke CM_Get_Sibling,addr NewDevInst,dDevInst,0 .if eax == CR_SUCCESS invoke EnumDevice,NewDevInst .endif Ret EnumDevice EndPВызов данной процедуры происходит следующим образом:
Код (Text):
;получаем корневой элемент invoke CM_Locate_DevNode,addr dnDevInst,NULL,CM_LOCATE_DEVNODE_NORMAL ;запускаем обход дерева invoke EnumDevice,dnDevInstПо полученному полю Status можно выяснить, предусмотрено ли безопасное извлечение устройства.
Код (Text):
.if (Status & DN_REMOVABLE) ;Устройство можно извлечь .else ;Извлечение не поддерживается .endifСледующий код является ключевым, именно он и позволяет безопасно отключить найденное устройство.
Код (Text):
……………… ;по идентификатору устройства узнаем его дескриптор invoke CM_Request_Device_Eject, dnDevInst, NULL, NULL, NULL, NULL ………………При таким вызове система сама оповестит пользователя о том, что диск больше не доступен или о том, что в данный момент устройство используется и не может быть извлечено. Если необходимо что бы система не реагировала, то можно вызвать функцию с такими параметрами:
Код (Text):
.data pVetoType dd 0 pVetoName db 5 dup (0) .code ……………… mov pVetoType,0 mov pVetoName[0],0 invoke CM_Request_Device_Eject,dnDevInst,addr pVetoType,addr pVetoName,MAX_PATH,NULL ………………Также предусмотрена возможность получения иконки, ассоциированной с каким либо устройством (иконки можно видеть в диспетчере устройств). Для этого лишь необходим GUID класса устройства. Его можно получить, например, функцией CM_Get_DevNode_Registry_Property с параметром CM_DRP_CLASSGUID.
Код (Text):
.data ClassImageList ClassImageListData .code ……………… invoke SetupDiGetClassImageList,addr ClassImageList ……………… invoke SetupDiGetClassImageIndex,addr ClassImageList,addr ClassGUID,addr dwImageIndex ……………… invoke SetupDiDestroyClassImageList,addr ClassImageListСледующий важный момент - как отловить момент подключения отключения устройства. Для этого существует специальное сообщение WM_DEVICECHANGE посылаемое всем главным окнам в системе.
Код (Text):
.if eax == WM_DEVICECHANGE mov eax,wParam .if eax == DBT_DEVICEARRIVAL mov eax,lParam assume eax:ptr _DEV_BROADCAST_HDR mov eax,[eax].dbch_devicetype assume eax:nothing .if eax == DBT_DEVTYP_VOLUME mov eax,lParam assume eax:ptr _DEV_BROADCAST_VOLUME mov edx,[eax].dbcv_unitmask xor ebx,ebx .while ebx<26 mov ecx,edx and edx,01h .if edx==1 mov cx,word ptr [eax].dbcv_flags .if cx == 00h ; Добавлен новый диск .endif .break .else mov edx,ecx shr edx,1 .endif inc ebx .endw assume eax:nothing .endif .elseif eax == DBT_DEVNODES_CHANGED ; Добавлено/удалено устройство .endifВ приложении к статье можно найти программу, выполняющую вышеперечисленные действия.
Кроме того, можно запретить все устройства хранения для USB. Похожим способом можно отключать и другие устройства.
Код (Text):
ChangeStateUSBDevice proc fState:DWORD LOCAL hDevInfoSet :DWORD LOCAL dwTmp :DWORD LOCAL BufStr[255] :BYTE LOCAL Result :DWORD mov Result,FALSE mov pcp.ClassInstallHeader.cbSize,sizeof SP_CLASSINSTALL_HEADER mov pcp.ClassInstallHeader.InstallFunction,DIF_PROPERTYCHANGE mov eax,fState mov pcp.StateChange,eax mov pcp.Scope,DICS_FLAG_GLOBAL invoke SetupDiGetClassDevs,addr DISK_GUID,NULL,NULL,DIGCF_PRESENT mov hDevInfoSet,eax mov DevInfoData.cbSize,sizeof SP_DEVINFO_DATA xor ecx,ecx loop1: push ecx invoke SetupDiEnumDeviceInfo,hDevInfoSet, ecx, addr DevInfoData .if eax==0 jmp @ .endif invoke SetupDiGetDeviceRegistryProperty,hDevInfoSet,\ addr DevInfoData,SPDRP_COMPATIBLEIDS,\ addr dwTmp,addr BufStr,sizeof BufStr,NULL invoke lstrcmp,addr BufStr,offset aUSBSTOR .if eax==0 invoke SetupDiSetClassInstallParams,hDevInfoSet,\ addr DevInfoData,addr pcp,sizeof SP_PROPCHANGE_PARAMS .if eax==0 jmp @ .endif invoke SetupDiCallClassInstaller,DIF_PROPERTYCHANGE,\ hDevInfoSet, addr DevInfoData .if eax==0 jmp @ .endif mov Result,TRUE .endif pop ecx inc ecx jmp loop1 invoke SetupDiDestroyDeviceInfoList,hDevInfoSet @: mov eax,Result Ret ChangeStateUSBDevice EndPПриложения:
- DevEject.rar – Исходный текст программы, перечисляющие устройства и позволяющие безопасно извлечь выбранное устройство.
- USB_Disable.rar – Программа может отключать/включать USB накопители.
- Dr_USB.rar – вариация на тему «безопасное извлечение USB-устройств.» Программа в форме меню выводит список извлекаемых устройств с указанием буквы логического диска, если это устройство предназначено для хранения информации.
Литература
© GMax
- Axo, Альфред, В. Хопкрофт, Джон, Ульман, Джеффри, Д. – Структуры данных и алгоритмы
- http://ru.wikipedia.org/wiki/Двоичное_дерево
- http://www.codeproject.com/KB/system/RemoveDriveByLetter.aspx
- http://www.uwe-sieber.de
Безопасное извлечение USB-устройств
Дата публикации 21 фев 2008