Dokan 库
Copyright(c) Hiroki Asakawa http://dokan-dev.net/en

什么是Dokan库
=====================================================================
当你想要在Windows下创建一个新的文件系统的时候,比如,改进FAT或者NTFS,你需要开发一个文件系统驱动。在内核模式开发一个设备驱动是一个非常棘手的问题。通过使用Dokan库,你可以很轻松地创建一个属于你自己的文件系统,而不需要写一个设备驱动。Dokan库非常相思雨FUSE(Linux用户下的文件系统),但是工作在Windows环境下。

许可声明
===================================================================
Dokan库包括LGPL(宽松的GPL授权声明,表示开源代码可以用到非开源私有程序,译者注),MIT许可程序(和LGPL有些类似,编者注)

- 用户模式库(dokan.dll)          LGPL
- 驱动(dokan.sys)   LGPL
- 控制程序(dokanctl.exe)  MIT
- 挂载服务(mounter.exe)  MIT
- 样本程序(mirror.c)   MIT

需要进一步的细节,请查看许可文件。
LGPL license.lgpl.txt
GPL  license.gpl.txt
MIT  license.mit.txt

你可以通过以下网址获取源码 http://dokan-dev.net/en/download

环境
====================================================================
Dokan库运行在 Windowx XP,2003,Vista,2008,7 x86 和 Windows 2003,Vista,2008,7 x64.

它是怎么工作的
====================================================================
Dokan库包括一个用户态DLL(dokan.dll)和一个内核模式文件系统驱动(dokan.sys)。一旦Dokan文件系统驱动安装好之后,你就可以创建和Windows下可以看见的普通的文件系统一样的文件系统了。这个被我们称之为文件系统级的应用程序可以利用Dokan库来创建文件系统。
用户态程序的文件操作请求(比如创建文件,读取文件,写文件。。。)将会被发送到Windows I/O子系统(运行在内核态),紧接着送到Dokan文件系统驱动(dokan.sys)。
通过使用Dokan用户态库(dokan.dll)提供的函数,文件系统程序能够注册回调函数到文件系统驱动中。
文件系统驱动将会调用这些回调例程(routines),为了能够响应它收到的请求。回调例程的结果会被送回到用户态程序。
比如,当Windows浏览器请求打开一个目录的时候,OpenDirectory请求会被送到Dokan文件系统驱动并且驱动会调用文件系统程序提供的OpenDirectory回调函数。这个例程的结果将被送到Windows浏览器,作为OpenDirectory请求的响应。因此,Dokan文件系统驱动扮演者一个基于用户程序和文件系统程序之间的一个代理角色。
这种处理方式的优点就是它允许开发者在用户态下开发文件系统,这样安全并且容易调试。

库的部件组成和安装
=======================================================================
当我们运行安装程序是,它会安装Dokan文件系统驱动(dokan.sys),注册Dokan挂载服务(mounter.exe)和一系列库。安装的文件详细清单如下:

系统文件夹/dokan.dll                                Dokan用户态库
系统文件夹/drivers/dokan.sys                   Dokan挂载服务
ProgramFiles文件夹/Dokan/DokanLibrary/mounter.exe           Dokan挂载服务
ProgramFiles文件夹/Dokan/DokanLibrary/dokanctl.exe           Dokan控制程序
ProgramFiles文件夹/Dokan/DokanLibrary/dokan.lib                 Dokan导入库
ProgramFiles文件夹\Dokan\DokanLibrary\dokan.h                   Dokan库头文件
ProgramFiles文件夹\Dokan\DokanLibrary\readme.txt              这个文件(你正在读的,译者注)

你可以使用控制面板的 Add/Remove 程序来卸载Dokan。卸载后,需要重启你的电脑。

怎样创建你的文件系统
======================================================================
创建文件系统,一个应用程序需要在DOKAN_OPERATIONS结构实现函数(在dokan.h中声明)。函数一旦实现,你就可以把DOKAN_OPERRATIONS作为参数调用DokanMain函数。
为了挂载文件系统,在DOKAN_OPERATIONS中的函数语义和WindowsAPI语义相似并且有相同的名字。这些函数的参数和相应的WindowsAPI相同。这些函数被很多的线程调用,所以他们需要保证线程安全,否则很多问题可能产生。

这些函数典型的以下面的顺序调用:
1、创建文件CreatFile(打开文件)
2、其他函数
3、清除Cleanup
4、关闭文件CloseFile

