@@ -286,6 +286,131 @@ int mingw_core_config(const char *var, const char *value, void *cb)
286286 return 0 ;
287287}
288288
289+ enum phantom_symlink_result {
290+ PHANTOM_SYMLINK_RETRY ,
291+ PHANTOM_SYMLINK_DONE ,
292+ PHANTOM_SYMLINK_DIRECTORY
293+ };
294+
295+ static inline int is_wdir_sep (wchar_t wchar )
296+ {
297+ return wchar == L'/' || wchar == L'\\' ;
298+ }
299+
300+ static const wchar_t * make_relative_to (const wchar_t * path ,
301+ const wchar_t * relative_to , wchar_t * out ,
302+ size_t size )
303+ {
304+ size_t i = wcslen (relative_to ), len ;
305+
306+ /* Is `path` already absolute? */
307+ if (is_wdir_sep (path [0 ]) ||
308+ (iswalpha (path [0 ]) && path [1 ] == L':' && is_wdir_sep (path [2 ])))
309+ return path ;
310+
311+ while (i > 0 && !is_wdir_sep (relative_to [i - 1 ]))
312+ i -- ;
313+
314+ /* Is `relative_to` in the current directory? */
315+ if (!i )
316+ return path ;
317+
318+ len = wcslen (path );
319+ if (i + len + 1 > size ) {
320+ error ("Could not make '%S' relative to '%S' (too large)" ,
321+ path , relative_to );
322+ return NULL ;
323+ }
324+
325+ memcpy (out , relative_to , i * sizeof (wchar_t ));
326+ wcscpy (out + i , path );
327+ return out ;
328+ }
329+
330+ /*
331+ * Changes a file symlink to a directory symlink if the target exists and is a
332+ * directory.
333+ */
334+ static enum phantom_symlink_result
335+ process_phantom_symlink (const wchar_t * wtarget , const wchar_t * wlink )
336+ {
337+ HANDLE hnd ;
338+ BY_HANDLE_FILE_INFORMATION fdata ;
339+ wchar_t relative [MAX_LONG_PATH ];
340+ const wchar_t * rel ;
341+
342+ /* check that wlink is still a file symlink */
343+ if ((GetFileAttributesW (wlink )
344+ & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY ))
345+ != FILE_ATTRIBUTE_REPARSE_POINT )
346+ return PHANTOM_SYMLINK_DONE ;
347+
348+ /* make it relative, if necessary */
349+ rel = make_relative_to (wtarget , wlink , relative , ARRAY_SIZE (relative ));
350+ if (!rel )
351+ return PHANTOM_SYMLINK_DONE ;
352+
353+ /* let Windows resolve the link by opening it */
354+ hnd = CreateFileW (rel , 0 ,
355+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE , NULL ,
356+ OPEN_EXISTING , FILE_FLAG_BACKUP_SEMANTICS , NULL );
357+ if (hnd == INVALID_HANDLE_VALUE ) {
358+ errno = err_win_to_posix (GetLastError ());
359+ return PHANTOM_SYMLINK_RETRY ;
360+ }
361+
362+ if (!GetFileInformationByHandle (hnd , & fdata )) {
363+ errno = err_win_to_posix (GetLastError ());
364+ CloseHandle (hnd );
365+ return PHANTOM_SYMLINK_RETRY ;
366+ }
367+ CloseHandle (hnd );
368+
369+ /* if target exists and is a file, we're done */
370+ if (!(fdata .dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ))
371+ return PHANTOM_SYMLINK_DONE ;
372+
373+ /* otherwise recreate the symlink with directory flag */
374+ if (DeleteFileW (wlink ) && CreateSymbolicLinkW (wlink , wtarget , 1 ))
375+ return PHANTOM_SYMLINK_DIRECTORY ;
376+
377+ errno = err_win_to_posix (GetLastError ());
378+ return PHANTOM_SYMLINK_RETRY ;
379+ }
380+
381+ /* keep track of newly created symlinks to non-existing targets */
382+ struct phantom_symlink_info {
383+ struct phantom_symlink_info * next ;
384+ wchar_t * wlink ;
385+ wchar_t * wtarget ;
386+ };
387+
388+ static struct phantom_symlink_info * phantom_symlinks = NULL ;
389+ static CRITICAL_SECTION phantom_symlinks_cs ;
390+
391+ static void process_phantom_symlinks (void )
392+ {
393+ struct phantom_symlink_info * current , * * psi ;
394+ EnterCriticalSection (& phantom_symlinks_cs );
395+ /* process phantom symlinks list */
396+ psi = & phantom_symlinks ;
397+ while ((current = * psi )) {
398+ enum phantom_symlink_result result = process_phantom_symlink (
399+ current -> wtarget , current -> wlink );
400+ if (result == PHANTOM_SYMLINK_RETRY ) {
401+ psi = & current -> next ;
402+ } else {
403+ /* symlink was processed, remove from list */
404+ * psi = current -> next ;
405+ free (current );
406+ /* if symlink was a directory, start over */
407+ if (result == PHANTOM_SYMLINK_DIRECTORY )
408+ psi = & phantom_symlinks ;
409+ }
410+ }
411+ LeaveCriticalSection (& phantom_symlinks_cs );
412+ }
413+
289414/* Normalizes NT paths as returned by some low-level APIs. */
290415static wchar_t * normalize_ntpath (wchar_t * wbuf )
291416{
@@ -435,6 +560,8 @@ int mingw_mkdir(const char *path, int mode)
435560 return -1 ;
436561
437562 ret = _wmkdir (wpath );
563+ if (!ret )
564+ process_phantom_symlinks ();
438565 if (!ret && needs_hiding (path ))
439566 return set_hidden_flag (wpath , 1 );
440567 return ret ;
@@ -2251,6 +2378,42 @@ int symlink(const char *target, const char *link)
22512378 errno = err_win_to_posix (GetLastError ());
22522379 return -1 ;
22532380 }
2381+
2382+ /* convert to directory symlink if target exists */
2383+ switch (process_phantom_symlink (wtarget , wlink )) {
2384+ case PHANTOM_SYMLINK_RETRY : {
2385+ /* if target doesn't exist, add to phantom symlinks list */
2386+ wchar_t wfullpath [MAX_LONG_PATH ];
2387+ struct phantom_symlink_info * psi ;
2388+
2389+ /* convert to absolute path to be independent of cwd */
2390+ len = GetFullPathNameW (wlink , MAX_LONG_PATH , wfullpath , NULL );
2391+ if (!len || len >= MAX_LONG_PATH ) {
2392+ errno = err_win_to_posix (GetLastError ());
2393+ return -1 ;
2394+ }
2395+
2396+ /* over-allocate and fill phantom_symlink_info structure */
2397+ psi = xmalloc (sizeof (struct phantom_symlink_info )
2398+ + sizeof (wchar_t ) * (len + wcslen (wtarget ) + 2 ));
2399+ psi -> wlink = (wchar_t * )(psi + 1 );
2400+ wcscpy (psi -> wlink , wfullpath );
2401+ psi -> wtarget = psi -> wlink + len + 1 ;
2402+ wcscpy (psi -> wtarget , wtarget );
2403+
2404+ EnterCriticalSection (& phantom_symlinks_cs );
2405+ psi -> next = phantom_symlinks ;
2406+ phantom_symlinks = psi ;
2407+ LeaveCriticalSection (& phantom_symlinks_cs );
2408+ break ;
2409+ }
2410+ case PHANTOM_SYMLINK_DIRECTORY :
2411+ /* if we created a dir symlink, process other phantom symlinks */
2412+ process_phantom_symlinks ();
2413+ break ;
2414+ default :
2415+ break ;
2416+ }
22542417 return 0 ;
22552418}
22562419
@@ -2763,6 +2926,7 @@ int wmain(int argc, const wchar_t **wargv)
27632926
27642927 /* initialize critical section for waitpid pinfo_t list */
27652928 InitializeCriticalSection (& pinfo_cs );
2929+ InitializeCriticalSection (& phantom_symlinks_cs );
27662930
27672931 /* initialize critical section for fscache */
27682932 InitializeCriticalSection (& fscache_cs );
0 commit comments