Skip to content

Commit 6942cae

Browse files
committed
v1.2.8.0
1 parent beadc20 commit 6942cae

13 files changed

Lines changed: 2348 additions & 1709 deletions

Helpers/JunctionManager.cs

Lines changed: 157 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,107 @@
11
using System;
22
using System.Diagnostics;
33
using System.IO;
4+
using System.Runtime.InteropServices;
5+
using Serilog;
46

57
namespace Yellowcake.Helpers;
68

79
public static class JunctionManager
810
{
9-
public static void Create(string junctionPoint, string targetDir, bool overwrite)
11+
public static bool IsSupported => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ||
12+
RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ||
13+
RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
14+
15+
public static void Create(string junctionPoint, string targetDir, bool overwrite = false)
1016
{
1117
if (string.IsNullOrWhiteSpace(junctionPoint) || string.IsNullOrWhiteSpace(targetDir))
1218
throw new ArgumentException("Junction and target paths cannot be empty.");
1319

14-
string normalizedTarget = Path.GetFullPath(targetDir).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
15-
string normalizedJunction = Path.GetFullPath(junctionPoint).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
20+
var normalizedTarget = Path.GetFullPath(targetDir).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
21+
var normalizedJunction = Path.GetFullPath(junctionPoint).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
1622

1723
if (!Directory.Exists(normalizedTarget))
1824
throw new DirectoryNotFoundException($"Target directory does not exist: {normalizedTarget}");
1925

2026
PrepareJunctionPoint(normalizedJunction, overwrite);
21-
2227
EnsureParentDirectoryExists(normalizedJunction);
2328

24-
ExecuteMkLink(normalizedJunction, normalizedTarget);
29+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
30+
{
31+
CreateWindowsJunction(normalizedJunction, normalizedTarget);
32+
}
33+
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
34+
{
35+
CreateUnixSymlink(normalizedJunction, normalizedTarget);
36+
}
37+
else
38+
{
39+
throw new PlatformNotSupportedException("Symbolic links are not supported on this platform.");
40+
}
41+
42+
Log.Information("Created junction/symlink: {Junction} -> {Target}", normalizedJunction, normalizedTarget);
2543
}
2644

2745
public static void Remove(string junctionPoint)
2846
{
29-
if (!Directory.Exists(junctionPoint)) return;
47+
if (!Directory.Exists(junctionPoint) && !File.Exists(junctionPoint)) return;
3048

31-
var di = new DirectoryInfo(junctionPoint);
32-
if (di.Attributes.HasFlag(FileAttributes.ReparsePoint))
49+
try
3350
{
34-
di.Delete();
51+
if (IsSymbolicLink(junctionPoint))
52+
{
53+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
54+
{
55+
var di = new DirectoryInfo(junctionPoint);
56+
di.Delete();
57+
}
58+
else
59+
{
60+
File.Delete(junctionPoint);
61+
}
62+
63+
Log.Information("Removed junction/symlink: {Path}", junctionPoint);
64+
}
65+
else
66+
{
67+
Directory.Delete(junctionPoint, true);
68+
Log.Information("Removed directory: {Path}", junctionPoint);
69+
}
3570
}
36-
else
71+
catch (Exception ex)
72+
{
73+
Log.Error(ex, "Failed to remove junction/symlink: {Path}", junctionPoint);
74+
throw;
75+
}
76+
}
77+
78+
public static bool IsSymbolicLink(string path)
79+
{
80+
try
3781
{
38-
Directory.Delete(junctionPoint, true);
82+
if (Directory.Exists(path))
83+
{
84+
var di = new DirectoryInfo(path);
85+
return di.Attributes.HasFlag(FileAttributes.ReparsePoint);
86+
}
87+
88+
if (File.Exists(path))
89+
{
90+
var fi = new FileInfo(path);
91+
return fi.Attributes.HasFlag(FileAttributes.ReparsePoint);
92+
}
3993
}
94+
catch (Exception ex)
95+
{
96+
Log.Debug(ex, "Error checking if path is symbolic link: {Path}", path);
97+
}
98+
99+
return false;
40100
}
41101

42102
private static void PrepareJunctionPoint(string path, bool overwrite)
43103
{
44-
if (!Directory.Exists(path)) return;
104+
if (!Directory.Exists(path) && !File.Exists(path)) return;
45105

46106
if (!overwrite)
47107
throw new InvalidOperationException($"The junction point already exists: {path}");
@@ -51,19 +111,20 @@ private static void PrepareJunctionPoint(string path, bool overwrite)
51111

52112
private static void EnsureParentDirectoryExists(string path)
53113
{
54-
string? parent = Path.GetDirectoryName(path);
114+
var parent = Path.GetDirectoryName(path);
55115
if (parent != null && !Directory.Exists(parent))
56116
{
57117
Directory.CreateDirectory(parent);
118+
Log.Debug("Created parent directory: {Path}", parent);
58119
}
59120
}
60121

61-
private static void ExecuteMkLink(string junction, string target)
122+
private static void CreateWindowsJunction(string junction, string target)
62123
{
63124
var psi = new ProcessStartInfo
64125
{
65126
FileName = "cmd.exe",
66-
Arguments = $"/c mklink /j \"{junction}\" \"{target}\"",
127+
Arguments = $"/c mklink /J \"{junction}\" \"{target}\"",
67128
WindowStyle = ProcessWindowStyle.Hidden,
68129
CreateNoWindow = true,
69130
UseShellExecute = false,
@@ -74,20 +135,96 @@ private static void ExecuteMkLink(string junction, string target)
74135
try
75136
{
76137
using var process = Process.Start(psi);
77-
if (process == null) throw new Exception("Failed to start the junction creation process.");
138+
if (process == null)
139+
throw new InvalidOperationException("Failed to start the junction creation process.");
78140

79141
process.WaitForExit();
80142

81143
if (process.ExitCode != 0)
82144
{
83-
string error = process.StandardError.ReadToEnd();
84-
if (string.IsNullOrWhiteSpace(error)) error = process.StandardOutput.ReadToEnd();
85-
throw new Exception($"mklink failed with exit code {process.ExitCode}: {error.Trim()}");
145+
var error = process.StandardError.ReadToEnd();
146+
if (string.IsNullOrWhiteSpace(error))
147+
error = process.StandardOutput.ReadToEnd();
148+
149+
throw new InvalidOperationException($"mklink failed with exit code {process.ExitCode}: {error.Trim()}");
86150
}
87151
}
88152
catch (System.ComponentModel.Win32Exception ex)
89153
{
90-
throw new Exception("System error: Unable to execute mklink. Check your permissions.", ex);
154+
throw new InvalidOperationException("Unable to execute mklink. Administrator privileges may be required.", ex);
155+
}
156+
}
157+
158+
private static void CreateUnixSymlink(string symlink, string target)
159+
{
160+
var psi = new ProcessStartInfo
161+
{
162+
FileName = "/bin/ln",
163+
Arguments = $"-s \"{target}\" \"{symlink}\"",
164+
WindowStyle = ProcessWindowStyle.Hidden,
165+
CreateNoWindow = true,
166+
UseShellExecute = false,
167+
RedirectStandardError = true,
168+
RedirectStandardOutput = true
169+
};
170+
171+
try
172+
{
173+
using var process = Process.Start(psi);
174+
if (process == null)
175+
throw new InvalidOperationException("Failed to start the symlink creation process.");
176+
177+
process.WaitForExit();
178+
179+
if (process.ExitCode != 0)
180+
{
181+
var error = process.StandardError.ReadToEnd();
182+
if (string.IsNullOrWhiteSpace(error))
183+
error = process.StandardOutput.ReadToEnd();
184+
185+
throw new InvalidOperationException($"ln failed with exit code {process.ExitCode}: {error.Trim()}");
186+
}
187+
}
188+
catch (System.ComponentModel.Win32Exception ex)
189+
{
190+
throw new InvalidOperationException("Unable to execute ln command. Check permissions.", ex);
191+
}
192+
}
193+
194+
public static string? GetTarget(string junctionPoint)
195+
{
196+
try
197+
{
198+
if (!IsSymbolicLink(junctionPoint)) return null;
199+
200+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
201+
{
202+
return new DirectoryInfo(junctionPoint).LinkTarget;
203+
}
204+
else
205+
{
206+
var psi = new ProcessStartInfo
207+
{
208+
FileName = "/bin/readlink",
209+
Arguments = $"\"{junctionPoint}\"",
210+
UseShellExecute = false,
211+
RedirectStandardOutput = true,
212+
CreateNoWindow = true
213+
};
214+
215+
using var process = Process.Start(psi);
216+
if (process == null) return null;
217+
218+
var output = process.StandardOutput.ReadToEnd().Trim();
219+
process.WaitForExit();
220+
221+
return process.ExitCode == 0 ? output : null;
222+
}
223+
}
224+
catch (Exception ex)
225+
{
226+
Log.Debug(ex, "Failed to get junction target for: {Path}", junctionPoint);
227+
return null;
91228
}
92229
}
93230
}

0 commit comments

Comments
 (0)