]> cygwin.com Git - cygwin-apps/setup.git/blame - mklink2.cc
Added dpiAwareness element to manifest
[cygwin-apps/setup.git] / mklink2.cc
CommitLineData
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
12SymlinkTypeEnum 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
15doesn'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. */
21IShellLink *sl;
22
51ebb760 23extern "C"
904d24fe 24void
3c054baf 25make_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
46static int
47mkmagiccygsymlink (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
83typedef 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
97static int
98mkwslsymlink (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
199static int
200mknativesymlink (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
325int
326mkcygsymlink (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 344static struct {
d2e8d256
CV
345 FILE_LINK_INFORMATION fli;
346 WCHAR namebuf[32768];
347} sfli;
348
349extern "C"
350int
351mkcyghardlink (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}
This page took 0.183483 seconds and 6 git commands to generate.