wht0395
驱动小牛
驱动小牛
  • 注册日期2006-07-06
  • 最后登录2009-06-10
  • 粉丝0
  • 关注0
  • 积分583分
  • 威望135点
  • 贡献值0点
  • 好评度122点
  • 原创分0分
  • 专家分0分
阅读:4948回复:16

[恢复]文件过滤系统设计(1)(2)(3)

楼主#
更多 发布于:2007-01-03 14:16
原贴作者:XiangXiangRen

文件过滤系统设计(1) (附全部源代码)


  首先说明一下,此文所附的代码,也就是<<Windows文件系统过滤驱动开发教程>>的代码。阅读此文需要预先了解文件过滤驱动,最好读过<<Windows文件系统过滤驱动开发教程>>。我计划再发一系列文章总结这方面的最新积累的经验,当然我不敢取名“教程”,因为我没有资格教育任何人。

我不幸被卷入数个与文件过滤有关的项目中,以致为此耗费了数年的时光.我期望我的设计趋于理想,把我的编码工作引入美好的状态.

从理想的角度来说,文件过滤是与操作系统无关的.因为首先我做文件过滤的时候并未关心文件系统的类型.FAT32还是NTFS? 如果机器上有其他文件系统,我也打算过滤它.然后,操作系统并未与文件系统相关.Windows上也可以安装ext2(听闻已经有人开发了相关的驱动),linux上安装FAT32与NTFS(NTFS的驱动可能尚不完美)已经是事实.

提炼与操作系统无关的文件操作是一个比较复杂的接口设计工作.能碰到千奇百怪的困难.所以我认为,应该反过来入手,首先精简Windows的文件操作的接口,再在这个基础上追求操作系统无关化.当然你也可以认为操作系统无关是完全不可能的,这个目标是错误的.但是我已经精简了文件系统过滤操作的接口,使之后的编程工作变得简单.

我把这个项目称为FSFE(File System Filter Engine),这是一个非商业化的项目,所有代码都是本人利用工作业余时间编写.你可以下载这些代码并修改使用,但是不能用于商业开发中.

在Windows下研究文件过滤一般都从SFilter入手.SFilter可以看作一个文件过滤和Windows的文件系统之间的一个"适配器",但绝对不是你要开发的文件过滤系统本身.既然Windows的文件系统操作以IRP作为单元,并在dispatch functions中处理IRP,那么你的文件过滤只需要考虑过滤IRP就可以了.又何必去考虑哪些文件系统何时激活,又合适加载了卷,何时卷被卸载这些无关的问题呢?

我希望我的编码不需要考虑那些问题,而是直接如此编码(为了避免更多的板砖我使用尽量DDK原装的数据类型):

// 这是我的主函数
void fsfe_main()
{
  // 我为create irp的处理设置一个回调函数
  fsfe_set_create(my_create);

  // 我为close 和 clean up的irp处理设置一个回调函数
  fsfe_set_close_clean_up(my_close_clean_up);
}

以上是我打算过滤create和close的情况.create的过滤一般用来阻止某些文件被访问,或者"重新定向"对某些文件的访问(比如把文件操作映射到另外一个磁盘或目录中).

我相信计算机中所有的文件的create irp都会发到我这里.这时我就可以编写我的过滤了:

