繁体   English   中英

如何检测Win32 NTFS下的文件移动?

[英]How to detect a file move under win32 NTFS?

我有一个数据库,可以将其他信息附加到文件系统(NTFS)的任何文件中。 文件ID是其完整路径,因此,为了保持一致性,我需要注意是否删除,重命名或移动了DB内部的任何文件。

目前,我正在尝试通过将ReadDirectoryChangesW函数与FILE_NOTIFY_CHANGE_FILE_NAME结合使用来实现此目的。 FILE_NOTIFY_CHANGE_DIR_NAME作为过滤条件。

问题是这样,我只收到有关重命名,创建和删除的通知。 因此,我需要根据“添加”和“删除”事件以及相关的文件名猜测移动发生的时间(在同一卷上,移动[ctrl-x,ctrl-v]实际上是文件删除,紧随其后的是文件创建,路径不同,但文件名保持不变)。

有人知道是否有更好的解决方案?

根据观察,这是我的理解

关于在NTFS下移动文件

同一卷内

对于相同的文件名和不同的路径(无论移动文件的大小如何),在“已删除”事件之后立即(无延迟或延迟很小)之后是“添加”事件。

如果正在监视整个卷,则为特殊情况:删除文件时,实际上将其添加到回收站(路径包含卷回收站的路径,但文件名不同(某种哈希))。

在两个不同的卷之间

首先,目标卷上有一个“添加”事件。
之后,复制完成后,原始卷上将发生“已删除”事件。

(注意:同时可能会发生几个事件:文件越大,延迟时间越长。)

