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;
接上文。
如何实现 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); ///
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是最详细的,如果觉得他代码太多,
看得眼花,不妨可以看看我稍后会发布到优快云的代码,只是代码粗浅,而且有很多BUG和省略了许多细节。
这样,一个实现整个目录重定向的驱动就基本算完成了。可是应用层还需要处理相关逻辑,才能算真正实现完整的目录重定向。
待续。
本文在 优快云上BLOG:
http://blog.youkuaiyun.com/fanxiushu/article/details/43636575 以及后续章节
本文在优快云上提供的程序:
http://download.youkuaiyun.com/detail/fanxiushu/8448785
本文在 优快云上提供的源代码工程:
http://download.youkuaiyun.com/detail/fanxiushu/8545567
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是最详细的,如果觉得他代码太多,
看得眼花,不妨可以看看我稍后会发布到优快云的代码,只是代码粗浅,而且有很多BUG和省略了许多细节。
这样,一个实现整个目录重定向的驱动就基本算完成了。可是应用层还需要处理相关逻辑,才能算真正实现完整的目录重定向。
待续。
本文在 优快云上BLOG:
http://blog.youkuaiyun.com/fanxiushu/article/details/43636575 以及后续章节
本文在优快云上提供的程序:
http://download.youkuaiyun.com/detail/fanxiushu/8448785
本文在 优快云上提供的源代码工程:
http://download.youkuaiyun.com/detail/fanxiushu/8545567
2017-01更新,完整版本程序下载地址:
优快云上的下载地址:
http://download.youkuaiyun.com/detail/fanxiushu/9719017
GITHUB上的下载地址:
https://github.com/fanxiushu/xFsRedir/raw/master/xFsRedir-1.0.0.1.zip
这个是1.0.0.1版本,如果寻找新版本,请关注:
https://github.com/fanxiushu/xFsRedir
或发送邮件 fanxiushu@sohu.com 如发现BUG,可在优快云或者GitHUB上提出来,或者发送邮件 fanxiushu@sohu.com