From ce49f4ebd8d02be990f2bfe9e2b78869685a9e9a Mon Sep 17 00:00:00 2001 From: Andrew Sampson Date: Thu, 29 Dec 2016 13:10:17 -0500 Subject: [PATCH] Fix #37 --- .../Analyzer/FileFinders/RedisFileFinder.cs | 14 +- SteamCleaner/SteamCleaner.csproj | 2 + SteamCleaner/Utilities/Files/SymbolicLink.cs | 150 ++++++++++++++++++ .../Files/SymbolicLinkReparseData.cs | 30 ++++ 4 files changed, 193 insertions(+), 3 deletions(-) create mode 100644 SteamCleaner/Utilities/Files/SymbolicLink.cs create mode 100644 SteamCleaner/Utilities/Files/SymbolicLinkReparseData.cs diff --git a/SteamCleaner/Analyzer/FileFinders/RedisFileFinder.cs b/SteamCleaner/Analyzer/FileFinders/RedisFileFinder.cs index b43fd7e..6d93f1d 100644 --- a/SteamCleaner/Analyzer/FileFinders/RedisFileFinder.cs +++ b/SteamCleaner/Analyzer/FileFinders/RedisFileFinder.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; +using SteamCleaner.Utilities.Files; #endregion @@ -38,16 +39,23 @@ public void Search(List files, IEnumerable paths) { continue; - } + } if (!dirRegex.IsMatch(path)) { continue; } - AddFiles(files, path); - Search(files, Directory.GetDirectories(path)); + var targetPath = path; + if (SymbolicLink.IsSymbolic(path) && SymbolicLink.Exists(path)) + { + targetPath = SymbolicLink.GetTarget(path); + } + AddFiles(files, targetPath); + Search(files, Directory.GetDirectories(targetPath)); } } + + private void AddFiles(List files, string path) { files.AddRange(from f in Directory.GetFiles(path) diff --git a/SteamCleaner/SteamCleaner.csproj b/SteamCleaner/SteamCleaner.csproj index 3ba136b..6732985 100644 --- a/SteamCleaner/SteamCleaner.csproj +++ b/SteamCleaner/SteamCleaner.csproj @@ -135,6 +135,8 @@ + + diff --git a/SteamCleaner/Utilities/Files/SymbolicLink.cs b/SteamCleaner/Utilities/Files/SymbolicLink.cs new file mode 100644 index 0000000..bd1cc1d --- /dev/null +++ b/SteamCleaner/Utilities/Files/SymbolicLink.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Win32.SafeHandles; + +namespace SteamCleaner.Utilities.Files +{ + public static class SymbolicLink + { + private const uint genericReadAccess = 0x80000000; + + private const uint fileFlagsForOpenReparsePointAndBackupSemantics = 0x02200000; + + private const int ioctlCommandGetReparsePoint = 0x000900A8; + + private const uint openExisting = 0x3; + + private const uint pathNotAReparsePointError = 0x80071126; + + private const uint shareModeAll = 0x7; // Read, Write, Delete + + private const uint symLinkTag = 0xA000000C; + + private const int targetIsAFile = 0; + + private const int targetIsADirectory = 1; + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern SafeFileHandle CreateFile( + string lpFileName, + uint dwDesiredAccess, + uint dwShareMode, + IntPtr lpSecurityAttributes, + uint dwCreationDisposition, + uint dwFlagsAndAttributes, + IntPtr hTemplateFile); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, int dwFlags); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern bool DeviceIoControl( + IntPtr hDevice, + uint dwIoControlCode, + IntPtr lpInBuffer, + int nInBufferSize, + IntPtr lpOutBuffer, + int nOutBufferSize, + out int lpBytesReturned, + IntPtr lpOverlapped); + public static bool IsSymbolic(string path) + { + FileInfo pathInfo = new FileInfo(path); + return pathInfo.Attributes.HasFlag(FileAttributes.ReparsePoint); + } + public static void CreateDirectoryLink(string linkPath, string targetPath) + { + if (!CreateSymbolicLink(linkPath, targetPath, targetIsADirectory) || Marshal.GetLastWin32Error() != 0) + { + try + { + Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); + } + catch (COMException exception) + { + throw new IOException(exception.Message, exception); + } + } + } + + public static void CreateFileLink(string linkPath, string targetPath) + { + if (!CreateSymbolicLink(linkPath, targetPath, targetIsAFile)) + { + Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); + } + } + + public static bool Exists(string path) + { + if (!Directory.Exists(path) && !File.Exists(path)) + { + return false; + } + string target = GetTarget(path); + return target != null; + } + + private static SafeFileHandle getFileHandle(string path) + { + return CreateFile(path, genericReadAccess, shareModeAll, IntPtr.Zero, openExisting, + fileFlagsForOpenReparsePointAndBackupSemantics, IntPtr.Zero); + } + + public static string GetTarget(string path) + { + SymbolicLinkReparseData reparseDataBuffer; + + using (SafeFileHandle fileHandle = getFileHandle(path)) + { + if (fileHandle.IsInvalid) + { + Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); + } + + int outBufferSize = Marshal.SizeOf(typeof(SymbolicLinkReparseData)); + IntPtr outBuffer = IntPtr.Zero; + try + { + outBuffer = Marshal.AllocHGlobal(outBufferSize); + int bytesReturned; + bool success = DeviceIoControl( + fileHandle.DangerousGetHandle(), ioctlCommandGetReparsePoint, IntPtr.Zero, 0, + outBuffer, outBufferSize, out bytesReturned, IntPtr.Zero); + + fileHandle.Close(); + + if (!success) + { + if (((uint)Marshal.GetHRForLastWin32Error()) == pathNotAReparsePointError) + { + return null; + } + Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); + } + + reparseDataBuffer = (SymbolicLinkReparseData)Marshal.PtrToStructure( + outBuffer, typeof(SymbolicLinkReparseData)); + } + finally + { + Marshal.FreeHGlobal(outBuffer); + } + } + if (reparseDataBuffer.ReparseTag != symLinkTag) + { + return null; + } + + string target = Encoding.Unicode.GetString(reparseDataBuffer.PathBuffer, + reparseDataBuffer.PrintNameOffset, reparseDataBuffer.PrintNameLength); + + return target; + } + } +} \ No newline at end of file diff --git a/SteamCleaner/Utilities/Files/SymbolicLinkReparseData.cs b/SteamCleaner/Utilities/Files/SymbolicLinkReparseData.cs new file mode 100644 index 0000000..e3b7eb2 --- /dev/null +++ b/SteamCleaner/Utilities/Files/SymbolicLinkReparseData.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace SteamCleaner.Utilities.Files +{ + /// + /// Refer to http://msdn.microsoft.com/en-us/library/windows/hardware/ff552012%28v=vs.85%29.aspx + /// + [StructLayout(LayoutKind.Sequential)] + public struct SymbolicLinkReparseData + { + // Not certain about this! + private const int maxUnicodePathLength = 260 * 2; + + public uint ReparseTag; + public ushort ReparseDataLength; + public ushort Reserved; + public ushort SubstituteNameOffset; + public ushort SubstituteNameLength; + public ushort PrintNameOffset; + public ushort PrintNameLength; + public uint Flags; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = maxUnicodePathLength)] + public byte[] PathBuffer; + } +}