10

Possible Duplicate:
Best way to determine if two path reference to same file in C#

There are several ways to specify a directory location.

For example:
\\machineName\c$\rootPath\subPath
\\machineName\shareName (share pointing to subPath)
C:\rootPath\subPath
subPath (relative path if already in C:\rootPath

I need to determine that all of these paths "equal" each other (are really the same physical location on the hard drive).

Is there any way I can do this in C#?

Community
  • 1
  • 1
GeneS
  • 145
  • 7

6 Answers6

5

As Oded states this is tricky to do in .Net. You could possibly cheat (depening on your exact requirements and permissions etc) by writing a file with a long randomly generated filename to the location and then seeing if you can see it from the other location. Bit of a hack but this is a pretty sound test I think, rather than relying on resolving mapped drives etc etc.

Ok many apologies for the VB - it's all I've got on this tiny netbook... C# wouldn't be too different...

usage eg

If sameLocation("\\machineName\c$\rootPath\subPath","\\machineName\shareName") Then...

Public Function sameLocation(ByVal sPath1 As String, ByVal sPath2 As String) As TriState
    Dim sFile As String = randomFilename()
    Dim sFullPath1 As String = sPath1 & "\" & sFile
    Dim sFullPath2 As String = sPath2 & "\" & sFile
    Dim bReturn As Boolean = False
    Try
        Dim fs As New FileStream(sFullPath1, FileMode.CreateNew)
        fs.Close()
    Catch ex As Exception
        Return TriState.UseDefault
    End Try

    Try
        bReturn = File.Exists(sFullPath2)
    Catch ex As Exception
        Return TriState.UseDefault
    End Try

    File.Delete(sFullPath1)
    Return bReturn
End Function

Public Function randomFilename() As String
    Dim r As New Random
    Randomize(My.Computer.Clock.TickCount)
    Dim sb As New StringBuilder
    Dim chars As Int16 = 100
    While chars > 0
        chars -= 1
        sb.Append(Chr(r.Next(26) + 65))
    End While
    Return sb.ToString
End Function

You could add more security, ie reading the timestamp etc...

El Ronnoco
  • 11,753
  • 5
  • 38
  • 65
4

You need to use GetFileInformationByHandle.
Check out this answer in StackOverflow and MSDN help.

Here is a method I wrote that works with directories:

using System;
using System.Runtime.InteropServices;

namespace CompareByPath    
{    
  public static class DirectoryHelper  
  {
    // all user defined types copied from 
    // http://pinvoke.net/default.aspx/kernel32.CreateFile
    // http://pinvoke.net/default.aspx/kernel32.GetFileInformationByHandle
    // http://pinvoke.net/default.aspx/kernel32.CloseHandle

    public const short INVALID_HANDLE_VALUE = -1;

    struct BY_HANDLE_FILE_INFORMATION
    {
      public uint FileAttributes;
      public System.Runtime.InteropServices.ComTypes.FILETIME CreationTime;
      public System.Runtime.InteropServices.ComTypes.FILETIME LastAccessTime;
      public System.Runtime.InteropServices.ComTypes.FILETIME LastWriteTime;
      public uint VolumeSerialNumber;
      public uint FileSizeHigh;
      public uint FileSizeLow;
      public uint NumberOfLinks;
      public uint FileIndexHigh;
      public uint FileIndexLow;
    }

    [Flags]
    public enum EFileAccess : uint
    {
      GenericRead = 0x80000000,
      GenericWrite = 0x40000000,
      GenericExecute = 0x20000000,
      GenericAll = 0x10000000
    }

    [Flags]
    public enum EFileShare : uint
    {
      None = 0x00000000,
      Read = 0x00000001,
      Write = 0x00000002,
      Delete = 0x00000004
    }

    [Flags]
    public enum EFileAttributes : uint
    {
      Readonly = 0x00000001,
      Hidden = 0x00000002,
      System = 0x00000004,
      Directory = 0x00000010,
      Archive = 0x00000020,
      Device = 0x00000040,
      Normal = 0x00000080,
      Temporary = 0x00000100,
      SparseFile = 0x00000200,
      ReparsePoint = 0x00000400,
      Compressed = 0x00000800,
      Offline = 0x00001000,
      NotContentIndexed = 0x00002000,
      Encrypted = 0x00004000,
      Write_Through = 0x80000000,
      Overlapped = 0x40000000,
      NoBuffering = 0x20000000,
      RandomAccess = 0x10000000,
      SequentialScan = 0x08000000,
      DeleteOnClose = 0x04000000,
      BackupSemantics = 0x02000000,
      PosixSemantics = 0x01000000,
      OpenReparsePoint = 0x00200000,
      OpenNoRecall = 0x00100000,
      FirstPipeInstance = 0x00080000
    }

    public enum ECreationDisposition : uint
    {
      New = 1,
      CreateAlways = 2,
      OpenExisting = 3,
      OpenAlways = 4,
      TruncateExisting = 5
    }

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

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
    static extern IntPtr CreateFile(String lpFileName, UInt32 dwDesiredAccess, UInt32 dwShareMode, IntPtr lpSecurityAttributes, UInt32 dwCreationDisposition, UInt32 dwFlagsAndAttributes, IntPtr hTemplateFile);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool CloseHandle(IntPtr hObject);

    public static bool CompareDirectories(string d1, string d2)
    {
      bool result = false;

      BY_HANDLE_FILE_INFORMATION info1;
      BY_HANDLE_FILE_INFORMATION info2;

      IntPtr fileHandle1 = CreateFile(d1, (uint)EFileAccess.GenericRead, (uint)EFileShare.Read, IntPtr.Zero, (uint)ECreationDisposition.OpenExisting, (uint)(EFileAttributes.Directory | EFileAttributes.BackupSemantics), IntPtr.Zero);
      if (fileHandle1.ToInt32() != INVALID_HANDLE_VALUE)
      {
        bool rc = GetFileInformationByHandle(fileHandle1, out info1);
        if ( rc )
        {
          IntPtr fileHandle2 = CreateFile(d2, (uint)EFileAccess.GenericRead, (uint)EFileShare.Read, IntPtr.Zero, (uint)ECreationDisposition.OpenExisting, (uint)(EFileAttributes.Directory | EFileAttributes.BackupSemantics), IntPtr.Zero);
          if (fileHandle2.ToInt32() != INVALID_HANDLE_VALUE)
          {
            rc = GetFileInformationByHandle(fileHandle2, out info2);
            if ( rc )
            {
              if (( info1.FileIndexHigh == info2.FileIndexHigh) &&
                  ( info1.FileIndexLow == info2.FileIndexLow) &&
                  ( info1.VolumeSerialNumber == info2.VolumeSerialNumber))
              {
                result = true;
              }
            }
          }

          CloseHandle(fileHandle2);
        }
      }

      CloseHandle(fileHandle1);

      return result;
    }
  }
}
Community
  • 1
  • 1
Zamboni
  • 7,897
  • 5
  • 43
  • 52
2

There are many aliasing schemes in Windows:

  • short vs. long names
  • symlinks and hardlinks
  • UNC names vs. mapped drives
  • several mapped drives to the same network path
  • d:\folder\ paths vs. \?\d\folder\ paths
  • NTFS mount points

And any of these can appear on any level in the directory tree. In .NET, you can resolve some of these, but not others.

As a hackish way around it, try the locking/unlocking. Lock the file as name 1, try opening as name 2, make sure it fails. Then unlock, try opening again, make sure it succeeds. And so forth, a few times to avoid false positives. Unlike El Ronnoco's way, this one detects both path-level and file-level aliasing.

On some network filesystems, locking might not be supported at all. Also, it might take a while - each lock/unlock/open operation is a network roundtrip.

But it really depends on your requirements. If short/long names is all you have to deal with, it's an overkill.

EDIT: additional complications if the file is read-only, or already open by someone else.

Seva Alekseyev
  • 59,826
  • 25
  • 160
  • 281
  • +1 I like the answer but I'm not sure what you mean about my method destroying the existing contents? My method would involve creating an empty file. – El Ronnoco Feb 03 '11 at 20:30
  • @El Ronnoco: misunderstood, sorry. Your way does not detect filename-level aliasing, though. – Seva Alekseyev Feb 03 '11 at 20:36
  • @Seva That's ok, I don't even know what that is! :) – El Ronnoco Feb 03 '11 at 20:49
  • @El Ronnoco: if you have Windows Vista or 7, try this. Create a file called c:\a.txt. Put some text in it. Then run the following command on the command line: `mklink c:\b.txt c:\a.txt`. Then open the file c:\b.txt, enjoy the view. Your "create a random file in the same path" approach will never find out that these two are the same file. For a harder version of the same, try `mklink /H`. – Seva Alekseyev Feb 03 '11 at 21:20
  • @Seva Interesting, I didn't know that. Though the OP does indicate in his question that he's interested in _directory locations_ as opposed to actual files. Obviously writing random data to files would be a bit of a crazy method. There'd probably be some way to do it. Is there a filesystem file attribute that could be modified and then checked? – El Ronnoco Feb 04 '11 at 15:00
  • Read-only attribute, I guess... But that requires writing permissions on the file. Locking, AFAIK, does not. You can open the file for reading with exclusive lock, and subsequent open-for-reading operations will fail with a sharing violation. – Seva Alekseyev Feb 04 '11 at 19:57
0

There is no native way to do this in .NET - it is just too low level.

You may be able to use the windows API to achieve this (looking at directory inode or some other identifier), but I don't know what API would expose this.

Oded
  • 489,969
  • 99
  • 883
  • 1,009
0

AKAIK you can even map the same drive to many drive letters or subdirectories. Also network shared directories can be multiplied and you never know if they are the same or not.

Maybe you could add information why do you need to know it.

Al Kepp
  • 5,831
  • 2
  • 28
  • 48
0

Good question, there may not be an elegant answer.

The best I can find to point you on your way is the net share command-line statement. If you can capture and digest the text produced by this command programmatically, you can map network shares 1:1 to their local destination folders. Then you can look for instances of those share maps in a given path and replace them with the local folder equivalents before performing a basic string comparison.

KeithS
  • 70,210
  • 21
  • 112
  • 164