ocran 1.4.6 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.txt +68 -0
  3. data/README.md +295 -10
  4. data/ext/extconf.rb +5 -1
  5. data/lib/ocran/aot_toolchain.rb +154 -0
  6. data/lib/ocran/command_output.rb +8 -1
  7. data/lib/ocran/compatibility_scan.rb +414 -0
  8. data/lib/ocran/cosmo_toolchain.rb +29 -7
  9. data/lib/ocran/dir_builder.rb +88 -38
  10. data/lib/ocran/direction.rb +190 -30
  11. data/lib/ocran/ed_resource.rb +113 -0
  12. data/lib/ocran/ed_resource_builder.rb +199 -0
  13. data/lib/ocran/file_path_set.rb +9 -5
  14. data/lib/ocran/gem_spec_queryable.rb +7 -3
  15. data/lib/ocran/host_config_helper.rb +11 -1
  16. data/lib/ocran/inno_setup_script_builder.rb +24 -14
  17. data/lib/ocran/launcher_batch_builder.rb +8 -11
  18. data/lib/ocran/npm_package.rb +106 -0
  19. data/lib/ocran/option.rb +425 -35
  20. data/lib/ocran/pe_imports.rb +96 -0
  21. data/lib/ocran/pe_resources.rb +226 -0
  22. data/lib/ocran/refine_pathname.rb +29 -32
  23. data/lib/ocran/roundhouse_builder.rb +316 -0
  24. data/lib/ocran/runner.rb +43 -11
  25. data/lib/ocran/runtime_sdk.rb +252 -0
  26. data/lib/ocran/spinel_builder.rb +183 -0
  27. data/lib/ocran/spinel_compatibility.rb +149 -0
  28. data/lib/ocran/static_runtime/boot.rb +443 -0
  29. data/lib/ocran/static_runtime/build_static_ext.rb +86 -0
  30. data/lib/ocran/static_runtime_builder.rb +814 -0
  31. data/lib/ocran/stub_builder.rb +70 -25
  32. data/lib/ocran/version.rb +1 -1
  33. data/lib/ocran/wasm_builder.rb +570 -0
  34. data/lib/ocran/wasm_compatibility.rb +198 -0
  35. data/lib/ocran/windows_command_escaping.rb +21 -0
  36. data/lib/ocran/zip_payload_builder.rb +18 -9
  37. data/lib/ocran/zip_writer.rb +48 -9
  38. data/src/Makefile +18 -2
  39. data/src/Makefile.mswin +65 -0
  40. data/src/inst_dir.c +34 -6
  41. data/src/path_utils.c +144 -0
  42. data/src/script_info.c +5 -0
  43. data/src/stub.c +77 -19
  44. data/src/stub.rc +1 -1
  45. data/src/system_utils.c +276 -155
  46. data/src/system_utils.h +46 -3
  47. data/src/system_utils_posix.c +243 -148
  48. data/src/unpack.c +9 -0
  49. metadata +22 -4
data/src/system_utils.c CHANGED
@@ -1,101 +1,9 @@
1
1
  #include <windows.h>
2
- #include <ntstatus.h>
3
- #include <ntdef.h>
4
2
  #include <bcrypt.h>
5
3
  #include <stdbool.h>
6
4
  #include "error.h"
7
5
  #include "system_utils.h"
8
6
 
