@@ -331,6 +331,126 @@ static inline int is_wdir_sep(wchar_t wchar)
331331 return wchar == L'/' || wchar == L'\\' ;
332332}
333333
334+ static const wchar_t * make_relative_to (const wchar_t * path ,
335+ const wchar_t * relative_to , wchar_t * out ,
336+ size_t size )
337+ {
338+ size_t i = wcslen (relative_to ), len ;
339+
340+ /* Is `path` already absolute? */
341+ if (is_wdir_sep (path [0 ]) ||
342+ (iswalpha (path [0 ]) && path [1 ] == L':' && is_wdir_sep (path [2 ])))
343+ return path ;
344+
345+ while (i > 0 && !is_wdir_sep (relative_to [i - 1 ]))
346+ i -- ;
347+
348+ /* Is `relative_to` in the current directory? */
349+ if (!i )
350+ return path ;
351+
352+ len = wcslen (path );
353+ if (i + len + 1 > size ) {
354+ error ("Could not make '%ls' relative to '%ls' (too large)" ,
355+ path , relative_to );
356+ return NULL ;
357+ }
358+
359+ memcpy (out , relative_to , i * sizeof (wchar_t ));
360+ wcscpy (out + i , path );
361+ return out ;
362+ }
363+
364+ enum phantom_symlink_result {
365+ PHANTOM_SYMLINK_RETRY ,
366+ PHANTOM_SYMLINK_DONE ,
367+ PHANTOM_SYMLINK_DIRECTORY
368+ };
369+
370+ /*
371+ * Changes a file symlink to a directory symlink if the target exists and is a
372+ * directory.
373+ */
374+ static enum phantom_symlink_result
375+ process_phantom_symlink (const wchar_t * wtarget , const wchar_t * wlink )
376+ {
377+ HANDLE hnd ;
378+ BY_HANDLE_FILE_INFORMATION fdata ;
379+ wchar_t relative [MAX_LONG_PATH ];
380+ const wchar_t * rel ;
381+
382+ /* check that wlink is still a file symlink */
383+ if ((GetFileAttributesW (wlink )
384+ & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY ))
385+ != FILE_ATTRIBUTE_REPARSE_POINT )
386+ return PHANTOM_SYMLINK_DONE ;
387+
388+ /* make it relative, if necessary */
389+ rel = make_relative_to (wtarget , wlink , relative , ARRAY_SIZE (relative ));
390+ if (!rel )
391+ return PHANTOM_SYMLINK_DONE ;
392+
393+ /* let Windows resolve the link by opening it */
394+ hnd = CreateFileW (rel , 0 ,
395+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE , NULL ,
396+ OPEN_EXISTING , FILE_FLAG_BACKUP_SEMANTICS , NULL );
397+ if (hnd == INVALID_HANDLE_VALUE ) {
398+ errno = err_win_to_posix (GetLastError ());
399+ return PHANTOM_SYMLINK_RETRY ;
400+ }
401+
402+ if (!GetFileInformationByHandle (hnd , & fdata )) {
403+ errno = err_win_to_posix (GetLastError ());
404+ CloseHandle (hnd );
405+ return PHANTOM_SYMLINK_RETRY ;
406+ }
407+ CloseHandle (hnd );
408+
409+ /* if target exists and is a file, we're done */
410+ if (!(fdata .dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ))
411+ return PHANTOM_SYMLINK_DONE ;
412+
413+ /* otherwise recreate the symlink with directory flag */
414+ if (DeleteFileW (wlink ) && CreateSymbolicLinkW (wlink , wtarget , 1 ))
415+ return PHANTOM_SYMLINK_DIRECTORY ;
416+
417+ errno = err_win_to_posix (GetLastError ());
418+ return PHANTOM_SYMLINK_RETRY ;
419+ }
420+
421+ /* keep track of newly created symlinks to non-existing targets */
422+ struct phantom_symlink_info {
423+ struct phantom_symlink_info * next ;
424+ wchar_t * wlink ;
425+ wchar_t * wtarget ;
426+ };
427+
428+ static struct phantom_symlink_info * phantom_symlinks = NULL ;
429+ static CRITICAL_SECTION phantom_symlinks_cs ;
430+
431+ static void process_phantom_symlinks (void )
432+ {
433+ struct phantom_symlink_info * current , * * psi ;
434+ EnterCriticalSection (& phantom_symlinks_cs );
435+ /* process phantom symlinks list */
436+ psi = & phantom_symlinks ;
437+ while ((current = * psi )) {
438+ enum phantom_symlink_result result = process_phantom_symlink (
439+ current -> wtarget , current -> wlink );
440+ if (result == PHANTOM_SYMLINK_RETRY ) {
441+ psi = & current -> next ;
442+ } else {
443+ /* symlink was processed, remove from list */
444+ * psi = current -> next ;
445+ free (current );
446+ /* if symlink was a directory, start over */
447+ if (result == PHANTOM_SYMLINK_DIRECTORY )
448+ psi = & phantom_symlinks ;
449+ }
450+ }
451+ LeaveCriticalSection (& phantom_symlinks_cs );
452+ }
453+
334454/* Normalizes NT paths as returned by some low-level APIs. */
335455static wchar_t * normalize_ntpath (wchar_t * wbuf )
336456{
@@ -517,6 +637,8 @@ int mingw_mkdir(const char *path, int mode UNUSED)
517637 return -1 ;
518638
519639 ret = _wmkdir (wpath );
640+ if (!ret )
641+ process_phantom_symlinks ();
520642 if (!ret && needs_hiding (path ))
521643 return set_hidden_flag (wpath , 1 );
522644 return ret ;
@@ -2996,6 +3118,42 @@ int symlink(const char *target, const char *link)
29963118 errno = err_win_to_posix (GetLastError ());
29973119 return -1 ;
29983120 }
3121+
3122+ /* convert to directory symlink if target exists */
3123+ switch (process_phantom_symlink (wtarget , wlink )) {
3124+ case PHANTOM_SYMLINK_RETRY : {
3125+ /* if target doesn't exist, add to phantom symlinks list */
3126+ wchar_t wfullpath [MAX_LONG_PATH ];
3127+ struct phantom_symlink_info * psi ;
3128+
3129+ /* convert to absolute path to be independent of cwd */
3130+ len = GetFullPathNameW (wlink , MAX_LONG_PATH , wfullpath , NULL );
3131+ if (!len || len >= MAX_LONG_PATH ) {
3132+ errno = err_win_to_posix (GetLastError ());
3133+ return -1 ;
3134+ }
3135+
3136+ /* over-allocate and fill phantom_symlink_info structure */
3137+ psi = xmalloc (sizeof (struct phantom_symlink_info )
3138+ + sizeof (wchar_t ) * (len + wcslen (wtarget ) + 2 ));
3139+ psi -> wlink = (wchar_t * )(psi + 1 );
3140+ wcscpy (psi -> wlink , wfullpath );
3141+ psi -> wtarget = psi -> wlink + len + 1 ;
3142+ wcscpy (psi -> wtarget , wtarget );
3143+
3144+ EnterCriticalSection (& phantom_symlinks_cs );
3145+ psi -> next = phantom_symlinks ;
3146+ phantom_symlinks = psi ;
3147+ LeaveCriticalSection (& phantom_symlinks_cs );
3148+ break ;
3149+ }
3150+ case PHANTOM_SYMLINK_DIRECTORY :
3151+ /* if we created a dir symlink, process other phantom symlinks */
3152+ process_phantom_symlinks ();
3153+ break ;
3154+ default :
3155+ break ;
3156+ }
29993157 return 0 ;
30003158}
30013159
@@ -3985,6 +4143,7 @@ int wmain(int argc, const wchar_t **wargv)
39854143
39864144 /* initialize critical section for waitpid pinfo_t list */
39874145 InitializeCriticalSection (& pinfo_cs );
4146+ InitializeCriticalSection (& phantom_symlinks_cs );
39884147
39894148 /* initialize critical section for fscache */
39904149 InitializeCriticalSection (& fscache_cs );
0 commit comments