检测打开文件的文件删除

5
我正在使用读取权限打开文件,并允许文件后续的读取、写入和删除共享访问(尾随该文件)。如果在处理期间删除该文件,是否有一种方法可以检测到该文件正在等待删除(请参见文件部分http://msdn.microsoft.com/en-us/library/aa363858(v=VS.85).aspx)?如果某个外部进程(拥有进程)发出了删除请求,则我希望尽快关闭我的句柄,以允许文件删除,不会干扰拥有进程中的任何逻辑。
我正在使用C#,但看不到检测挂起删除的方法。该文件是使用FileStream对象打开的。是否有一些在C#或其他Windows函数中检测删除操作的方法?

应该提到你正在使用DELETE_ON_CLOSE/FILE_SHARE_DELETE。我怀疑这是非常常见的用法。 - user166390
5个回答

3
您可以使用Windows API函数GetFileInformationByHandleEx来检测您打开的文件是否有待删除状态。第二个参数是枚举值,可以让您指定函数应返回哪种类型的信息。FileStandardInfo(1)值将导致它返回包含DeletePending布尔值的FILE_STANDARD_INFO结构。
以下是演示实用程序:
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!\r\n";
                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;
    }
}

这应该是答案...我刚刚在我的程序中使用了这个,它不断地从日志文件中读取,但需要检测是否已被删除,并在发生这种情况时释放它,以便在重新创建时可以开始读取。 - jceddy

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

由于文件未关闭时无法删除,因此FileSystemWatcher不起作用。因为我有一个文件句柄,所以在我关闭我的句柄之前,它处于挂起删除状态。 - Mitch
在复制文件后,您将释放对原始文件的句柄。如果我没记错的话,FSW 将拥有指向文件所在目录而不是文件本身的句柄。 - Jeff Mattfield
你说得对,我应该仔细阅读你的答案。但是这些文件可能会有任何大小,在某些情况下处理副本是不可行的。 - Mitch

0

FileSystemWatcher可能是最接近的东西,但它无法检测“挂起”删除;当文件被删除时,FileSystemWatcher会触发一个事件,您可以附加一个处理程序来优雅地中断文件处理。如果您在打开文件时获取的锁定(或缺少锁定)使得文件可以被删除,那么当发生这种情况时,只需关闭您的只读FileStream即可不影响文件系统。

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


问题在于我有一个打开的文件句柄,这将阻止删除完成——它是挂起的。我需要检测删除操作,以便关闭我的句柄,使删除操作完成。 - Mitch
1
据我所知,那是不可能的。删除操作将会失败 - 实际上,你所要求的是一种检测其他进程中是否有删除文件的意图的方法。在操作系统中实现一种通知所有当前文件用户某个进程正在尝试删除正在使用的文件的方式是不现实的,因为这会使得删除操作被挂起直到其他用户决定关闭它们的使用(如果他们拒绝怎么办?)。如果删除进程后来改变了想法或者因为某些原因退出呢?交互问题太可怕了。 - Steve Townsend
你是说拥有进程的删除文件请求会失败吗?如果是这样,那是不正确的。删除成功,并且该文件处于待删除状态,直到所有句柄关闭。一旦没有更多的句柄打开该文件,它就被删除了。在待删除期间,对文件的后续访问请求将收到访问被拒绝的异常。请参见:http://msdn.microsoft.com/en-us/library/aa363915(v=VS.85).aspx或http://msdn.microsoft.com/en-us/library/aa363858(v=VS.85).aspx。 - Mitch

0

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

在这些限制下,我唯一能想到的“解决方案”是对文件访问进行轮询并检查您收到的异常(如果有)。也许有更加棘手的东西(比 win32 文件 API 更低级别的东西?!),但这已经走上了“艰难的道路” :-)


1
我接受这个答案,因为这是我能做的全部。每次需要从句柄返回并读取文件时,我都会尝试重新打开文件以保持句柄处于打开状态。如果我收到一个UnauthorizedAccessException(请参阅MSDN),那么我就会假设该文件已被删除并关闭我的句柄。我知道这很耗费资源,但我想不出其他办法。 - Mitch

0

不,没有干净的方法来做到这一点。如果您担心其他进程打开和/或修改文件,则 oplocks 可以帮助您。但是,如果您只是想通知何时将删除处理设置为已删除,那么没有直接的方法可以做到这一点(除非构建文件系统过滤器、挂钩 API 等,所有这些都对应用程序而言都很可怕,除非有非常充分的理由)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接