ocran 1.4.5 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.txt +74 -0
- data/README.md +296 -9
- data/ext/extconf.rb +5 -1
- data/lib/ocran/aot_toolchain.rb +154 -0
- data/lib/ocran/command_output.rb +8 -1
- data/lib/ocran/compatibility_scan.rb +414 -0
- data/lib/ocran/cosmo_toolchain.rb +29 -7
- data/lib/ocran/dir_builder.rb +88 -38
- data/lib/ocran/direction.rb +232 -33
- data/lib/ocran/ed_resource.rb +113 -0
- data/lib/ocran/ed_resource_builder.rb +199 -0
- data/lib/ocran/file_path_set.rb +9 -5
- data/lib/ocran/gem_spec_queryable.rb +7 -3
- data/lib/ocran/host_config_helper.rb +11 -1
- data/lib/ocran/inno_setup_script_builder.rb +24 -14
- data/lib/ocran/launcher_batch_builder.rb +8 -11
- data/lib/ocran/npm_package.rb +106 -0
- data/lib/ocran/option.rb +433 -35
- data/lib/ocran/pe_imports.rb +96 -0
- data/lib/ocran/pe_resources.rb +226 -0
- data/lib/ocran/refine_pathname.rb +29 -32
- data/lib/ocran/roundhouse_builder.rb +316 -0
- data/lib/ocran/runner.rb +96 -11
- data/lib/ocran/runtime_environment.rb +14 -6
- data/lib/ocran/runtime_sdk.rb +252 -0
- data/lib/ocran/spinel_builder.rb +183 -0
- data/lib/ocran/spinel_compatibility.rb +149 -0
- data/lib/ocran/static_runtime/boot.rb +443 -0
- data/lib/ocran/static_runtime/build_static_ext.rb +86 -0
- data/lib/ocran/static_runtime_builder.rb +814 -0
- data/lib/ocran/stub_builder.rb +70 -25
- data/lib/ocran/version.rb +1 -1
- data/lib/ocran/wasm_builder.rb +570 -0
- data/lib/ocran/wasm_compatibility.rb +198 -0
- data/lib/ocran/windows_command_escaping.rb +21 -0
- data/lib/ocran/zip_payload_builder.rb +18 -9
- data/lib/ocran/zip_writer.rb +48 -9
- data/src/Makefile +18 -2
- data/src/Makefile.mswin +65 -0
- data/src/inst_dir.c +34 -6
- data/src/path_utils.c +144 -0
- data/src/script_info.c +5 -0
- data/src/stub.c +77 -19
- data/src/stub.rc +1 -1
- data/src/system_utils.c +276 -155
- data/src/system_utils.h +46 -3
- data/src/system_utils_posix.c +243 -148
- data/src/unpack.c +9 -0
- metadata +23 -5
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
|
-
|
|
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
|
|
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
|
-
|
|
302
|
-
p--;
|
|
303
|
-
}
|
|
194
|
+
p = q - 1;
|
|
304
195
|
*p = '\0';
|
|
305
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
852
|
-
*
|
|
853
|
-
*
|
|
854
|
-
*
|
|
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
|
|
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
|
-
|
|
995
|
-
|
|
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,
|
|
187
|
-
*
|
|
188
|
-
*
|
|
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
|
*
|