文件访问操作(展现目录,读取文件属性。。。)之前,经常需要调用文件创建函数(打开目录,创建文件。。。)。在另一方面,当CloseFile Windows API关闭文件的时候,Dokan文件系统驱动会调用清除函数Cleanup。当操作成功结束,每一个函数应该返回0,否则返回一个负数表示错误码。错误码和Windows系统错误码相反。比如当CreatFile不能打开文件,你应该返回-2(-1*ERROR_FILE_NOT_FOUND)。

每一个函数的最后一个参数是一个DOKAN_FILE_INFO结构:
   typedef struct _DOKAN_FILE_INFO {

ULONG64 Context;
       ULONG64 DokanContext;
       ULONG   ProcessId;
       BOOL    IsDirectory;

} DOKAN_FILE_INFO, *PDOKAN_FILE_INFO;

每个用户态的文件句柄(file handle)都和DOKAN_FILE_INFO结构相关。因此,如果相同的文件句柄已经使用,结构内容就不会改变。当CreatFile系统调用打开文件时,DOKAN_FILE_INFO结构就会创建;当CloseFile系统调用关闭文件时,这个结构就会销毁。
这个结构中每一个成员的意义如下:
Context:一个指定的值,由文件系统应用程序指定。文件系统程序可以自由的使用这个变量来存储在文件访问阶段的固定值(从创建文件到关闭文件)比如文件句柄等等。
DokanContext:保留,Dokan库使用。
ProcessId:打开文件进程的进程ID。
IsDirectory:判断打开的文件是否是一个目录。

其他的如下
   int (*CreateFile) (
       LPCWSTR,      // FileName,文件名
       DWORD,        // DesiredAccess,需要访问
       DWORD,        // ShareMode,共享模式
       DWORD,        // CreationDisposition,创建意向
       DWORD,        // FlagsAndAttributes,标识和属性
       PDOKAN_FILE_INFO);

int (*OpenDirectory) (
       LPCWSTR,          // FileName
       PDOKAN_FILE_INFO);

int (*CreateDirectory) (
       LPCWSTR,          // FileName
       PDOKAN_FILE_INFO);

当变量IsDirectory设置为TRUE,操作的文件是目录。当IsDirectory时FALSE时,如果当前的操作是对目录操作,则写文件系统程序的程序员需要把它设置为TRUE。如果IsDirectory的值是FALSE,但是当前操作不是对目录操作,则程序员不需要更变该值。需要注意的是,当访问目录时,把IsDirectory的值设置为TRUE对于Dokan库而言是非常重要的。如果它没有被准确的设定,那么Dokan库将不知道这个操作是作用于目录,会带来许多问题。如果CreationDisposition 设定为 CREATE_ALWAYS 或者OPEN_ALWAYS 并且当前文件已经存在,那么CreatFile函数需要返回ERROR_ALEADY_EXISTS(183)

int (*Cleanup) (
       LPCWSTR,      // FileName
       PDOKAN_FILE_INFO);

int (*CloseFile) (
       LPCWSTR,      // FileName
       PDOKAN_FILE_INFO);

当Windows API提供的CloseHandle函数一旦执行,Cleanup函数将被调用。当函数CreateFile被调用的时候,如果文件系统程序在Context变量中存放了文件句柄,这个将在Cleanup函数中关闭,而不是CloseFile函数。
如果用户态程序调用CloseHandle,并且马上打开相同的文件,那么文件系统程序CloseFile函数在CreatFile API被调用之前可能不会被调用。这有可能引发共享问题。

值得注意的是:当用户使用内存映射文件时,WriteFile或者ReadFile函数可能Cleanup之后会被调用,这是为了完成I/O操作。文件系统应用也应该在这种情况下正常运行。

int (*FindFiles) (
       LPCWSTR,           // PathName,路径名
       PFillFindData,     //调用此函数时, 使用PWIN32_FIND_DATAW作为参数
       PDOKAN_FILE_INFO); //  (see PFillFindData definition)

// 你应该实现 FindFiles 或者 FindFilesWithPattern
   int (*FindFilesWithPattern) (
       LPCWSTR,           // PathName,路径名
       LPCWSTR,           // SearchPattern,查询方式
       PFillFindData,     // call this function with PWIN32_FIND_DATAW
       PDOKAN_FILE_INFO);

