Безопасное извлечение USB-устройств (Программирование под Win 32)
Цель данной статьи — описание метода безопасного извлечения USB-устройств. В данном случаи под USB-устройствами будут, подразумевается в основном устройства хранения информации (Flash, HDD), но все описанные ниже действия применимы и устройствам других классов. Существует мнение, что данная функция не нужна при обращении к USB-устройствам. Обычно нет ничего страшного в том, что устройство было извлечено небезопасно. В крайнем случае, может потеряться информация, которая еще не была записана на USB (например, если у вас включено кэширование записи на диск). Но все же осторожность не повредит. Следующий вопрос, который может возникнуть, это зачем писать то, что уже реализовано в Windows. Например, быстро вызвать окно безопасного отключения устройств, можно выполнив команду:
rundll32.exe shell32.dll,Control_RunDLL hotplug.dll
Здесь можно заметить, что реализация Microsoft не всем нравится. Тем более что можно развить мысль, например, вести поиск процессов, из-за которых невозможно безопасное извлечение. Т.е. процессов, которые имеют незакрытые дескрипторы файлов или папок, расположенных на съемном носителе.
Итак, начнем с того, что построим список всех устройств компьютера, с указанием, какие из них поддерживают безопасное извлечение. Все устройства в компьютере связаны “родственными узами”, т.е. могут быть представлены узлами некого дерева. Это выглядит примерно так:
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∏_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 – используется нами для получения корневого узла дерева устройств.
CM_Locate_DevNode(
OUT PDEVINST pdnDevInst,
IN DEVINSTID pDeviceID, OPTIONAL
IN ULONG ulFlags
);
Параметры
pdnDevInst – указатель на переменную, куда будет записан дескриптор устройства
pDeviceID – указатель на строку с идентификатором устройства. Если ноль, то возвращается корневой узел дерева устройств
ulFlags – обычно функцию вызывают с флагом CM_LOCATE_DEVNODE_NORMAL
CM_Get_DevNode_Status – позволяет получить статус устройства по которому можно определить, можно ли извлечь данное устройство. Если в статусе флаг DN_REMOVABLE установлен, то устройство можно извлечь.
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 – выдает размер строки идентификатора устройства
CM_Get_Device_ID_Size(
OUT PULONG pulLen,
IN DEVINST dnDevInst,
IN ULONG ulFlags
);
Параметры
pulLen – указатель на переменную для записи длины строки
dnDevInst – идентификатор устройства
ulFlags – не используется. Должен быть нулем
CM_Get_Device_ID – позволяет узнать идентификатор устройства.
CM_Get_Device_ID(
IN DEVINST dnDevInst,
OUT PTCHAR Buffer,
IN ULONG BufferLen,
IN ULONG ulFlags
);
Параметры
dnDevInst – идентификатор устройства
Buffer – указатель на буфер для записи строки идентификатора устройства
BufferLen – длина строки идентификатора устройства
ulFlags – не используется. Должен быть нулем
CM_Get_Child – необходима для получения потомка данного узла
CM_Get_Child(
OUT PDEVINST pdnDevInst,
IN DEVINST dnDevInst,
IN ULONG ulFlags
);
Параметры
pdnDevInst – указатель на идентификатор устройства потомка
dnDevInst – идентификатор устройства
ulFlags – не используется. Должен быть нулем
CM_Get_Sibling – необходима для получения узла, находящегося следом за данным
CM_Get_Sibling(
OUT PDEVINST pdnDevInst,
IN DEVINST DevInst,
IN ULONG ulFlags
);
Параметры
pdnDevInst – указатель на идентификатор смежного устройства
dnDevInst – идентификатор устройства
ulFlags – не используется. Должен быть нулем
CM_Locate_DevNode – позволяет получить дескриптор устройства по строке идентификатору
CM_Locate_DevNode (
OUT PDEVINST pdnDevInst,
IN DEVINSTID pDeviceID, OPTIONAL
IN ULONG ulFlags,
);
Параметры
pdnDevInst – указатель на дескриптор устройства
pDeviceID – указатель на строку идентификатор устройства
ulFlags – флаг мы будем использовать CM_LOCATE_DEVNODE_NORMAL
CM_Request_Device_Eject – как раз эта функция и позволяет безопасно извлечь устройство
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 – извлекает спец. информацию об устройстве из соответствующих ключей реестра.
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 – не используется. Должен быть нулем
Теперь приступаем непосредственно к коду. Ниже приводится фрагмент, отвечающий за обход дерева устройств. Что бы не загромождать код, устройства будем перечислять в виде списка.
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
Вызов данной процедуры происходит следующим образом:
;получаем корневой элемент
invoke CM_Locate_DevNode,addr dnDevInst,NULL,CM_LOCATE_DEVNODE_NORMAL
;запускаем обход дерева
invoke EnumDevice,dnDevInst
По полученному полю Status можно выяснить, предусмотрено ли безопасное извлечение устройства.
.if (Status & DN_REMOVABLE)
;Устройство можно извлечь
.else
;Извлечение не поддерживается
.endif
Следующий код является ключевым, именно он и позволяет безопасно отключить найденное устройство.
………………
;по идентификатору устройства узнаем его дескриптор
invoke CM_Request_Device_Eject, dnDevInst, NULL, NULL, NULL, NULL
………………
При таким вызове система сама оповестит пользователя о том, что диск больше не доступен или о том, что в данный момент устройство используется и не может быть извлечено. Если необходимо что бы система не реагировала, то можно вызвать функцию с такими параметрами:
.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.
.data
ClassImageList ClassImageListData <>
.code
………………
invoke SetupDiGetClassImageList,addr ClassImageList
………………
invoke SetupDiGetClassImageIndex,addr ClassImageList,addr ClassGUID,addr dwImageIndex
………………
invoke SetupDiDestroyClassImageList,addr ClassImageList
Следующий важный момент — как отловить момент подключения отключения устройства. Для этого существует специальное сообщение WM_DEVICECHANGE посылаемое всем главным окнам в системе.
.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. Похожим способом можно отключать и другие устройства.
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-устройств.» Программа в форме меню выводит список извлекаемых устройств с указанием буквы логического диска, если это устройство предназначено для хранения информации.
Литература
Axo, Альфред, В. Хопкрофт, Джон, Ульман, Джеффри, Д. – Структуры данных и алгоритмы
ru.wikipedia.org/wiki/Двоичное_дерево
www.codeproject.com/KB/system/RemoveDriveByLetter.aspx
www.uwe-sieber.de
[C] GMax
Источник WASM.RU /20.02.2008/