关于 c#:Detecting a File Delete on a Open File

Detecting a File Delete on an Open File

我正在打开一个具有读访问权限的文件,并允许随后对该文件进行读|写|删除文件共享访问(拖尾文件)。如果文件在处理过程中被删除,是否有办法检测文件是否处于待删除状态(请参阅文件部分 http://msdn.microsoft.com/en-us/library/aa363858(v=VS.85).aspx) ?如果某个外部进程(拥有进程)发出了删除,我想尽快关闭我的句柄以允许文件删除,以免干扰拥有进程中的任何逻辑。

我在 C# 中,看不到检测挂起删除的方法。该文件是使用 FileStream 对象打开的。有什么方法可以检测 C# 或其他 Windows 函数中的删除吗?


您可以使用 Windows API 函数 GetFileInformationByHandleEx 来检测您打开的文件上的挂起删除。第二个参数是一个枚举值,可让您指定函数应返回的信息类型。 FileStandardInfo (1) 值将导致它返回 FILE_STANDARD_INFO 结构,其中包括一个 DeletePending 布尔值。

这是一个演示实用程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
using System;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;

internal static class Native
{
    [DllImport("kernel32.dll", SetLastError = true)]
    public extern static bool GetFileInformationByHandleEx(IntPtr  hFile,
                                                           int     FileInformationClass,
                                                           IntPtr  lpFileInformation,
                                                           uint    dwBufferSize);

    public struct FILE_STANDARD_INFO
    {
        public long AllocationSize;
        public long EndOfFile;
        public uint NumberOfLinks;
        public byte DeletePending;
        public byte Directory;
    }
    public const int FileStandardInfo = 1;
}

internal static class Program
{
    public static bool IsDeletePending(FileStream fs)
    {
        IntPtr buf = Marshal.AllocHGlobal(4096);
        try
        {
            IntPtr handle = fs.SafeFileHandle.DangerousGetHandle();
            if (!Native.GetFileInformationByHandleEx(handle,
                                                     Native.FileStandardInfo,
                                                     buf,
                                                     4096))
            {
                Exception ex = new Exception("GetFileInformationByHandleEx() failed");
                ex.Data["error"] = Marshal.GetLastWin32Error();
                throw ex;
            }
            else
            {
                Native.FILE_STANDARD_INFO info = Marshal.PtrToStructure<Native.FILE_STANDARD_INFO>(buf);
                return info.DeletePending != 0;
            }
        }
        finally
        {
            Marshal.FreeHGlobal(buf);
        }
    }

    public static int Main(string[] args)
    {
        TimeSpan MAX_WAIT_TIME = TimeSpan.FromSeconds(10);

        if (args.Length == 0)
        {
            args = new string[] {"deleteme.txt" };
        }

        for (int i = 0; i < args.Length; ++i)
        {
            string filename = args[i];
            FileStream fs = null;

            try
            {
                fs = File.Open(filename,
                               FileMode.CreateNew,
                               FileAccess.Write,
                               FileShare.ReadWrite | FileShare.Delete);

                byte[] buf = new byte[4096];
                UTF8Encoding utf8 = new UTF8Encoding(false);

                string text ="hello world!\
\
"
;
                int written = utf8.GetBytes(text, 0, text.Length, buf, 0);
                fs.Write(buf, 0, written);
                fs.Flush();

                Console.WriteLine("{0}: created and wrote line", filename);

                DateTime t0 = DateTime.UtcNow;
                for (;;)
                {
                    Thread.Sleep(16);
                    if (IsDeletePending(fs))
                    {
                        Console.WriteLine("{0}: detected pending delete", filename);
                        break;
                    }
                    if (DateTime.UtcNow - t0 > MAX_WAIT_TIME)
                    {
                        Console.WriteLine("{0}: timeout reached with no delete", filename);
                        break;
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("{0}: {1}", filename, ex.Message);
            }
            finally
            {
                if (fs != null)
                {
                    Console.WriteLine("{0}: closing", filename);
                    fs.Dispose();
                }
            }
        }
        return 0;
    }
}


不,没有干净的方法可以做到这一点。如果您担心其他进程打开和/或修改文件,那么 oplocks 可以帮助您。但是,如果您只是在寻找删除处置设置为已删除的通知,则没有一种直接的方法可以做到这一点(无需构建文件系统过滤器、挂钩 API 等。所有这些对于应用程序来说都是令人毛骨悚然的确实这样做没有很好的理由)。


如果文件足够小,您的应用程序可以处理文件的副本,而不是文件本身。此外,如果您的应用程序需要知道拥有进程是否删除了原始文件,请在文件上设置 FileSystemWatcher (FSW)。当文件消失时,FSW 可以设置一个标志来中断处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private bool _fileExists = true;

public void Process(string pathToOriginalFile, string pathToCopy)
{
    File.Copy(pathToOriginalFile, pathToCopy);

    FileSystemWatcher watcher = new FileSystemWatcher();
    watcher.Path = pathToOriginalFile;
    watcher.Deleted += new FileSystemEventHandler(OnFileDeleted);

    bool doneProcessing = false;
    watcher.EnableRaisingEvents = true;

    while(_fileExists && !doneProcessing)
    {
        // process the copy here
    }

    ...
}

private void OnFileDeleted(object source, FileSystemEventArgs e)
{
    _fileExists = false;
}


我会使用不同的信号机制。 (我假设所有文件访问都在您的控制范围内,而不是来自封闭的外部程序,主要是由于使用了标志。)

我能想到的在这些范围内的唯一"解决方案"是对文件访问进行轮询并检查您返回的异常(如果有的话)。也许有一些更棘手的东西(在比 win32 文件 API 更低级别的地方?!?),但这已经在"uhg 路径"上 :-)


FileSystemWatcher 可能是最接近的东西,但它无法检测到"待处理"删除;当文件被删除时,将在 FileSystemWatcher 上引发一个事件,您可以附加一个处理程序,该处理程序将优雅地中断您的文件处理。如果您在打开文件时获得的锁定(或缺少锁定)使得文件可能被完全删除,那么在发生这种情况时简单地关闭您的只读 FileStream 不会影响文件系统。

文件观察器的基本步骤是创建一个,将 FileInfo 对象的实例传递给构造函数。 FileInfos 可以通过实例化一个文件来廉价地创建,将文件的路径和文件名作为字符串传递给它。然后,将其 NotifyFilter 设置为您要在此文件上观察的文件系统修改的类型。最后,将进程的事件处理程序附加到 OnDeleted 事件。这个事件处理程序可能很简单,只需在主进程可以读取的某个位置设置一个位标志,然后关闭 FileStream。然后,您将在下次尝试使用流时遇到异常;抓住它,读取标志,如果它被设置,就优雅地停止做文件。您还可以将文件处理放在单独的工作线程中,事件处理程序可以通过一些优雅的方法告诉线程终止。