文件过滤驱动实现目录重定向(二)

                                                                By Fanxiushu  2015 02-16 转载或引用请注明原始作者
接上文。
如何实现 sfPassThrough  派遣函数,才能达到目录重定向呢。
我们首先要解决重定向的目的地,这里采用的是把所有需要重定向的IRP请求全部发送到应用层。
之所以这样做,因为在应用层,可以更简单,更快捷, 更灵活的实现数据处理。
 
需要创建一个控制设备,用来跟应用层交互数据。
 这样我们的驱动中至少有两类设备,一类是文件过滤驱动设备,一类是控制设备,
如果驱动中还加入了动态挂载的功能,还必须包括文件系统控制过滤设备。
如何在 sfPassThrough 派遣函数中区别这些不同类的设备呢,可以在IoCreateDevice创建者设备的时候,
指定设备扩展结构的第一个字段为设备类型,比如1是文件过滤设备,2是控制设备,3是文件控制过滤设备。
 这样在 sfPassThrough 中
struct  vdo_exts_t
{
     ULONG   devType;
     ....   
};
NTSTATUS sfPassThrough(PDEVICE_OBJECT pDevice, PIRP Irp)
{
    NTSTATUS status = STATUS_SUCCESS;
    vdo_exts_t* ext = (vdo_exts_t*)pDevice->DeviceExtension;
    if ( ext->devType == 1 ) { //文件系统过滤设备
        ///
        status = fs_dispatch_function(pDevice, Irp);  ///进入到真正的文件过滤派遣函数处理
     }
     else if (ext->devType == 2){ //控制设备
         ///
        status = cdo_dispatch_function(pDevice, Irp);  ///负责把过滤的IRP请求数据转发到应用层和从应用层接收处理结果。
    }
    else if (ext->devType == 3){ //文件系统控制过滤设备
         ///
        status = fso_dispatch_function(pDevice, Irp);  ////负责处理系统卷的挂载卸载等
    }
    。。。。。。
}

在传递数据到应用层,尽量使用MmMapLockedPagesSpecifyCache 直接把内核内存映射到用户空间,尤其在处理
IRP_MJ_READ和IRP_MJ_WRITE的时候,这样能减少大数据量的copy。

重定向的IRP转发到应用层的处理思路大致如下:
首先在应用层创建一个信号量传递到内核驱动,驱动使用这个信号量来通知应用层有新的IRP到达。
我们在fs_dispatch_function里首先分析这个IRP是不是需要重定向的IRP,
如果是,则把这个Irp挂载到一个等待队列,并且增加信号量,让应用层知道有IRP需要处理。
然后fs_dispatch_function 返回STATUS_PENDING 等待处理结果。
 
应用层程序有一个或者多个线程在这个信号量上调用WaitForSingleObject等待。
WaitForSingleObject返回,说明文件过滤有新的过滤请求达到。
然后调用DeviceIoControl 函数,投递一个 BEGIN IOCTL到驱动,于是驱动的  cdo_dispatch_function 函数被调用。
我们在 cdo_dispatch_function 中取出一个进入等待队列的过滤 IRP, 直接在cdo_dispatch_function中处理,
把需要请求的内容返回给应用层。
这个时候这个过滤IRP进入处理队列,等待应用层 最终分析处理这个请求,应用层分析完成这个请求之后,
再次发送一个表示这个过滤IRP完成的END IOCTL给驱动,
于是cdo_dispatch_function接收到这个过滤IRP的处理结果的END IOCTL,并把这个过滤IRP完成。
 这样一个过滤IRP才算真正解析处理完成。

 这个处理办法看起来比较麻烦,确实也比较麻烦。只是我暂时没想到更好更简单的办法把请求传递到应用层。

 接着进入正题,如何处理fs_dispatch_function函数。假设我们监控的需要重定向的目录是 D:\Redir 目录。
 万事都有开头,处理目录重定向的开头就是 IRP_MJ_CREATE。
为何如此说呢,因为任何文件的操作都是从打开文件打开即IRP_MJ_CREATE开始的。
(也许此话有些说得有点绝对,但是99%都是从IRP_MJ_CREATE开始,不包括我不理解和不知道的情况)
在IRP_MJ_CREATE中 , 从IRP获得 FILE_OBJECT:
 PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
 PFILE_OBJECT fileObject = irpStack->FileObject;
 这个fileobject指向的FileName就包含了打开这个文件的路径,
当然还需要查看是不是个相对路径,通过 fileObject->RelatedFileObject判断, 
通过分析fileObject->FileName和  fileObject->RelatedFileObject 来确定这个文件打开的路径是不是在D:\Redir目录中,
如果是,则说明这个IRP就是需要被重定向处理的IRP。(这里忽略8.3格式的短文件名,有兴趣可自行研究)
 然后,我们需要把这个CREATE的IRP保存到一个 MAP结构中,通常 MAP的key是FILE_OBJECT,
MAP的value是我们自己定义的另外一个包含更多信息的结构。
接着把这个IRP加入到等待队列,然后增加信号量通知应用层来取这个IRP。
fs_dispatch_function不再下传给下层驱动,而是返回STATUS_PENDING等待处理。

然后就是处理其他类型IRP,所有除开IRP_MJ_CREATE的IRP,都必须从 MAP结构中查找FILE_OBJECT,
如果存在于MAP结构中,说明这个文件操作是在 D:\Redir重定向目录中的,我们必须把它重定向处理,不能下传给下层驱动。