调用FindFiles或者FindFilesWithPattern函数是为了相应目录列表请求。你应该实现FindFiles或者FindFilesWithPattern(其中一个,译者注)。对于每一个目录入口(entry),文件系统程序都应该调用FillFindData函数(作为一个函数参数供 FindFiles, FindFilesWithPattern调用),FillFindData会使用包含目录信息的WIN32_FIND_DATAW结构:
FillFindData( &win32FindDataw, DokanFileInfo )。
处理通配符模式(wildcard patterns)是文件系统的责任,因为Windows命令终端(shells)不能够很好地处理模式匹配。当文件系统程序提供FindFiles,通配符模式将会通过Dokan库来自动的处理。你可以通过实现FindFilesWithPattern函数来控制通配符匹配的过程。Dokan库(dokan.dll)提供的DokanIsNameInExpression函数接口可以用来处理通配符匹配。

挂载
====================================================================
#define DOKAN_OPTION_DEBUG       1 // 输出调试信息
   #define DOKAN_OPTION_STDERR      2 // 输出调试信息到标准错误输出
   #define DOKAN_OPTION_ALT_STREAM  4 // use alternate stream
   #define DOKAN_OPTION_KEEP_ALIVE  8 // 使用自动卸载
   #define DOKAN_OPTION_NETWORK    16 // 使用网络驱动
                               //你需要安装Dokan网络提供器(provider)
   #define DOKAN_OPTION_REMOVABLE  32 // 使用可移除驱动

typedef struct _DOKAN_OPTIONS {
       USHORT  Version;  // 支持的Dokan版本, 比如. "530" (Dokan ver 0.5.3)
       ULONG   ThreadCount;  // 使用的线程数
       ULONG   Options;  // combination of DOKAN_OPTIONS_*
       ULONG64 GlobalContext;  // FileSystem 可以使用这个
       LPCWSTR MountPoint;  // 挂载点 "M:\" (驱动字符) 或者
                            // "C:\mount\dokan" (NTFS中的路径名)
   } DOKAN_OPTIONS, *PDOKAN_OPTIONS;

int DOKANAPI DokanMain(
       PDOKAN_OPTIONS    DokanOptions,
       PDOKAN_OPERATIONS DokanOperations);

如上所述,文件系统可以通过DokanMain函数挂载。函数一直阻塞(block)直到文件系统卸载。再把这些参数传递给DokanMain 函数之前,文件系统程序应该为Dokan运行库填写满DokanOptions中的选项,为文件系统操作填写满DokanOperations中的函数指针(比如CreateFile, ReadFile, CloseHandle, ...)。DokanOperations结构中的函数需要保证线程安全,因为他们都被执行不同上下文(contexts)的多线程调用(不是调用DokanMain的线程)。

Dokan选项如下:
 版本:Dokan库的版本号。你需要一个支持的版本。Dokan库可能因为版本号的不同改变一些行为(behavior)。比如ie.530(Dokan 0.5.3)
 线程数:Dokan库内部的线程数。如果这个值设定为0,那么将使用默认的值。当我们调试文件系统的时候,文件系统程序应该设定为1来避免多线程的并发冲突。
 选项:DOKAN_OPTION_*常量的结合。
 全局域:你的文件系统可以使用这个变量来存放一个挂载特定的结构。
 挂载点:一个挂载点挂载点 "M:\" (驱动字符) 或者 "C:\mount\dokan" (需要是空的)NTFS中的路径名。

如果挂载成功,那么返回值是DOKAN_SUCCESS,否则返回值如下。
 #define DOKAN_SUCCESS                0
   #define DOKAN_ERROR                 -1 /* 普通错误 */
   #define DOKAN_DRIVE_LETTER_ERROR    -2 /* 坏的驱动字符 */
   #define DOKAN_DRIVER_INSTALL_ERROR  -3 /* 不能安装驱动 */
   #define DOKAN_START_ERROR           -4 /* 驱动出了某个问题 */
   #define DOKAN_MOUNT_ERROR           -5 /* 不能分配驱动字符或者挂载点 */
   #define DOKAN_MOUNT_POINT_ERROR     -6 /* 挂载点无效 */

卸载
=======================================================================
文件系统可以通过调用DokanUnmount函数来卸载。大部分情况下,程序或者shell使用文件系统挂起,卸载操作来解决问题。因为当文件系统卸载的时候,我们可以把系统恢复到原来的状态。
用户可能使用DokanCtl来卸载文件系统:
 > dokanctl.exe /u DriveLetter

杂项:
======================================================================
如果在Dokan库或者使用库的文件系统程序中有bug,那么你的Windows系统将会蓝屏。因此,我们强烈建议你使用虚拟机来开发文件系统应用程序。

05-08 08:44