web-dev-qa-db-ja.com

Windowsの一意のファイル識別子

移動、名前変更、コンテンツの変更に関係なく、ファイルの存続期間中、ファイル(および場合によってはディレクトリ)を一意に識別する方法はありますか? (Windows 2000以降)。ファイルのコピーを作成すると、そのコピーに独自の一意の識別子が与えられます。

私のアプリケーションは、さまざまなメタデータを個々のファイルに関連付けます。ファイルが変更、名前変更、または移動された場合、ファイルの関連付けを自動的に検出して更新できると便利です。

FileSystemWatcherは、これらの種類の変更を通知するイベントを提供できますが、多くのファイルシステムイベントが迅速に発生した場合、簡単に埋めることができる(およびイベントが失われる)メモリバッファーを使用します。

ファイルの内容が変更される可能性があるため、ハッシュは使用されません。ハッシュは変更されます。

私はファイル作成日を使用することを考えていましたが、これが一意にならない状況がいくつかあります(つまり、複数のファイルがコピーされる場合)。

NTFSのファイルSID(セキュリティID?)についても聞いたことがありますが、これが目的の動作をするかどうかはわかりません。

何か案は?

45
Ash

GetFileInformationByHandle を呼び出すと、BY_HANDLE_FILE_INFORMATION.nFileIndexHigh/LowでファイルIDが取得されます。このインデックスはボリューム内で一意であり、ファイルを(ボリューム内で)移動したり、名前を変更したりしても同じままです。

NTFSが使用されていると想定できる場合は、代替データストリームを使用してメタデータを格納することも検討してください。

22
Mattias S

一意のファイルインデックスを返すサンプルコードを次に示します。

ApproachA()は、少し調べて思いついたものです。 ApproachB()は、MattiasとRubensによって提供されたリンクの情報のおかげです。特定のファイルが与えられた場合、両方のアプローチは同じファイルインデックスを返します(私の基本的なテスト中)。

MSDNからのいくつかの警告:

ファイルIDのサポートはファイルシステム固有です。ファイルシステムはファイルIDを自由に再利用できるため、ファイルIDは時間が経過しても一意であるとは限りません。 場合によっては、ファイルのファイルIDが時間とともに変化することがあります。

FATファイルシステムでは、ファイルIDは、含まれているディレクトリの最初のクラスタと、ファイルのエントリのディレクトリ内のバイトオフセットから生成されます。一部のデフラグ製品は、このバイトオフセットを変更します。 (Windowsのインボックスデフラグは行われません。)したがって、FATファイルIDは時間とともに変化する可能性があります。 FATファイルシステムでファイルの名前を変更すると、ファイルIDも変更されます。ただし、新しいファイル名が古いファイル名よりも長い場合のみです。

NTFSファイルシステムでは、ファイルは削除されるまで同じファイルIDを保持します。 ReplaceFile関数を使用すると、ファイルIDを変更せずに、あるファイルを別のファイルに置き換えることができます。ただし、置換後のファイルではなく、置換後のファイルのファイルIDは、結果のファイルのファイルIDとして保持されます。

上記の最初の太字のコメントは心配です。このステートメントがFATのみに適用されるかどうかは明らかではありません。2番目の太字のテキストと矛盾しているようです。確認する唯一の方法は、さらにテストを行うことだと思います。

[更新:私のテストでは、ファイルが1つの内部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;   
       }
  }
30
Ash

こちらをご覧ください Windowsの一意のファイルID 。これも役に立ちます: NTFS上のファイルの一意のID?

4
Rubens Farias

ユーザーは、一意のディレクトリ識別についても言及します。このプロセスは、ファイルの一意の情報を取得するよりも少し複雑です。ただし、それは可能です。特定のフラグである適切なCREATE_FILEfunction を呼び出す必要があります。そのハンドルを使用すると、Ashの answerGetFileInformationByHandle関数を呼び出すことができます。

これには、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#署名の可能性を手助けしてくれました。

0
Thomas