检测打开文件上的文件删除

时间:2010-09-07 16:08:34

标签: c# windows file-io tail delete-file

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

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

5 个答案:

答案 0 :(得分:1)

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

这是一个演示实用程序:

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;
    }
}

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

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

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

答案 3 :(得分: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;
}

答案 4 :(得分:0)

不,没有干净的方法可以做到这一点。如果您担心打开和/或修改文件的其他进程,那么oplock可以帮助您。但是,如果你只是在寻找删除处置被设置为删除的时间的通知,那么没有一种直接的方法可以做到这一点(没有建立文件系统过滤器,挂钩API等等,所有这些对于应用程序来说都是怪异的做得很好没有理由。)