Fix format() returning a std::string containing a terminating null
[cygwin-setup.git] / mklink2.cc
blob6e7a0020d9ecbdbbb61b86f6a560fc19d591febd
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 wto[tlen];
278 if (to[0] == '/')
280 absto = get_root_dir();
281 absto.append(to);
282 mklongpath (wto, to, tlen);
283 wto[1] = '?';
285 else
287 mklongrelpath (wto, to, tlen);
290 DWORD flags = isdir ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0;
291 /* Windows 10 1703 and later allow unprivileged symlink creation when
292 'Developer Mode' is on.*/
293 VersionInfo v = GetVer();
294 if ((v.major() > 10) ||
295 ((v.major() == 10) && (v.buildNumber() >= 15063)))
296 flags |= SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
298 status = CreateSymbolicLinkW (wfrom, wto, flags);
300 if (!status)
301 Log (LOG_PLAIN) << "Linking " << from << " to " << to << " failed " << std::hex << GetLastError() << endLog;
303 return !status;
307 mkcygsymlink (const char *from, const char *to)
309 if (symlinkType == SymlinkTypeWsl)
311 if (!mkwslsymlink (from, to))
312 return 0;
315 if (symlinkType == SymlinkTypeNative)
317 if (!mknativesymlink (from, to))
318 return 0;
321 /* fall back to magic symlink, if selected method fails */
322 return mkmagiccygsymlink(from, to);
325 static struct {
326 FILE_LINK_INFORMATION fli;
327 WCHAR namebuf[32768];
328 } sfli;
330 extern "C"
332 mkcyghardlink (const char *from, const char *to)
334 size_t flen = strlen (from) + 7;
335 size_t tlen = strlen (to) + 7;
336 wchar_t wfrom[flen];
337 wchar_t wto[tlen];
338 mklongpath (wfrom, from, flen);
339 wfrom[1] = '?';
340 mklongpath (wto, to, tlen);
341 wto[1] = '?';
343 HANDLE fh;
344 NTSTATUS status;
345 UNICODE_STRING uto;
346 OBJECT_ATTRIBUTES attr;
347 IO_STATUS_BLOCK io;
349 /* Open the existing file. */
350 RtlInitUnicodeString (&uto, wto);
351 InitializeObjectAttributes (&attr, &uto, OBJ_CASE_INSENSITIVE, NULL, NULL);
352 status = NtOpenFile (&fh, READ_CONTROL, &attr, &io, FILE_SHARE_VALID_FLAGS,
353 FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT);
354 if (!NT_SUCCESS (status))
355 return 1;
356 /* Create from as link to to. */
357 flen = wcslen (wfrom) * sizeof (WCHAR);
358 ULONG size = sizeof (FILE_LINK_INFORMATION) + flen;
359 sfli.fli.ReplaceIfExists = TRUE;
360 sfli.fli.RootDirectory = NULL;
361 sfli.fli.FileNameLength = flen;
362 memcpy (sfli.fli.FileName, wfrom, flen);
363 status = NtSetInformationFile (fh, &io, &sfli.fli, size, FileLinkInformation);
364 NtClose (fh);
365 return NT_SUCCESS (status) ? 0 : 1;