@@ -328,6 +328,126 @@ static inline int is_wdir_sep(wchar_t wchar)
328328 return wchar == L'/' || wchar == L'\\' ;
329329}
330330
331+ static const wchar_t * make_relative_to (const wchar_t * path ,
332+ const wchar_t * relative_to , wchar_t * out ,
333+ size_t size )
334+ {
335+ size_t i = wcslen (relative_to ), len ;
336+
337+ /* Is `path` already absolute? */
338+ if (is_wdir_sep (path [0 ]) ||
339+ (iswalpha (path [0 ]) && path [1 ] == L':' && is_wdir_sep (path [2 ])))
340+ return path ;
341+
342+ while (i > 0 && !is_wdir_sep (relative_to [i - 1 ]))
343+ i -- ;
344+
345+ /* Is `relative_to` in the current directory? */
346+ if (!i )
347+ return path ;
348+
349+ len = wcslen (path );
350+ if (i + len + 1 > size ) {
351+ error ("Could not make '%ls' relative to '%ls' (too large)" ,
352+ path , relative_to );
353+ return NULL ;
354+ }
355+
356+ memcpy (out , relative_to , i * sizeof (wchar_t ));
357+ wcscpy (out + i , path );
358+ return out ;
359+ }
360+
361+ enum phantom_symlink_result {
362+ PHANTOM_SYMLINK_RETRY ,
363+ PHANTOM_SYMLINK_DONE ,
364+ PHANTOM_SYMLINK_DIRECTORY
365+ };
366+
367+ /*
368+ * Changes a file symlink to a directory symlink if the target exists and is a
369+ * directory.
370+ */
371+ static enum phantom_symlink_result
372+ process_phantom_symlink (const wchar_t * wtarget , const wchar_t * wlink )
373+ {
374+ HANDLE hnd ;
375+ BY_HANDLE_FILE_INFORMATION fdata ;
376+ wchar_t relative [MAX_LONG_PATH ];
377+ const wchar_t * rel ;
378+
379+ /* check that wlink is still a file symlink */
380+ if ((GetFileAttributesW (wlink )
381+ & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY ))
382+ != FILE_ATTRIBUTE_REPARSE_POINT )
383+ return PHANTOM_SYMLINK_DONE ;
384+
385+ /* make it relative, if necessary */
386+ rel = make_relative_to (wtarget , wlink , relative , ARRAY_SIZE (relative ));
387+ if (!rel )
388+ return PHANTOM_SYMLINK_DONE ;
389+
390+ /* let Windows resolve the link by opening it */
391+ hnd = CreateFileW (rel , 0 ,
392+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE , NULL ,
393+ OPEN_EXISTING , FILE_FLAG_BACKUP_SEMANTICS , NULL );
394+ if (hnd == INVALID_HANDLE_VALUE ) {
395+ errno = err_win_to_posix (GetLastError ());
396+ return PHANTOM_SYMLINK_RETRY ;
397+ }
398+
399+ if (!GetFileInformationByHandle (hnd , & fdata )) {
400+ errno = err_win_to_posix (GetLastError ());
401+ CloseHandle (hnd );
402+ return PHANTOM_SYMLINK_RETRY ;
403+ }
404+ CloseHandle (hnd );
405+
406+ /* if target exists and is a file, we're done */
407+ if (!(fdata .dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ))
408+ return PHANTOM_SYMLINK_DONE ;
409+
410+ /* otherwise recreate the symlink with directory flag */
411+ if (DeleteFileW (wlink ) && CreateSymbolicLinkW (wlink , wtarget , 1 ))
412+ return PHANTOM_SYMLINK_DIRECTORY ;
413+
414+ errno = err_win_to_posix (GetLastError ());
415+ return PHANTOM_SYMLINK_RETRY ;
416+ }
417+
418+ /* keep track of newly created symlinks to non-existing targets */
419+ struct phantom_symlink_info {
420+ struct phantom_symlink_info * next ;
421+ wchar_t * wlink ;
422+ wchar_t * wtarget ;
423+ };
424+
425+ static struct phantom_symlink_info * phantom_symlinks = NULL ;
426+ static CRITICAL_SECTION phantom_symlinks_cs ;
427+
428+ static void process_phantom_symlinks (void )
429+ {
430+ struct phantom_symlink_info * current , * * psi ;
431+ EnterCriticalSection (& phantom_symlinks_cs );
432+ /* process phantom symlinks list */
433+ psi = & phantom_symlinks ;
434+ while ((current = * psi )) {
435+ enum phantom_symlink_result result = process_phantom_symlink (
436+ current -> wtarget , current -> wlink );
437+ if (result == PHANTOM_SYMLINK_RETRY ) {
438+ psi = & current -> next ;
439+ } else {
440+ /* symlink was processed, remove from list */
441+ * psi = current -> next ;
442+ free (current );
443+ /* if symlink was a directory, start over */
444+ if (result == PHANTOM_SYMLINK_DIRECTORY )
445+ psi = & phantom_symlinks ;
446+ }
447+ }
448+ LeaveCriticalSection (& phantom_symlinks_cs );
449+ }
450+
331451/* Normalizes NT paths as returned by some low-level APIs. */
332452static wchar_t * normalize_ntpath (wchar_t * wbuf )
333453{
@@ -511,6 +631,8 @@ int mingw_mkdir(const char *path, int mode UNUSED)
511631 return -1 ;
512632
513633 ret = _wmkdir (wpath );
634+ if (!ret )
635+ process_phantom_symlinks ();
514636 if (!ret && needs_hiding (path ))
515637 return set_hidden_flag (wpath , 1 );
516638 return ret ;
@@ -2961,6 +3083,42 @@ int symlink(const char *target, const char *link)
29613083 errno = err_win_to_posix (GetLastError ());
29623084 return -1 ;
29633085 }
3086+
3087+ /* convert to directory symlink if target exists */
3088+ switch (process_phantom_symlink (wtarget , wlink )) {
3089+ case PHANTOM_SYMLINK_RETRY : {
3090+ /* if target doesn't exist, add to phantom symlinks list */
3091+ wchar_t wfullpath [MAX_LONG_PATH ];
3092+ struct phantom_symlink_info * psi ;
3093+
3094+ /* convert to absolute path to be independent of cwd */
3095+ len = GetFullPathNameW (wlink , MAX_LONG_PATH , wfullpath , NULL );
3096+ if (!len || len >= MAX_LONG_PATH ) {
3097+ errno = err_win_to_posix (GetLastError ());
3098+ return -1 ;
3099+ }
3100+
3101+ /* over-allocate and fill phantom_symlink_info structure */
3102+ psi = xmalloc (sizeof (struct phantom_symlink_info )
3103+ + sizeof (wchar_t ) * (len + wcslen (wtarget ) + 2 ));
3104+ psi -> wlink = (wchar_t * )(psi + 1 );
3105+ wcscpy (psi -> wlink , wfullpath );
3106+ psi -> wtarget = psi -> wlink + len + 1 ;
3107+ wcscpy (psi -> wtarget , wtarget );
3108+
3109+ EnterCriticalSection (& phantom_symlinks_cs );
3110+ psi -> next = phantom_symlinks ;
3111+ phantom_symlinks = psi ;
3112+ LeaveCriticalSection (& phantom_symlinks_cs );
3113+ break ;
3114+ }
3115+ case PHANTOM_SYMLINK_DIRECTORY :
3116+ /* if we created a dir symlink, process other phantom symlinks */
3117+ process_phantom_symlinks ();
3118+ break ;
3119+ default :
3120+ break ;
3121+ }
29643122 return 0 ;
29653123}
29663124
@@ -3925,6 +4083,7 @@ int wmain(int argc, const wchar_t **wargv)
39254083
39264084 /* initialize critical section for waitpid pinfo_t list */
39274085 InitializeCriticalSection (& pinfo_cs );
4086+ InitializeCriticalSection (& phantom_symlinks_cs );
39284087
39294088 /* initialize critical section for fscache */
39304089 InitializeCriticalSection (& fscache_cs );
0 commit comments