@@ -338,6 +338,126 @@ static inline int is_wdir_sep(wchar_t wchar)
338338 return wchar == L'/' || wchar == L'\\' ;
339339}
340340
341+ static const wchar_t * make_relative_to (const wchar_t * path ,
342+ const wchar_t * relative_to , wchar_t * out ,
343+ size_t size )
344+ {
345+ size_t i = wcslen (relative_to ), len ;
346+
347+ /* Is `path` already absolute? */
348+ if (is_wdir_sep (path [0 ]) ||
349+ (iswalpha (path [0 ]) && path [1 ] == L':' && is_wdir_sep (path [2 ])))
350+ return path ;
351+
352+ while (i > 0 && !is_wdir_sep (relative_to [i - 1 ]))
353+ i -- ;
354+
355+ /* Is `relative_to` in the current directory? */
356+ if (!i )
357+ return path ;
358+
359+ len = wcslen (path );
360+ if (i + len + 1 > size ) {
361+ error ("Could not make '%ls' relative to '%ls' (too large)" ,
362+ path , relative_to );
363+ return NULL ;
364+ }
365+
366+ memcpy (out , relative_to , i * sizeof (wchar_t ));
367+ wcscpy (out + i , path );
368+ return out ;
369+ }
370+
371+ enum phantom_symlink_result {
372+ PHANTOM_SYMLINK_RETRY ,
373+ PHANTOM_SYMLINK_DONE ,
374+ PHANTOM_SYMLINK_DIRECTORY
375+ };
376+
377+ /*
378+ * Changes a file symlink to a directory symlink if the target exists and is a
379+ * directory.
380+ */
381+ static enum phantom_symlink_result
382+ process_phantom_symlink (const wchar_t * wtarget , const wchar_t * wlink )
383+ {
384+ HANDLE hnd ;
385+ BY_HANDLE_FILE_INFORMATION fdata ;
386+ wchar_t relative [MAX_LONG_PATH ];
387+ const wchar_t * rel ;
388+
389+ /* check that wlink is still a file symlink */
390+ if ((GetFileAttributesW (wlink )
391+ & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY ))
392+ != FILE_ATTRIBUTE_REPARSE_POINT )
393+ return PHANTOM_SYMLINK_DONE ;
394+
395+ /* make it relative, if necessary */
396+ rel = make_relative_to (wtarget , wlink , relative , ARRAY_SIZE (relative ));
397+ if (!rel )
398+ return PHANTOM_SYMLINK_DONE ;
399+
400+ /* let Windows resolve the link by opening it */
401+ hnd = CreateFileW (rel , 0 ,
402+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE , NULL ,
403+ OPEN_EXISTING , FILE_FLAG_BACKUP_SEMANTICS , NULL );
404+ if (hnd == INVALID_HANDLE_VALUE ) {
405+ errno = err_win_to_posix (GetLastError ());
406+ return PHANTOM_SYMLINK_RETRY ;
407+ }
408+
409+ if (!GetFileInformationByHandle (hnd , & fdata )) {
410+ errno = err_win_to_posix (GetLastError ());
411+ CloseHandle (hnd );
412+ return PHANTOM_SYMLINK_RETRY ;
413+ }
414+ CloseHandle (hnd );
415+
416+ /* if target exists and is a file, we're done */
417+ if (!(fdata .dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ))
418+ return PHANTOM_SYMLINK_DONE ;
419+
420+ /* otherwise recreate the symlink with directory flag */
421+ if (DeleteFileW (wlink ) && CreateSymbolicLinkW (wlink , wtarget , 1 ))
422+ return PHANTOM_SYMLINK_DIRECTORY ;
423+
424+ errno = err_win_to_posix (GetLastError ());
425+ return PHANTOM_SYMLINK_RETRY ;
426+ }
427+
428+ /* keep track of newly created symlinks to non-existing targets */
429+ struct phantom_symlink_info {
430+ struct phantom_symlink_info * next ;
431+ wchar_t * wlink ;
432+ wchar_t * wtarget ;
433+ };
434+
435+ static struct phantom_symlink_info * phantom_symlinks = NULL ;
436+ static CRITICAL_SECTION phantom_symlinks_cs ;
437+
438+ static void process_phantom_symlinks (void )
439+ {
440+ struct phantom_symlink_info * current , * * psi ;
441+ EnterCriticalSection (& phantom_symlinks_cs );
442+ /* process phantom symlinks list */
443+ psi = & phantom_symlinks ;
444+ while ((current = * psi )) {
445+ enum phantom_symlink_result result = process_phantom_symlink (
446+ current -> wtarget , current -> wlink );
447+ if (result == PHANTOM_SYMLINK_RETRY ) {
448+ psi = & current -> next ;
449+ } else {
450+ /* symlink was processed, remove from list */
451+ * psi = current -> next ;
452+ free (current );
453+ /* if symlink was a directory, start over */
454+ if (result == PHANTOM_SYMLINK_DIRECTORY )
455+ psi = & phantom_symlinks ;
456+ }
457+ }
458+ LeaveCriticalSection (& phantom_symlinks_cs );
459+ }
460+
341461/* Normalizes NT paths as returned by some low-level APIs. */
342462static wchar_t * normalize_ntpath (wchar_t * wbuf )
343463{
@@ -521,6 +641,8 @@ int mingw_mkdir(const char *path, int mode UNUSED)
521641 return -1 ;
522642
523643 ret = _wmkdir (wpath );
644+ if (!ret )
645+ process_phantom_symlinks ();
524646 if (!ret && needs_hiding (path ))
525647 return set_hidden_flag (wpath , 1 );
526648 return ret ;
@@ -3019,6 +3141,42 @@ int symlink(const char *target, const char *link)
30193141 errno = err_win_to_posix (GetLastError ());
30203142 return -1 ;
30213143 }
3144+
3145+ /* convert to directory symlink if target exists */
3146+ switch (process_phantom_symlink (wtarget , wlink )) {
3147+ case PHANTOM_SYMLINK_RETRY : {
3148+ /* if target doesn't exist, add to phantom symlinks list */
3149+ wchar_t wfullpath [MAX_LONG_PATH ];
3150+ struct phantom_symlink_info * psi ;
3151+
3152+ /* convert to absolute path to be independent of cwd */
3153+ len = GetFullPathNameW (wlink , MAX_LONG_PATH , wfullpath , NULL );
3154+ if (!len || len >= MAX_LONG_PATH ) {
3155+ errno = err_win_to_posix (GetLastError ());
3156+ return -1 ;
3157+ }
3158+
3159+ /* over-allocate and fill phantom_symlink_info structure */
3160+ psi = xmalloc (sizeof (struct phantom_symlink_info )
3161+ + sizeof (wchar_t ) * (len + wcslen (wtarget ) + 2 ));
3162+ psi -> wlink = (wchar_t * )(psi + 1 );
3163+ wcscpy (psi -> wlink , wfullpath );
3164+ psi -> wtarget = psi -> wlink + len + 1 ;
3165+ wcscpy (psi -> wtarget , wtarget );
3166+
3167+ EnterCriticalSection (& phantom_symlinks_cs );
3168+ psi -> next = phantom_symlinks ;
3169+ phantom_symlinks = psi ;
3170+ LeaveCriticalSection (& phantom_symlinks_cs );
3171+ break ;
3172+ }
3173+ case PHANTOM_SYMLINK_DIRECTORY :
3174+ /* if we created a dir symlink, process other phantom symlinks */
3175+ process_phantom_symlinks ();
3176+ break ;
3177+ default :
3178+ break ;
3179+ }
30223180 return 0 ;
30233181}
30243182
@@ -3980,6 +4138,7 @@ int wmain(int argc, const wchar_t **wargv)
39804138
39814139 /* initialize critical section for waitpid pinfo_t list */
39824140 InitializeCriticalSection (& pinfo_cs );
4141+ InitializeCriticalSection (& phantom_symlinks_cs );
39834142
39844143 /* initialize critical section for fscache */
39854144 InitializeCriticalSection (& fscache_cs );
0 commit comments