]>
Commit | Line | Data |
---|---|---|
b24c88b3 | 1 | #include <stdlib.h> |
d2e8d256 | 2 | #include <wchar.h> |
904d24fe | 3 | #include "win32.h" |
d2e8d256 | 4 | #include "ntdll.h" |
904d24fe | 5 | #include "shlobj.h" |
b24c88b3 | 6 | #include "mklink2.h" |
2f0315ad | 7 | #include "filemanip.h" |
93b295d6 JT |
8 | #include "winioctl.h" |
9 | #include "LogSingleton.h" | |
10 | #include "mount.h" | |
11 | ||
12 | SymlinkTypeEnum symlinkType = SymlinkTypeMagic; // default to historical behaviour | |
904d24fe DD |
13 | |
14 | /* This part of the code must be in C because the C++ interface to COM | |
15 | doesn't work. */ | |
16 | ||
e36b0f1e CV |
17 | /* Initialized in WinMain. This is required under Windows 7. If |
18 | CoCreateInstance gets called from here, it fails to create the | |
19 | instance with an undocumented error code 0x80110474. | |
20 | FIXME: I have no idea why this happens. */ | |
21 | IShellLink *sl; | |
22 | ||
51ebb760 | 23 | extern "C" |
904d24fe | 24 | void |
3c054baf | 25 | make_link_2 (char const *exepath, char const *args, char const *icon, char const *lname) |
904d24fe | 26 | { |
904d24fe | 27 | IPersistFile *pf; |
4875ac88 | 28 | WCHAR widepath[MAX_PATH]; |
e36b0f1e CV |
29 | if (sl) |
30 | { | |
c0e55e84 | 31 | sl->QueryInterface (IID_IPersistFile, (void **) &pf); |
904d24fe | 32 | |
c0e55e84 YS |
33 | sl->SetPath (exepath); |
34 | sl->SetArguments (args); | |
35 | sl->SetIconLocation (icon, 0); | |
904d24fe | 36 | |
e36b0f1e | 37 | MultiByteToWideChar (CP_ACP, 0, lname, -1, widepath, MAX_PATH); |
c0e55e84 | 38 | pf->Save (widepath, TRUE); |
904d24fe | 39 | |
c0e55e84 | 40 | pf->Release (); |
e36b0f1e | 41 | } |
904d24fe | 42 | } |
b24c88b3 RC |
43 | |
44 | #define SYMLINK_COOKIE "!<symlink>" | |
45 | ||
93b295d6 JT |
46 | static int |
47 | mkmagiccygsymlink (const char *from, const char *to) | |
2f0315ad | 48 | { |
770e3aed | 49 | char buf[strlen (SYMLINK_COOKIE) + 4096]; |
2f0315ad CV |
50 | unsigned long w; |
51 | const size_t len = strlen (from) + 7; | |
52 | WCHAR wfrom[len]; | |
21aa9a8b | 53 | HANDLE h; |
26922cd2 CV |
54 | SECURITY_DESCRIPTOR sd; |
55 | acl_t acl; | |
56 | SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), | |
57 | nt_sec.GetPosixPerms (from, NULL, NULL, 0644, | |
58 | sd, acl), | |
59 | FALSE }; | |
2f0315ad CV |
60 | |
61 | mklongpath (wfrom, from, len); | |
26922cd2 | 62 | h = CreateFileW (wfrom, GENERIC_WRITE, 0, &sa, CREATE_NEW, |
21aa9a8b | 63 | FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, 0); |
2f0315ad | 64 | if (h == INVALID_HANDLE_VALUE) |
26922cd2 | 65 | return 1; |
2f0315ad | 66 | strcpy (buf, SYMLINK_COOKIE); |
770e3aed | 67 | strncat (buf, to, 4095); |
2f0315ad CV |
68 | if (WriteFile (h, buf, strlen (buf) + 1, &w, NULL)) |
69 | { | |
70 | CloseHandle (h); | |
71 | SetFileAttributesW (wfrom, FILE_ATTRIBUTE_SYSTEM); | |
72 | return 0; | |
73 | } | |
74 | CloseHandle (h); | |
75 | DeleteFileW (wfrom); | |
76 | return 1; | |
77 | } | |
78 | ||
93b295d6 JT |
79 | #ifndef IO_REPARSE_TAG_LX_SYMLINK |
80 | #define IO_REPARSE_TAG_LX_SYMLINK (0xa000001d) | |
81 | #endif | |
82 | ||
83 | typedef struct _REPARSE_LX_SYMLINK_BUFFER | |
84 | { | |
85 | DWORD ReparseTag; | |
86 | WORD ReparseDataLength; | |
87 | WORD Reserved; | |
88 | struct { | |
89 | DWORD FileType; /* Value is apparently always 2 for symlinks. */ | |
90 | char PathBuffer[1];/* UTF-8 encoded POSIX path | |
91 | Isn't \0 terminated. | |
92 | Length is ReparseDataLength - sizeof (FileType). | |
93 | */ | |
94 | } LxSymlinkReparseBuffer; | |
95 | } REPARSE_LX_SYMLINK_BUFFER,*PREPARSE_LX_SYMLINK_BUFFER; | |
96 | ||
97 | static int | |
98 | mkwslsymlink (const char *from, const char *to) | |
99 | { | |
100 | /* Construct the reparse path */ | |
101 | std::string lxsymto; | |
102 | if (to[0] == '/') | |
103 | { | |
104 | /* If 'to' is absolute and starts with '/cygdrive' or /proc/cygdrive', | |
105 | this is a problem because: (i) the cygdrive prefix might be different, | |
106 | and (ii) the target drive might not exist, on the install system. | |
107 | ||
108 | Because of these problems, we don't expect any install packages to have | |
109 | links like that (they should instead be created by post-install | |
110 | scripts), but fail if they do. | |
111 | */ | |
112 | if ((strncmp(to, "/cygdrive", 9) == 0) || | |
113 | (strncmp(to, "/proc/cygdrive", 14) == 0)) | |
114 | { | |
115 | Log (LOG_PLAIN) << "Refusing to create WSL symlink to" << to << " as it starts with /cygdrive" << endLog; | |
116 | return 1; | |
117 | } | |
118 | ||
119 | /* Otherwise, we convert the absolute path 'to' into a form a WSL | |
120 | compatible form, constructed from the '/mnt' prefix and the cygwin root | |
121 | directory e.g. /mnt/c/cygwin64/ */ | |
122 | lxsymto = "/mnt/"; | |
123 | std::string root = get_root_dir(); | |
124 | if (root[1] == ':') | |
125 | { | |
126 | lxsymto.append(1, tolower(root.c_str()[0])); | |
127 | lxsymto.append("/"); | |
128 | lxsymto.append(&(root[3])); | |
129 | } | |
130 | else | |
131 | { | |
132 | // root dir is UNC path ??? | |
133 | lxsymto.append(root.c_str()); | |
134 | } | |
135 | lxsymto.append(to); | |
136 | } | |
137 | else | |
138 | { | |
139 | /* Otherwise 'to' is relative to 'from', so leave it alone */ | |
140 | lxsymto = to; | |
141 | } | |
142 | ||
143 | /* Create reparse point. */ | |
144 | SECURITY_DESCRIPTOR sd; | |
145 | acl_t acl; | |
146 | nt_sec.GetPosixPerms (from, NULL, NULL, 0644, sd, acl); | |
147 | ||
148 | const size_t flen = strlen (from) + 7; | |
149 | WCHAR wfrom[flen]; | |
150 | mklongpath (wfrom, from, flen); | |
151 | wfrom[1] = '?'; | |
152 | ||
153 | HANDLE fh; | |
154 | UNICODE_STRING ufrom; | |
155 | IO_STATUS_BLOCK io; | |
156 | OBJECT_ATTRIBUTES attr; | |
157 | RtlInitUnicodeString (&ufrom, wfrom); | |
158 | InitializeObjectAttributes (&attr, &ufrom, OBJ_CASE_INSENSITIVE, NULL, &sd); | |
159 | NTSTATUS status = NtCreateFile (&fh, | |
160 | DELETE | FILE_GENERIC_WRITE | READ_CONTROL | WRITE_DAC, | |
161 | &attr, | |
162 | &io, | |
163 | NULL, | |
164 | FILE_ATTRIBUTE_NORMAL, | |
165 | FILE_SHARE_VALID_FLAGS, | |
166 | FILE_CREATE, | |
167 | FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE | |
168 | | FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT, | |
169 | NULL, 0); | |
170 | if (!NT_SUCCESS (status)) | |
171 | { | |
172 | Log (LOG_PLAIN) << "NtCreateFile status " << std::hex << status << endLog; | |
173 | return 1; | |
174 | } | |
175 | ||
176 | /* Set content of the reparse point */ | |
177 | size_t tlen = lxsymto.length(); | |
178 | REPARSE_LX_SYMLINK_BUFFER *rpl = (REPARSE_LX_SYMLINK_BUFFER *) new char[sizeof(REPARSE_LX_SYMLINK_BUFFER) + tlen]; | |
179 | rpl->ReparseTag = IO_REPARSE_TAG_LX_SYMLINK; | |
180 | rpl->ReparseDataLength = sizeof (DWORD) + tlen; | |
181 | rpl->Reserved = 0; | |
182 | rpl->LxSymlinkReparseBuffer.FileType = 2; | |
183 | memcpy(rpl->LxSymlinkReparseBuffer.PathBuffer, lxsymto.c_str(), tlen); | |
184 | ||
185 | status = NtFsControlFile (fh, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT, | |
186 | (LPVOID) rpl, | |
187 | REPARSE_DATA_BUFFER_HEADER_SIZE + rpl->ReparseDataLength, | |
188 | NULL, 0); | |
189 | if (!NT_SUCCESS (status)) | |
190 | { | |
191 | Log (LOG_PLAIN) << "FSCTL_SET_REPARSE_POINT status " << std::hex << status << endLog; | |
192 | } | |
193 | ||
04abf40f | 194 | delete[] rpl; |
93b295d6 JT |
195 | NtClose(fh); |
196 | return NT_SUCCESS (status) ? 0 : 1; | |
197 | } | |
198 | ||
c0aead38 JT |
199 | static int |
200 | mknativesymlink (const char *from, const char *to) | |
201 | { | |
202 | /* Construct the absolute Windows path of 'to' ... */ | |
203 | std::string absto; | |
204 | if (to[0] == '/') | |
205 | { | |
206 | absto = get_root_dir(); | |
207 | absto.append(to); | |
208 | } | |
209 | else | |
210 | { | |
211 | /* 'from' is already absolute */ | |
212 | absto.append(from); | |
213 | /* remove the last pathname component */ | |
214 | size_t i = absto.rfind('/'); | |
215 | if (i != std::string::npos) | |
216 | absto.resize(i); | |
217 | /* ... and add relative path 'to'. */ | |
218 | absto.append("/"); | |
219 | absto.append(to); | |
220 | } | |
221 | ||
222 | /* ... so we can discover if it's a file or directory (if it already exists) */ | |
223 | size_t abstlen = strlen (absto.c_str()) + 7; | |
224 | wchar_t wabsto[abstlen]; | |
225 | mklongpath (wabsto, absto.c_str(), abstlen); | |
226 | wabsto[1] = '?'; | |
227 | ||
228 | bool isdir = FALSE; | |
229 | bool isdir_known = FALSE; | |
230 | HANDLE fh; | |
231 | NTSTATUS status; | |
232 | UNICODE_STRING uto; | |
233 | OBJECT_ATTRIBUTES attr; | |
234 | IO_STATUS_BLOCK io; | |
235 | RtlInitUnicodeString (&uto, wabsto); | |
236 | InitializeObjectAttributes (&attr, &uto, OBJ_CASE_INSENSITIVE, NULL, NULL); | |
237 | status = NtOpenFile (&fh, FILE_READ_ATTRIBUTES, &attr, &io, FILE_SHARE_VALID_FLAGS, | |
238 | FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT); | |
239 | if (NT_SUCCESS (status)) | |
240 | { | |
241 | FILE_BASIC_INFORMATION fi; | |
242 | status = NtQueryInformationFile(fh, &io, &fi, sizeof(fi), FileBasicInformation); | |
243 | if (!NT_SUCCESS (status)) | |
244 | Log (LOG_BABBLE) << "Querying " << absto << " failed " << std::hex << status << endLog; | |
245 | else | |
246 | { | |
247 | isdir = fi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY; | |
248 | isdir_known = TRUE; | |
249 | Log (LOG_BABBLE) << "Querying " << absto << " isdir is " << isdir << endLog; | |
250 | } | |
251 | NtClose(fh); | |
252 | } | |
253 | else | |
254 | { | |
255 | Log (LOG_BABBLE) << "Opening " << absto << " failed " << std::hex << status << endLog; | |
256 | } | |
257 | ||
258 | /* | |
259 | Fail, if we failed to determine if the symlink target is a directory | |
260 | (probably because it doesn't exist (yet)) | |
261 | ||
262 | (We could guess that it's a file, since that works for Cygwin (and WSL), | |
263 | which don't care if the directory flag in the symlink is wrong (when the | |
264 | target comes into existence), but native tools will fail. | |
265 | */ | |
266 | ||
267 | if (!isdir_known) | |
268 | return 1; | |
269 | ||
270 | /* Try to create the native symlink. */ | |
271 | const size_t flen = strlen (from) + 7; | |
272 | WCHAR wfrom[flen]; | |
273 | mklongpath (wfrom, from, flen); | |
274 | wfrom[1] = '?'; | |
275 | ||
276 | size_t tlen = strlen (to) + 7; | |
3eff9155 JT |
277 | wchar_t wrelto[tlen]; |
278 | wchar_t *wto; | |
c0aead38 JT |
279 | if (to[0] == '/') |
280 | { | |
3eff9155 | 281 | wto = wabsto; |
aa80df6c JT |
282 | // convert back from nt namespace to win32 file namespace to use with |
283 | // CreateSymbolicLinkW() | |
284 | wabsto[1] = '\\'; | |
2fd81bdf JT |
285 | // Some parts of Windows don't correctly handle a win32 file namespace |
286 | // prefix in the symlink target. So, for maximum interoperability, we use | |
287 | // a short path instead, if the target path will be less than MAX_PATH. | |
288 | if (wcslen(wabsto) < (MAX_PATH + 4)) | |
289 | wto = wabsto + 4; | |
c0aead38 JT |
290 | } |
291 | else | |
292 | { | |
3eff9155 JT |
293 | mklongrelpath (wrelto, to, tlen); |
294 | wto = wrelto; | |
c0aead38 JT |
295 | } |
296 | ||
297 | DWORD flags = isdir ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0; | |
298 | /* Windows 10 1703 and later allow unprivileged symlink creation when | |
299 | 'Developer Mode' is on.*/ | |
300 | VersionInfo v = GetVer(); | |
301 | if ((v.major() > 10) || | |
302 | ((v.major() == 10) && (v.buildNumber() >= 15063))) | |
303 | flags |= SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; | |
304 | ||
450d4db7 JT |
305 | typedef BOOLEAN (WINAPI *PFNCREATESYMBOLICLINKW)(LPCWSTR, LPCWSTR, DWORD); |
306 | static PFNCREATESYMBOLICLINKW pfnCreateSymbolicLinkW = 0; | |
307 | static bool doOnce = FALSE; | |
308 | ||
309 | if (!doOnce) | |
310 | { | |
311 | doOnce = TRUE; | |
312 | pfnCreateSymbolicLinkW = (PFNCREATESYMBOLICLINKW)GetProcAddress(GetModuleHandle("kernel32"), "CreateSymbolicLinkW"); | |
313 | } | |
314 | ||
315 | status = 0; | |
316 | if (pfnCreateSymbolicLinkW) | |
317 | status = pfnCreateSymbolicLinkW (wfrom, wto, flags); | |
c0aead38 JT |
318 | |
319 | if (!status) | |
320 | Log (LOG_PLAIN) << "Linking " << from << " to " << to << " failed " << std::hex << GetLastError() << endLog; | |
321 | ||
322 | return !status; | |
323 | } | |
324 | ||
93b295d6 JT |
325 | int |
326 | mkcygsymlink (const char *from, const char *to) | |
327 | { | |
328 | if (symlinkType == SymlinkTypeWsl) | |
329 | { | |
330 | if (!mkwslsymlink (from, to)) | |
331 | return 0; | |
332 | } | |
333 | ||
c0aead38 JT |
334 | if (symlinkType == SymlinkTypeNative) |
335 | { | |
336 | if (!mknativesymlink (from, to)) | |
337 | return 0; | |
338 | } | |
339 | ||
93b295d6 JT |
340 | /* fall back to magic symlink, if selected method fails */ |
341 | return mkmagiccygsymlink(from, to); | |
342 | } | |
343 | ||
10b2d051 | 344 | static struct { |
d2e8d256 CV |
345 | FILE_LINK_INFORMATION fli; |
346 | WCHAR namebuf[32768]; | |
347 | } sfli; | |
348 | ||
349 | extern "C" | |
350 | int | |
351 | mkcyghardlink (const char *from, const char *to) | |
352 | { | |
d2e8d256 CV |
353 | size_t flen = strlen (from) + 7; |
354 | size_t tlen = strlen (to) + 7; | |
355 | wchar_t wfrom[flen]; | |
356 | wchar_t wto[tlen]; | |
357 | mklongpath (wfrom, from, flen); | |
358 | wfrom[1] = '?'; | |
359 | mklongpath (wto, to, tlen); | |
360 | wto[1] = '?'; | |
361 | ||
362 | HANDLE fh; | |
363 | NTSTATUS status; | |
364 | UNICODE_STRING uto; | |
365 | OBJECT_ATTRIBUTES attr; | |
366 | IO_STATUS_BLOCK io; | |
367 | ||
368 | /* Open the existing file. */ | |
369 | RtlInitUnicodeString (&uto, wto); | |
370 | InitializeObjectAttributes (&attr, &uto, OBJ_CASE_INSENSITIVE, NULL, NULL); | |
371 | status = NtOpenFile (&fh, READ_CONTROL, &attr, &io, FILE_SHARE_VALID_FLAGS, | |
372 | FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT); | |
373 | if (!NT_SUCCESS (status)) | |
374 | return 1; | |
375 | /* Create from as link to to. */ | |
376 | flen = wcslen (wfrom) * sizeof (WCHAR); | |
377 | ULONG size = sizeof (FILE_LINK_INFORMATION) + flen; | |
378 | sfli.fli.ReplaceIfExists = TRUE; | |
379 | sfli.fli.RootDirectory = NULL; | |
380 | sfli.fli.FileNameLength = flen; | |
381 | memcpy (sfli.fli.FileName, wfrom, flen); | |
382 | status = NtSetInformationFile (fh, &io, &sfli.fli, size, FileLinkInformation); | |
383 | NtClose (fh); | |
384 | return NT_SUCCESS (status) ? 0 : 1; | |
385 | } |