9 #include "LogSingleton.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
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. */
25 make_link_2 (char const *exepath
, char const *args
, char const *icon
, char const *lname
)
28 WCHAR widepath
[MAX_PATH
];
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
);
44 #define SYMLINK_COOKIE "!<symlink>"
47 mkmagiccygsymlink (const char *from
, const char *to
)
49 char buf
[strlen (SYMLINK_COOKIE
) + 4096];
51 const size_t len
= strlen (from
) + 7;
54 SECURITY_DESCRIPTOR sd
;
56 SECURITY_ATTRIBUTES sa
= { sizeof (SECURITY_ATTRIBUTES
),
57 nt_sec
.GetPosixPerms (from
, NULL
, NULL
, 0644,
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
)
66 strcpy (buf
, SYMLINK_COOKIE
);
67 strncat (buf
, to
, 4095);
68 if (WriteFile (h
, buf
, strlen (buf
) + 1, &w
, NULL
))
71 SetFileAttributesW (wfrom
, FILE_ATTRIBUTE_SYSTEM
);
79 #ifndef IO_REPARSE_TAG_LX_SYMLINK
80 #define IO_REPARSE_TAG_LX_SYMLINK (0xa000001d)
83 typedef struct _REPARSE_LX_SYMLINK_BUFFER
86 WORD ReparseDataLength
;
89 DWORD FileType
; /* Value is apparently always 2 for symlinks. */
90 char PathBuffer
[1];/* UTF-8 encoded POSIX path
92 Length is ReparseDataLength - sizeof (FileType).
94 } LxSymlinkReparseBuffer
;
95 } REPARSE_LX_SYMLINK_BUFFER
,*PREPARSE_LX_SYMLINK_BUFFER
;
98 mkwslsymlink (const char *from
, const char *to
)
100 /* Construct the reparse path */
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
;
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/ */
123 std::string root
= get_root_dir();
126 lxsymto
.append(1, tolower(root
.c_str()[0]));
128 lxsymto
.append(&(root
[3]));
132 // root dir is UNC path ???
133 lxsymto
.append(root
.c_str());
139 /* Otherwise 'to' is relative to 'from', so leave it alone */
143 /* Create reparse point. */
144 SECURITY_DESCRIPTOR sd
;
146 nt_sec
.GetPosixPerms (from
, NULL
, NULL
, 0644, sd
, acl
);
148 const size_t flen
= strlen (from
) + 7;
150 mklongpath (wfrom
, from
, flen
);
154 UNICODE_STRING ufrom
;
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
,
164 FILE_ATTRIBUTE_NORMAL
,
165 FILE_SHARE_VALID_FLAGS
,
167 FILE_SYNCHRONOUS_IO_NONALERT
| FILE_NON_DIRECTORY_FILE
168 | FILE_OPEN_FOR_BACKUP_INTENT
| FILE_OPEN_REPARSE_POINT
,
170 if (!NT_SUCCESS (status
))
172 Log (LOG_PLAIN
) << "NtCreateFile status " << std::hex
<< status
<< endLog
;
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
;
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
,
187 REPARSE_DATA_BUFFER_HEADER_SIZE
+ rpl
->ReparseDataLength
,
189 if (!NT_SUCCESS (status
))
191 Log (LOG_PLAIN
) << "FSCTL_SET_REPARSE_POINT status " << std::hex
<< status
<< endLog
;
196 return NT_SUCCESS (status
) ? 0 : 1;
200 mknativesymlink (const char *from
, const char *to
)
202 /* Construct the absolute Windows path of 'to' ... */
206 absto
= get_root_dir();
211 /* 'from' is already absolute */
213 /* remove the last pathname component */
214 size_t i
= absto
.rfind('/');
215 if (i
!= std::string::npos
)
217 /* ... and add relative path '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
);
229 bool isdir_known
= FALSE
;
233 OBJECT_ATTRIBUTES attr
;
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
;
247 isdir
= fi
.FileAttributes
& FILE_ATTRIBUTE_DIRECTORY
;
249 Log (LOG_BABBLE
) << "Querying " << absto
<< " isdir is " << isdir
<< endLog
;
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.
270 /* Try to create the native symlink. */
271 const size_t flen
= strlen (from
) + 7;
273 mklongpath (wfrom
, from
, flen
);
276 size_t tlen
= strlen (to
) + 7;
277 wchar_t wrelto
[tlen
];
282 // convert back from nt namespace to win32 file namespace to use with
283 // CreateSymbolicLinkW()
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))
293 mklongrelpath (wrelto
, to
, tlen
);
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 status
= CreateSymbolicLinkW (wfrom
, wto
, flags
);
308 Log (LOG_PLAIN
) << "Linking " << from
<< " to " << to
<< " failed " << std::hex
<< GetLastError() << endLog
;
314 mkcygsymlink (const char *from
, const char *to
)
316 if (symlinkType
== SymlinkTypeWsl
)
318 if (!mkwslsymlink (from
, to
))
322 if (symlinkType
== SymlinkTypeNative
)
324 if (!mknativesymlink (from
, to
))
328 /* fall back to magic symlink, if selected method fails */
329 return mkmagiccygsymlink(from
, to
);
333 FILE_LINK_INFORMATION fli
;
334 WCHAR namebuf
[32768];
339 mkcyghardlink (const char *from
, const char *to
)
341 size_t flen
= strlen (from
) + 7;
342 size_t tlen
= strlen (to
) + 7;
345 mklongpath (wfrom
, from
, flen
);
347 mklongpath (wto
, to
, tlen
);
353 OBJECT_ATTRIBUTES attr
;
356 /* Open the existing file. */
357 RtlInitUnicodeString (&uto
, wto
);
358 InitializeObjectAttributes (&attr
, &uto
, OBJ_CASE_INSENSITIVE
, NULL
, NULL
);
359 status
= NtOpenFile (&fh
, READ_CONTROL
, &attr
, &io
, FILE_SHARE_VALID_FLAGS
,
360 FILE_OPEN_FOR_BACKUP_INTENT
| FILE_OPEN_REPARSE_POINT
);
361 if (!NT_SUCCESS (status
))
363 /* Create from as link to to. */
364 flen
= wcslen (wfrom
) * sizeof (WCHAR
);
365 ULONG size
= sizeof (FILE_LINK_INFORMATION
) + flen
;
366 sfli
.fli
.ReplaceIfExists
= TRUE
;
367 sfli
.fli
.RootDirectory
= NULL
;
368 sfli
.fli
.FileNameLength
= flen
;
369 memcpy (sfli
.fli
.FileName
, wfrom
, flen
);
370 status
= NtSetInformationFile (fh
, &io
, &sfli
.fli
, size
, FileLinkInformation
);
372 return NT_SUCCESS (status
) ? 0 : 1;