@@ -337,6 +337,126 @@ static inline int is_wdir_sep(wchar_t wchar)
337337 return wchar == L'/' || wchar == L'\\' ;
338338}
339339
340+ static const wchar_t * make_relative_to (const wchar_t * path ,
341+ const wchar_t * relative_to , wchar_t * out ,
342+ size_t size )
343+ {
344+ size_t i = wcslen (relative_to ), len ;
345+
346+ /* Is `path` already absolute? */
347+ if (is_wdir_sep (path [0 ]) ||
348+ (iswalpha (path [0 ]) && path [1 ] == L':' && is_wdir_sep (path [2 ])))
349+ return path ;
350+
351+ while (i > 0 && !is_wdir_sep (relative_to [i - 1 ]))
352+ i -- ;
353+
354+ /* Is `relative_to` in the current directory? */
355+ if (!i )
356+ return path ;
357+
358+ len = wcslen (path );
359+ if (i + len + 1 > size ) {
360+ error ("Could not make '%ls' relative to '%ls' (too large)" ,
361+ path , relative_to );
362+ return NULL ;
363+ }
364+
365+ memcpy (out , relative_to , i * sizeof (wchar_t ));
366+ wcscpy (out + i , path );
367+ return out ;
368+ }
369+
370+ enum phantom_symlink_result {
371+ PHANTOM_SYMLINK_RETRY ,
372+ PHANTOM_SYMLINK_DONE ,
373+ PHANTOM_SYMLINK_DIRECTORY
374+ };
375+
376+ /*
377+ * Changes a file symlink to a directory symlink if the target exists and is a
378+ * directory.
379+ */
380+ static enum phantom_symlink_result
381+ process_phantom_symlink (const wchar_t * wtarget , const wchar_t * wlink )
382+ {
383+ HANDLE hnd ;
384+ BY_HANDLE_FILE_INFORMATION fdata ;
385+ wchar_t relative [MAX_LONG_PATH ];
386+ const wchar_t * rel ;
387+
388+ /* check that wlink is still a file symlink */
389+ if ((GetFileAttributesW (wlink )
390+ & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY ))
391+ != FILE_ATTRIBUTE_REPARSE_POINT )
392+ return PHANTOM_SYMLINK_DONE ;
393+
394+ /* make it relative, if necessary */
395+ rel = make_relative_to (wtarget , wlink , relative , ARRAY_SIZE (relative ));
396+ if (!rel )
397+ return PHANTOM_SYMLINK_DONE ;
398+
399+ /* let Windows resolve the link by opening it */
400+ hnd = CreateFileW (rel , 0 ,
401+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE , NULL ,
402+ OPEN_EXISTING , FILE_FLAG_BACKUP_SEMANTICS , NULL );
403+ if (hnd == INVALID_HANDLE_VALUE ) {
404+ errno = err_win_to_posix (GetLastError ());
405+ return PHANTOM_SYMLINK_RETRY ;
406+ }
407+
408+ if (!GetFileInformationByHandle (hnd , & fdata )) {
409+ errno = err_win_to_posix (GetLastError ());
410+ CloseHandle (hnd );
411+ return PHANTOM_SYMLINK_RETRY ;
412+ }
413+ CloseHandle (hnd );
414+
415+ /* if target exists and is a file, we're done */
416+ if (!(fdata .dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ))
417+ return PHANTOM_SYMLINK_DONE ;
418+
419+ /* otherwise recreate the symlink with directory flag */
420+ if (DeleteFileW (wlink ) && CreateSymbolicLinkW (wlink , wtarget , 1 ))
421+ return PHANTOM_SYMLINK_DIRECTORY ;
422+
423+ errno = err_win_to_posix (GetLastError ());
424+ return PHANTOM_SYMLINK_RETRY ;
425+ }
426+
427+ /* keep track of newly created symlinks to non-existing targets */
428+ struct phantom_symlink_info {
429+ struct phantom_symlink_info * next ;
430+ wchar_t * wlink ;
431+ wchar_t * wtarget ;
432+ };
433+
434+ static struct phantom_symlink_info * phantom_symlinks = NULL ;
435+ static CRITICAL_SECTION phantom_symlinks_cs ;
436+
437+ static void process_phantom_symlinks (void )
438+ {
439+ struct phantom_symlink_info * current , * * psi ;
440+ EnterCriticalSection (& phantom_symlinks_cs );
441+ /* process phantom symlinks list */
442+ psi = & phantom_symlinks ;
443+ while ((current = * psi )) {
444+ enum phantom_symlink_result result = process_phantom_symlink (
445+ current -> wtarget , current -> wlink );
446+ if (result == PHANTOM_SYMLINK_RETRY ) {
447+ psi = & current -> next ;
448+ } else {
449+ /* symlink was processed, remove from list */
450+ * psi = current -> next ;
451+ free (current );
452+ /* if symlink was a directory, start over */
453+ if (result == PHANTOM_SYMLINK_DIRECTORY )
454+ psi = & phantom_symlinks ;
455+ }
456+ }
457+ LeaveCriticalSection (& phantom_symlinks_cs );
458+ }
459+
340460/* Normalizes NT paths as returned by some low-level APIs. */
341461static wchar_t * normalize_ntpath (wchar_t * wbuf )
342462{
@@ -520,6 +640,8 @@ int mingw_mkdir(const char *path, int mode UNUSED)
520640 return -1 ;
521641
522642 ret = _wmkdir (wpath );
643+ if (!ret )
644+ process_phantom_symlinks ();
523645 if (!ret && needs_hiding (path ))
524646 return set_hidden_flag (wpath , 1 );
525647 return ret ;
@@ -2785,6 +2907,42 @@ int symlink(const char *target, const char *link)
27852907 errno = err_win_to_posix (GetLastError ());
27862908 return -1 ;
27872909 }
2910+
2911+ /* convert to directory symlink if target exists */
2912+ switch (process_phantom_symlink (wtarget , wlink )) {
2913+ case PHANTOM_SYMLINK_RETRY : {
2914+ /* if target doesn't exist, add to phantom symlinks list */
2915+ wchar_t wfullpath [MAX_LONG_PATH ];
2916+ struct phantom_symlink_info * psi ;
2917+
2918+ /* convert to absolute path to be independent of cwd */
2919+ len = GetFullPathNameW (wlink , MAX_LONG_PATH , wfullpath , NULL );
2920+ if (!len || len >= MAX_LONG_PATH ) {
2921+ errno = err_win_to_posix (GetLastError ());
2922+ return -1 ;
2923+ }
2924+
2925+ /* over-allocate and fill phantom_symlink_info structure */
2926+ psi = xmalloc (sizeof (struct phantom_symlink_info )
2927+ + sizeof (wchar_t ) * (len + wcslen (wtarget ) + 2 ));
2928+ psi -> wlink = (wchar_t * )(psi + 1 );
2929+ wcscpy (psi -> wlink , wfullpath );
2930+ psi -> wtarget = psi -> wlink + len + 1 ;
2931+ wcscpy (psi -> wtarget , wtarget );
2932+
2933+ EnterCriticalSection (& phantom_symlinks_cs );
2934+ psi -> next = phantom_symlinks ;
2935+ phantom_symlinks = psi ;
2936+ LeaveCriticalSection (& phantom_symlinks_cs );
2937+ break ;
2938+ }
2939+ case PHANTOM_SYMLINK_DIRECTORY :
2940+ /* if we created a dir symlink, process other phantom symlinks */
2941+ process_phantom_symlinks ();
2942+ break ;
2943+ default :
2944+ break ;
2945+ }
27882946 return 0 ;
27892947}
27902948
@@ -3745,6 +3903,7 @@ int wmain(int argc, const wchar_t **wargv)
37453903
37463904 /* initialize critical section for waitpid pinfo_t list */
37473905 InitializeCriticalSection (& pinfo_cs );
3906+ InitializeCriticalSection (& phantom_symlinks_cs );
37483907
37493908 /* initialize critical section for fscache */
37503909 InitializeCriticalSection (& fscache_cs );
0 commit comments