Drop setting root_scope as a side-effect of read_mounts()
[cygwin-setup.git] / mklink2.cc
blob7763ad5d5faebff6550c3fb8975d3758fad9a0ce
1 #include <stdlib.h>
2 #include <wchar.h>
3 #include "win32.h"
4 #include "ntdll.h"
5 #include "shlobj.h"
6 #include "mklink2.h"
7 #include "filemanip.h"
8 #include "winioctl.h"
9 #include "LogSingleton.h"
10 #include "mount.h"
12 SymlinkTypeEnum symlinkType = SymlinkTypeMagic; // default to historical behaviour
14 /* This part of the code must be in C because the C++ interface to COM
15 doesn't work. */
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;
23 extern "C"
24 void
25 make_link_2 (char const *exepath, char const *args, char const *icon, char const *lname)
27 IPersistFile *pf;
28 WCHAR widepath[MAX_PATH];
29 if (sl)
31 sl->QueryInterface (IID_IPersistFile, (void **) &pf);
33 sl->SetPath (exepath);
34 sl->SetArguments (args);
35 sl->SetIconLocation (icon, 0);
37 MultiByteToWideChar (CP_ACP, 0, lname, -1, widepath, MAX_PATH);
38 pf->Save (widepath, TRUE);
40 pf->Release ();
44 #define SYMLINK_COOKIE "!<symlink>"
46 static int
47 mkmagiccygsymlink (const char *from, const char *to)
49 char buf[strlen (SYMLINK_COOKIE) + 4096];
50 unsigned long w;
51 const size_t len = strlen (from) + 7;
52 WCHAR wfrom[len];
53 HANDLE h;
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 };
61 mklongpath (wfrom, from, len);
62 h = CreateFileW (wfrom, GENERIC_WRITE, 0, &sa, CREATE_NEW,
63 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, 0);
64 if (h == INVALID_HANDLE_VALUE)
65 return 1;
66 strcpy (buf, SYMLINK_COOKIE);
67 strncat (buf, to, 4095);
68 if (WriteFile (h, buf, strlen (buf) + 1, &w, NULL))
70 CloseHandle (h);
71 SetFileAttributesW (wfrom, FILE_ATTRIBUTE_SYSTEM);
72 return 0;
74 CloseHandle (h);
75 DeleteFileW (wfrom);
76 return 1;
79 #ifndef IO_REPARSE_TAG_LX_SYMLINK
80 #define IO_REPARSE_TAG_LX_SYMLINK (0xa000001d)
81 #endif
83 typedef struct _REPARSE_LX_SYMLINK_BUFFER
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).
94 } LxSymlinkReparseBuffer;
95 } REPARSE_LX_SYMLINK_BUFFER,*PREPARSE_LX_SYMLINK_BUFFER;
97 static int
98 mkwslsymlink (const char *from, const char *to)
100 /* Construct the reparse path */
101 std::string lxsymto;
102 if (to[0] == '/')
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.
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.
112 if ((strncmp(to, "/cygdrive", 9) == 0) ||
113 (strncmp(to, "/proc/cygdrive", 14) == 0))
115 Log (LOG_PLAIN) << "Refusing to create WSL symlink to" << to << " as it starts with /cygdrive" << endLog;
116 return 1;
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] == ':')
126 lxsymto.append(1, tolower(root.c_str()[0]));
127 lxsymto.append("/");
128 lxsymto.append(&(root[3]));
130 else
132 // root dir is UNC path ???
133 lxsymto.append(root.c_str());
135 lxsymto.append(to);
137 else
139 /* Otherwise 'to' is relative to 'from', so leave it alone */
140 lxsymto = to;
143 /* Create reparse point. */
144 SECURITY_DESCRIPTOR sd;
145 acl_t acl;
146 nt_sec.GetPosixPerms (from, NULL, NULL, 0644, sd, acl);
148 const size_t flen = strlen (from) + 7;
149 WCHAR wfrom[flen];
150 mklongpath (wfrom, from, flen);
151 wfrom[1] = '?';
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))
172 Log (LOG_PLAIN) << "NtCreateFile status " << std::hex << status << endLog;
173 return 1;
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);
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))
191 Log (LOG_PLAIN) << "FSCTL_SET_REPARSE_POINT status " << std::hex << status << endLog;
194 delete[] rpl;
195 NtClose(fh);
196 return NT_SUCCESS (status) ? 0 : 1;
199 static int
200 mknativesymlink (const char *from, const char *to)
202 /* Construct the absolute Windows path of 'to' ... */
203 std::string absto;
204 if (to[0] == '/')
206 absto = get_root_dir();
207 absto.append(to);
209 else
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);
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] = '?';
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))
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
247 isdir = fi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY;
248 isdir_known = TRUE;
249 Log (LOG_BABBLE) << "Querying " << absto << " isdir is " << isdir << endLog;
251 NtClose(fh);
253 else
255 Log (LOG_BABBLE) << "Opening " << absto << " failed " << std::hex << status << endLog;
259 Fail, if we failed to determine if the symlink target is a directory
260 (probably because it doesn't exist (yet))
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.
267 if (!isdir_known)
268 return 1;
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] = '?';
276 size_t tlen = strlen (to) + 7;
277 wchar_t wrelto[tlen];
278 wchar_t *wto;
279 if (to[0] == '/')
281 wto = wabsto;
282 // convert back from nt namespace to win32 file namespace to use with
283 // CreateSymbolicLinkW()
284 wabsto[1] = '\\';
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;
291 else
293 mklongrelpath (wrelto, to, tlen);
294 wto = wrelto;
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;
305 typedef BOOLEAN (WINAPI *PFNCREATESYMBOLICLINKW)(LPCWSTR, LPCWSTR, DWORD);
306 static PFNCREATESYMBOLICLINKW pfnCreateSymbolicLinkW = 0;
307 static bool doOnce = FALSE;
309 if (!doOnce)
311 doOnce = TRUE;
312 pfnCreateSymbolicLinkW = (PFNCREATESYMBOLICLINKW)GetProcAddress(GetModuleHandle("kernel32"), "CreateSymbolicLinkW");
315 status = 0;
316 if (pfnCreateSymbolicLinkW)
317 status = pfnCreateSymbolicLinkW (wfrom, wto, flags);
319 if (!status)
320 Log (LOG_PLAIN) << "Linking " << from << " to " << to << " failed " << std::hex << GetLastError() << endLog;
322 return !status;
326 mkcygsymlink (const char *from, const char *to)
328 if (symlinkType == SymlinkTypeWsl)
330 if (!mkwslsymlink (from, to))
331 return 0;
334 if (symlinkType == SymlinkTypeNative)
336 if (!mknativesymlink (from, to))
337 return 0;
340 /* fall back to magic symlink, if selected method fails */
341 return mkmagiccygsymlink(from, to);
344 static struct {
345 FILE_LINK_INFORMATION fli;
346 WCHAR namebuf[32768];
347 } sfli;
349 extern "C"
351 mkcyghardlink (const char *from, const char *to)
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] = '?';
362 HANDLE fh;
363 NTSTATUS status;
364 UNICODE_STRING uto;
365 OBJECT_ATTRIBUTES attr;
366 IO_STATUS_BLOCK io;
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;