@@ -285,6 +285,131 @@ int mingw_core_config(const char *var, const char *value, void *cb)
285285 return 0 ;
286286}
287287
288+ enum phantom_symlink_result {
289+ PHANTOM_SYMLINK_RETRY ,
290+ PHANTOM_SYMLINK_DONE ,
291+ PHANTOM_SYMLINK_DIRECTORY
292+ };
293+
294+ static inline int is_wdir_sep (wchar_t wchar )
295+ {
296+ return wchar == L'/' || wchar == L'\\' ;
297+ }
298+
299+ static const wchar_t * make_relative_to (const wchar_t * path ,
300+ const wchar_t * relative_to , wchar_t * out ,
301+ size_t size )
302+ {
303+ size_t i = wcslen (relative_to ), len ;
304+
305+ /* Is `path` already absolute? */
306+ if (is_wdir_sep (path [0 ]) ||
307+ (iswalpha (path [0 ]) && path [1 ] == L':' && is_wdir_sep (path [2 ])))
308+ return path ;
309+
310+ while (i > 0 && !is_wdir_sep (relative_to [i - 1 ]))
311+ i -- ;
312+
313+ /* Is `relative_to` in the current directory? */
314+ if (!i )
315+ return path ;
316+
317+ len = wcslen (path );
318+ if (i + len + 1 > size ) {
319+ error ("Could not make '%S' relative to '%S' (too large)" ,
320+ path , relative_to );
321+ return NULL ;
322+ }
323+
324+ memcpy (out , relative_to , i * sizeof (wchar_t ));
325+ wcscpy (out + i , path );
326+ return out ;
327+ }
328+
329+ /*
330+ * Changes a file symlink to a directory symlink if the target exists and is a
331+ * directory.
332+ */
333+ static enum phantom_symlink_result
334+ process_phantom_symlink (const wchar_t * wtarget , const wchar_t * wlink )
335+ {
336+ HANDLE hnd ;
337+ BY_HANDLE_FILE_INFORMATION fdata ;
338+ wchar_t relative [MAX_LONG_PATH ];
339+ const wchar_t * rel ;
340+
341+ /* check that wlink is still a file symlink */
342+ if ((GetFileAttributesW (wlink )
343+ & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY ))
344+ != FILE_ATTRIBUTE_REPARSE_POINT )
345+ return PHANTOM_SYMLINK_DONE ;
346+
347+ /* make it relative, if necessary */
348+ rel = make_relative_to (wtarget , wlink , relative , ARRAY_SIZE (relative ));
349+ if (!rel )
350+ return PHANTOM_SYMLINK_DONE ;
351+
352+ /* let Windows resolve the link by opening it */
353+ hnd = CreateFileW (rel , 0 ,
354+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE , NULL ,
355+ OPEN_EXISTING , FILE_FLAG_BACKUP_SEMANTICS , NULL );
356+ if (hnd == INVALID_HANDLE_VALUE ) {
357+ errno = err_win_to_posix (GetLastError ());
358+ return PHANTOM_SYMLINK_RETRY ;
359+ }
360+
361+ if (!GetFileInformationByHandle (hnd , & fdata )) {
362+ errno = err_win_to_posix (GetLastError ());
363+ CloseHandle (hnd );
364+ return PHANTOM_SYMLINK_RETRY ;
365+ }
366+ CloseHandle (hnd );
367+
368+ /* if target exists and is a file, we're done */
369+ if (!(fdata .dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ))
370+ return PHANTOM_SYMLINK_DONE ;
371+
372+ /* otherwise recreate the symlink with directory flag */
373+ if (DeleteFileW (wlink ) && CreateSymbolicLinkW (wlink , wtarget , 1 ))
374+ return PHANTOM_SYMLINK_DIRECTORY ;
375+
376+ errno = err_win_to_posix (GetLastError ());
377+ return PHANTOM_SYMLINK_RETRY ;
378+ }
379+
380+ /* keep track of newly created symlinks to non-existing targets */
381+ struct phantom_symlink_info {
382+ struct phantom_symlink_info * next ;
383+ wchar_t * wlink ;
384+ wchar_t * wtarget ;
385+ };
386+
387+ static struct phantom_symlink_info * phantom_symlinks = NULL ;
388+ static CRITICAL_SECTION phantom_symlinks_cs ;
389+
390+ static void process_phantom_symlinks (void )
391+ {
392+ struct phantom_symlink_info * current , * * psi ;
393+ EnterCriticalSection (& phantom_symlinks_cs );
394+ /* process phantom symlinks list */
395+ psi = & phantom_symlinks ;
396+ while ((current = * psi )) {
397+ enum phantom_symlink_result result = process_phantom_symlink (
398+ current -> wtarget , current -> wlink );
399+ if (result == PHANTOM_SYMLINK_RETRY ) {
400+ psi = & current -> next ;
401+ } else {
402+ /* symlink was processed, remove from list */
403+ * psi = current -> next ;
404+ free (current );
405+ /* if symlink was a directory, start over */
406+ if (result == PHANTOM_SYMLINK_DIRECTORY )
407+ psi = & phantom_symlinks ;
408+ }
409+ }
410+ LeaveCriticalSection (& phantom_symlinks_cs );
411+ }
412+
288413/* Normalizes NT paths as returned by some low-level APIs. */
289414static wchar_t * normalize_ntpath (wchar_t * wbuf )
290415{
@@ -434,6 +559,8 @@ int mingw_mkdir(const char *path, int mode)
434559 return -1 ;
435560
436561 ret = _wmkdir (wpath );
562+ if (!ret )
563+ process_phantom_symlinks ();
437564 if (!ret && needs_hiding (path ))
438565 return set_hidden_flag (wpath , 1 );
439566 return ret ;
@@ -2237,6 +2364,42 @@ int symlink(const char *target, const char *link)
22372364 errno = err_win_to_posix (GetLastError ());
22382365 return -1 ;
22392366 }
2367+
2368+ /* convert to directory symlink if target exists */
2369+ switch (process_phantom_symlink (wtarget , wlink )) {
2370+ case PHANTOM_SYMLINK_RETRY : {
2371+ /* if target doesn't exist, add to phantom symlinks list */
2372+ wchar_t wfullpath [MAX_LONG_PATH ];
2373+ struct phantom_symlink_info * psi ;
2374+
2375+ /* convert to absolute path to be independent of cwd */
2376+ len = GetFullPathNameW (wlink , MAX_LONG_PATH , wfullpath , NULL );
2377+ if (!len || len >= MAX_LONG_PATH ) {
2378+ errno = err_win_to_posix (GetLastError ());
2379+ return -1 ;
2380+ }
2381+
2382+ /* over-allocate and fill phantom_symlink_info structure */
2383+ psi = xmalloc (sizeof (struct phantom_symlink_info )
2384+ + sizeof (wchar_t ) * (len + wcslen (wtarget ) + 2 ));
2385+ psi -> wlink = (wchar_t * )(psi + 1 );
2386+ wcscpy (psi -> wlink , wfullpath );
2387+ psi -> wtarget = psi -> wlink + len + 1 ;
2388+ wcscpy (psi -> wtarget , wtarget );
2389+
2390+ EnterCriticalSection (& phantom_symlinks_cs );
2391+ psi -> next = phantom_symlinks ;
2392+ phantom_symlinks = psi ;
2393+ LeaveCriticalSection (& phantom_symlinks_cs );
2394+ break ;
2395+ }
2396+ case PHANTOM_SYMLINK_DIRECTORY :
2397+ /* if we created a dir symlink, process other phantom symlinks */
2398+ process_phantom_symlinks ();
2399+ break ;
2400+ default :
2401+ break ;
2402+ }
22402403 return 0 ;
22412404}
22422405
@@ -2748,6 +2911,7 @@ int wmain(int argc, const wchar_t **wargv)
27482911
27492912 /* initialize critical section for waitpid pinfo_t list */
27502913 InitializeCriticalSection (& pinfo_cs );
2914+ InitializeCriticalSection (& phantom_symlinks_cs );
27512915
27522916 /* set up default file mode and file modes for stdin/out/err */
27532917 _fmode = _O_BINARY ;
0 commit comments