Windows中的唯一文件标识符

时间:2009-12-08 11:46:50

标签: c# .net windows filesystems

是否有办法在文件的生命周期内唯一标识文件(可能还有目录),无论移动,重命名和内容修改如何? (Windows 2000及更高版本)。制作文件的副本应该为副本提供它自己的唯一标识符。

我的应用程序将各种元数据与单个文件相关联。如果文件被修改,重命名或移动,那么能够自动检测和更新文件关联将非常有用。

FileSystemWatcher可以提供通知这些更改的事件,但是如果很多文件系统事件快速发生,它会使用一个可以轻松填充(和事件丢失)的内存缓冲区。

哈希是没用的,因为文件的内容可以改变,因此哈希值会改变。

我曾想过使用文件创建日期,但有些情况下这不是唯一的(即复制多个文件时)。

我还听说过NTFS中有一个文件SID(安全ID?),但我不确定这是否能满足我的要求。

有什么想法吗?

5 个答案:

答案 0 :(得分:30)

以下是返回唯一文件索引的示例代码。

方法A()是我在经过一些研究后想出来的。 ApproachB()归功于Mattias和Rubens提供的链接中的信息。给定一个特定文件,两种方法都返回相同的文件索引(在我的基本测试期间)。

来自MSDN的一些警告:

  

支持文件ID是文件   系统专用。文件ID不是   保证随着时间的推移是独特的,   因为文件系统可以自由重用   他们。 在某些情况下,文件ID为   文件可以随时间变化。

     

在FAT文件系统中,文件ID是   从第一个集群生成   包含目录和字节   在目录中的偏移量   该文件的条目。一些   碎片整理产品改变了这一点   字节偏移量。 (Windows收件箱   碎片整理不会。)因此,FAT   文件ID可以随时间变化。重命名   FAT文件系统中的文件也可以   更改文件ID,但仅限于   新文件名比旧文件名长   之一。

     

在NTFS文件系统中,文件保留   在删除之前使用相同的文件ID 。   您可以用另一个文件替换一个文件   文件ID而不更改文件ID   使用ReplaceFile函数。   但是,文件ID   替换文件,而不是替换   文件,保留为文件ID   生成的文件。

上面的第一个粗体评论让我担心。目前尚不清楚这个陈述是否仅适用于FAT,它似乎与第二个粗体文本相矛盾。我想进一步测试是确定的唯一方法。

[更新:在我的测试中,当文件从一个内部NTFS硬盘驱动器移动到另一个内部NTFS硬盘驱动器时,文件索引/ ID会发生变化。]

    public class WinAPI
    {
        [DllImport("ntdll.dll", SetLastError = true)]
        public static extern IntPtr NtQueryInformationFile(IntPtr fileHandle, ref IO_STATUS_BLOCK IoStatusBlock, IntPtr pInfoBlock, uint length, FILE_INFORMATION_CLASS fileInformation);

        public struct IO_STATUS_BLOCK
        {
            uint status;
            ulong information;
        }
        public struct _FILE_INTERNAL_INFORMATION {
          public ulong  IndexNumber;
        } 

        // Abbreviated, there are more values than shown
        public enum FILE_INFORMATION_CLASS
        {
            FileDirectoryInformation = 1,     // 1
            FileFullDirectoryInformation,     // 2
            FileBothDirectoryInformation,     // 3
            FileBasicInformation,         // 4
            FileStandardInformation,      // 5
            FileInternalInformation      // 6
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool GetFileInformationByHandle(IntPtr hFile,out BY_HANDLE_FILE_INFORMATION lpFileInformation);

        public struct BY_HANDLE_FILE_INFORMATION
        {
            public uint FileAttributes;
            public FILETIME CreationTime;
            public FILETIME LastAccessTime;
            public FILETIME LastWriteTime;
            public uint VolumeSerialNumber;
            public uint FileSizeHigh;
            public uint FileSizeLow;
            public uint NumberOfLinks;
            public uint FileIndexHigh;
            public uint FileIndexLow;
        }
  }

  public class Test
  {
       public ulong ApproachA()
       {
                WinAPI.IO_STATUS_BLOCK iostatus=new WinAPI.IO_STATUS_BLOCK();

                WinAPI._FILE_INTERNAL_INFORMATION objectIDInfo = new WinAPI._FILE_INTERNAL_INFORMATION();

                int structSize = Marshal.SizeOf(objectIDInfo);

                FileInfo fi=new FileInfo(@"C:\Temp\testfile.txt");
                FileStream fs=fi.Open(FileMode.Open,FileAccess.Read,FileShare.ReadWrite);

                IntPtr res=WinAPI.NtQueryInformationFile(fs.Handle, ref iostatus, memPtr, (uint)structSize, WinAPI.FILE_INFORMATION_CLASS.FileInternalInformation);

                objectIDInfo = (WinAPI._FILE_INTERNAL_INFORMATION)Marshal.PtrToStructure(memPtr, typeof(WinAPI._FILE_INTERNAL_INFORMATION));

                fs.Close();

                Marshal.FreeHGlobal(memPtr);   

                return objectIDInfo.IndexNumber;

       }

       public ulong ApproachB()
       {
               WinAPI.BY_HANDLE_FILE_INFORMATION objectFileInfo=new WinAPI.BY_HANDLE_FILE_INFORMATION();

                FileInfo fi=new FileInfo(@"C:\Temp\testfile.txt");
                FileStream fs=fi.Open(FileMode.Open,FileAccess.Read,FileShare.ReadWrite);

                WinAPI.GetFileInformationByHandle(fs.Handle, out objectFileInfo);

                fs.Close();

                ulong fileIndex = ((ulong)objectFileInfo.FileIndexHigh << 32) + (ulong)objectFileInfo.FileIndexLow;

                return fileIndex;   
       }
  }

答案 1 :(得分:21)

如果您致电GetFileInformationByHandle,您将获得BY_HANDLE_FILE_INFORMATION.nFileIndexHigh / Low的文件ID。该索引在卷中是唯一的,即使您移动文件(在卷内)或重命名它也保持不变。

如果您可以假设使用了NTFS,您可能还需要考虑使用备用数据流来存储元数据。

答案 2 :(得分:4)

请看一下:Unique file ids for Windows。这也很有用:Unique ID for files on NTFS?

答案 3 :(得分:0)

用户还提到了唯一的目录标识。这个过程比检索文件的唯一信息有点复杂;但是,这是可能的。它要求您调用特定标志的相应CREATE_FILE function。使用该句柄,您可以在Ash的answer中调用GetFileInformationByHandle函数。

这还需要kernel32.dll导入:

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern SafeFileHandle CreateFile(
            string lpFileName,
            [MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess,
            [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode,
            IntPtr securityAttributes,
            [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition,
            uint dwFlagsAndAttributes,
            IntPtr hTemplateFile
        );

稍后我会更多地充实这个答案。但是,通过上述相关答案,这应该开始有意义了。我最喜欢的资源是pinvoke,它帮助我了解.Net C#签名的可能性。

答案 4 :(得分:0)

您可以用于文件 uid 的一件事是创建时间戳,只需确保当您将文件扫描到您的程序中时,您调整任何与已经遇到的相同的创建时间,因此它们有细微的不同,显然一些文件甚至如果 NTFS 上的 TS 是通过大量文件副本同时创建的,则它们可以具有相同的 TS,但在正常情况下,您不会得到重复项,并且调整很少(如果有的话)。