GetFinalPathNameByHandle Revisited

前に、

なんていう記事を書いていたんですが、いろんなファイルパスからの同一のファイルにアクセスできる様な状況、つまり、シンボリックリンクやら、ジャンクションやら、ハードリンク、それらにおいて、ファイル名→ハンドル→ID→ファイル名とやると、元(?)の名前に戻るのかな?的な疑問があったので改めて実験してみました。

使う関数は、

GetFinalPathNameByHandle function

コードは、

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

class FileIdTest
{
  public static void Main(string[] args)
  {
    foreach (var fileName in args)
    {
      Console.WriteLine("{0} -> {1}", fileName, realFn(fileName));
    }
  }

  static string realFn(string fileName)
  {
    using (var fs = File.OpenRead(fileName))
    {
      int size = 2048;
      var sb = new StringBuilder(size);
      if (GetFinalPathNameByHandle(fs.SafeFileHandle, sb, size, 0) == 0)
        return fileName.ToLower();

      var fn = sb.ToString();
      if (fn.StartsWith(@"\\?\UNC"))
        return @"\" + fn.Substring(7);
      if (fn.StartsWith(@"\\?\"))
        return fn.Substring(4);
      return fn;
    }
  }

  [DllImport("kernel32.dll", SetLastError = true)]
    private static extern int GetFinalPathNameByHandle(Microsoft.Win32.SafeHandles.SafeFileHandle hFile, StringBuilder lpszFilePath, int cchFilePath, int dwFlags);
}

C:\Users 下は、

2013/08/22  23:45    <SYMLINKD>     All Users [C:\ProgramData]
2013/09/10  18:07    <DIR>          Default
2013/08/22  23:45    <JUNCTION>     Default User [C:\Users\Default]

All Users が C:\ProgramData のシンボリックリンクです。
なので、

ftest "C:\Users\All Users\hoge.txt"
C:\Users\All Users\hoge.txt -> C:\ProgramData\hoge.txt

という具合に、元のディレクトリ名でファイル名が返ってきます。

似たような所で、 C:\Users\Default User というジャンクションは、 C:\Users\Default への「ジャンクション」ですが、

ftest.exe "c:\Users\Default User\test.txt"
c:\Users\Default User\test.txt -> C:\Users\Default\test.txt

挙動としては同じです。

一方で、ハードリンクに対しては、別のファイルIDが返ってくるので、どちらかの入り口に集約されるようなことはありませんでした。 Windows/NTFS の仕様に疎いので、これがどのレイヤーでの挙動なのか分かりません。思った挙動と違ってビックリです。

あと、ファイル名が違うのに、実質的に同じファイルになるパターンとしては、CIFS経由のアクセスですかね。これらも、別のファイルIDになります。こっちは想定通りではありましたが。