@@ -296,6 +296,131 @@ int mingw_core_config(const char *var, const char *value,
296296 return 0 ;
297297}
298298
299+ static inline int is_wdir_sep (wchar_t wchar )
300+ {
301+ return wchar == L'/' || wchar == L'\\' ;
302+ }
303+
304+ static const wchar_t * make_relative_to (const wchar_t * path ,
305+ const wchar_t * relative_to , wchar_t * out ,
306+ size_t size )
307+ {
308+ size_t i = wcslen (relative_to ), len ;
309+
310+ /* Is `path` already absolute? */
311+ if (is_wdir_sep (path [0 ]) ||
312+ (iswalpha (path [0 ]) && path [1 ] == L':' && is_wdir_sep (path [2 ])))
313+ return path ;
314+
315+ while (i > 0 && !is_wdir_sep (relative_to [i - 1 ]))
316+ i -- ;
317+
318+ /* Is `relative_to` in the current directory? */
319+ if (!i )
320+ return path ;
321+
322+ len = wcslen (path );
323+ if (i + len + 1 > size ) {
324+ error ("Could not make '%ls' relative to '%ls' (too large)" ,
325+ path , relative_to );
326+ return NULL ;
327+ }
328+
329+ memcpy (out , relative_to , i * sizeof (wchar_t ));
330+ wcscpy (out + i , path );
331+ return out ;
332+ }
333+
334+ enum phantom_symlink_result {
335+ PHANTOM_SYMLINK_RETRY ,
336+ PHANTOM_SYMLINK_DONE ,
337+ PHANTOM_SYMLINK_DIRECTORY
338+ };
339+
340+ /*
341+ * Changes a file symlink to a directory symlink if the target exists and is a
342+ * directory.
343+ */
344+ static enum phantom_symlink_result
345+ process_phantom_symlink (const wchar_t * wtarget , const wchar_t * wlink )
346+ {
347+ HANDLE hnd ;
348+ BY_HANDLE_FILE_INFORMATION fdata ;
349+ wchar_t relative [MAX_PATH ];
350+ const wchar_t * rel ;
351+
352+ /* check that wlink is still a file symlink */
353+ if ((GetFileAttributesW (wlink )
354+ & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY ))
355+ != FILE_ATTRIBUTE_REPARSE_POINT )
356+ return PHANTOM_SYMLINK_DONE ;
357+
358+ /* make it relative, if necessary */
359+ rel = make_relative_to (wtarget , wlink , relative , ARRAY_SIZE (relative ));
360+ if (!rel )
361+ return PHANTOM_SYMLINK_DONE ;
362+
363+ /* let Windows resolve the link by opening it */
364+ hnd = CreateFileW (rel , 0 ,
365+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE , NULL ,
366+ OPEN_EXISTING , FILE_FLAG_BACKUP_SEMANTICS , NULL );
367+ if (hnd == INVALID_HANDLE_VALUE ) {
368+ errno = err_win_to_posix (GetLastError ());
369+ return PHANTOM_SYMLINK_RETRY ;
370+ }
371+
372+ if (!GetFileInformationByHandle (hnd , & fdata )) {
373+ errno = err_win_to_posix (GetLastError ());
374+ CloseHandle (hnd );
375+ return PHANTOM_SYMLINK_RETRY ;
376+ }
377+ CloseHandle (hnd );
378+
379+ /* if target exists and is a file, we're done */
380+ if (!(fdata .dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ))
381+ return PHANTOM_SYMLINK_DONE ;
382+
383+ /* otherwise recreate the symlink with directory flag */
384+ if (DeleteFileW (wlink ) && CreateSymbolicLinkW (wlink , wtarget , 1 ))
385+ return PHANTOM_SYMLINK_DIRECTORY ;
386+
387+ errno = err_win_to_posix (GetLastError ());
388+ return PHANTOM_SYMLINK_RETRY ;
389+ }
390+
391+ /* keep track of newly created symlinks to non-existing targets */
392+ struct phantom_symlink_info {
393+ struct phantom_symlink_info * next ;
394+ wchar_t * wlink ;
395+ wchar_t * wtarget ;
396+ };
397+
398+ static struct phantom_symlink_info * phantom_symlinks = NULL ;
399+ static CRITICAL_SECTION phantom_symlinks_cs ;
400+
401+ static void process_phantom_symlinks (void )
402+ {
403+ struct phantom_symlink_info * current , * * psi ;
404+ EnterCriticalSection (& phantom_symlinks_cs );
405+ /* process phantom symlinks list */
406+ psi = & phantom_symlinks ;
407+ while ((current = * psi )) {
408+ enum phantom_symlink_result result = process_phantom_symlink (
409+ current -> wtarget , current -> wlink );
410+ if (result == PHANTOM_SYMLINK_RETRY ) {
411+ psi = & current -> next ;
412+ } else {
413+ /* symlink was processed, remove from list */
414+ * psi = current -> next ;
415+ free (current );
416+ /* if symlink was a directory, start over */
417+ if (result == PHANTOM_SYMLINK_DIRECTORY )
418+ psi = & phantom_symlinks ;
419+ }
420+ }
421+ LeaveCriticalSection (& phantom_symlinks_cs );
422+ }
423+
299424/* Normalizes NT paths as returned by some low-level APIs. */
300425static wchar_t * normalize_ntpath (wchar_t * wbuf )
301426{
@@ -479,6 +604,8 @@ int mingw_mkdir(const char *path, int mode UNUSED)
479604 if (xutftowcs_path (wpath , path ) < 0 )
480605 return -1 ;
481606 ret = _wmkdir (wpath );
607+ if (!ret )
608+ process_phantom_symlinks ();
482609 if (!ret && needs_hiding (path ))
483610 return set_hidden_flag (wpath , 1 );
484611 return ret ;
@@ -2723,6 +2850,42 @@ int symlink(const char *target, const char *link)
27232850 errno = err_win_to_posix (GetLastError ());
27242851 return -1 ;
27252852 }
2853+
2854+ /* convert to directory symlink if target exists */
2855+ switch (process_phantom_symlink (wtarget , wlink )) {
2856+ case PHANTOM_SYMLINK_RETRY : {
2857+ /* if target doesn't exist, add to phantom symlinks list */
2858+ wchar_t wfullpath [MAX_PATH ];
2859+ struct phantom_symlink_info * psi ;
2860+
2861+ /* convert to absolute path to be independent of cwd */
2862+ len = GetFullPathNameW (wlink , MAX_PATH , wfullpath , NULL );
2863+ if (!len || len >= MAX_PATH ) {
2864+ errno = err_win_to_posix (GetLastError ());
2865+ return -1 ;
2866+ }
2867+
2868+ /* over-allocate and fill phantom_symlink_info structure */
2869+ psi = xmalloc (sizeof (struct phantom_symlink_info )
2870+ + sizeof (wchar_t ) * (len + wcslen (wtarget ) + 2 ));
2871+ psi -> wlink = (wchar_t * )(psi + 1 );
2872+ wcscpy (psi -> wlink , wfullpath );
2873+ psi -> wtarget = psi -> wlink + len + 1 ;
2874+ wcscpy (psi -> wtarget , wtarget );
2875+
2876+ EnterCriticalSection (& phantom_symlinks_cs );
2877+ psi -> next = phantom_symlinks ;
2878+ phantom_symlinks = psi ;
2879+ LeaveCriticalSection (& phantom_symlinks_cs );
2880+ break ;
2881+ }
2882+ case PHANTOM_SYMLINK_DIRECTORY :
2883+ /* if we created a dir symlink, process other phantom symlinks */
2884+ process_phantom_symlinks ();
2885+ break ;
2886+ default :
2887+ break ;
2888+ }
27262889 return 0 ;
27272890}
27282891
@@ -3424,6 +3587,7 @@ int wmain(int argc, const wchar_t **wargv)
34243587
34253588 /* initialize critical section for waitpid pinfo_t list */
34263589 InitializeCriticalSection (& pinfo_cs );
3590+ InitializeCriticalSection (& phantom_symlinks_cs );
34273591
34283592 /* set up default file mode and file modes for stdin/out/err */
34293593 _fmode = _O_BINARY ;
0 commit comments