Безопасное извлечение 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/

Поделиться в соц сетях

Оставить комментарий

  Подписаться  
Уведомление о

Есть идеи, замечания, предложения? Воспользуйтесь формой Обратная связь или отправьте сообщение по адресу replay@sciencestory.ru
© 2017 Истории науки. Информация на сайте опубликована в ознакомительных целях может иметь ограничение 18+