ULONG
my_create(
  IN PDEVICE_OBJECT cur,     // 当前设备
  IN PDEVICE_OBJECT next,     // 下一个设备  
  IN PIRP irp,         // irp
  OUT NTSTATUS status,     // 如果irp被完成,状态返回到这里
  OUT PVOID *context)     // 可以为完成函数指定一个上下文
{
  // create传入的参数我并不一定都要使用.如果我要打印请求生成文件
  // 的路径,只要这样这就可以了:
  PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
  PFILE_OBJECT file = irpsp->FileObject;

  if(file != NULL)
  {
    DbgPrint("CREATE %wZ\r\n", &file->FileName);
  }
    
  // 自己定义的一个常量.意义为"继续完成",对IRP我不干涉。
  return WDFF_CB_GO_ON;
}

  irpsp->FileObject->FileName只是一个请求路径。直接用这个路径用来做路径过滤(比如限制对某个目录下的文件访问)是不现实的。首先这个路径可能不是绝对路径,可能只是某个目录的相对路径,而且不含有盘符。更糟的是可能出现短名,如mypath~1之类,让你的过滤落空。后面我们专门研究如何做路径过滤。但是这个演示还是不错的。

  假设你手头有Sfilter的代码,那么你可以按如下的解释实现以上的功能.

  首先Sfilter将被编译成一个Lib.使用Lib使你或者以你的成果为基础的开发者不必再关心Sfilter的代码.

  以上带来的一个担心是调试BUG的问题.但是当然需要调试的时候,你会发现有代码的lib调试起来和你常用的全代码的情况一样的方便.

  令一个容易被误会的问题常常是lib的大小的问题.一般的lib编译出来(尤其是功能非常丰富的lib)编译出来都非常的大.其实连接一个lib绝对不等于把这个lib的全部大小放到你的驱动中.连接器只连接必要的部分.你未使用到的部分会被剔除.因此你会发现你连接的结果往往比lib本身要小很多.

  修改SFilter目录下的SOURCE文件.把

  TARGETTYPE=DRIVER
  
  修改为

  TARGETTYPE=LIBRARY

  然后我可以在SFilter.c中,DriverEntry的实现之前,加入

  extern void fsfe_main();   // 外部函数声明
    
  然后在DriverEntry中,合适的位置调用:

  NT_STATUS DriverEntry(...)
  {
    ...
    // 可以把fsfe_main设计成接受一些参数,但是我没有
    // 用这样的设计.
    fsfe_main( ... );

    ... // 继续初始化
  }
  
  这样你的代码中不必再有DriverEntry,但是必须有fsfe_main.当然你必须连接这个库.在你的SOURCE文件中,加上:

  TARGETLIBS = sfilter.lib  

  check版本和free版本需要不同的库.你可以像这样:

  !IF "$(DDKBUILDENV)"=="fre"
  TARGETLIBS = sfilter.lib
  !else
  TARGETLIBS = sfilterd.lib
  !ENDIF  

  以上是编译问题.下面考虑分发函数.实际上与fsfe_main的嵌入相同.但是为了这些函数不是固定名字,而是可以设置的,我必须在SFilter中定义一组函数指针:

  一个IRP处理的过滤函数类型:

  typedef LONG
  (*wdff_callback_pre_func)(
    IN PDEVICE_OBJECT cur,     // 当前设备
    IN PDEVICE_OBJECT next,     // 下一个设备  
    IN PIRP irp,         // irp
    OUT NTSTATUS status,     // 如果irp被完成,状态返回到这里
    OUT PVOID *context);
    
  一个IRP完成的过滤函数:

  typedef LONG
  (*wdff_callback_post_func)(
            IN PDEVICE_OBJECT cur,
            IN PDEVICE_OBJECT dev,
            IN PIRP irp,
            IN PVOID context);  

  // 一个含有一组回调指针的结构,有点类似linux驱动开发的分发函数指针数组。
  typedef struct _wdff_callback
  {
    wdff_callback_pre_func read_write;
    wdff_callback_post_func read_write_comp;
    wdff_callback_pre_func create;
    wdff_callback_post_func create_comp;
    wdff_callback_pre_func close_clean_up;
    wdff_callback_post_func close_clean_up_comp;
    wdff_callback_pre_func device_io_ctrl;
    wdff_callback_post_func device_io_ctrl_comp;
    wdff_callback_pre_func other;
    wdff_callback_post_func other_comp;
  } wdff_callback;

  我在这中间做了一些简化的处理.比如常用的IRP被单独提出来作为函数指针.而"其他"的IRP处理则被集合到一个叫做other的处理函数中.

  现在在SFilter中定义一个如下的变量:

  wdff_callback g_callback;

  我很容易提供接口来设置它.我可以在库中导出一个函数:

  wd_void fsfe_set_create(wdff_callback_pre_func create)
  {
    g_callback.create = create;
  };
  
  其他的接口都是类似的.下面的问题,就是在SFilter中何时调用这个回调函数了.相信这个难不到对SFilter了解的诸位:

  NT_STATUS SfCreate(...)
  {
    ...
    if(g_callback.create != NULL)
    {
        (g_callback.create)( ...)
    }
  }

  但是具体到我给出的代码中,情况有一些不同.但是都是细节问题,有兴趣的读者可以自己研究.我提供的代码并不是SFilter修改而来,而是我自己编写的过滤框架。比SFilter的优点在于,提供了路径过滤,影设备读写,2000下动态加载卸载等一些新功能。

  所附代码有如下的说明:

  0.使用这些代码到你的计算机上,一切后果自负。

  1.编译必须安装DDK 3790或者以上的版本,用WNET编译。必须定义环境变量DDKROOT或者BASEDIR.

  2.得到代码后解压。用VC6.0或者VC7.0打开工作空间后点Build,Rebuild All或批构建可直接编译之。编译方法与编译应用程序同。不要试图用ddk或者ds提供的工具去编译它,那样会碰到一些麻烦。如果失败请检查环境变量。

  3.编译后得到的lib在lib目录下,主要有wdf.lib和fsfe.lib.需要的头文件在inc目录下。

  4.使用这些lib的例子在Sample目录下。这些目录下的工程也会在工作空间全部编译的时候被编译。得到的sys每一个都可以在2k-2003中所有的系统上动态加载和卸载。加载后打开DbgView察看输出即可。目前只有一个例子 create_filter.我会在后面的系列文章中编写更多的例子。create_filter.sys加载后会打印出系统当前打开的文件路径(带盘符)。

  5.如果出现了兰屏,死机或者其他意外,请不要过于愤怒。如果你能修改程序的bug,希望你发一份给我指出我的错误,我非常感谢。

  值得指出的是,有时DbgPrint似乎会导致蓝屏。在正式发布的商业软件中不应该包含DbgPrint调用。我目前包含了许多。这是为了作为演示。

  其他方面的问题将在后续的文章中继续说明。