9
- /*
10
- * Returns true if `path` is a “clean” relative path:
11
- * - not empty
12
- * - does not start with a path separator
13
- * - on Windows, no drive-letter spec (e.g. "C:\")
14
- * - no empty segments ("//")
15
- * - no "." or ".." segments
16
- */
17
- bool IsCleanRelativePath(const char *path)
18
- {
19
- if (!path || !*path) {
20
- return false;
21
- }
22
-
23
- #ifdef _WIN32
24
- /* Forbid Windows drive specification (e.g. "C:\") */
25
- if (((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z'))
26
- && path[1] == ':'
27
- && is_path_separator(path[2])) {
28
- return false;
29
- }
30
- #endif
31
-
32
- /* Forbid absolute path (leading '/' or '\') */
33
- if (is_path_separator(*path)) {
34
- return false;
35
- }
36
-
37
- /* Validate each path segment */
38
- const char *p = path;
39
- while (*p) {
40
- const char *start = p;
41
-
42
- /* Advance until next separator or end-of-string */
43
- while (*p && !is_path_separator(*p)) {
44
- p++;
45
- }
46
-
47
- size_t len = p - start;
48
-
49
- /* Reject empty, "." or ".." segments */
50
- if (len == 0
51
- || (len == 1 && start[0] == '.')
52
- || (len == 2 && start[0] == '.' && start[1] == '.')) {
53
- return false;
54
- }
55
-
56
- /* Skip over the separator */
57
- if (*p) {
58
- p++;
59
- }
60
- }
61
-
62
- return true;
63
- }
64
-
65
- // Combines two file path components into a single path, handling path separators.
66
- char *JoinPath(const char *p1, const char *p2)
67
- {
68
- if (p1 == NULL || *p1 == '\0') {
69
- APP_ERROR("p1 is null or empty");
70
- return NULL;
71
- }
72
-
73
- if (p2 == NULL || *p2 == '\0') {
74
- APP_ERROR("p2 is null or empty");
75
- return NULL;
76
- }
77
-
78
- size_t p1_len = strlen(p1);
79
- if (is_path_separator(p1[p1_len - 1])) { p1_len--; }
80
-
81
- size_t p2_len = strlen(p2);
82
- const char *p2_start = p2;
83
- if (is_path_separator(*p2_start)) { p2_start++; p2_len--; }
84
-
85
- size_t joined_len = p1_len + 1 + p2_len;
86
- char *joined_path = calloc(1, joined_len + 1);
87
- if (!joined_path) {
88
- APP_ERROR("Failed to allocate buffer for join path");
89
- return NULL;
90
- }
91
- memcpy(joined_path, p1, p1_len);
92
- joined_path[p1_len] = PATH_SEPARATOR;
93
- memcpy(joined_path + p1_len + 1, p2_start, p2_len);
94
- joined_path[joined_len] = '\0';
95
-
96
- return joined_path;
97
- }
98
-
99
7
  char *ToLongPath(const char *path)
100
8
  {
101
9
  if (!path) {
@@ -122,38 +30,6 @@ char *ToLongPath(const char *path)
122
30
  return buf;
123
31
  }
124
32
 
125
- char *GetParentPath(const char *path)
126
- {
127
- if (!path) {
128
- APP_ERROR("path is NULL");
129
- return NULL;
130
- }
131
-
132
- size_t len = strlen(path);
133
- size_t i = len;
134
-
135
- /* Skip any trailing separators */
136
- while (i > 0 && is_path_separator(path[i])) {
137
- i--;
138
- }
139
-
140
- /* Skip the last segment’s characters */
141
- while (i > 0 && !is_path_separator(path[i])) {
142
- i--;
143
- }
144
-
145
- /* i==0 ⇒ empty parent */
146
-
147
- char *out = malloc(i + 1);
148
- if (!out) {
149
- APP_ERROR("Memory allocation failed for parent path");
150
- return NULL;
151
- }
152
- memcpy(out, path, i);
153
- out[i] = '\0';
154
- return out;
155
- }
156
-
157
33
  /**
158
34
  * Converts a NULL-terminated UTF-16 string to a malloc-allocated UTF-8 string.
159
35
  *
@@ -266,8 +142,12 @@ bool CreateDirectoriesRecursively(const char *dir)
266
142
  memcpy(path, dir, path_len);
267
143
  path[path_len] = '\0';
268
144
 
145
+ // Walk up to the deepest existing ancestor, cutting the path at each
146
+ // separator on the way; p points at the last cut (or the end). The walk
147
+ // stops at the root ("C:\", "\"), which is never cut off.
148
+ size_t root = path_root_length(path);
269
149
  char *p = path + path_len;
270
- do {
150
+ for (;;) {
271
151
  // Convert to UTF-16 for API call
272
152
  wpath = utf8_to_utf16(path);
273
153
  if (!wpath) {
@@ -278,8 +158,6 @@ bool CreateDirectoriesRecursively(const char *dir)
278
158
  DWORD path_attr = GetFileAttributesW(wpath);
279
159
  if (path_attr != INVALID_FILE_ATTRIBUTES) {
280
160
  if (path_attr & FILE_ATTRIBUTE_DIRECTORY) {
281
- free(wpath);
282
- wpath = NULL;
283
161
  break;
284
162
  } else {
285
163
  APP_ERROR("Directory name conflicts with a file(%s)", path);
@@ -287,22 +165,37 @@ bool CreateDirectoriesRecursively(const char *dir)
287
165
  }
288
166
  } else {
289
167
  DWORD err = GetLastError();
290
- if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
291
- // continue;
292
- } else {
168
+ if (err != ERROR_FILE_NOT_FOUND && err != ERROR_PATH_NOT_FOUND) {
293
169
  APP_ERROR("Cannot access the directory, Error=%lu", err);
294
170
  goto cleanup;
295
171
  }
296
172
  }
297
173
 
174
+ // Find the separator before the last remaining segment
175
+ char *q = p;
176
+ while (q > path + root && !is_path_separator(q[-1])) {
177
+ q--;
178
+ }
179
+ if (q <= path + root) {
180
+ // No ancestor below the root exists: the remaining first
181
+ // segment has to be created as well.
182
+ if (!CreateDirectoryW(wpath, NULL)
183
+ && GetLastError() != ERROR_ALREADY_EXISTS) {
184
+ DWORD err = GetLastError();
185
+ APP_ERROR("Failed to create directory '%s', Error=%lu", path, err);
186
+ goto cleanup;
187
+ }
188
+ break;
189
+ }
190
+
298
191
  free(wpath);
299
192
  wpath = NULL;
300
193
 
301
- while (p > path && !is_path_separator(*p)) {
302
- p--;
303
- }
194
+ p = q - 1;
304
195
  *p = '\0';
305
- } while (p >= path);
196
+ }
197
+ free(wpath);
198
+ wpath = NULL;
306
199
 
307
200
  char *end = path + path_len;
308
201
  for (; p < end; p++) {
@@ -317,7 +210,10 @@ bool CreateDirectoriesRecursively(const char *dir)
317
210
  goto cleanup;
318
211
  }
319
212
 
320
- if (!CreateDirectoryW(wpath, NULL)) {
213
+ // Already existing: a trailing separator names the directory just
214
+ // created once more, or another process was quicker.
215
+ if (!CreateDirectoryW(wpath, NULL)
216
+ && GetLastError() != ERROR_ALREADY_EXISTS) {
321
217
  DWORD err = GetLastError();
322
218
  APP_ERROR("Failed to create directory '%s', Error=%lu", path, err);
323
219
  goto cleanup;
@@ -339,7 +235,9 @@ cleanup:
339
235
  return result;
340
236
  }
341
237
 
342
- // Deletes a directory and all its contents recursively.
238
+ // Deletes a directory and all its contents recursively. Keeps going after a
239
+ // failure so that as much as possible gets deleted; returns false if
240
+ // anything could not be.
343
241
  bool DeleteRecursively(const char *path)
344
242
  {
345
243
  if (!path || !*path) {
@@ -347,6 +245,8 @@ bool DeleteRecursively(const char *path)
347
245
  return false;
348
246
  }
349
247
 
248
+ bool success = true;
249
+
350
250
  char *findPath = JoinPath(path, "*");
351
251
  if (!findPath) {
352
252
  APP_ERROR("Failed to build find path for deletion");
@@ -375,6 +275,7 @@ bool DeleteRecursively(const char *path)
375
275
  char *name = utf16_to_utf8(wname);
376
276
  if (!name) {
377
277
  APP_ERROR("Failed to convert filename to UTF-8");
278
+ success = false;
378
279
  continue;
379
280
  }
380
281
 
@@ -382,22 +283,49 @@ bool DeleteRecursively(const char *path)
382
283
  free(name);
383
284
  if (!subPath) {
384
285
  APP_ERROR("Failed to build delete file path");
385
- break;
286
+ success = false;
287
+ continue;
386
288
  }
387
289
 
388
290
  wchar_t *wsubPath = utf8_to_utf16(subPath);
389
291
  if (!wsubPath) {
390
292
  APP_ERROR("Failed to convert subpath to UTF-16");
391
293
  free(subPath);
294
+ success = false;
392
295
  continue;
393
296
  }
394
297
 
395
- if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
396
- DeleteRecursively(subPath);
397
- } else if (!DeleteFileW(wsubPath)) {
398
- DWORD err = GetLastError();
399
- APP_ERROR("Failed to delete file, Error=%lu", err);
400
- MoveFileExW(wsubPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
298
+ DWORD attrs = findData.dwFileAttributes;
299
+ if ((attrs & FILE_ATTRIBUTE_DIRECTORY)
300
+ && (attrs & FILE_ATTRIBUTE_REPARSE_POINT)) {
301
+ // A junction or directory symlink: remove the link itself.
302
+ // Recursing would delete the contents of its target, which
303
+ // lies outside the directory being deleted.
304
+ if (!RemoveDirectoryW(wsubPath)) {
305
+ DWORD err = GetLastError();
306
+ APP_ERROR("Failed to delete directory link, Error=%lu", err);
307
+ success = false;
308
+ }
309
+ } else if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
310
+ if (!DeleteRecursively(subPath)) {
311
+ success = false;
312
+ }
313
+ } else {
314
+ // DeleteFileW refuses read-only files, e.g. ones the
315
+ // application copied from a read-only source or git's
316
+ // object files. Links are left as they are: their
317
+ // attributes may be their target's.
318
+ if ((attrs & FILE_ATTRIBUTE_READONLY)
319
+ && !(attrs & FILE_ATTRIBUTE_REPARSE_POINT)) {
320
+ SetFileAttributesW(wsubPath, FILE_ATTRIBUTE_NORMAL);
321
+ }
322
+ if (!DeleteFileW(wsubPath)) {
323
+ DWORD err = GetLastError();
324
+ APP_ERROR("Failed to delete file, Error=%lu", err);
325
+ // Only succeeds with administrator rights; best effort.
326
+ MoveFileExW(wsubPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
327
+ success = false;
328
+ }
401
329
  }
402
330
 
403
331
  free(wsubPath);
@@ -412,15 +340,24 @@ bool DeleteRecursively(const char *path)
412
340
  return false;
413
341
  }
414
342
 
343
+ // RemoveDirectoryW refuses a read-only directory as well.
344
+ DWORD dir_attrs = GetFileAttributesW(wpath);
345
+ if (dir_attrs != INVALID_FILE_ATTRIBUTES
346
+ && (dir_attrs & FILE_ATTRIBUTE_READONLY)
347
+ && !(dir_attrs & FILE_ATTRIBUTE_REPARSE_POINT)) {
348
+ SetFileAttributesW(wpath, FILE_ATTRIBUTE_NORMAL);
349
+ }
350
+
415
351
  if (!RemoveDirectoryW(wpath)) {
416
352
  DWORD err = GetLastError();
417
353
  APP_ERROR("Failed to delete directory, Error=%lu", err);
354
+ // Only succeeds with administrator rights; best effort.
418
355
  MoveFileExW(wpath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
419
356
  free(wpath);
420
357
  return false;
421
358
  }
422
359
  free(wpath);
423
- return true;
360
+ return success;
424
361
  }
425
362
 
426
363
  static bool generate_unique_name(char *buffer, size_t buffer_size)
@@ -432,10 +369,10 @@ static bool generate_unique_name(char *buffer, size_t buffer_size)
432
369
  NTSTATUS b = BCryptGenRandom(
433
370
  0,
434
371
  (PUCHAR)buffer,
435
- buffer_size,
372
+ (ULONG)buffer_size,
436
373
  BCRYPT_USE_SYSTEM_PREFERRED_RNG
437
374
  );
438
- if (!NT_SUCCESS(b)) {
375
+ if (!BCRYPT_SUCCESS(b)) {
439
376
  return false;
440
377
  }
441
378
 
@@ -547,13 +484,24 @@ cleanup:
547
484
  // Retrieves the path to the temporary directory for the current user.
548
485
  char *GetTempDirectoryPath(void)
549
486
  {
550
- wchar_t *wtemp_dir = calloc(MAX_PATH, sizeof(*wtemp_dir));
487
+ /* A TEMP longer than the buffer makes GetTempPathW return the size it
488
+ needs and leave the buffer undefined, so ask for the size first. */
489
+ DWORD size = GetTempPathW(0, NULL);
490
+ if (size == 0) {
491
+ DWORD err = GetLastError();
492
+ APP_ERROR("Failed to get temp path length, Error=%lu", err);
493
+ return NULL;
494
+ }
495
+
496
+ wchar_t *wtemp_dir = calloc((size_t)size + 1, sizeof(*wtemp_dir));
551
497
  if (!wtemp_dir) {
552
498
  APP_ERROR("Memory allocation failed for temp directory");
553
499
  return NULL;
554
500
  }
555
501
 
556
- if (!GetTempPathW(MAX_PATH, wtemp_dir)) {
502
+ /* On success the length without the terminating NUL comes back. */
503
+ DWORD len = GetTempPathW(size + 1, wtemp_dir);
504
+ if (len == 0 || len > size) {
557
505
  DWORD err = GetLastError();
558
506
  APP_ERROR("Failed to get temp path, Error=%lu", err);
559
507
  free(wtemp_dir);
@@ -845,24 +793,120 @@ size_t GetMemoryMapSize(const MemoryMap *map)
845
793
  return map->size;
846
794
  }
847
795
 
796
+ /* Guards ChildProcess and TerminationRequested, which the console control
797
+ handler thread reads while the main thread launches and reaps the child. */
798
+ static CRITICAL_SECTION ChildLock;
799
+
800
+ /* Process handle of the running child; NULL while there is none. */
801
+ static HANDLE ChildProcess = NULL;
802
+
803
+ /* Set by the console control handler when Windows is about to terminate
804
+ the stub; the main thread then no longer starts the child. */
805
+ static bool TerminationRequested = false;
806
+
807
+ /* State of the cleanup routine, see RunCleanupRoutine(). */
808
+ #define CLEANUP_PENDING 0
809
+ #define CLEANUP_RUNNING 1
810
+ #define CLEANUP_DONE 2
811
+ static void (*CleanupRoutine)(void) = NULL;
812
+ static volatile LONG CleanupState = CLEANUP_PENDING;
813
+
814
+ void SetCleanupRoutine(void (*routine)(void))
815
+ {
816
+ CleanupRoutine = routine;
817
+ }
818
+
819
+ void RunCleanupRoutine(void)
820
+ {
821
+ if (InterlockedCompareExchange(&CleanupState, CLEANUP_RUNNING,
822
+ CLEANUP_PENDING) == CLEANUP_PENDING) {
823
+ if (CleanupRoutine) {
824
+ CleanupRoutine();
825
+ }
826
+ InterlockedExchange(&CleanupState, CLEANUP_DONE);
827
+ return;
828
+ }
829
+
830
+ /* The other thread is running it: wait until it is done, so that
831
+ neither thread ends the process in the middle of the cleanup. */
832
+ while (CleanupState != CLEANUP_DONE) {
833
+ Sleep(10);
834
+ }
835
+ }
836
+
837
+ /* Windows terminates the stub about 5 seconds after a close or shutdown
838
+ event. These split that time between the child and the cleanup. */
839
+ #define CLOSE_CHILD_WAIT_MS 2500
840
+ #define CLOSE_TERMINATE_WAIT_MS 500
841
+ #define CLOSE_EXTRACT_WAIT_MS 2500
842
+
848
843
  /**
849
844
  * @brief Handle console control events in the parent process.
850
845
  *
851
- * This handler ignores all console control events (Ctrl+C, Ctrl+Break, etc.)
852
- * in the parent process so it can complete cleanup without interruption.
853
- * Child processes (e.g., Ruby) receive these events and exit quickly,
854
- * allowing the parent to perform final cleanup tasks.
846
+ * Ctrl+C and Ctrl+Break are ignored in the parent: the child shares the
847
+ * console, receives the same event and decides whether to exit, and the
848
+ * parent cleans up after it as usual.
849
+ *
850
+ * Closing the console window and system shutdown are different: Windows
851
+ * terminates the process as soon as this handler returns, whatever it
852
+ * returns, so the cleanup has to happen here. The handler waits for the
853
+ * child (which got the same event) for a bounded time, ends it if it is
854
+ * still running (its open files could not be deleted otherwise), and runs
855
+ * the cleanup routine before returning. Should the stub still be extracting,
856
+ * the main thread does not start the child any more and the handler gives
857
+ * it a moment to reach its own cleanup first.
858
+ *
859
+ * CTRL_LOGOFF_EVENT is ignored like Ctrl+C: it reaches only services, and
860
+ * does so whenever any user logs off, which a service has to survive.
861
+ *
862
+ * Runs on a thread of its own that Windows creates for the event.
855
863
  *
856
864
  * @param dwCtrlType The type of console control event received.
857
- * @return TRUE to indicate the event was handled and should be ignored.
865
+ * @return TRUE to indicate the event was handled.
858
866
  */
859
867
  static BOOL WINAPI ConsoleHandleRoutine(DWORD dwCtrlType)
860
868
  {
869
+ if (dwCtrlType != CTRL_CLOSE_EVENT && dwCtrlType != CTRL_SHUTDOWN_EVENT) {
870
+ return TRUE;
871
+ }
872
+
873
+ DEBUG("Console control event %lu: cleaning up before Windows ends the stub",
874
+ (unsigned long)dwCtrlType);
875
+
876
+ HANDLE child = NULL;
877
+ EnterCriticalSection(&ChildLock);
878
+ TerminationRequested = true;
879
+ if (ChildProcess
880
+ && !DuplicateHandle(GetCurrentProcess(), ChildProcess,
881
+ GetCurrentProcess(), &child,
882
+ 0, FALSE, DUPLICATE_SAME_ACCESS)) {
883
+ child = NULL;
884
+ }
885
+ LeaveCriticalSection(&ChildLock);
886
+
887
+ if (child) {
888
+ if (WaitForSingleObject(child, CLOSE_CHILD_WAIT_MS) == WAIT_TIMEOUT) {
889
+ DEBUG("The application did not exit in time; terminating it");
890
+ TerminateProcess(child, (UINT)STATUS_CONTROL_C_EXIT);
891
+ WaitForSingleObject(child, CLOSE_TERMINATE_WAIT_MS);
892
+ }
893
+ CloseHandle(child);
894
+ } else {
895
+ DWORD start = GetTickCount();
896
+ while (CleanupState == CLEANUP_PENDING
897
+ && GetTickCount() - start < CLOSE_EXTRACT_WAIT_MS) {
898
+ Sleep(10);
899
+ }
900
+ }
901
+
902
+ RunCleanupRoutine();
861
903
  return TRUE;
862
904
  }
863
905
 
864
906
  bool InitializeSignalHandling(void)
865
907
  {
908
+ InitializeCriticalSection(&ChildLock);
909
+
866
910
  if (!SetConsoleCtrlHandler(ConsoleHandleRoutine, TRUE)) {
867
911
  DWORD err = GetLastError();
868
912
  APP_ERROR("Failed to set console control handler, Error=%lu", err);
@@ -871,6 +915,11 @@ bool InitializeSignalHandling(void)
871
915
  return true;
872
916
  }
873
917
 
918
+ /* Windows has no signal deaths to reproduce: the exit code says it all. */
919
+ void ReraiseChildSignal(void)
920
+ {
921
+ }
922
+
874
923
  bool SetEnvVar(const char *name, const char *value)
875
924
  {
876
925
  if (!name) {
@@ -960,10 +1009,41 @@ static size_t quoted_args(char *args, char *argv[])
960
1009
  return args_len;
961
1010
  }
962
1011
 
1012
+ /*
1013
+ * Creates a job that kills its processes when its last handle is closed,
1014
+ * i.e. when the stub exits or is killed, so that ending the stub (Task
1015
+ * Manager, taskkill /F, a service manager) does not orphan the application.
1016
+ * Processes in the job may create children outside of it, so only the
1017
+ * direct child is tied to the stub: whatever the application starts is
1018
+ * not affected. The handle is not inheritable, and must stay open for as
1019
+ * long as the child runs. Returns NULL on failure.
1020
+ */
1021
+ static HANDLE create_kill_on_close_job(void)
1022
+ {
1023
+ HANDLE job = CreateJobObjectW(NULL, NULL);
1024
+ if (!job) {
1025
+ DEBUG("CreateJobObjectW failed (%lu)", GetLastError());
1026
+ return NULL;
1027
+ }
1028
+
1029
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION info;
1030
+ ZeroMemory(&info, sizeof(info));
1031
+ info.BasicLimitInformation.LimitFlags =
1032
+ JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
1033
+ if (!SetInformationJobObject(job, JobObjectExtendedLimitInformation,
1034
+ &info, sizeof(info))) {
1035
+ DEBUG("SetInformationJobObject failed (%lu)", GetLastError());
1036
+ CloseHandle(job);
1037
+ return NULL;
1038
+ }
1039
+ return job;
1040
+ }
1041
+
963
1042
  bool CreateAndWaitForProcess(const char *app_name, char *argv[], int *exit_code)
964
1043
  {
965
1044
  PROCESS_INFORMATION pi = { 0 };
966
1045
  STARTUPINFOW si = { .cb = sizeof(si) };
1046
+ HANDLE job = NULL;
967
1047
  bool result = false;
968
1048
  char *cmd_line = NULL;
969
1049
  wchar_t *wapp_name = NULL;
@@ -991,8 +1071,39 @@ bool CreateAndWaitForProcess(const char *app_name, char *argv[], int *exit_code)
991
1071
  goto cleanup;
992
1072
  }
993
1073
 
994
- if (!CreateProcessW(wapp_name, wcmd_line, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
995
- APP_ERROR("Failed to create process (%lu)", GetLastError());
1074
+ /* Launch under ChildLock, so that the console control handler either
1075
+ sees the child or keeps it from being started at all. */
1076
+ EnterCriticalSection(&ChildLock);
1077
+ if (TerminationRequested) {
1078
+ LeaveCriticalSection(&ChildLock);
1079
+ APP_ERROR("Windows is terminating the stub; not starting the application");
1080
+ goto cleanup;
1081
+ }
1082
+ /* Suspended, so that the child is in the job before it runs any code. */
1083
+ if (!CreateProcessW(wapp_name, wcmd_line, NULL, NULL, TRUE, CREATE_SUSPENDED,
1084
+ NULL, NULL, &si, &pi)) {
1085
+ DWORD err = GetLastError();
1086
+ LeaveCriticalSection(&ChildLock);
1087
+ APP_ERROR("Failed to create process (%lu)", err);
1088
+ goto cleanup;
1089
+ }
1090
+ ChildProcess = pi.hProcess;
1091
+ LeaveCriticalSection(&ChildLock);
1092
+
1093
+ /* Tying the child to the stub is best effort: a job the stub itself
1094
+ runs in may forbid it, which must not keep the application from
1095
+ starting. */
1096
+ job = create_kill_on_close_job();
1097
+ if (job && !AssignProcessToJobObject(job, pi.hProcess)) {
1098
+ DEBUG("Could not assign the application to a job (%lu); it will "
1099
+ "outlive the stub if the stub is killed", GetLastError());
1100
+ CloseHandle(job);
1101
+ job = NULL;
1102
+ }
1103
+
1104
+ if (ResumeThread(pi.hThread) == (DWORD)-1) {
1105
+ APP_ERROR("Failed to start the application (%lu)", GetLastError());
1106
+ TerminateProcess(pi.hProcess, 1);
996
1107
  goto cleanup;
997
1108
  }
998
1109
 
@@ -1019,10 +1130,20 @@ cleanup:
1019
1130
  free(wcmd_line);
1020
1131
  }
1021
1132
  if (pi.hProcess && pi.hProcess != INVALID_HANDLE_VALUE) {
1133
+ /* The console control handler duplicates the handle under the
1134
+ lock; unpublish it before closing it. */
1135
+ EnterCriticalSection(&ChildLock);
1136
+ ChildProcess = NULL;
1137
+ LeaveCriticalSection(&ChildLock);
1022
1138
  CloseHandle(pi.hProcess);
1023
1139
  }
1024
1140
  if (pi.hThread && pi.hThread != INVALID_HANDLE_VALUE) {
1025
1141
  CloseHandle(pi.hThread);
1026
1142
  }
1143
+ if (job) {
1144
+ /* Kills the child if it still runs, which happens only when
1145
+ waiting for it failed. */
1146
+ CloseHandle(job);
1147
+ }
1027
1148
  return result;
1028
1149
  }
data/src/system_utils.h CHANGED
@@ -18,6 +18,18 @@ static inline bool is_path_separator(char c) {
18
18
  #endif
19
19
  }
20
20
 
21
+ /* Length of the root prefix of an absolute path ("/", or "C:\" on
22
+ Windows), which a parent path never loses; 0 for a relative path. */
23
+ static inline size_t path_root_length(const char *path) {
24
+ #ifdef _WIN32
25
+ if (((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z'))
26
+ && path[1] == ':' && is_path_separator(path[2])) {
27
+ return 3;
28
+ }
29
+ #endif
30
+ return is_path_separator(path[0]) ? 1 : 0;
31
+ }
32
+
21
33
  /**
22
34
  * @brief Check if a path is a “clean” relative path.
23
35
  *
@@ -183,9 +195,9 @@ size_t GetMemoryMapSize(const MemoryMap *map);
183
195
  * This function sets up console control and POSIX signal handlers so that
184
196
  * the parent process is not prematurely terminated during initialization and
185
197
  * cleanup phases. On Windows, a console control handler is registered to ignore
186
- * control events (e.g., Ctrl+C) in the parent process. On POSIX, relevant
187
- * signals (SIGINT, SIGTERM, SIGHUP) can be configured to allow cleanup
188
- * processing before exit.
198
+ * control events (e.g., Ctrl+C) in the parent process. On POSIX, SIGINT,
199
+ * SIGTERM, SIGHUP and SIGQUIT are caught and forwarded to the child once it
200
+ * runs, so the stub outlives it and can clean up.
189
201
  *
190
202
  * @return
191
203
  * - true if initialization succeeded
@@ -193,6 +205,37 @@ size_t GetMemoryMapSize(const MemoryMap *map);
193
205
  */
194
206
  bool InitializeSignalHandling(void);
195
207
 
208
+ /**
209
+ * @brief Registers the routine that deletes the extraction directory.
210
+ *
211
+ * The routine runs at most once, through RunCleanupRoutine(): at the end of
212
+ * main(), or on Windows from the console control handler when Windows is
213
+ * about to terminate the stub (console window closed, system shutdown),
214
+ * once the child has exited or been ended. It must therefore cope with
215
+ * being called at any point of main().
216
+ */
217
+ void SetCleanupRoutine(void (*routine)(void));
218
+
219
+ /**
220
+ * @brief Runs the registered cleanup routine unless it has run already.
221
+ *
222
+ * If another thread is running it at the time, waits until it is done.
223
+ */
224
+ void RunCleanupRoutine(void);
225
+
226
+ /**
227
+ * @brief Dies of the signal that killed the child, if one did.
228
+ *
229
+ * On POSIX, when the child launched by CreateAndWaitForProcess was killed by
230
+ * a signal, or a termination signal arrived before it could be started, the
231
+ * stub re-raises that signal with its default action so that its own parent
232
+ * sees the same kind of death. Call it after cleanup. Returns if there is no
233
+ * such signal, or if the process survives it (e.g. PID 1 in a container);
234
+ * the caller then exits with 128 + signal as reported in exit_code.
235
+ * No-op on Windows.
236
+ */
237
+ void ReraiseChildSignal(void);
238
+
196
239
  /**
197
240
  * @brief Sets or removes an environment variable.
198
241
  *