@@ -328,6 +328,131 @@ int mingw_core_config(const char *var, const char *value,
328328 return 0 ;
329329}
330330
331+ enum phantom_symlink_result {
332+ PHANTOM_SYMLINK_RETRY ,
333+ PHANTOM_SYMLINK_DONE ,
334+ PHANTOM_SYMLINK_DIRECTORY
335+ };
336+
337+ static inline int is_wdir_sep (wchar_t wchar )
338+ {
339+ return wchar == L'/' || wchar == L'\\' ;
340+ }
341+
342+ static const wchar_t * make_relative_to (const wchar_t * path ,
343+ const wchar_t * relative_to , wchar_t * out ,
344+ size_t size )
345+ {
346+ size_t i = wcslen (relative_to ), len ;
347+
348+ /* Is `path` already absolute? */
349+ if (is_wdir_sep (path [0 ]) ||
350+ (iswalpha (path [0 ]) && path [1 ] == L':' && is_wdir_sep (path [2 ])))
351+ return path ;
352+
353+ while (i > 0 && !is_wdir_sep (relative_to [i - 1 ]))
354+ i -- ;
355+
356+ /* Is `relative_to` in the current directory? */
357+ if (!i )
358+ return path ;
359+
360+ len = wcslen (path );
361+ if (i + len + 1 > size ) {
362+ error ("Could not make '%ls' relative to '%ls' (too large)" ,
363+ path , relative_to );
364+ return NULL ;
365+ }
366+
367+ memcpy (out , relative_to , i * sizeof (wchar_t ));
368+ wcscpy (out + i , path );
369+ return out ;
370+ }
371+
372+ /*
373+ * Changes a file symlink to a directory symlink if the target exists and is a
374+ * directory.
375+ */
376+ static enum phantom_symlink_result
377+ process_phantom_symlink (const wchar_t * wtarget , const wchar_t * wlink )
378+ {
379+ HANDLE hnd ;
380+ BY_HANDLE_FILE_INFORMATION fdata ;
381+ wchar_t relative [MAX_LONG_PATH ];
382+ const wchar_t * rel ;
383+
384+ /* check that wlink is still a file symlink */
385+ if ((GetFileAttributesW (wlink )
386+ & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY ))
387+ != FILE_ATTRIBUTE_REPARSE_POINT )
388+ return PHANTOM_SYMLINK_DONE ;
389+
390+ /* make it relative, if necessary */
391+ rel = make_relative_to (wtarget , wlink , relative , ARRAY_SIZE (relative ));
392+ if (!rel )
393+ return PHANTOM_SYMLINK_DONE ;
394+
395+ /* let Windows resolve the link by opening it */
396+ hnd = CreateFileW (rel , 0 ,
397+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE , NULL ,
398+ OPEN_EXISTING , FILE_FLAG_BACKUP_SEMANTICS , NULL );
399+ if (hnd == INVALID_HANDLE_VALUE ) {
400+ errno = err_win_to_posix (GetLastError ());
401+ return PHANTOM_SYMLINK_RETRY ;
402+ }
403+
404+ if (!GetFileInformationByHandle (hnd , & fdata )) {
405+ errno = err_win_to_posix (GetLastError ());
406+ CloseHandle (hnd );
407+ return PHANTOM_SYMLINK_RETRY ;
408+ }
409+ CloseHandle (hnd );
410+
411+ /* if target exists and is a file, we're done */
412+ if (!(fdata .dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ))
413+ return PHANTOM_SYMLINK_DONE ;
414+
415+ /* otherwise recreate the symlink with directory flag */
416+ if (DeleteFileW (wlink ) && CreateSymbolicLinkW (wlink , wtarget , 1 ))
417+ return PHANTOM_SYMLINK_DIRECTORY ;
418+
419+ errno = err_win_to_posix (GetLastError ());
420+ return PHANTOM_SYMLINK_RETRY ;
421+ }
422+
423+ /* keep track of newly created symlinks to non-existing targets */
424+ struct phantom_symlink_info {
425+ struct phantom_symlink_info * next ;
426+ wchar_t * wlink ;
427+ wchar_t * wtarget ;
428+ };
429+
430+ static struct phantom_symlink_info * phantom_symlinks = NULL ;
431+ static CRITICAL_SECTION phantom_symlinks_cs ;
432+
433+ static void process_phantom_symlinks (void )
434+ {
435+ struct phantom_symlink_info * current , * * psi ;
436+ EnterCriticalSection (& phantom_symlinks_cs );
437+ /* process phantom symlinks list */
438+ psi = & phantom_symlinks ;
439+ while ((current = * psi )) {
440+ enum phantom_symlink_result result = process_phantom_symlink (
441+ current -> wtarget , current -> wlink );
442+ if (result == PHANTOM_SYMLINK_RETRY ) {
443+ psi = & current -> next ;
444+ } else {
445+ /* symlink was processed, remove from list */
446+ * psi = current -> next ;
447+ free (current );
448+ /* if symlink was a directory, start over */
449+ if (result == PHANTOM_SYMLINK_DIRECTORY )
450+ psi = & phantom_symlinks ;
451+ }
452+ }
453+ LeaveCriticalSection (& phantom_symlinks_cs );
454+ }
455+
331456/* Normalizes NT paths as returned by some low-level APIs. */
332457static wchar_t * normalize_ntpath (wchar_t * wbuf )
333458{
@@ -511,6 +636,8 @@ int mingw_mkdir(const char *path, int mode)
511636 return -1 ;
512637
513638 ret = _wmkdir (wpath );
639+ if (!ret )
640+ process_phantom_symlinks ();
514641 if (!ret && needs_hiding (path ))
515642 return set_hidden_flag (wpath , 1 );
516643 return ret ;
@@ -2760,6 +2887,42 @@ int symlink(const char *target, const char *link)
27602887 errno = err_win_to_posix (GetLastError ());
27612888 return -1 ;
27622889 }
2890+
2891+ /* convert to directory symlink if target exists */
2892+ switch (process_phantom_symlink (wtarget , wlink )) {
2893+ case PHANTOM_SYMLINK_RETRY : {
2894+ /* if target doesn't exist, add to phantom symlinks list */
2895+ wchar_t wfullpath [MAX_LONG_PATH ];
2896+ struct phantom_symlink_info * psi ;
2897+
2898+ /* convert to absolute path to be independent of cwd */
2899+ len = GetFullPathNameW (wlink , MAX_LONG_PATH , wfullpath , NULL );
2900+ if (!len || len >= MAX_LONG_PATH ) {
2901+ errno = err_win_to_posix (GetLastError ());
2902+ return -1 ;
2903+ }
2904+
2905+ /* over-allocate and fill phantom_symlink_info structure */
2906+ psi = xmalloc (sizeof (struct phantom_symlink_info )
2907+ + sizeof (wchar_t ) * (len + wcslen (wtarget ) + 2 ));
2908+ psi -> wlink = (wchar_t * )(psi + 1 );
2909+ wcscpy (psi -> wlink , wfullpath );
2910+ psi -> wtarget = psi -> wlink + len + 1 ;
2911+ wcscpy (psi -> wtarget , wtarget );
2912+
2913+ EnterCriticalSection (& phantom_symlinks_cs );
2914+ psi -> next = phantom_symlinks ;
2915+ phantom_symlinks = psi ;
2916+ LeaveCriticalSection (& phantom_symlinks_cs );
2917+ break ;
2918+ }
2919+ case PHANTOM_SYMLINK_DIRECTORY :
2920+ /* if we created a dir symlink, process other phantom symlinks */
2921+ process_phantom_symlinks ();
2922+ break ;
2923+ default :
2924+ break ;
2925+ }
27632926 return 0 ;
27642927}
27652928
@@ -3676,6 +3839,7 @@ int wmain(int argc, const wchar_t **wargv)
36763839
36773840 /* initialize critical section for waitpid pinfo_t list */
36783841 InitializeCriticalSection (& pinfo_cs );
3842+ InitializeCriticalSection (& phantom_symlinks_cs );
36793843
36803844 /* initialize critical section for fscache */
36813845 InitializeCriticalSection (& fscache_cs );
0 commit comments