/// <summary> /// Return true if the path points to a junction. These are distinct /// from symlinks. /// </summary> /// <returns></returns> public bool IsJunction() { ReparsePoint rep; return(ReparsePoint.TryCreate(PurePath.ToString(), out rep) && rep.Tag == ReparsePoint.TagType.JunctionPoint); }
/// <inheritdoc/> public override bool IsSymlink() { ReparsePoint rep; return(ReparsePoint.TryCreate(PurePath.ToString(), out rep) && rep.Tag == ReparsePoint.TagType.SymbolicLink); }
/// <summary> /// Takes a full path to a reparse point and finds the target. /// </summary> /// <param name="path">Full path of the reparse point</param> /// <param name="link"></param> public static bool TryCreate(string path, out ReparsePoint link) { Debug.Assert(!string.IsNullOrEmpty(path) && path.Length > 2 && path[1] == ':' && path[2] == '\\'); var tag = TagType.None; string normalizedTarget; // Apparently we need to have backup privileges IntPtr token; TOKEN_PRIVILEGES tokenPrivileges = new TOKEN_PRIVILEGES { Privileges = new LUID_AND_ATTRIBUTES[1] }; bool success = OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, out token); if (!success) { link = null; return false; } success = LookupPrivilegeValue( null, SE_BACKUP_NAME, out tokenPrivileges.Privileges[0].Luid); if (!success) { link = null; return false; } tokenPrivileges.PrivilegeCount = 1; tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; success = AdjustTokenPrivileges(token, false, ref tokenPrivileges, Marshal.SizeOf(tokenPrivileges), IntPtr.Zero, IntPtr.Zero); CloseHandle(token); if (!success) { link = null; return false; } // Open the file and get its handle IntPtr handle = CreateFile(path, FileAccess.Read, FileShare.None, 0, FileMode.Open, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero); if (handle.ToInt32() < 0) { link = null; return false; } REPARSE_DATA_BUFFER buffer; // Make up the control code - see CTL_CODE on ntddk.h const uint controlCode = (FILE_DEVICE_FILE_SYSTEM << 16) | (FILE_ANY_ACCESS << 14) | (FSCTL_GET_REPARSE_POINT << 2) | METHOD_BUFFERED; uint bytesReturned; success = DeviceIoControl(handle, controlCode, IntPtr.Zero, 0, out buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, out bytesReturned, IntPtr.Zero); if (!success) { link = null; return false; } string subsString = ""; string printString = ""; // Note that according to http://wesnerm.blogs.com/net_undocumented/2006/10/symbolic_links_.html // Symbolic links store relative paths, while junctions use absolute paths // however, they can in fact be either, and may or may not have a leading \. Debug.Assert(buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK || buffer.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, "Unrecognised reparse tag"); // We only recognise these two if (buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK) { // for some reason symlinks seem to have an extra two characters on the front subsString = new string(buffer.ReparseTarget, (buffer.SubsNameOffset / 2 + 2), buffer.SubsNameLength / 2); printString = new string(buffer.ReparseTarget, (buffer.PrintNameOffset / 2 + 2), buffer.PrintNameLength / 2); tag = TagType.SymbolicLink; } else if (buffer.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) { // This could be a junction or a mounted drive - a mounted drive starts with "\\??\\Volume" subsString = new string(buffer.ReparseTarget, buffer.SubsNameOffset/2, buffer.SubsNameLength/2); printString = new string(buffer.ReparseTarget, buffer.PrintNameOffset / 2, buffer.PrintNameLength / 2); tag = subsString.StartsWith(@"\??\Volume") ? TagType.MountPoint : TagType.JunctionPoint; } Debug.Assert(!(string.IsNullOrEmpty(subsString) && string.IsNullOrEmpty(printString)), "Failed to retrieve parse point"); // the printstring should give us what we want if (!string.IsNullOrEmpty(printString)) { normalizedTarget = printString; } else { // if not we can use the substring with a bit of tweaking normalizedTarget = subsString; Debug.Assert(normalizedTarget.Length > 2, "Target string too short"); Debug.Assert( (normalizedTarget.StartsWith(@"\??\") && (normalizedTarget[5] == ':' || normalizedTarget.StartsWith(@"\??\Volume")) || (!normalizedTarget.StartsWith(@"\??\") && normalizedTarget[1] != ':')), "Malformed subsString"); // Junction points must be absolute Debug.Assert( buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK || normalizedTarget.StartsWith(@"\??\Volume") || normalizedTarget[1] == ':' || normalizedTarget.StartsWith(@"\??\") && normalizedTarget[5] == ':', "Relative junction point"); if (normalizedTarget.StartsWith(@"\??\")) { normalizedTarget = normalizedTarget.Substring(4); } } var target = normalizedTarget; // Symlinks can be relative. if (buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK && (normalizedTarget.Length < 2 || normalizedTarget[1] != ':')) { // it's relative, we need to tack it onto the path if (normalizedTarget[0] == '\\') { normalizedTarget = normalizedTarget.Substring(1); } if (path.EndsWith(@"\")) { path = path.Substring(0, path.Length - 1); } // Need to take the symlink name off the path normalizedTarget = path.Substring(0, path.LastIndexOf('\\')) + @"\" + normalizedTarget; // Note that if the symlink target path contains any ..s these are not normalised but returned as is. } // Remove any final slash for consistency if (normalizedTarget.EndsWith("\\")) { normalizedTarget = normalizedTarget.Substring(0, normalizedTarget.Length-1); } CloseHandle(handle); link = new ReparsePoint { _normalizedTarget = normalizedTarget, Target = target, Tag = tag }; return true; }
/// <summary> /// Takes a full path to a reparse point and finds the target. /// </summary> /// <param name="path">Full path of the reparse point</param> /// <param name="link"></param> public static bool TryCreate(string path, out ReparsePoint link) { Debug.Assert(!string.IsNullOrEmpty(path) && path.Length > 2 && path[1] == ':' && path[2] == '\\'); var tag = TagType.None; string normalizedTarget; // Apparently we need to have backup privileges IntPtr token; TOKEN_PRIVILEGES tokenPrivileges = new TOKEN_PRIVILEGES { Privileges = new LUID_AND_ATTRIBUTES[1] }; bool success = OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, out token); if (!success) { link = null; return(false); } success = LookupPrivilegeValue( null, SE_BACKUP_NAME, out tokenPrivileges.Privileges[0].Luid); if (!success) { link = null; return(false); } tokenPrivileges.PrivilegeCount = 1; tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; success = AdjustTokenPrivileges(token, false, ref tokenPrivileges, Marshal.SizeOf(tokenPrivileges), IntPtr.Zero, IntPtr.Zero); CloseHandle(token); if (!success) { link = null; return(false); } // Open the file and get its handle IntPtr handle = CreateFile(path, FileAccess.Read, FileShare.None, 0, FileMode.Open, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero); if (handle.ToInt32() < 0) { link = null; return(false); } REPARSE_DATA_BUFFER buffer; // Make up the control code - see CTL_CODE on ntddk.h const uint controlCode = (FILE_DEVICE_FILE_SYSTEM << 16) | (FILE_ANY_ACCESS << 14) | (FSCTL_GET_REPARSE_POINT << 2) | METHOD_BUFFERED; uint bytesReturned; success = DeviceIoControl(handle, controlCode, IntPtr.Zero, 0, out buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, out bytesReturned, IntPtr.Zero); if (!success) { link = null; return(false); } string subsString = ""; string printString = ""; // Note that according to http://wesnerm.blogs.com/net_undocumented/2006/10/symbolic_links_.html // Symbolic links store relative paths, while junctions use absolute paths // however, they can in fact be either, and may or may not have a leading \. Debug.Assert(buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK || buffer.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, "Unrecognised reparse tag"); // We only recognise these two if (buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK) { // for some reason symlinks seem to have an extra two characters on the front subsString = new string(buffer.ReparseTarget, (buffer.SubsNameOffset / 2 + 2), buffer.SubsNameLength / 2); printString = new string(buffer.ReparseTarget, (buffer.PrintNameOffset / 2 + 2), buffer.PrintNameLength / 2); tag = TagType.SymbolicLink; } else if (buffer.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) { // This could be a junction or a mounted drive - a mounted drive starts with "\\??\\Volume" subsString = new string(buffer.ReparseTarget, buffer.SubsNameOffset / 2, buffer.SubsNameLength / 2); printString = new string(buffer.ReparseTarget, buffer.PrintNameOffset / 2, buffer.PrintNameLength / 2); tag = subsString.StartsWith(@"\??\Volume") ? TagType.MountPoint : TagType.JunctionPoint; } Debug.Assert(!(string.IsNullOrEmpty(subsString) && string.IsNullOrEmpty(printString)), "Failed to retrieve parse point"); // the printstring should give us what we want if (!string.IsNullOrEmpty(printString)) { normalizedTarget = printString; } else { // if not we can use the substring with a bit of tweaking normalizedTarget = subsString; Debug.Assert(normalizedTarget.Length > 2, "Target string too short"); Debug.Assert( (normalizedTarget.StartsWith(@"\??\") && (normalizedTarget[5] == ':' || normalizedTarget.StartsWith(@"\??\Volume")) || (!normalizedTarget.StartsWith(@"\??\") && normalizedTarget[1] != ':')), "Malformed subsString"); // Junction points must be absolute Debug.Assert( buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK || normalizedTarget.StartsWith(@"\??\Volume") || normalizedTarget[1] == ':' || normalizedTarget.StartsWith(@"\??\") && normalizedTarget[5] == ':', "Relative junction point"); if (normalizedTarget.StartsWith(@"\??\")) { normalizedTarget = normalizedTarget.Substring(4); } } var target = normalizedTarget; // Symlinks can be relative. if (buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK && (normalizedTarget.Length < 2 || normalizedTarget[1] != ':')) { // it's relative, we need to tack it onto the path if (normalizedTarget[0] == '\\') { normalizedTarget = normalizedTarget.Substring(1); } if (path.EndsWith(@"\")) { path = path.Substring(0, path.Length - 1); } // Need to take the symlink name off the path normalizedTarget = path.Substring(0, path.LastIndexOf('\\')) + @"\" + normalizedTarget; // Note that if the symlink target path contains any ..s these are not normalised but returned as is. } // Remove any final slash for consistency if (normalizedTarget.EndsWith("\\")) { normalizedTarget = normalizedTarget.Substring(0, normalizedTarget.Length - 1); } CloseHandle(handle); link = new ReparsePoint { _normalizedTarget = normalizedTarget, Target = target, Tag = tag }; return(true); }