伪代码如下:
NTSTATUS fs_dispatch_function(PDEVICE_OBJECT devObj, PIRP Irp)
{
 NTSTATUS status;
 fileobj_t* obj = NULL;  //
 PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
 PFILE_OBJECT fileObject = irpStack->FileObject;
 ///
if( irpStack->MajorFunction ==  IRP_MJ_CREATE){
       .....
        //根据FILE_OBJECT的 FileName参数判断是否属于监控目录,如果是,则创建fileobj_t对象并加到MAP结构中
       status = create_fileobject(devObj, fileObject, &obj); 

       if (status == STATUS_NOT_FOUND){ // 不属于监控目录的请求,直接下发
             ////
            return xfs_call_lower_driver(devObj, Irp); ///
        }
        else if (status == STATUS_SUCCESS){   //是属于监控目录的文件对象创建,而且成功添加对象
              把此IRP加入到等待队列,返回STATUS_PENDING
        }
        。。。。
   }
  else { // 其他IRP
       obj = find_fileobject(fileObject); //根据FILE_OBJECT从MAP结构中查找,
       if(obj){  //如果找到,说明是属于重定向目录中的文件操作。
              switch (irpStack->MajorFunction){
               case IRP_MJ_CLEANUP:
                     做一些清理工作,比如取消目录监控IRP,设置某些标志等,可以直接完成,不必加入等待队列给应用层
                     break;

               case IRP_MJ_CLOSE:
                     把此IRP加入等待队列,等待应用层的处理,等应用层处理完成,就需要把这个对象从MAP结构中删除,
                     因为这是文件对象的最后一个IRP操作,返回后系统就会删除FILE_OBJECT对象。
                     break;

               case IRP_MJ_READ:
               case IRP_MJ_WRITE:
               case IRP_MJ_DIRECTORY_CONTROL:
               case IRP_MJ_QUERY_INFORMATION:
               case IRP_MJ_SET_INFORMATION:
                   这些IRP,需要使用 Irp->UserBuffer,因此需要调用 MmProbeAndLockPages 函数锁定UserBuffer到Irp->MdlAddress。
                   这里需要注意MmProbeAndLockPages 调用时候,可能发生缺页中断,系统会再次调用我们的fs_dispatch_function。
                   因此必须注意函数的重入问题.
                   加入等待队列,通知应用层有IRP要处理。
                   break;
              case IRP_MJ_QUERY_SECURITY:
                  处理安全信息的IRP,win7系统上需要处理这个IRP,否则重定向后,被重定向的目录里的exe文件无法执行。
                  break;
             case IRP_MJ_QUERY_VOLUME_INFORMATION:
                 查询目录所在卷设备相关信息,可以预先查询真正的磁盘卷信息,保存起来,然后这里直接返回需要的数据就可以。
                  break;
             default:
                  返回 STATUS_NOT_SUPPORTED错误。根据需要,还应该处理其他一些类型的IRP 。
               }
       }
        else{  //此IRP不是重定向目录的IRP
               return xfs_call_lower_driver(devObj, Irp); ///下传给下层驱动
        }
  }
 
在 cdo_dispatch_function 函数中,根据上边所说的,应用层发现有新的文件重定向IRP到来,
发来一个 BEGIN IOCTL,此函数才会被调用。
因此在此函数中会接着处理 在 fs_dispatch_function 函数里插入等待队列里的重定向IRP。
如果是IRP_MJ_CREATE的IRP,则分析里边的需要的信息,把应用层需要的数据,传递给应用层,
然后把此IRP加入到处理队列,等待应用层的处理结果的END IOCTL到来。
如果是 IRP_MJ_READ或IRP_MJ_WRITE请求,则调用 MmMapLockedPagesSpecifyCache 把 Irp->MdlAddress映射到用户程序空间,
这样应用层程序可直接操作 读写数据地址空间,直接进行数据交换。应用层完成之后发送END IOCTL来结束读写IRP。
如果是 IRP_MJ_DIRECTORY_CONTROL表示是查询某个目录下的子目录或文件,应用层程序把查询到的子目录或文件通过 END IOCTL
发送给驱动,驱动根据 IRP_MJ_DIRECTOR_CONTRL 信息的格式填写 Irp->MdlAddress内容,然后完成此IRP。
如果是 IRP_MJ_QUERY_INFORMATION 或者 IRP_MJ_SET_INFORMATION, 基本上也是差不多的处理。
只是这些处理的细节和格式却是比较繁琐,这里也就不列举了。
有兴趣可详细研读 微软的 FastFat源代码,里边处理这些IRP是最详细的,如果觉得他代码太多,
看得眼花,不妨可以看看我稍后会发布到CSDN的代码,只是代码粗浅,而且有很多BUG和省略了许多细节。

这样,一个实现整个目录重定向的驱动就基本算完成了。可是应用层还需要处理相关逻辑,才能算真正实现完整的目录重定向。

待续。

本文在 CSDN上BLOG:
    http://blog.csdn.net/fanxiushu/article/details/43636575 以及后续章节

本文在CSDN上提供的程序:
http://download.csdn.net/detail/fanxiushu/8448785

本文在 CSDN上提供的源代码工程:
    http://download.csdn.net/detail/fanxiushu/8545567


你可能感兴趣的:(C++,windows,驱动开发)