11.文件和目录的生成打开,关闭与删除
我们已经分析了读,写与读类似。文件系统还有其他的操作。比如文件或目录的打开(打开已经存在的或者创建新的),关闭。文件或目录的移动,删除。
实际上FILE_OBJECT并不仅仅指文件对象。在windows文件系统中,目录和文件都是用FileObject来抽象的。这里产生一个问题,对于一个已经有的FileObject,我如何判断这是一个目录还是一个文件呢?
对于一个已经存在的FileObject,我没有找到除了发送IRP来向卷设备询问这个FileObject的信息之外更好的办法。自己发送IRP很麻 烦。不是我很乐意做的那种事情。但是FileObject都是在CreateFile的时候诞生的。在诞生的过程中,确实有机会得到这个即将诞生的 FileObject,是一个文件还是一个目录。
Create的时候,获得当前IO_STACK_LOCATION,假设为irpsp,那么irpsp->Parameters.Create的结构为:
struct {
PIO_SECURITY_CONTEXT SecurityContext;
ULONG Options;
USHORT FileAttributes;
USHORT ShareAccess;
ULONG EaLength;
};
这个结构中的参数是与CreateFile这个api中的参数对应的,请自己研究。我先写一些函数包装来方便读取irpsp.
_inline wd_ulong wd_irpsp_file_opts(wd_irpsp *irpsp)
{
return irpsp->Parameters.Create.Options;
}
_inline wd_ushort wd_irpsp_file_attrs(wd_irpsp *irpsp)
{
return irpsp->Parameters.Create.FileAttributes;
}
enum {wd_file_opt_dir = FILE_DIRECTORY_FILE};
enum {wd_file_attr_dir = FILE_ATTRIBUTE_DIRECTORY};
然后我们搞清上边Options和FileAttributes的意思。是不是Options里边有FILE_DIRECTORY_FILE标记就表示这 是一个目录?实际上,CreateOpen是一种尝试性的动作。无论如何,我们只有当CreateOpen成功的时候,判断FileObject才有意 义。否则是空谈。
成功有两种可能,一是已经打开了原有的文件或者目录,另一种是新建立了文件或者目录。Options里边带有FILE_DIRECTORY_FILE表示 打开或者生成的对象是一个目录。那么,如果在Create的完成函数中,证实生成或者打开是成功的,那么返回得到的FILE_OBJECT,确实应该是一 个目录。
当我经常要使用我过滤时得到的文件或者目录对象的时候,我一般在Create成功的的时候捕获他们,并把他们记录在一个“集合”中。这时你得写一个用来表 示“集合”的数据结构。你可以用链表或者数组,只是注意保证多线程安全性。因为Create的时候已经得到了属性表示FileObject是否是目录,你 就没有必要再发送IRP来询问FileObject的Attribute了。
对了上边有FileAttributes。但是这个东西并不可靠。因为在生成或者打开的时候,你只需要设置Options。我认为这个字段并无法说明你打开的文件对象是目录。
这你需要设置一下Create的完成函数。如果设置这里不再重复,请参考上边对文件读操作。
wd_stat my_create_comp(in wd_dev *dev,
in wd_irp *irp,
in wd_void *context)
{
wd_irpsp *irpsp = wd_irp_cur_sp(irp);
wd_file *file = wd_irpsp_file(irpsp);
UNREFERENCED_PARAMETER(dev);
if(wd_suc(wd_irp_status(irp))
{
// 如果成功了,把这个FileObject记录到集合里,这是一个
// 刚刚打开或者生成的目录
if(file &&
(wd_irpsp_file_opts(irpsp) & wd_file_opt_dir))
add_obj_to_set(file);
}
return wd_irp_status(irp);
}
这里顺便解释一下UNREFERENCED_PARAMETER宏。我曾经不理解这个宏的意思。其实就是因为本函数传入了三个参数,这些参数你未必会用 到。如果你不用的话,大家知道c编译器会发出一条警告。一般认为驱动应该去掉所有的警告,所以用了这个宏来“使用”一下没有用到过的参数。你完全可以不用 他们。
现在所有的目录都被你记录。那么得到一个FileObject的时候,判断一下这个FileObject在不在你的集合里,如果在,就说明是目录,反之是文件。
当这个FileObject被关闭的时候你应该把它从你的集合中删除。你可以捕获Close的IRP来做这个。记得本教程很早以前,我们已经安装过 my_close函数的来处理IRP(请回忆或者翻阅第3节的内容),那么很简单了,在该函数中从你的集合中删除该FileObject即可。作为保险的 做法,应该观察一下关闭是否成功。如果成功的话,再进行你的从集合中删除元素工作。
因为判断FileObject是文件还是目录的问题,我们已经见识了文件的打开和关闭工作。现在看一下文件是如何被删除的。
删除的操作,第一步是打开文件,打开文件的时候必须设置为可以删除。如果打开失败,则直接导致无法删除文件。第二步设置文件属性为用于删除,第三步关闭文件即可。关闭的时候,文件被系统删除。
不过请注意这里的“删除”并非把文件删除到回收站。如果要测试,你必须按住shift彻底删除文件。文件删除到回收站只是一种改名操作。改名操作我们留到以后再讨论。
第一步是打开文件,我应该可以在文件被打开的时候,捕获到的irpsp的参数,记得前边的参数结构,中间有:
PIO_SECURITY_CONTEXT SecurityContext;
相关的结构如下:
typedef struct _IO_SECURITY_CONTEXT {
PSECURITY_QUALITY_OF_SERVICE SecurityQos;
PACCESS_STATE AccessState;
ACCESS_MASK DesiredAccess;
ULONG FullCreateOptions;
} IO_SECURITY_CONTEXT, *PIO_SECURITY_CONTEXT;
注意其中的DesiredAccess,其中必须有DELETE标记,才可以删除文件。
第二步是设置为”关闭时删除”。这是通过发送一个IRP(Set Information)来设置的。捕获主功能码为IRP_MJ_SET_INFORMATION的IRP后:
首先,IrpSp->Parameters.SetFile.FileInformationClass应该为FileDispositionInformation。
然后,Irp->AssociatedIrp.SystemBuffer指向一个如下的结构:
typedef struct _FILE_DISPOSITION_INFORMATION {
BOOLEAN DeleteFile;
} FILE_DISPOSITION_INFORMATION;
如果DeleteFile为TRUE,那么这是一个删除文件的操作。文件将在这个FileObject Close的时候被删除。
以上的我都未实际调试,也不再提供示例的代码。有兴趣的读者请自己完成。