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 // }