如果这些文件在您的控制之下(如果您在添加到数据库时具有写访问权,那么您始终始终至少具有读访问权),则可以通过GUID在备用数据流中标记它们,也可以使用对象ID( https: //msdn.microsoft.com/zh-CN/library/aa364557%28v=VS.85%29.aspx ),无论哪种方法都更合适。

实际上,没有任何关于“移动”事件的通知,因为此类事件始终是路径/文件名重命名或删除和创建双重事件的结果。
尽管使用USN日记可以使事情变得容易一些,但仍有一些其他工作要做。

在这种情况下,我需要即时检查文件系统更改(我的应用程序在后台运行),因此使用日志(日志)毫无意义。

这是我想到的用于DeviceIoControl和ReadDirectoryChangesW函数以及包含自定义FileActionInfo项的队列的逻辑。

struct FileActionInfo {
    WCHAR fileName[FILE_NAME_MAX];
    CHAR drive;
    DWORD action;
    time_t timeStamp;

};

在使用USN时猜测所有移动事件也应该很有用:

算法

- when a 'added' event occurs
    - if previous event was a 'removed' event on same volume
        - if 'added' event contains recycle bin path, ignore it (file deleted)
        - else if 'removed' event contains recycle bin path, handle as a 'restored'/'undelete' event, remove 'removed' event from queue
        - else 
            - if 'added' event has same filename, handle as a 'moved' event, remove 'removed' event from queue
            - else push 'added' event to queue
    - else push 'added' event to queue


- when a 'removed' event occurs, search the queue for an 'added' event for the same filename on a different volume
    - if found, handle it as a 'moved' event and remove 'added' event from queue
    - else push 'removed' event to queue, launch a delayedRemoval thread


delayedRemoval thread(&FileActionInfo) {
    // we cannot wait forever , because 'added' event might never occur (if the file was actually deleted).
    sleep(2000)
    if given 'removed' event is still in the queue
        handle as an actual 'removed' event, and remove it from queue
    return;
}

例外

  • 如果在同一会话期间在不同卷上创建了两个具有相同文件名的文件,而之后又删除了其中一个,则将其错误地视为“移动”事件
  • 随着时间的流逝,FileActionInfo队列可能会变大,我们可以不时清理一次(设置移动文件的最大延迟)
    • 如果复制持续时间超过允许的延迟,我们可能会错过“移动”事件
    • 队列越大,将事件搜索到其中的时间就越长,因此我们需要快速访问:(几个对象以不同的访问模式存储相同的项目:向量,哈希表)

以防万一可能有用,这是我写的FileActionQueue.h。
最重要的方法是Last()和Search(LPCWSTR fileName,DWORD操作,PCHAR驱动器)。

#pragma once


#include <time.h>
#include <string>
#include <cwchar>
#include <vector>
#include <map>

using std::wstring;
using std::vector;
using std::map;

/* constants defined in winnt.h :
#define FILE_ACTION_ADDED                   0x00000001   
#define FILE_ACTION_REMOVED                 0x00000002   
#define FILE_ACTION_MODIFIED                0x00000003   
#define FILE_ACTION_RENAMED_OLD_NAME        0x00000004   
#define FILE_ACTION_RENAMED_NEW_NAME        0x00000005
*/
#define FILE_ACTION_MOVED                    0x00000006

class FileActionInfo {
public:
    LPWSTR    fileName;
    CHAR    drive;
    DWORD    action;
    time_t    timestamp;

    FileActionInfo(LPCWSTR fileName, CHAR drive, DWORD action) {        
        this->fileName = (WCHAR*) GlobalAlloc(GPTR, sizeof(WCHAR)*(wcslen(fileName)+1));
        wcscpy(this->fileName, fileName);
        this->drive = drive;
        this->action = action;
        this->timestamp = time(NULL);
    }

    ~FileActionInfo() {
        GlobalFree(this->fileName);    
    }
};

/*
There are two structures storing pointers to FileActionInfo items : a vector and a map. 
This is because we need to be able to:
1) quickly retrieve the latest added item
2) quickly search among all queued items (in which case we use fileName as hashcode)
*/
class FileActionQueue {
private:
    vector<FileActionInfo*> *qActionQueue;
    map<wstring, vector<FileActionInfo*>> *mActionMap;

    void Queue(vector<FileActionInfo*> *v, FileActionInfo* lpAction) {
        v->push_back(lpAction);
    }

    void Dequeue(vector<FileActionInfo*> *v, FileActionInfo* lpAction) {
        for(int i = 0, nCount = v->size(); i < nCount; ++i){
            if(lpAction == v->at(i)) {
                v->erase(v->begin() + i);
                break;
            }
        }
    }

public:

    FileActionQueue() {
        this->qActionQueue = new vector<FileActionInfo*>;
        this->mActionMap = new map<wstring, vector<FileActionInfo*>>;
    }

    ~FileActionQueue() {
        delete qActionQueue;
        delete mActionMap;    
    }

    void Add(FileActionInfo* lpAction) {
        this->Queue(&((*this->mActionMap)[lpAction->fileName]), lpAction);
        this->Queue(this->qActionQueue, lpAction);
    }

    void Remove(FileActionInfo* lpAction) {
        this->Dequeue(&((*this->mActionMap)[lpAction->fileName]), lpAction);
        this->Dequeue(this->qActionQueue, lpAction);
    }

    FileActionInfo* Last() {
        vector<FileActionInfo*> *v = this->qActionQueue;
        if(v->size() == 0) return NULL;
        return v->at(v->size()-1);
    }

    FileActionInfo* Search(LPCWSTR fileName, DWORD action, PCHAR drives) {
        FileActionInfo* result = NULL;
        vector<FileActionInfo*> *v;
        if( v = &((*this->mActionMap)[fileName])) {
            for(int i = 0, nCount = v->size(); i < nCount && !result; ++i){
                FileActionInfo* lpAction = v->at(i);
                if(wcscmp(lpAction->fileName, fileName) == 0 && lpAction->action == action) {
                    int j = 0;
                    while(drives[j] && !result) {
                        if(lpAction->drive == drives[j]) result = lpAction;
                        ++j;
                    }            
                }
            }
        }
        return result;
    }
};

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM