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