1 module symlinkd.symlink;
2 
3 import std.range;
4 import std.typecons;
5 import std.traits;
6 
7 version (Windows)
8 {
9 	import core.sys.windows.windows;
10 	
11 	import std.internal.cstring : tempCString;
12 
13 	import std.conv : to;
14 	import std.file : FileException;
15 	import std.path : isRooted;
16 	import std.string : startsWith;
17 
18 	import symlinkd.windows;
19 
20 	// TODO: actual support for Unicode (*W) functions?
21 	private enum prefix = `\\?\`;
22 }
23 
24 /// Specifies the type of a symlink's target.
25 /// See_Also: createSymlink
26 enum SymlinkTargetType
27 {
28 	file,
29 	directory,
30 }
31 
32 alias SymlinkCreateUnprivileged = Flag!"SymlinkCreateUnprivileged";
33 
34 /**
35 	Creates a symbolic link.
36 
37 	Params:
38 		target            = The filesystem object to create a link to.
39 		link              = The path to the new symbolic link.
40 		targetType        = Indicates the filesystem object type of `target`. Has no effect on non-Windows.
41 		allowUnprivileged = Windows only.
42 		                    Allows creation of symbolic links without administrative privileges.
43 		                    Developer Mode must be enabled on the system for it to function.
44 
45 	Returns: `true` on success.
46 
47 	See_Also: SymlinkTargetType
48 */
49 bool createSymlink(TargetT, LinkT)(TargetT target, LinkT link, SymlinkTargetType targetType,
50                                    SymlinkCreateUnprivileged allowUnprivileged = SymlinkCreateUnprivileged.no)
51 	if ((isInputRange!TargetT && !isInfinite!TargetT &&
52 	     isSomeChar!(ElementEncodingType!TargetT) || isConvertibleToString!TargetT) &&
53 		(isInputRange!LinkT && !isInfinite!LinkT && isSomeChar!(ElementEncodingType!LinkT) ||
54 		 isConvertibleToString!LinkT))
55 {
56 	version (Posix)
57 	{
58 		import std.file : symlink;
59 		symlink(target, link);
60 		return true;
61 	}
62 	else version (Windows)
63 	{
64 		static if (isConvertibleToString!TargetT || isConvertibleToString!LinkT)
65 		{
66 			import std.meta : staticMap;
67 			alias Types = staticMap!(convertToString, TargetT, LinkT);
68 			return symlink!Types(original, link);
69 		}
70 		else
71 		{
72 			DWORD flags = 0;
73 
74 			if (targetType == SymlinkTargetType.directory)
75 			{
76 				flags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
77 			}
78 
79 			if (allowUnprivileged)
80 			{
81 				flags |= SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
82 			}
83 
84 			// avoid MAX_PATH issues
85 			auto target_ = target.isRooted && !target.startsWith(prefix) ? prefix ~ target : target;
86 			auto link_   = link.isRooted && !link.startsWith(prefix) ? prefix ~ link : link;
87 
88 			auto tz = target_.tempCString();
89 			auto lz = link_.tempCString();
90 
91 			return !!CreateSymbolicLinkA(lz, tz, flags);
92 		}
93 	}
94 	else
95 	{
96 		static assert(false, __PRETTY_FUNCTION__ ~ " is not implemented on your platform!");
97 	}
98 }
99 
100 alias SymlinkStripPrefix = Flag!"SymlinkStripPrefix";
101 
102 /**
103 	Returns the path to a symbolic link's target.
104 
105 	Params:
106 		link        = The path to the symbolic link.
107 		stripPrefix = Windows only. Strips `\\?\` from the path.
108 
109 	Returns:
110 		The link's target.
111 */
112 string readSymlink(R)(R link, SymlinkStripPrefix stripPrefix = SymlinkStripPrefix.yes)
113 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || isConvertibleToString!R)
114 {
115 	version (Posix)
116 	{
117 		import std.file : readLink;
118 		return std.file.readLink(link);
119 	}
120 	else version (Windows)
121 	{
122 		static if (isConvertibleToString!R)
123 		{
124 			return readLink!(convertToString!R)(link);
125 		}
126 		else
127 		{
128 			auto strz = link.tempCString;
129 			auto handle = CreateFileA(strz, FILE_READ_EA, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, null,
130 			                          OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, null);
131 
132 			scope (exit) CloseHandle(handle);
133 
134 			if (handle == INVALID_HANDLE_VALUE)
135 			{
136 				throw new FileException(link, "Unable to open file.");
137 			}
138 
139 			const requiredLength = GetFinalPathNameByHandleA(handle, null, 0, 0);
140 
141 			if (requiredLength < 1)
142 			{
143 				return null;
144 			}
145 
146 			auto buffer = new char[requiredLength + 1];
147 			GetFinalPathNameByHandleA(handle, buffer.ptr, cast(uint)buffer.length, 0);
148 
149 			auto result = to!string(buffer);
150 
151 			if (stripPrefix && result.startsWith(prefix))
152 			{
153 				result = result[prefix.length .. $];
154 			}
155 
156 			return result;
157 		}
158 	}
159 	else
160 	{
161 		static assert(false, __PRETTY_FUNCTION__ ~ " is not implemented on your platform!");
162 	}
163 }
164 
165 // version (symlinkd_aliases)
166 // {
167 	public alias symlink  = createSymlink;
168 	public alias readLink = readSymlink;
169 // }