描述:Wdf源代码
附件:  wdf_filter0.rar (353 K) (请到http://bbs.driverdevelop.com/read.php?tid-97831-keyword-.html下载)

最新喜欢:

linshierlinshi...
什么不懂的学生
wht0395
驱动小牛
驱动小牛
  • 注册日期2006-07-06
  • 最后登录2009-06-10
  • 粉丝0
  • 关注0
  • 积分583分
  • 威望135点
  • 贡献值0点
  • 好评度122点
  • 原创分0分
  • 专家分0分
沙发#
发布于:2007-01-03 14:19
文件过滤系统设计(2)如何实现路径过滤


本文作者为上海楚狂人,有意见和建议者请联系(QQ16191935,MSN walled_river@hotmail.com

文件过滤系统设计(2)如何实现路径过滤


  文件过滤系统中很多过滤都是以操作的文件的路径作为过滤条件的.比如你想控制某个目录下的文件被加密.或者是被映射到其他地方.或者是其他的操作,你都必须用到路径过滤.但是即使是这么常见的一个操作,在IFSDDK的基础上做起来也绝对不是那么简单.

  取得文件的路径有三种情况:

  第一:在文件打开之前从打开文件的请求中提取路径.FileMon从FileObject->FileName中提取文件路径其实就是对这个的实现.后面我们讨论这种方法的困难.

  第二:在文件CREATE IRP处理结束后获取路径.这是SFilter演示了的也是最容易解决的.

  第三:在文件过滤其他IRP时,(如改名,查询,设置,读,写)的时候得到FileObject所对应的文件路径.

  以上三种情况以第一种路径获取最为麻烦.第二种情况最为简单.我先介绍一下我在第二种情况下的做法.

  
  *             *             *
  

  a.在CREATE IRP处理结束后获取路径.

  基本做法与SFilter同.因为此时FileObject已经生成结束,对于这个对象,你可以使用ObQueryNameString.这个调用返回一个路径.对某一个文件对象,返回结果大致如此:

  "\Device\HardDiskVolume1\MyDirectory\MyFile.Name"

  这里再次强调一下,必须在Create完成之后再进行这个调用.如果直接在sfCreate()函数的早期调用这个,你只能得到:

  "\Device\HardDiskVolume1"

  这个也有一定的作用,可以设法获得盘符.但是得不到完整路径.

  另一个需要注意的是,ObQueryNameString只能在CREATE IRP和CLEAN UP IRP的处理中使用,除非你能自己下发IRP,一般都会使系统进入死锁.

  下面的问题是如何获得盘符.盘符的取得是要把"\Device\HardDiskVolume1"这样的名字转换为"C:"这样的符号连接名.也可以直接用卷设备的DEVICE_OBJECT去获取.这可以用到两个函数:

  NTSTATUS
  RtlVolumeDeviceToDosName(
    IN PVOID VolumeDeviceObject,
    OUT PUNICODE_STRING DosName);

  NTSTATUS
  IoVolumeDeviceToDosName(
    IN PVOID VolumeDeviceObject,
    OUT PUNICODE_STRING DosName);

  其中第二个函数据称是更高级的版本.但是要XP以上系统才支持.2K只有RtlVolumeDeviceToDosName.这给你编写2K和XP兼容的驱动带来困难,因为你不得不动态导入IoVolumeDeviceToDosName,否则你的驱动在2K下可能无法加载.

  但是这两个函数在2K下尤其是驱动静态加载的时候都似乎都有问题.在2K+SP4的情况下一般有IoVolumeDeviceToDosName函数的导出存在.我两个函数都试验了.每次都是动态加载没有问题,而静态加载过程中调用却会在IoVolumeDeviceToDosName或IoVolumeDeviceToDosName中死机.似乎有一个导致死锁的Device Io Control IRP被发到某一个设备导致系统死了.具体的原因我不清楚,而且也不知道是否是我编程的错误导致的.但是这种情况使我只好寻找更加可靠的办法.如果你有正确的方法,希望你能发一个邮件给我(mfc_tan_wen@163.com).

  既然"C:","D:"这样的东西其实是符号连接,对应了"\Device\HardDiskVolume1","\Device\HardDiskVolume2"这样的设备的话,我可以用ZwQuerySymbolicLinkObject查询这个符号连接,找出它所对应的实际名,然后与"\Device\HardDiskVolume1"这样的字符串做比较,从而把"\Device\HardDiskVolume1"这样的设备名字转变为盘符.

  以下代码直接使用了wdf_filter中的代码,有些地方非DDK数据结构.但是相信能表达基本方法:

  // 以下的代码得到一个符号连接的目标
  wd_ustr_h wd_symbolic_target_ustr(wd_ustr *symbolic)
  {

    ... // 初始化对象特性表,代码被省略,请察看源代码...
    InitializeObjectAttributes( ...
    ...
    
    // 打开符号联接
    status = ZwOpenSymbolicLinkObject(
        &link_handle,
        GENERIC_READ,
        &attributes);

    if(!wd_suc(status))
        return NULL;

    wd_ustr_init_em(&target, buf, 8*sizeof(wd_wchar));

    // 查询符号联接对象  
    status = ZwQuerySymbolicLinkObject(link_handle, &target, &length);

    ...   // 判断返回值并分配空间等代码被省略...

    if(wd_suc(status))
    {
        // 保存一个目标字符串句柄指针,并返回
        target_ret = wd_ustr_h_alloc_from_ustr(&target);
    }

    if(dbuf != NULL)
        wd_free(dbuf);

    ZwClose(link_handle);
    return target_ret;
  }            

  下面的方法是把一个类似"\Device\HardDiskVolume2"这样的字符串和"C:"-"Z:"所有的目标进行比较,以得到正确的盘符:

  wd_ustr_h wd_vol_name_dos_name(wd_wchar *name)
  {
    // 符号连接的全称应该是L"\\DosDevices\\X:".X可以替换成C-Z任意一个字母.这里没有考虑A,B两个软驱.
    wd_wchar vol_syb[] = { L"\\DosDevices\\X:" };
    wd_ustr vol_name;
    wd_wchar c;
    if(name == NULL)
        return NULL;

    wd_ustr_init(&vol_name, name);
  
    // 遍历,逐个得到目标并比对字符串  
    for(c = L'A'; c < (L'Z'+1); ++c)
    {
        wd_ustr_h my_target = NULL;
        vol_syb[12] = c;
        my_target = wd_symbolic_target(vol_syb);
        if( my_target != NULL &&
          wd_ustr_cmp(wd_ustr_h_ustr(my_target), &vol_name, wd_true) == 0 )
        {
          wd_ustr_h_free(my_target);
          break;  
        }
        if(my_target != NULL)
        {
          wd_printf0("FF:%wZ\r\n",wd_ustr_h_ustr(my_target));
          wd_ustr_h_free(my_target);
        }
    }

    // 判断返回结果  
    if(c == L'Z'+1)
        return NULL;
    else
        return wd_ustr_h_alloc(&vol_syb[12]);
  }  

  得到盘符后就可以组合得到完整的路径.这个方式我测试过,无论动态加载或者静态加载启动的时候调用,都不会死机.



  *             *             *
  


  b.在文件过滤其他IRP时,(如改名,查询,设置,读,写)的时候得到FileObject所对应的文件路径.

  在文件读写的时候往往没有好的办法可以得到文件路径.对FileObject进行ObQueryNameString很容易导致死机.这似乎是微软自己留下的问题.但是如果你直接向下层设备发IRP进行查询,就不会死机.发送QueryIRP的代码比较简单,可以在网上找到.但是IRP的构建依赖于非文档的方法,总是不那么可靠,未来难保在兼容性上不出现问题.我希望不用非文档的方法.

  此时如果你读FileObject->FileName,会发现这个名字一般都依然存在.你可以用它作为文件路径.不过这依然有不少问题.首先FileObject->FileName只是为了生成这个文件而填写的请求路径.既然这个文件已经生成,那么这个路径就是可以丢弃的了(它存在并不表示不可能被丢弃).其次这个文件路径里面可能含有短名(例如mydire~1),这样的路径和你想要得全路径不同,有可能导致跳过你的安全过滤.而且其中不含有盘符.如果需要得到盘符,可能还需要Query,这又是一个常见的死机原因.

  我用了一个可能不是很有效率的办法,既然我在Create IRP处理结束后已经得到了我要的长路径,那么我可以把它存在一个表中.当FileObject被CleanUp的时候,我清除这个表项以避免内存泄漏.然后再无论是Read,Write,Query,Set或者是Rename(Set的一种)的时候,我都可以通过这个表来查询我的路径.

  理论上应该用哈希表增加效率.但是我算法不熟悉,所以只用了简单的链表来保存数据.这些代码都在文件wd_file_to_name中,示例如下:

  // 在CREATE IRP处理结束后调用这个函数即可.只需要传入FileObject,自动保存路径到链表中.
  wd_bool wd_file_fton_create(wd_file *file)
  {
    wd_stat status;

    // 这是一条记录,里面将记录文件的路径信息
    wd_file_fton_record *record =
        wd_malloc(wd_false,sizeof(wd_file_fton_record));

    if(record == NULL)
        return wd_false;

    // 结构初始化
    wd_ustr_init_em(&record->file_name,record->name_buf,WD_MAX_PATH*sizeof(wd_wchar));
    wd_ustr_init_em(&record->vol_name,record->vol_buf,8);

    // 得到文件名和盘符,方法前面已经介绍了
    if(!wd_file_get_name(file,&record->file_name))
    {
        wd_free(record);
        return wd_false;
    };
    status = wd_file_get_vol_name(file,&record->vol_name);
    if(!wd_suc(status))
    {
        wd_free(record);
        return wd_false;
    }

    record->file = file;

    // 写入记录.把记录追加到链表中.
    wd_list_hold(g_wd_file_to_name_list);
    wd_list_append(g_wd_file_to_name_list,(wd_void *)record);
    g_wd_cur_file_count++;
    wd_list_loose(g_wd_file_to_name_list);

    return wd_true;
  }
  
  // 然后就是我要的查询.任何时候我可以通过FileObject得到找到全路径.返回值是一个字符串句柄.
  wd_ustr_h wd_file_ftop(wd_file *file)
  {
    wd_ustr_h path;
    if(g_wd_file_to_name_list && file)
    {
        wd_list_node *node;
        wd_list_hold(g_wd_file_to_name_list);
        node = wd_list_head(g_wd_file_to_name_list);
        while(node)
        {
          wd_file_fton_record *record =
            (wd_file_fton_record *)wd_list_node_data(node);
          // 如果链表中有,则读出之
          if(record->file == file)
          {
            path = wd_ustr_h_alloc_from_ustr(&record->vol_name);

            if(wd_ustr_buf(&record->file_name)[0] != L'\\')
                wd_ustr_appd(path,L"\\");

            wd_ustr_appd_ustr(path,&record->file_name);
            wd_list_loose(g_wd_file_to_name_list);

            return path;
          }
          node = wd_list_node_next(node);
        }
        wd_list_loose(g_wd_file_to_name_list);
    }
    return NULL;
  }
  
  当然,在FileObject的CLEANUP中,你必须删除掉这个节点.    

  以上已经集成成模块,所以使用起来也很简单.首先:

  #include "wdf/wd_file_to_name.h"

  然后在my_create_comp中调用wd_file_fton_create(file)即可.

  其次在my_close_clean_up中如下:

  wd_int
  my_close_clean_up(in_ wd_dev *cur,
            in_ wd_dev *next,
            in_ wd_irp *irp,
            out_ wd_stat *status,
            out_ wd_void **context)
  {
    wd_irpsp *irpsp = wd_irp_cur_sp(irp);
    wd_file *file = wd_irpsp_file(irpsp);
    *context = NULL;
    if(file != NULL && wd_irpsp_major( irpsp ) == IRP_MJ_CLEANUP)
    {
        wd_file_fton_close(file);
    }
    return WDFF_CB_GO_ON;
  }


  *             *             *

  
  c. 最后一个问题是如何在Create IRP处理之前得到路径名.

  可能唯一的途径是通过FileObject->FileName.要注意FileObject->ReleatedObject不为空的情况.这个时候FileObject->FileName是RelatedObject的相对路径.首先要ObQueryNameString这个对象.得到路径之后再和FileObject->FileName组合.

  然后是其中可能含有的短名转换为长名的问题.我没有找到简易的方法.我的同事CardMagic提供了一个非常麻烦但是确实有效的办法.其思想是:

  首先你假设你得到一个路径 \aaaaaa~1\bbbbbb~1\cccccc~1\dddddd~1.txt.然后你把它分解成:

  \
  aaaaaa~1
  bbbbbb~1
  cccccc~1  
  dddddd~1.txt

  以上5个对象.首先打开用ZwCreateFile打开第一个目录.第一个目录总是"\",这不可能是短名.然后调用ZwQueryDirectoryFile枚举下面所有的文件和目录.如果你用FileIdBothDirectoryInformation进行查询.那么会得到一组FILE_ID_BOTH_DIR_INFORMATION,代表下面每个文件和目录:

  typedef struct _FILE_ID_BOTH_DIR_INFORMATION {
    ULONG NextEntryOffset;
      ULONG FileIndex;
    LARGE_INTEGER CreationTime;
    LARGE_INTEGER LastAccessTime;
    LARGE_INTEGER LastWriteTime;
    LARGE_INTEGER ChangeTime;
    LARGE_INTEGER EndOfFile;
    LARGE_INTEGER AllocationSize;
    ULONG FileAttributes;
    ULONG FileNameLength;
    ULONG EaSize;
    CCHAR ShortNameLength;
    WCHAR ShortName[12];         // 这里有短名
    LARGE_INTEGER FileId;
    WCHAR FileName[1];         // 这里有长名  
  } FILE_ID_BOTH_DIR_INFORMATION, *PFILE_ID_BOTH_DIR_INFORMATION;

  长短名都到手了,那么我们当然可以找到"\"之下的第一个"aaaaaa~1"所对应的长名了.然后依次类推,逐个查询!这真是个疯狂的办法,但是确实有效.

  有时我认为可以直接打开\aaaaaa~1\bbbbbb~1\cccccc~1\dddddd~1.txt 或者\aaaaaa~1\bbbbbb~1\cccccc~1\来QueryNameString,但是CardMagic说,网上有人说这样做依然不可靠,可能得到短名.

  这个过程的代码非我所有,我只介绍一下方法,无法提供代码给大家了.


  *             *             *


  代码wdf_filter1中,增加了一个新的Sample FileFilter.sys.这个驱动编译后,用DbgView.exe可以看见READ,WRITE,QUERY的文件全路径.


描述:示例源代码
附件:  wdf_filter1.rar (298 K)(请到http://bbs.driverdevelop.com/read.php?tid-97831-keyword-.html 下载)
什么不懂的学生
wht0395
驱动小牛
驱动小牛
  • 注册日期2006-07-06
  • 最后登录2009-06-10
  • 粉丝0
  • 关注0
  • 积分583分
  • 威望135点
  • 贡献值0点
  • 好评度122点
  • 原创分0分
  • 专家分0分
板凳#
发布于:2007-01-03 14:34
Windows文件系统过滤驱动开发教程(3)


3.分发例程,fast io

上一节仅仅生成了控制设备对象。但是不要忘记,驱动开发的主要工作是撰写分发例程(dispatch functions.).接上一接,我们已经知道自己的DriverObject保存在上文代码的driver中。现在我写如下一个函数来指定一个默认的dispatch function给它。

//-----------------wdf.h中的代码----------------------
typedef PDRIVER_DISPATCH wd_disp_fuc;
_inline wd_void wd_drv_set_dispatch(in wd_drv* driver,
in wd_disp_fuc disp)
{
wd_size i;
for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
driver->MajorFunction = disp;
}

在前边的wd_main中,我只要加

wd_drv_set_dispatch(driver,my_dispatch_func);

就为这个驱动指定了一个默认的Dispatch Function.所有的irp请求,都会被发送到这个函数。但是,我可能不希望这个函数处理过于复杂,而希望把一些常见的请求独立出来,如Read,Write,Create,Close,那我又写了几个函数专门用来设置这几个Dispatch Functions.

//-----------------wdf.h中的代码----------------------
_inline wd_void wd_drv_set_read(
in wd_drv* driver,
in wd_disp_fuc read)
{
driver->MajorFunction[IRP_MJ_READ] = read;
}
_inline wd_void wd_drv_set_write(
in wd_drv* driver,
in wd_disp_fuc write)
{
driver->MajorFunction[IRP_MJ_WRITE] = write;
}

wd_void wd_drv_set_create(in wd_drv* driver,
in wd_disp_fuc create)
{
driver->MajorFunction[IRP_MJ_CREATE] = create;
driver->MajorFunction[IRP_MJ_CREATE_NAMED_PIPE] = create;
driver->MajorFunction[IRP_MJ_CREATE_MAILSLOT] = create;
}

wd_void wd_drv_set_file_sys_control(in wd_drv* driver,
in wd_disp_fuc control)
{
driver->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] = control;
}

wd_void wd_drv_set_clean_up(in wd_drv* driver,
in wd_disp_fuc clean_up)
{
driver->MajorFunction[IRP_MJ_CLEANUP] = clean_up;
}

wd_void wd_drv_set_close(in wd_drv* driver,
in wd_disp_fuc close)
{
driver->MajorFunction[IRP_MJ_CLOSE] = close;
}

别看我罗列n多代码,其实就是在设置driver->MajorFunction这个数组而已。因此在wd_main对dispatch functions的设置,就变成了下边这样的:

// 开始设置几个分发例程
wd_drv_set_dispatch(driver,my_disp_default);
wd_drv_set_create(driver,my_disp_create);
wd_drv_set_clean_up(driver,my_disp_clean_up);
wd_drv_set_file_sys_control(driver,my_disp_file_sys_ctl);
wd_drv_set_close(driver,my_disp_close);
wd_drv_set_read(driver,my_disp_read);
wd_drv_set_write(driver,my_disp_write);

下面的任务都在写my_xxx系列的这些函数了。但是对于这个DriverObject的设置,还并不是仅仅这么简单。

由于你的驱动将要绑定到文件系统驱动的上边,文件系统除了处理正常的IRP之外,还要处理所谓的FastIo.FastIo是Cache Manager调用所引发的一种没有irp的请求。换句话说,除了正常的Dispatch Functions之外,你还得为DriverObject撰写另一组Fast Io Functions.这组函数的指针在driver->FastIoDispatch.我不知道这个指针留空会不会导致系统崩溃。在这里本来是没有空间的,所以为了保存这一组指针,你必须自己分配空间。

下面是我常用的内存分配函数。

//-----------------wdf.h中的代码----------------------
// 最简单的分配内存的函数,可以指定分页非分页
_inline wd_pvoid wd_malloc(wd_bool paged,wd_size size)
{
if(paged)
return ExAllocatePool(PagedPool,size);
else
return ExAllocatePool(NonPagedPool,size);
}

// 释放内存
_inline wd_void wd_free(wd_pvoid point)
{
ExFreePool(point);
}

_inline wd_void wd_memzero(
wd_pvoid point,
wd_size size)
{
RtlZeroMemory(point,size);
}

有了上边的基础,我就可以自己写一个初始化FastIoDispatch指针的函数。

//-----------------wdf.h中的代码----------------------
wd_bool wd_fio_disp_init(wd_drv *driver,wd_ulong size)
{
wd_fio_disp *disp = wd_malloc(wd_false,size);
if(disp == wd_null)
return wd_false;
wd_memzero((wd_pvoid)disp,size);
driver->FastIoDispatch = disp;
driver->FastIoDispatch->SizeOfFastIoDispatch = size;
return wd_true;
}

这个函数为FastIoDispacth指针分配足够的空间并填写它的大小。下面是再写一系列的函数来设置这个函数指针数组。实际上,FastIo接口函数实在太多了,所以我仅仅写出这些设置函数的几个作为例子:
//-----------------wdf.h中的代码----------------------
_inline wd_void wd_fio_disp_set_query_standard(
wd_drv *driver,
wd_fio_query_standard_func func)
{
driver->FastIoDispatch->FastIoQueryStandardInfo = func;
}

_inline wd_void wd_fio_disp_set_io_lock(
wd_drv *driver,
wd_fio_io_lock_func func)
{
driver->FastIoDispatch->FastIoLock = func;
}

_inline wd_void wd_fio_disp_set_io_unlock_s(
wd_drv *driver,
wd_fio_unlock_single_func func)
{
driver->FastIoDispatch->FastIoUnlockSingle = func;
}

...

好,如果你坚持读到了这里,应该表示祝贺了。我们回顾一下,wd_main中,应该做哪些工作。

a.生成一个控制设备。当然此前你必须给控制设置指定名称。

b.设置Dispatch Functions.

c.设置Fast Io Functions.

// ----------------wd_main 的近况----------------------------

...

wd_dev *g_cdo = NULL;

wd_stat wd_main(in wd_drv* driver,
in wd_ustr* reg_path)
{
wd_ustr name;
wd_stat status = wd_stat_suc;

// 然后我生成控制设备,虽然现在我的控制设备什么都不干
wd_ustr_init(&name,L\"\\FileSystem\\Filters\\our_fs_filters\");
status = wdff_cdo_create(driver,0,&name,&g_cdo);

if(!wd_suc(status))
{
if(status == wd_stat_path_not_found)
{
// 这种情况发生于FileSystemFilters路径不存在。这个路径是
// 在xp上才加上的。所以2000下可能会运行到这里
wd_ustr_init(&name,L\"\\FileSystem\\our_fs_filters\");
status = wdff_cdo_create(driver,0,&name,&g_cdo);
};
if(!wd_suc(status))
{
wd_printf0(\"error: create cdo failed.rn\");
return status;
}
}

wd_printf0(\"success: create cdo ok.rn\");

// 开始设置几个分发例程
wd_drv_set_dispatch(driver,my_disp_default);
wd_drv_set_create(driver,my_disp_create);
wd_drv_set_clean_up(driver,my_disp_clean_up);
wd_drv_set_file_sys_control(driver,my_disp_file_sys_ctl);
wd_drv_set_close(driver,my_disp_close);
wd_drv_set_read(driver,my_disp_read);
wd_drv_set_write(driver,my_disp_write);

// 指定fast io处理函数
if(!wd_fio_disp_init(driver,sizeof(wd_fio_disp)))
{
wd_dev_del(g_cdo);
wd_printf0(\"error: fast io disp init failed.rn\");
return wd_stat_insufficient_res;
}

// 下面指定的这些函数都定义在wdf_filter_fio.h中,其实这些函数都统
// 一的返回了false
wd_fio_disp_set_check(
driver,
my_fio_check);
wd_fio_disp_set_read(
driver,
my_fio_read);
wd_fio_disp_set_write(
driver,
my_fio_write);
wd_fio_disp_set_query_basic(
driver,
my_fio_query_basic_info);

...

}
FastIo函数个数数量不明,我只觉得很多。因此不打算全部罗列,以\"...\"敷衍之。某些读者可能会认为这些代码无法调试安装。其实您可以参考sfilter中的示例自己完成这些代码。

现在我们的my_xxx系列的函数还没有开始写,因此驱动也不能编译通过。在后边的内容中再逐步介绍。
什么不懂的学生
wht0395
驱动小牛
驱动小牛
  • 注册日期2006-07-06
  • 最后登录2009-06-10
  • 粉丝0
  • 关注0
  • 积分583分
  • 威望135点
  • 贡献值0点
  • 好评度122点
  • 原创分0分
  • 专家分0分
地板#
发布于:2007-01-03 14:37
---------------------------
回复贴作者:wowocock
---------------------------

顺便介绍下FASTIO接口介绍
FASTIO接口介绍

作者:陆麟
转载请征得作者同意.
2003.3.22



--------------------------------------------------------------------------------

NT下FASTIO是一套IO MANAGER与DEVICE DRIVER沟通的另外一套API. 在进行基于IRP为基础的接口调用前, IO MANAGER会尝试使用FAST IO接口来加速各种IO操作. FASTIO本身的文档并不多见, 本篇就是要介绍一下FASTIO接口.
FastIoCheckIfPossible, 此调用并不是IO MANAGER直接调用. 而是被FsRtlXXX系列函数调用. 用于确认读写操作是否可以用FASTIO接口进行.
FastIoRead/FastIoWrite, 很明显, 是读写处理的调用.
FastIoQueryBasicInfo/FastIoQueryStandardInfo, 用于获取各种文件信息. 例如创建,修改日期等.
FastIoLock/FastIoUnlockSingle/FastIoUnlockAll/FastIoUnlockAllByKey,用于对文件的锁定操作. 在NT中.有2中锁定需要存在.1.排他性锁. 2.共享锁. 排他性锁在写操作前获取,不准其他进程获得写操作权限, 而共享锁则代表需要读文件某区间. 禁止有写动作出现. 在同一地址上, 如果有多个共享锁请求, 那是被允许的.
FastIoDeviceControl用于提供NtDeviceIoControlFile的支持.
AcquireFileForNtCreateSection/ReleaseFileForNtCreateSection是NTFS在映射文件内容到内存页面前进行的操作.
FastIoDetachDevice, 当REMOVABLE介质被拿走后, FILE SYSTEM的DEVICE对象会在任意的时刻被销毁. 只有正确处理这个调用才能把上层DEVICE和将要销毁的DEVICE脱钩. 如果不解决这个函数, 系统会当.
FastIoQueryNetworkOpenInfo, 当CIFS也就是网上邻居,更准确的说是网络重定向驱动尝试获取文件信息, 会使用这个调用. 该调用是因为各种历史原因而产生. 当时设计CIFS时为避免多次在网上传输文件信息请求, 在NT4时传输协议增加了一个FileNetworkOpenInformation的网络文件请求. 而FSD则增加了这个接口. 用于在一次操作中获得所有的文件信息. 客户段发送FileNetworkOpenInformation, 服务器端的FSD用本接口完成信息填写.
FastIoAcquireForModWrite, Modified Page Writer会调用这个接口来获取文件锁. 如果实现这个接口. 则能使得文件锁定范围减小到调用指定的范围. 不实现此接口, 整个文件被锁.
FastIoPrepareMdlWrite, FSD提供MDL. 以后向此MDL写入数据就代表向文件写入数据. 调用参数中有FILE_BOJECT描述要写的目标文件.
FastIoMdlWriteComplete, 写操作完成. FSD回收MDL.
FastIoReadCompressed, 当此调用被调用时, 读到的数据是压缩后的.应该兼容于标准的NT提供的压缩库. 因为调用者负责解压缩.
FastIoWriteCompressed,当此调用被调用时, 可以将数据是压缩后存储.
FastIoMdlReadCompressed/FastIoMdlReadCompleteCompressed, MDL版本的压缩读. 当后一个接口被调用时,MDL必须被释放.
FastIoMdlWriteCompressed/FastIoMdlWriteCompleteCompressed, MDL版本的压缩写.当后一个接口被调用时,MDL必须被释放.
FastIoQueryOpen, 这不是打开文件的操作. 但是却提供了一个IRP_MJ_CREATE的IRP. 我在以前版本的SECUSTAR的软件中错误地实现了功能. 这个操作是打开文件/获取文件基本信息/关闭文件的一个操作.
FastIoReleaseForModWrite,释放FastIoAcquireForModWrite调用所占有的LOCK.
FastIoAcquireForCcFlush/FastIoReleaseForCcFlush FsRtl会调用此接口,在LAZY WRITE线程将要把修改后的文件数据写入前调用.获取文件锁.
什么不懂的学生
longshentailang
驱动牛犊
驱动牛犊
  • 注册日期2005-12-06
  • 最后登录2008-03-24
  • 粉丝0
  • 关注0
  • 积分830分
  • 威望84点
  • 贡献值4点
  • 好评度83点
  • 原创分0分
  • 专家分0分
地下室#
发布于:2007-01-09 12:02
谢谢lz了
shencheng
驱动牛犊
驱动牛犊
  • 注册日期2006-08-05
  • 最后登录2007-05-27
  • 粉丝0
  • 关注0
  • 积分50分
  • 威望6点
  • 贡献值0点
  • 好评度5点
  • 原创分0分
  • 专家分0分
5楼#
发布于:2007-01-11 11:15
3ks
JTIGER163
驱动牛犊
驱动牛犊
  • 注册日期2006-12-29
  • 最后登录2008-05-26
  • 粉丝0
  • 关注0
  • 积分240分
  • 威望25点
  • 贡献值0点
  • 好评度24点
  • 原创分0分
  • 专家分0分
6楼#
发布于:2007-01-11 11:51
3ks
驱网无线,快乐无限
cloudliu2000
驱动牛犊
驱动牛犊
  • 注册日期2003-07-21
  • 最后登录2010-11-17
  • 粉丝0
  • 关注0
  • 积分17分
  • 威望71点
  • 贡献值0点
  • 好评度1点
  • 原创分0分
  • 专家分0分
7楼#
发布于:2007-01-12 21:52
good
l_pan008
驱动牛犊
驱动牛犊
  • 注册日期2004-08-01
  • 最后登录2011-09-19
  • 粉丝0
  • 关注0
  • 积分90分
  • 威望9点
  • 贡献值0点
  • 好评度9点
  • 原创分0分
  • 专家分0分
8楼#
发布于:2007-02-28 10:09
真是相当的感谢,你太有才了
l_pan008
驱动牛犊
驱动牛犊
  • 注册日期2004-08-01
  • 最后登录2011-09-19
  • 粉丝0
  • 关注0
  • 积分90分
  • 威望9点
  • 贡献值0点
  • 好评度9点
  • 原创分0分
  • 专家分0分
9楼#
发布于:2007-02-28 10:10
LZ一定要继续啊
l_pan008
驱动牛犊
驱动牛犊
  • 注册日期2004-08-01
  • 最后登录2011-09-19
  • 粉丝0
  • 关注0
  • 积分90分
  • 威望9点
  • 贡献值0点
  • 好评度9点
  • 原创分0分
  • 专家分0分
10楼#
发布于:2007-02-28 10:18
附件的链接无效啊,能不能直接放到这里
chunvv
驱动牛犊
驱动牛犊
  • 注册日期2005-04-30
  • 最后登录2011-03-24
  • 粉丝0
  • 关注0
  • 积分26分
  • 威望245点
  • 贡献值0点
  • 好评度39点
  • 原创分0分
  • 专家分0分
11楼#
发布于:2007-03-21 23:36
真是相当的感谢
kabunhi
驱动牛犊
驱动牛犊
  • 注册日期2007-06-27
  • 最后登录2016-01-09
  • 粉丝1
  • 关注0
  • 积分461分
  • 威望57点
  • 贡献值0点
  • 好评度46点
  • 原创分0分
  • 专家分0分
12楼#
发布于:2007-07-11 17:03
看完了  
linkwander
驱动牛犊
驱动牛犊
  • 注册日期2007-06-18
  • 最后登录2016-01-09
  • 粉丝0
  • 关注0
  • 积分400分
  • 威望41点
  • 贡献值0点
  • 好评度40点
  • 原创分0分
  • 专家分0分
13楼#
发布于:2007-07-11 19:20
学习学习
linkwander
驱动牛犊
驱动牛犊
  • 注册日期2007-06-18
  • 最后登录2016-01-09
  • 粉丝0
  • 关注0
  • 积分400分
  • 威望41点
  • 贡献值0点
  • 好评度40点
  • 原创分0分
  • 专家分0分
14楼#
发布于:2007-07-11 19:23
附件的链接好像没有,LZ能不能重新弄一下
shakesky
驱动牛犊
驱动牛犊
  • 注册日期2006-03-16
  • 最后登录2011-06-25
  • 粉丝0
  • 关注0
  • 积分581分
  • 威望69点
  • 贡献值0点
  • 好评度58点
  • 原创分0分
  • 专家分0分
15楼#
发布于:2007-07-11 21:59
附件有问题,强烈要求重新弄一下
pandaforum
驱动小牛
驱动小牛
  • 注册日期2007-02-13
  • 最后登录2011-09-06
  • 粉丝0
  • 关注0
  • 积分728分
  • 威望282点
  • 贡献值1点
  • 好评度66点
  • 原创分0分
  • 专家分0分
16楼#
发布于:2007-07-12 20:56
我贴一下附件下载地址,嘿嘿
http://bbs.driverdevelop.com/htm_data/39/0701/97797.html
游客

返回顶部