Cygwin: (mostly) drop NT4 and Samba < 3.0 support
[newlib-cygwin.git] / winsup / cygwin / forkable.cc
blobcd317a1ae09eeea09e92727d69747c13b9135de1
1 /* forkable.cc
3 Copyright 2015 Red Hat, Inc.
5 This software is a copyrighted work licensed under the terms of the
6 Cygwin license. Please consult the file "CYGWIN_LICENSE" for
7 details. */
9 #include "winsup.h"
10 #include "cygerrno.h"
11 #include "perprocess.h"
12 #include "sync.h"
13 #include "environ.h"
14 #include "security.h"
15 #include "path.h"
16 #include "fhandler.h"
17 #include "dtable.h"
18 #include "cygheap.h"
19 #include "pinfo.h"
20 #include "shared_info.h"
21 #include "dll_init.h"
22 #include "child_info.h"
23 #include "cygtls.h"
24 #include "exception.h"
25 #include <wchar.h>
26 #include <sys/reent.h>
27 #include <assert.h>
28 #include <tls_pbuf.h>
30 #define MUTEXSEP L"@"
31 #define PATHSEP L"\\"
33 /* Create the lastsepcount directories found in ntdirname, where
34 counting is done along path separators (including trailing ones).
35 Returns true when these directories exist afterwards, false otherways.
36 The ntdirname is used for the path-splitting. */
37 static bool
38 mkdirs (PWCHAR ntdirname, int lastsepcount)
40 bool success = true;
41 int i = lastsepcount;
42 for (--i; i > 0; --i)
44 PWCHAR lastsep = wcsrchr (ntdirname, L'\\');
45 if (!lastsep)
46 break;
47 *lastsep = L'\0';
50 for (++i; i <= lastsepcount; ++i)
52 if (success && (i == 0 || wcslen (wcsrchr (ntdirname, L'\\')) > 1))
54 UNICODE_STRING dn;
55 RtlInitUnicodeString (&dn, ntdirname);
56 OBJECT_ATTRIBUTES oa;
57 InitializeObjectAttributes (&oa, &dn, 0, NULL, NULL);
58 HANDLE dh = NULL;
59 NTSTATUS status;
60 IO_STATUS_BLOCK iosb;
61 status = NtCreateFile (&dh, GENERIC_READ | SYNCHRONIZE,
62 &oa, &iosb, NULL, FILE_ATTRIBUTE_NORMAL,
63 FILE_SHARE_READ | FILE_SHARE_WRITE,
64 FILE_CREATE,
65 FILE_DIRECTORY_FILE
66 | FILE_SYNCHRONOUS_IO_NONALERT,
67 NULL, 0);
68 if (NT_SUCCESS(status))
69 NtClose (dh);
70 else if (status != STATUS_OBJECT_NAME_COLLISION) /* already exists */
71 success = false;
72 debug_printf ("%y = NtCreateFile (%p, dir %W)", status, dh, ntdirname);
74 if (i < lastsepcount)
75 ntdirname[wcslen (ntdirname)] = L'\\'; /* restore original value */
77 return success;
80 /* Recursively remove the directory specified in ntmaxpathbuf,
81 using ntmaxpathbuf as the buffer to form subsequent filenames. */
82 static void
83 rmdirs (WCHAR ntmaxpathbuf[NT_MAX_PATH])
85 PWCHAR basebuf = wcsrchr (ntmaxpathbuf, L'\\'); /* find last pathsep */
86 if (basebuf && *(basebuf+1))
87 basebuf += wcslen (basebuf); /* last pathsep is not trailing one */
88 if (!basebuf)
89 basebuf = ntmaxpathbuf + wcslen (ntmaxpathbuf);
90 *basebuf = L'\0'; /* kill trailing pathsep, if any */
92 NTSTATUS status;
93 HANDLE hdir = dll_list::ntopenfile (ntmaxpathbuf, &status,
94 FILE_DIRECTORY_FILE |
95 FILE_DELETE_ON_CLOSE);
96 if (hdir == INVALID_HANDLE_VALUE)
97 return;
99 *basebuf++ = L'\\'; /* (re-)add trailing pathsep */
101 struct {
102 FILE_DIRECTORY_INFORMATION fdi;
103 WCHAR buf[NAME_MAX];
104 } fdibuf;
105 IO_STATUS_BLOCK iosb;
107 while (NT_SUCCESS (status = NtQueryDirectoryFile (hdir, NULL, NULL, NULL,
108 &iosb,
109 &fdibuf, sizeof (fdibuf),
110 FileDirectoryInformation,
111 FALSE, NULL, FALSE)))
113 PFILE_DIRECTORY_INFORMATION pfdi = &fdibuf.fdi;
114 while (true)
116 int namelen = pfdi->FileNameLength / sizeof (WCHAR);
117 wcsncpy (basebuf, pfdi->FileName, namelen);
118 basebuf[namelen] = L'\0';
120 if (pfdi->FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
122 if (wcscmp (basebuf, L".") && wcscmp (basebuf, L".."))
123 rmdirs (ntmaxpathbuf);
125 else
127 UNICODE_STRING fn;
128 RtlInitUnicodeString (&fn, ntmaxpathbuf);
130 path_conv pc (&fn);
131 unlink_nt (pc, true); /* move to bin */
134 if (!pfdi->NextEntryOffset)
135 break;
136 pfdi = (PFILE_DIRECTORY_INFORMATION)((caddr_t)pfdi
137 + pfdi->NextEntryOffset);
140 if (status != STATUS_NO_MORE_FILES)
141 debug_printf ("%y = NtQueryDirectoryFile (%p, io %y, info %d)",
142 status, hdir, iosb.Status, iosb.Information);
144 CloseHandle (hdir);
147 /* Get the NTFS file id for the real file behind the dll handle.
148 As we may open a wrong (or no) file while the dll is renamed,
149 we retry until we get the same file id a second time.
150 We use NtQueryVirtualMemory (MemorySectionName) for the current
151 file name, as GetModuleFileNameW () yields the as-loaded name.
152 While we have the file handle open, also read the attributes.
153 NOTE: Uses dll_list::nt_max_path_buf (). */
154 bool
155 dll::stat_real_file_once ()
157 if (fii.IndexNumber.QuadPart != -1LL)
158 return true;
160 NTSTATUS fstatus;
161 HANDLE fhandle = dll_list::ntopenfile (ntname, &fstatus);
162 if (fhandle == INVALID_HANDLE_VALUE)
164 system_printf ("WARNING: Unable (ntstatus %y) to open real file %W",
165 fstatus, ntname);
166 return false;
169 if (!dll_list::read_fii (fhandle, &fii))
170 system_printf ("WARNING: Unable to read real file attributes for %W",
171 ntname);
173 NtClose (fhandle);
174 return fii.IndexNumber.QuadPart != -1LL;
177 /* easy use of NtOpenFile */
178 HANDLE
179 dll_list::ntopenfile (PCWCHAR ntname, NTSTATUS *pstatus, ULONG openopts,
180 ACCESS_MASK access, HANDLE rootDir)
182 NTSTATUS status;
183 if (!pstatus)
184 pstatus = &status;
186 UNICODE_STRING fn;
187 if (openopts & FILE_OPEN_BY_FILE_ID)
188 RtlInitCountedUnicodeString (&fn, ntname, 8);
189 else
190 RtlInitUnicodeString (&fn, ntname);
192 OBJECT_ATTRIBUTES oa;
193 InitializeObjectAttributes (&oa, &fn, 0, rootDir, NULL);
195 access |= FILE_READ_ATTRIBUTES;
196 if (openopts & FILE_DELETE_ON_CLOSE)
197 access |= DELETE;
198 if (openopts & FILE_DIRECTORY_FILE)
199 access |= FILE_LIST_DIRECTORY;
200 else
201 openopts |= FILE_NON_DIRECTORY_FILE;
203 access |= SYNCHRONIZE;
204 openopts |= FILE_SYNCHRONOUS_IO_NONALERT;
206 HANDLE fh = INVALID_HANDLE_VALUE;
207 ULONG share = FILE_SHARE_VALID_FLAGS;
208 IO_STATUS_BLOCK iosb;
209 *pstatus = NtOpenFile (&fh, access, &oa, &iosb, share, openopts);
210 if (openopts & FILE_OPEN_BY_FILE_ID)
211 debug_printf ("%y = NtOpenFile (%p, a %xh, sh %xh, o %xh, io %y, by id %llX)",
212 *pstatus, fh, access, share, openopts, iosb.Status, *(LONGLONG*)fn.Buffer);
213 else
214 debug_printf ("%y = NtOpenFile (%p, a %xh, sh %xh, o %xh, io %y, '%W')",
215 *pstatus, fh, access, share, openopts, iosb.Status, fn.Buffer);
217 return NT_SUCCESS(*pstatus) ? fh : INVALID_HANDLE_VALUE;
220 bool
221 dll_list::read_fii (HANDLE fh, PFILE_INTERNAL_INFORMATION pfii)
223 pfii->IndexNumber.QuadPart = -1LL;
225 NTSTATUS status;
226 IO_STATUS_BLOCK iosb;
227 status = NtQueryInformationFile (fh, &iosb,
228 pfii, sizeof (*pfii),
229 FileInternalInformation);
230 if (!NT_SUCCESS (status))
232 system_printf ("WARNING: %y = NtQueryInformationFile (%p,"
233 " InternalInfo, io.Status %y)",
234 status, fh, iosb.Status);
235 pfii->IndexNumber.QuadPart = -1LL;
236 return false;
238 return true;
241 /* Into buf if not NULL, write the IndexNumber in pli.
242 Return the number of characters (that would be) written. */
243 static int
244 format_IndexNumber (PWCHAR buf, ssize_t bufsize, LARGE_INTEGER const *pli)
246 if (!buf)
247 return 16;
248 if (bufsize >= 0 && bufsize <= 16)
249 return 0;
250 return __small_swprintf (buf, L"%016X", pli->QuadPart);
253 /* Into buf if not NULL, write the ntname of cygwin installation_root.
254 Return the number of characters (that would be) written. */
255 static int
256 rootname (PWCHAR buf, ssize_t bufsize)
258 UNICODE_STRING &cygroot = cygheap->installation_root;
259 if (!buf)
260 return 6 /* "\??\UN" */ + cygroot.Length / sizeof (WCHAR);
261 return dll_list::form_ntname (buf, bufsize, cygroot.Buffer) - buf;
264 /* Into buf if not NULL, write the string representation of current user sid.
265 Return the number of characters (that would be) written. */
266 static int
267 sidname (PWCHAR buf, ssize_t bufsize)
269 if (!buf)
270 return 128;
271 if (bufsize >= 0 && bufsize <= 128)
272 return 0;
273 UNICODE_STRING sid;
274 WCHAR sidbuf[128+1];
275 RtlInitEmptyUnicodeString (&sid, sidbuf, sizeof (sidbuf));
276 RtlConvertSidToUnicodeString (&sid, cygheap->user.sid (), FALSE);
277 return wcpcpy (buf, sid.Buffer) - buf;
280 /* Into buf if not NULL, write the IndexNumber of the main executable.
281 Return the number of characters (that would be) written. */
282 static int
283 exename (PWCHAR buf, ssize_t bufsize)
285 if (!buf)
286 return format_IndexNumber (NULL, bufsize, NULL);
287 dll *d = dlls.main_executable;
288 return format_IndexNumber (buf, bufsize, &d->fii.IndexNumber);
291 /* Into buf if not NULL, write the current Windows Thread Identifier.
292 Return the number of characters (that would be) written. */
293 static int
294 winthrname (PWCHAR buf, ssize_t bufsize)
296 if (!buf)
297 return sizeof (DWORD) * 4;
298 if (bufsize >= 0 && bufsize <= (int)sizeof (DWORD) * 4)
299 return 0;
301 return __small_swprintf (buf, L"%08X%08X",
302 GetCurrentProcessId(), GetCurrentThreadId());
305 struct namepart {
306 PCWCHAR text; /* used when no pathfunc, description otherwise */
307 int (*textfunc)(PWCHAR buf, ssize_t bufsize);
308 bool mutex_from_dir; /* on path-separators add mutex-separator */
309 bool create_dir;
311 /* mutex name is formed along dir names */
312 static namepart const
313 forkable_nameparts[] = {
314 /* text textfunc mutex_from_dir create */
315 { L"<cygroot>", rootname, false, false, },
316 { L"\\var\\run\\", NULL, false, false, },
317 { L"cygfork", NULL, true, false, },
318 { L"<sid>", sidname, true, true, },
319 { L"<exe>", exename, false, false, },
320 { MUTEXSEP, NULL, false, false, },
321 { L"<winthr>", winthrname, true, true, },
323 { NULL, NULL },
326 /* Nominate the hardlink to an individual DLL inside dirx_name,
327 that ends with the path separator (hence the "x" varname).
328 With NULL as dirx_name, never nominate the hardlink any more.
329 With "" as dirx_name, denominate the hardlink. */
330 void
331 dll::nominate_forkable (PCWCHAR dirx_name)
333 if (!dirx_name)
335 debug_printf ("type %d disable %W", type, ntname);
336 forkable_ntname = NULL; /* never create a hardlink for this dll */
339 if (!forkable_ntname)
340 return;
342 PWCHAR next = wcpcpy (forkable_ntname, dirx_name);
344 if (!*forkable_ntname)
345 return; /* denominate */
347 if (type == DLL_LOAD)
349 /* Multiple dynamically loaded dlls can have identical basenames
350 * when loaded from different directories. But still the original
351 * basename may serve as linked dependency for another dynamically
352 * loaded dll. So we have to create a separate directory for the
353 * dynamically loaded dll - using the dll's IndexNumber as name. */
354 next += format_IndexNumber (next, -1, &fii.IndexNumber);
355 next = wcpcpy (next, L"\\");
357 wcpcpy (next, modname);
360 /* Create the nominated hardlink for one indivitual dll,
361 inside another subdirectory when dynamically loaded.
363 We've not found a performant way yet to protect fork against
364 updates to main executables and/or dlls that do not reside on
365 the same NTFS filesystem as the <cygroot>/var/run/cygfork/
366 directory. But as long as the main executable can be hardlinked,
367 dll redirection works for any other hardlink-able dll, while
368 non-hardlink-able dlls are used from their original location. */
369 bool
370 dll::create_forkable ()
372 if (!forkable_ntname || !*forkable_ntname)
373 return true; /* disabled */
375 PWCHAR ntname = forkable_ntname;
377 PWCHAR last = NULL;
378 bool success = true;
379 if (type >= DLL_LOAD)
381 last = wcsrchr (ntname, L'\\');
382 if (!last)
383 return false;
384 *last = L'\0';
385 success = mkdirs (ntname, 1);
386 *last = L'\\';
387 if (!success)
388 return false;
391 /* open device as parent handle for FILE_OPEN_BY_FILE_ID */
392 PWCHAR devname = dll_list::nt_max_path_buf ();
393 PWCHAR n = ntname;
394 PWCHAR d = devname;
395 int pathseps = 0;
396 while (*n)
398 if (*d == L'\\' && ++pathseps > 4)
399 break; // "\\??\\UNC\\server\\share"
400 *d = *n++;
401 if (*d++ == L':')
402 break; // "\\??\\C:"
404 *d = L'\0';
406 HANDLE devhandle = dll_list::ntopenfile (devname);
407 if (devhandle == INVALID_HANDLE_VALUE)
408 return false; /* impossible */
410 int ntlen = wcslen (ntname);
411 int bufsize = sizeof (FILE_LINK_INFORMATION) + ntlen * sizeof (*ntname);
412 PFILE_LINK_INFORMATION pfli = (PFILE_LINK_INFORMATION) alloca (bufsize);
414 wcscpy (pfli->FileName, ntname);
416 pfli->FileNameLength = ntlen * sizeof (*ntname);
417 pfli->ReplaceIfExists = FALSE; /* allow concurrency */
418 pfli->RootDirectory = NULL;
420 /* When we get STATUS_TRANSACTION_NOT_ACTIVE from hardlink creation,
421 the current process has renamed the file while it had the readonly
422 attribute. The rename() function uses a transaction for combined
423 writeable+rename action if possible to provide atomicity.
424 Although the transaction is closed afterwards, creating a hardlink
425 for this file requires the FILE_WRITE_ATTRIBUTES access, for unknown
426 reason. On the other hand, always requesting FILE_WRITE_ATTRIBUTES
427 would fail for users that do not own the original file. */
428 bool ret = false;
429 int access = 0; /* first attempt */
430 while (true)
432 HANDLE fh = dll_list::ntopenfile ((PCWCHAR)&fii.IndexNumber, NULL,
433 FILE_OPEN_BY_FILE_ID,
434 access,
435 devhandle);
436 if (fh == INVALID_HANDLE_VALUE)
437 break; /* impossible */
439 IO_STATUS_BLOCK iosb;
440 NTSTATUS status = NtSetInformationFile (fh, &iosb, pfli, bufsize,
441 FileLinkInformation);
442 NtClose (fh);
443 debug_printf ("%y = NtSetInformationFile (%p, FileLink %W, iosb.Status %y)",
444 status, fh, pfli->FileName, iosb.Status);
445 if (NT_SUCCESS (status) || status == STATUS_OBJECT_NAME_COLLISION)
447 ret = true;
448 break;
451 if (status != STATUS_TRANSACTION_NOT_ACTIVE ||
452 access == FILE_WRITE_ATTRIBUTES)
453 break;
455 access = FILE_WRITE_ATTRIBUTES; /* second attempt */
458 NtClose (devhandle);
460 return ret;
463 /* return the number of characters necessary to store one forkable name */
464 size_t
465 dll_list::forkable_ntnamesize (dll_type type, PCWCHAR fullntname, PCWCHAR modname)
467 /* per process, this is the first forkables-method ever called */
468 if (cygwin_shared->forkable_hardlink_support == 0) /* Unknown */
470 /* check existence of forkables dir */
471 /* nt_max_path_buf () is already used in dll_list::alloc.
472 But as this is run in the very first cygwin process only,
473 using some heap is not a performance issue here. */
474 PWCHAR pbuf = (PWCHAR) cmalloc_abort (HEAP_BUF,
475 NT_MAX_PATH * sizeof (WCHAR));
476 PWCHAR pnext = pbuf;
477 for (namepart const *part = forkable_nameparts; part->text; ++part)
479 if (part->textfunc)
480 pnext += part->textfunc (pnext, -1);
481 else
482 pnext += __small_swprintf (pnext, L"%W", part->text);
483 if (part->mutex_from_dir)
484 break; /* up to first mutex-naming dir */
487 UNICODE_STRING fn;
488 RtlInitUnicodeString (&fn, pbuf);
490 HANDLE dh = INVALID_HANDLE_VALUE;
491 fs_info fsi;
492 if (fsi.update (&fn, NULL) &&
493 /* FIXME: !fsi.is_readonly () && */
494 fsi.is_ntfs ())
495 dh = ntopenfile (pbuf, NULL, FILE_DIRECTORY_FILE);
496 if (dh != INVALID_HANDLE_VALUE)
498 cygwin_shared->forkable_hardlink_support = 1; /* Yes */
499 NtClose (dh);
500 debug_printf ("enabled");
502 else
504 cygwin_shared->forkable_hardlink_support = -1; /* No */
505 debug_printf ("disabled, missing or not on NTFS %W", fn.Buffer);
507 cfree (pbuf);
510 if (!forkables_supported ())
511 return 0;
513 if (!forkables_dirx_size)
515 DWORD forkables_mutex_size = 0;
516 bool needsep = false;
517 for (namepart const *part = forkable_nameparts; part->text; ++part)
519 if (needsep)
521 forkables_dirx_size += wcslen (PATHSEP);
522 forkables_mutex_size += wcslen (MUTEXSEP);
524 needsep = part->mutex_from_dir;
525 int len = 0;
526 if (part->textfunc)
527 len = part->textfunc (NULL, 0);
528 else
529 len = wcslen (part->text);
530 forkables_dirx_size += len;
531 forkables_mutex_size += len;
533 /* trailing path sep */
534 forkables_dirx_size += wcslen (PATHSEP);
535 /* trailing zeros */
536 ++forkables_dirx_size;
537 ++forkables_mutex_size;
539 /* allocate here, to avoid cygheap size changes during fork */
540 forkables_dirx_ntname = (PWCHAR) cmalloc (HEAP_2_DLL,
541 (forkables_dirx_size + forkables_mutex_size) *
542 sizeof (*forkables_dirx_ntname));
543 *forkables_dirx_ntname = L'\0';
545 forkables_mutex_name = forkables_dirx_ntname + forkables_dirx_size;
546 *forkables_mutex_name = L'\0';
549 size_t ret = forkables_dirx_size;
550 if (type >= DLL_LOAD)
551 ret += format_IndexNumber (NULL, -1, NULL) + 1; /* one more directory */
552 return ret + wcslen (modname);
555 /* Prepare top-level names necessary to nominate individual DLL hardlinks,
556 eventually releasing/removing previous forkable hardlinks. */
557 void
558 dll_list::prepare_forkables_nomination ()
560 PWCHAR pbuf = nt_max_path_buf ();
562 bool needsep = false;
563 bool domutex = false;
564 namepart const *part;
565 for (part = forkable_nameparts; part->text; ++part)
567 if (part->mutex_from_dir)
568 domutex = true; /* mutex naming starts with first mutex_from_dir */
569 if (!domutex)
570 continue;
571 if (needsep)
572 pbuf += __small_swprintf (pbuf, L"%W", MUTEXSEP);
573 needsep = part->mutex_from_dir;
574 if (part->textfunc)
575 pbuf += part->textfunc (pbuf, -1);
576 else
577 pbuf += __small_swprintf (pbuf, L"%W", part->text);
580 if (!wcscmp (forkables_mutex_name, nt_max_path_buf ()))
581 return; /* nothing changed */
583 if (*forkables_mutex_name &&
584 wcscmp (forkables_mutex_name, nt_max_path_buf ()))
586 /* The mutex name has changed since last fork and we either have
587 dlopen'ed a more recent or dlclose'd the most recent dll,
588 so we will not use the current forkable hardlinks any more.
589 Removing from the file system is done later, upon exit. */
590 close_mutex ();
591 denominate_forkables ();
593 wcscpy (forkables_mutex_name, nt_max_path_buf ());
595 pbuf = forkables_dirx_ntname;
596 needsep = false;
597 for (namepart const *part = forkable_nameparts; part->text; ++part)
599 if (needsep)
600 pbuf += __small_swprintf (pbuf, L"%W", PATHSEP);
601 needsep = part->mutex_from_dir;
602 if (part->textfunc)
603 pbuf += part->textfunc (pbuf, -1);
604 else
605 pbuf += __small_swprintf (pbuf, L"%W", part->text);
607 pbuf += __small_swprintf (pbuf, L"%W", PATHSEP);
609 debug_printf ("forkables dir %W", forkables_dirx_ntname);
610 debug_printf ("forkables mutex %W", forkables_mutex_name);
613 /* Test if creating hardlinks is necessary. If creating hardlinks is possible
614 in general, each individual dll is tested if its previously created
615 hardlink (if any, or the original file) still is the same.
616 Testing is protected against hardlink removal by concurrent processes. */
617 void
618 dll_list::update_forkables_needs ()
620 if (forkables_created)
622 /* already have created hardlinks in this process, ... */
623 dll *d = &start;
624 while ((d = d->next) != NULL)
625 if (d->forkable_ntname && !*d->forkable_ntname)
627 /* ... but another dll was loaded since last fork */
628 debug_printf ("needed, since last fork loaded %W", d->ntname);
629 forkables_created = false;
630 break;
635 /* Create the nominated forkable hardlinks and directories as necessary,
636 mutex-protected to avoid concurrent processes removing them. */
637 bool
638 dll_list::update_forkables ()
640 /* existence of mutex indicates that we use these hardlinks */
641 if (!forkables_mutex)
643 /* neither my parent nor myself did have need for hardlinks yet */
644 forkables_mutex = CreateMutexW (&sec_none, FALSE,
645 forkables_mutex_name);
646 debug_printf ("%p = CreateMutexW (%W): %E",
647 forkables_mutex, forkables_mutex_name);
648 if (!forkables_mutex)
649 return false;
651 /* Make sure another process does not rmdirs_synchronized () */
652 debug_printf ("WFSO (%p, %W, inf)...",
653 forkables_mutex, forkables_mutex_name);
654 DWORD ret = WaitForSingleObject (forkables_mutex, INFINITE);
655 debug_printf ("%u = WFSO (%p, %W)",
656 ret, forkables_mutex, forkables_mutex_name);
657 switch (ret)
659 case WAIT_OBJECT_0:
660 case WAIT_ABANDONED:
661 break;
662 default:
663 system_printf ("cannot wait for mutex %W: %E",
664 forkables_mutex_name);
665 return false;
668 BOOL bret = ReleaseMutex (forkables_mutex);
669 debug_printf ("%d = ReleaseMutex (%p, %W)",
670 bret, forkables_mutex, forkables_mutex_name);
673 return create_forkables ();
676 /* Create the nominated forkable hardlinks and directories as necessary,
677 as well as the .local file for dll-redirection. */
678 bool
679 dll_list::create_forkables ()
681 bool success = true;
683 int lastsepcount = 1; /* we have trailing pathsep */
684 for (namepart const *part = forkable_nameparts; part->text; ++part)
685 if (part->create_dir)
686 ++lastsepcount;
688 PWCHAR ntname = nt_max_path_buf ();
689 wcsncpy (ntname, forkables_dirx_ntname, NT_MAX_PATH);
691 if (!mkdirs (ntname, lastsepcount))
692 success = false;
694 if (success)
696 /* create the DotLocal file as empty file */
697 wcsncat (ntname, main_executable->modname, NT_MAX_PATH);
698 wcsncat (ntname, L".local", NT_MAX_PATH);
700 UNICODE_STRING fn;
701 RtlInitUnicodeString (&fn, ntname);
703 OBJECT_ATTRIBUTES oa;
704 InitializeObjectAttributes (&oa, &fn, 0, NULL, NULL);
705 HANDLE hlocal = NULL;
706 NTSTATUS status;
707 IO_STATUS_BLOCK iosb;
708 status = NtCreateFile (&hlocal, GENERIC_WRITE | SYNCHRONIZE,
709 &oa, &iosb, NULL, FILE_ATTRIBUTE_NORMAL,
710 FILE_SHARE_READ | FILE_SHARE_WRITE,
711 FILE_CREATE,
712 FILE_NON_DIRECTORY_FILE
713 | FILE_SYNCHRONOUS_IO_NONALERT,
714 NULL, 0);
715 if (NT_SUCCESS (status))
716 CloseHandle (hlocal);
717 else if (status != STATUS_OBJECT_NAME_COLLISION) /* already exists */
718 success = false;
719 debug_printf ("%y = NtCreateFile (%p, %W)", status, hlocal, ntname);
722 if (success)
724 dll *d = &start;
725 while ((d = d->next))
726 if (!d->create_forkable ())
727 d->nominate_forkable (NULL); /* never again */
728 debug_printf ("hardlinks created");
731 return success;
734 static void
735 rmdirs_synchronized (WCHAR ntbuf[NT_MAX_PATH], int depth, int maxdepth,
736 PFILE_DIRECTORY_INFORMATION pfdi, ULONG fdisize)
738 if (depth == maxdepth)
740 debug_printf ("sync on %W", ntbuf);
741 /* calculate mutex name from path parts, using
742 full path name length to allocate mutex name buffer */
743 WCHAR mutexname[wcslen (ntbuf)];
744 mutexname[0] = L'\0';
745 PWCHAR mutexnext = mutexname;
747 /* mutex name is formed by dir names */
748 int pathcount = 0;
749 for (namepart const *part = forkable_nameparts; part->text; ++part)
750 if (part->mutex_from_dir)
751 ++pathcount;
753 PWCHAR pathseps[pathcount];
755 /* along the path separators split needed path parts */
756 int i = pathcount;
757 while (--i >= 0)
758 if ((pathseps[i] = wcsrchr (ntbuf, L'\\')))
759 *pathseps[i] = L'\0';
760 else
761 return; /* something's wrong */
763 /* build the mutex name from dir names */
764 for (i = 0; i < pathcount; ++i)
766 if (i > 0)
767 mutexnext = wcpcpy (mutexnext, MUTEXSEP);
768 mutexnext = wcpcpy (mutexnext, &pathseps[i][1]);
769 *pathseps[i] = L'\\'; /* restore full path */
772 HANDLE mutex = CreateMutexW (&sec_none_nih, TRUE, mutexname);
773 DWORD lasterror = GetLastError ();
774 debug_printf ("%p = CreateMutexW (%W): %E", mutex, mutexname);
775 if (mutex)
777 if (lasterror != ERROR_ALREADY_EXISTS)
779 debug_printf ("cleaning up for mutex %W", mutexname);
780 rmdirs (ntbuf);
782 BOOL bret = CloseHandle (mutex);
783 debug_printf ("%d = CloseHandle (%p, %W): %E",
784 bret, mutex, mutexname);
786 return;
789 IO_STATUS_BLOCK iosb;
790 NTSTATUS status;
792 HANDLE hdir = dll_list::ntopenfile (ntbuf, &status,
793 FILE_DIRECTORY_FILE |
794 (depth ? FILE_DELETE_ON_CLOSE : 0));
795 if (hdir == INVALID_HANDLE_VALUE)
796 return;
798 PWCHAR plast = ntbuf + wcslen (ntbuf);
799 while (NT_SUCCESS (status = NtQueryDirectoryFile (hdir,
800 NULL, NULL, NULL, &iosb,
801 pfdi, fdisize,
802 FileDirectoryInformation,
803 TRUE, NULL, FALSE)))
804 if (pfdi->FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
806 int namelen = pfdi->FileNameLength / sizeof (WCHAR);
807 if (!wcsncmp (pfdi->FileName, L".", namelen) ||
808 !wcsncmp (pfdi->FileName, L"..", namelen))
809 continue;
810 *plast = L'\\';
811 wcsncpy (plast+1, pfdi->FileName, namelen);
812 plast[1+namelen] = L'\0';
813 rmdirs_synchronized (ntbuf, depth+1, maxdepth, pfdi, fdisize);
814 *plast = L'\0';
816 if (status != STATUS_NO_MORE_FILES)
817 debug_printf ("%y = NtQueryDirectoryFile (%p, io %y, info %d)",
818 status, hdir, iosb.Status, iosb.Information);
819 CloseHandle (hdir);
822 /* Try to lock the mutex handle with almost no timeout, then close the
823 mutex handle. Locking before closing is to get the mutex closing
824 promoted synchronously, otherways we might end up with no one
825 succeeding in create-with-lock, which is the precondition
826 to actually remove the hardlinks from the filesystem. */
827 bool
828 dll_list::close_mutex ()
830 if (!forkables_mutex || !*forkables_mutex_name)
831 return false;
833 HANDLE hmutex = forkables_mutex;
834 forkables_mutex = NULL;
836 bool locked = false;
837 DWORD ret = WaitForSingleObject (hmutex, 1);
838 debug_printf ("%u = WFSO (%p, %W, 1)",
839 ret, hmutex, forkables_mutex_name);
840 switch (ret)
842 case WAIT_OBJECT_0:
843 case WAIT_ABANDONED:
844 locked = true;
845 break;
846 case WAIT_TIMEOUT:
847 break;
848 default:
849 system_printf ("error locking mutex %W: %E", forkables_mutex_name);
850 break;
852 BOOL bret = CloseHandle (hmutex);
853 debug_printf ("%d = CloseHandle (%p, %W): %E",
854 bret, hmutex, forkables_mutex_name);
855 return locked;
858 /* Release the forkable hardlinks, and remove them if the
859 mutex can be create-locked after locked-closing. */
860 void
861 dll_list::cleanup_forkables ()
863 if (!forkables_supported ())
864 return;
866 bool locked = close_mutex ();
868 /* Start the removal below with current forkables dir,
869 which is cleaned in denominate_forkables (). */
870 PWCHAR buf = nt_max_path_buf ();
871 PWCHAR pathsep = wcpncpy (buf, forkables_dirx_ntname, NT_MAX_PATH);
872 buf[NT_MAX_PATH-1] = L'\0';
874 denominate_forkables ();
876 if (!locked)
877 return;
879 /* drop last path separator */
880 while (--pathsep >= buf && *pathsep != L'\\');
881 *pathsep = L'\0';
883 try_remove_forkables (buf, pathsep - buf, NT_MAX_PATH);
886 void
887 dll_list::try_remove_forkables (PWCHAR dirbuf, size_t dirlen, size_t dirbufsize)
889 /* Instead of just the current forkables, try to remove any forkables
890 found, to ensure some cleanup even in situations like power-loss. */
891 PWCHAR end = dirbuf + wcslen (dirbuf);
892 int backcount = 0;
893 for (namepart const *part = forkable_nameparts; part->text; ++part)
894 if (part->create_dir)
896 /* drop one path separator per create_dir */
897 while (--end >= dirbuf && *end != L'\\');
898 if (end < dirbuf)
899 return;
900 *end = L'\0';
901 ++backcount;
904 /* reading one at a time to reduce stack pressure */
905 struct {
906 FILE_DIRECTORY_INFORMATION fdi;
907 WCHAR buf[NAME_MAX];
908 } fdibuf;
909 rmdirs_synchronized (dirbuf, 0, backcount, &fdibuf.fdi, sizeof (fdibuf));
912 void
913 dll_list::denominate_forkables ()
915 *forkables_dirx_ntname = L'\0';
916 *forkables_mutex_name = L'\0';
918 dll *d = &start;
919 while ((d = d->next))
920 d->nominate_forkable (forkables_dirx_ntname);
923 /* Set or clear HANDLE_FLAG_INHERIT for all handles necessary
924 to maintain forkables-hardlinks. */
925 void
926 dll_list::set_forkables_inheritance (bool inherit)
928 DWORD mask = HANDLE_FLAG_INHERIT;
929 DWORD flags = inherit ? HANDLE_FLAG_INHERIT : 0;
931 if (forkables_mutex)
932 SetHandleInformation (forkables_mutex, mask, flags);
935 /* create the forkable hardlinks, if necessary */
936 void
937 dll_list::request_forkables ()
939 if (!forkables_supported ())
940 return;
942 prepare_forkables_nomination ();
944 update_forkables_needs ();
946 set_forkables_inheritance (true);
948 if (forkables_created)
949 return; /* nothing new to create */
951 dll *d = &start;
952 while ((d = d->next))
953 d->nominate_forkable (forkables_dirx_ntname);
955 if (update_forkables ())
956 forkables_created = true;
960 void
961 dll_list::release_forkables ()
963 if (!forkables_supported ())
964 return;
966 set_forkables_inheritance (false);