@@ -270,6 +270,131 @@ int mingw_core_config(const char *var, const char *value, void *cb)
270270 return 0 ;
271271}
272272
273+ enum phantom_symlink_result {
274+ PHANTOM_SYMLINK_RETRY ,
275+ PHANTOM_SYMLINK_DONE ,
276+ PHANTOM_SYMLINK_DIRECTORY
277+ };
278+
279+ static inline int is_wdir_sep (wchar_t wchar )
280+ {
281+ return wchar == L'/' || wchar == L'\\' ;
282+ }
283+
284+ static const wchar_t * make_relative_to (const wchar_t * path ,
285+ const wchar_t * relative_to , wchar_t * out ,
286+ size_t size )
287+ {
288+ size_t i = wcslen (relative_to ), len ;
289+
290+ /* Is `path` already absolute? */
291+ if (is_wdir_sep (path [0 ]) ||
292+ (iswalpha (path [0 ]) && path [1 ] == L':' && is_wdir_sep (path [2 ])))
293+ return path ;
294+
295+ while (i > 0 && !is_wdir_sep (relative_to [i - 1 ]))
296+ i -- ;
297+
298+ /* Is `relative_to` in the current directory? */
299+ if (!i )
300+ return path ;
301+
302+ len = wcslen (path );
303+ if (i + len + 1 > size ) {
304+ error ("Could not make '%S' relative to '%S' (too large)" ,
305+ path , relative_to );
306+ return NULL ;
307+ }
308+
309+ memcpy (out , relative_to , i * sizeof (wchar_t ));
310+ wcscpy (out + i , path );
311+ return out ;
312+ }
313+
314+ /*
315+ * Changes a file symlink to a directory symlink if the target exists and is a
316+ * directory.
317+ */
318+ static enum phantom_symlink_result
319+ process_phantom_symlink (const wchar_t * wtarget , const wchar_t * wlink )
320+ {
321+ HANDLE hnd ;
322+ BY_HANDLE_FILE_INFORMATION fdata ;
323+ wchar_t relative [MAX_LONG_PATH ];
324+ const wchar_t * rel ;
325+
326+ /* check that wlink is still a file symlink */
327+ if ((GetFileAttributesW (wlink )
328+ & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY ))
329+ != FILE_ATTRIBUTE_REPARSE_POINT )
330+ return PHANTOM_SYMLINK_DONE ;
331+
332+ /* make it relative, if necessary */
333+ rel = make_relative_to (wtarget , wlink , relative , ARRAY_SIZE (relative ));
334+ if (!rel )
335+ return PHANTOM_SYMLINK_DONE ;
336+
337+ /* let Windows resolve the link by opening it */
338+ hnd = CreateFileW (rel , 0 ,
339+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE , NULL ,
340+ OPEN_EXISTING , FILE_FLAG_BACKUP_SEMANTICS , NULL );
341+ if (hnd == INVALID_HANDLE_VALUE ) {
342+ errno = err_win_to_posix (GetLastError ());
343+ return PHANTOM_SYMLINK_RETRY ;
344+ }
345+
346+ if (!GetFileInformationByHandle (hnd , & fdata )) {
347+ errno = err_win_to_posix (GetLastError ());
348+ CloseHandle (hnd );
349+ return PHANTOM_SYMLINK_RETRY ;
350+ }
351+ CloseHandle (hnd );
352+
353+ /* if target exists and is a file, we're done */
354+ if (!(fdata .dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ))
355+ return PHANTOM_SYMLINK_DONE ;
356+
357+ /* otherwise recreate the symlink with directory flag */
358+ if (DeleteFileW (wlink ) && CreateSymbolicLinkW (wlink , wtarget , 1 ))
359+ return PHANTOM_SYMLINK_DIRECTORY ;
360+
361+ errno = err_win_to_posix (GetLastError ());
362+ return PHANTOM_SYMLINK_RETRY ;
363+ }
364+
365+ /* keep track of newly created symlinks to non-existing targets */
366+ struct phantom_symlink_info {
367+ struct phantom_symlink_info * next ;
368+ wchar_t * wlink ;
369+ wchar_t * wtarget ;
370+ };
371+
372+ static struct phantom_symlink_info * phantom_symlinks = NULL ;
373+ static CRITICAL_SECTION phantom_symlinks_cs ;
374+
375+ static void process_phantom_symlinks (void )
376+ {
377+ struct phantom_symlink_info * current , * * psi ;
378+ EnterCriticalSection (& phantom_symlinks_cs );
379+ /* process phantom symlinks list */
380+ psi = & phantom_symlinks ;
381+ while ((current = * psi )) {
382+ enum phantom_symlink_result result = process_phantom_symlink (
383+ current -> wtarget , current -> wlink );
384+ if (result == PHANTOM_SYMLINK_RETRY ) {
385+ psi = & current -> next ;
386+ } else {
387+ /* symlink was processed, remove from list */
388+ * psi = current -> next ;
389+ free (current );
390+ /* if symlink was a directory, start over */
391+ if (result == PHANTOM_SYMLINK_DIRECTORY )
392+ psi = & phantom_symlinks ;
393+ }
394+ }
395+ LeaveCriticalSection (& phantom_symlinks_cs );
396+ }
397+
273398/* Normalizes NT paths as returned by some low-level APIs. */
274399static wchar_t * normalize_ntpath (wchar_t * wbuf )
275400{
@@ -419,6 +544,8 @@ int mingw_mkdir(const char *path, int mode)
419544 return -1 ;
420545
421546 ret = _wmkdir (wpath );
547+ if (!ret )
548+ process_phantom_symlinks ();
422549 if (!ret && needs_hiding (path ))
423550 return set_hidden_flag (wpath , 1 );
424551 return ret ;
@@ -2289,6 +2416,42 @@ int symlink(const char *target, const char *link)
22892416 errno = err_win_to_posix (GetLastError ());
22902417 return -1 ;
22912418 }
2419+
2420+ /* convert to directory symlink if target exists */
2421+ switch (process_phantom_symlink (wtarget , wlink )) {
2422+ case PHANTOM_SYMLINK_RETRY : {
2423+ /* if target doesn't exist, add to phantom symlinks list */
2424+ wchar_t wfullpath [MAX_LONG_PATH ];
2425+ struct phantom_symlink_info * psi ;
2426+
2427+ /* convert to absolute path to be independent of cwd */
2428+ len = GetFullPathNameW (wlink , MAX_LONG_PATH , wfullpath , NULL );
2429+ if (!len || len >= MAX_LONG_PATH ) {
2430+ errno = err_win_to_posix (GetLastError ());
2431+ return -1 ;
2432+ }
2433+
2434+ /* over-allocate and fill phantom_symlink_info structure */
2435+ psi = xmalloc (sizeof (struct phantom_symlink_info )
2436+ + sizeof (wchar_t ) * (len + wcslen (wtarget ) + 2 ));
2437+ psi -> wlink = (wchar_t * )(psi + 1 );
2438+ wcscpy (psi -> wlink , wfullpath );
2439+ psi -> wtarget = psi -> wlink + len + 1 ;
2440+ wcscpy (psi -> wtarget , wtarget );
2441+
2442+ EnterCriticalSection (& phantom_symlinks_cs );
2443+ psi -> next = phantom_symlinks ;
2444+ phantom_symlinks = psi ;
2445+ LeaveCriticalSection (& phantom_symlinks_cs );
2446+ break ;
2447+ }
2448+ case PHANTOM_SYMLINK_DIRECTORY :
2449+ /* if we created a dir symlink, process other phantom symlinks */
2450+ process_phantom_symlinks ();
2451+ break ;
2452+ default :
2453+ break ;
2454+ }
22922455 return 0 ;
22932456}
22942457
@@ -2834,6 +2997,7 @@ int wmain(int argc, const wchar_t **wargv)
28342997
28352998 /* initialize critical section for waitpid pinfo_t list */
28362999 InitializeCriticalSection (& pinfo_cs );
3000+ InitializeCriticalSection (& phantom_symlinks_cs );
28373001
28383002 /* set up default file mode and file modes for stdin/out/err */
28393003 _fmode = _O_BINARY ;
0 commit comments