ocran 1.3.18 → 1.4.1

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.txt +309 -292
  3. data/LICENSE.txt +22 -22
  4. data/README.md +549 -533
  5. data/exe/ocran +5 -5
  6. data/ext/extconf.rb +15 -0
  7. data/lib/ocran/build_constants.rb +16 -16
  8. data/lib/ocran/build_facade.rb +17 -17
  9. data/lib/ocran/build_helper.rb +110 -105
  10. data/lib/ocran/command_output.rb +22 -22
  11. data/lib/ocran/dir_builder.rb +162 -0
  12. data/lib/ocran/direction.rb +636 -458
  13. data/lib/ocran/file_path_set.rb +69 -69
  14. data/lib/ocran/gem_spec_queryable.rb +172 -172
  15. data/lib/ocran/host_config_helper.rb +57 -44
  16. data/lib/ocran/inno_setup_script_builder.rb +111 -111
  17. data/lib/ocran/launcher_batch_builder.rb +85 -85
  18. data/lib/ocran/library_detector.rb +61 -61
  19. data/lib/ocran/library_detector_posix.rb +55 -0
  20. data/lib/ocran/option.rb +323 -273
  21. data/lib/ocran/refine_pathname.rb +104 -104
  22. data/lib/ocran/runner.rb +115 -105
  23. data/lib/ocran/runtime_environment.rb +46 -46
  24. data/lib/ocran/stub_builder.rb +298 -264
  25. data/lib/ocran/version.rb +5 -5
  26. data/lib/ocran/windows_command_escaping.rb +15 -15
  27. data/lib/ocran.rb +7 -7
  28. data/share/ocran/lzma.exe +0 -0
  29. data/src/Makefile +75 -0
  30. data/src/edicon.c +161 -0
  31. data/src/error.c +100 -0
  32. data/src/error.h +66 -0
  33. data/src/inst_dir.c +334 -0
  34. data/src/inst_dir.h +157 -0
  35. data/src/lzma/7zTypes.h +529 -0
  36. data/src/lzma/Compiler.h +43 -0
  37. data/src/lzma/LzmaDec.c +1363 -0
  38. data/src/lzma/LzmaDec.h +236 -0
  39. data/src/lzma/Precomp.h +10 -0
  40. data/src/script_info.c +246 -0
  41. data/src/script_info.h +7 -0
  42. data/src/stub.c +133 -0
  43. data/src/stub.manifest +29 -0
  44. data/src/stub.rc +3 -0
  45. data/src/system_utils.c +1002 -0
  46. data/src/system_utils.h +209 -0
  47. data/src/system_utils_posix.c +500 -0
  48. data/src/unpack.c +574 -0
  49. data/src/unpack.h +85 -0
  50. data/src/vit-ruby.ico +0 -0
  51. metadata +52 -16
  52. data/share/ocran/edicon.exe +0 -0
  53. data/share/ocran/stub.exe +0 -0
  54. data/share/ocran/stubw.exe +0 -0
@@ -0,0 +1,1002 @@
1
+ #include <windows.h>
2
+ #include <ntstatus.h>
3
+ #include <ntdef.h>
4
+ #include <bcrypt.h>
5
+ #include <stdbool.h>
6
+ #include "error.h"
7
+ #include "system_utils.h"
8
+
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
+ char *GetParentPath(const char *path)
100
+ {
101
+ if (!path) {
102
+ APP_ERROR("path is NULL");
103
+ return NULL;
104
+ }
105
+
106
+ size_t len = strlen(path);
107
+ size_t i = len;
108
+
109
+ /* Skip any trailing separators */
110
+ while (i > 0 && is_path_separator(path[i])) {
111
+ i--;
112
+ }
113
+
114
+ /* Skip the last segment’s characters */
115
+ while (i > 0 && !is_path_separator(path[i])) {
116
+ i--;
117
+ }
118
+
119
+ /* i==0 ⇒ empty parent */
120
+
121
+ char *out = malloc(i + 1);
122
+ if (!out) {
123
+ APP_ERROR("Memory allocation failed for parent path");
124
+ return NULL;
125
+ }
126
+ memcpy(out, path, i);
127
+ out[i] = '\0';
128
+ return out;
129
+ }
130
+
131
+ /**
132
+ * Converts a NULL-terminated UTF-16 string to a malloc-allocated UTF-8 string.
133
+ *
134
+ * @param utf16 Pointer to a NULL-terminated UTF-16 (wchar_t*) input string.
135
+ * @return malloc-allocated UTF-8 string on success (caller must free()),
136
+ * or NULL on failure (error logged via APP_ERROR).
137
+ */
138
+ static char *utf16_to_utf8(const wchar_t *utf16)
139
+ {
140
+ int utf8_size = WideCharToMultiByte(
141
+ CP_UTF8,
142
+ WC_ERR_INVALID_CHARS,
143
+ utf16, -1, NULL, 0, NULL, NULL
144
+ );
145
+ if (utf8_size == 0) {
146
+ DWORD err = GetLastError();
147
+ APP_ERROR(
148
+ "Failed to calculate buffer size for UTF-8 conversion, Error=%lu",
149
+ err
150
+ );
151
+ return NULL;
152
+ }
153
+
154
+ char *utf8 = calloc((size_t)utf8_size, sizeof(*utf8));
155
+ if (!utf8) {
156
+ APP_ERROR("Memory allocation failed for UTF-8 conversion");
157
+ return NULL;
158
+ }
159
+
160
+ int written = WideCharToMultiByte(
161
+ CP_UTF8,
162
+ WC_ERR_INVALID_CHARS,
163
+ utf16, -1, utf8, utf8_size, NULL, NULL
164
+ );
165
+ if (written == 0) {
166
+ DWORD err = GetLastError();
167
+ APP_ERROR("Failed to convert UTF-16 to UTF-8, Error=%lu", err);
168
+ free(utf8);
169
+ return NULL;
170
+ }
171
+ return utf8;
172
+ }
173
+
174
+ /**
175
+ * Converts a NULL-terminated UTF-8 string to a malloc-allocated UTF-16 string.
176
+ *
177
+ * @param utf8 Pointer to a NULL-terminated UTF-8 (char*) input string.
178
+ * @return malloc-allocated UTF-16 string on success (caller must free()),
179
+ * or NULL on failure (error logged via APP_ERROR).
180
+ */
181
+ static wchar_t *utf8_to_utf16(const char *utf8)
182
+ {
183
+ if (!utf8) {
184
+ APP_ERROR("utf8 is NULL");
185
+ return NULL;
186
+ }
187
+
188
+ int utf16_size = MultiByteToWideChar(
189
+ CP_UTF8,
190
+ MB_ERR_INVALID_CHARS,
191
+ utf8, -1, NULL, 0
192
+ );
193
+ if (utf16_size == 0) {
194
+ DWORD err = GetLastError();
195
+ APP_ERROR(
196
+ "Failed to calculate buffer size for UTF-16 conversion, Error=%lu",
197
+ err
198
+ );
199
+ return NULL;
200
+ }
201
+
202
+ wchar_t *utf16 = calloc((size_t)utf16_size, sizeof(*utf16));
203
+ if (!utf16) {
204
+ APP_ERROR("Memory allocation failed for UTF-16 conversion");
205
+ return NULL;
206
+ }
207
+
208
+ int written = MultiByteToWideChar(
209
+ CP_UTF8,
210
+ MB_ERR_INVALID_CHARS,
211
+ utf8, -1, utf16, utf16_size
212
+ );
213
+ if (written == 0) {
214
+ DWORD err = GetLastError();
215
+ APP_ERROR("Failed to convert UTF-8 to UTF-16, Error=%lu", err);
216
+ free(utf16);
217
+ return NULL;
218
+ }
219
+ return utf16;
220
+ }
221
+
222
+ // Recursively creates a directory and all its parent directories.
223
+ bool CreateDirectoriesRecursively(const char *dir)
224
+ {
225
+ if (!dir || !*dir) {
226
+ APP_ERROR("dir is NULL or empty");
227
+ return false;
228
+ }
229
+
230
+ bool result = false;
231
+ wchar_t *wpath = NULL;
232
+
233
+ size_t path_len = strlen(dir);
234
+ char *path = malloc(path_len + 1);
235
+ if (!path) {
236
+ APP_ERROR("Memory allocation failed for path");
237
+
238
+ goto cleanup;
239
+ }
240
+ memcpy(path, dir, path_len);
241
+ path[path_len] = '\0';
242
+
243
+ char *p = path + path_len;
244
+ do {
245
+ // Convert to UTF-16 for API call
246
+ wpath = utf8_to_utf16(path);
247
+ if (!wpath) {
248
+ APP_ERROR("Failed to convert path to UTF-16");
249
+ goto cleanup;
250
+ }
251
+
252
+ DWORD path_attr = GetFileAttributesW(wpath);
253
+ if (path_attr != INVALID_FILE_ATTRIBUTES) {
254
+ if (path_attr & FILE_ATTRIBUTE_DIRECTORY) {
255
+ free(wpath);
256
+ wpath = NULL;
257
+ break;
258
+ } else {
259
+ APP_ERROR("Directory name conflicts with a file(%s)", path);
260
+ goto cleanup;
261
+ }
262
+ } else {
263
+ DWORD err = GetLastError();
264
+ if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
265
+ // continue;
266
+ } else {
267
+ APP_ERROR("Cannot access the directory, Error=%lu", err);
268
+ goto cleanup;
269
+ }
270
+ }
271
+
272
+ free(wpath);
273
+ wpath = NULL;
274
+
275
+ while (p > path && !is_path_separator(*p)) {
276
+ p--;
277
+ }
278
+ *p = '\0';
279
+ } while (p >= path);
280
+
281
+ char *end = path + path_len;
282
+ for (; p < end; p++) {
283
+ if (*p) continue;
284
+
285
+ *p = PATH_SEPARATOR;
286
+
287
+ // Convert to UTF-16 for API call
288
+ wpath = utf8_to_utf16(path);
289
+ if (!wpath) {
290
+ APP_ERROR("Failed to convert path to UTF-16");
291
+ goto cleanup;
292
+ }
293
+
294
+ if (!CreateDirectoryW(wpath, NULL)) {
295
+ DWORD err = GetLastError();
296
+ APP_ERROR("Failed to create directory '%s', Error=%lu", path, err);
297
+ goto cleanup;
298
+ }
299
+
300
+ free(wpath);
301
+ wpath = NULL;
302
+ }
303
+
304
+ result = true;
305
+
306
+ cleanup:
307
+ if (wpath) {
308
+ free(wpath);
309
+ }
310
+ if (path) {
311
+ free(path);
312
+ }
313
+ return result;
314
+ }
315
+
316
+ // Deletes a directory and all its contents recursively.
317
+ bool DeleteRecursively(const char *path)
318
+ {
319
+ if (!path || !*path) {
320
+ APP_ERROR("path is NULL or empty");
321
+ return false;
322
+ }
323
+
324
+ char *findPath = JoinPath(path, "*");
325
+ if (!findPath) {
326
+ APP_ERROR("Failed to build find path for deletion");
327
+ return false;
328
+ }
329
+
330
+ wchar_t *wfindPath = utf8_to_utf16(findPath);
331
+ free(findPath);
332
+ if (!wfindPath) {
333
+ APP_ERROR("Failed to convert find path to UTF-16");
334
+ return false;
335
+ }
336
+
337
+ WIN32_FIND_DATAW findData;
338
+ HANDLE handle = FindFirstFileW(wfindPath, &findData);
339
+ free(wfindPath);
340
+
341
+ if (handle != INVALID_HANDLE_VALUE) {
342
+ do {
343
+ const wchar_t *wname = findData.cFileName;
344
+ if ( wname[0]==L'.' && (!wname[1] || (wname[1]==L'.' && !wname[2])) ) {
345
+ continue;
346
+ }
347
+
348
+ // Convert filename from UTF-16 to UTF-8
349
+ char *name = utf16_to_utf8(wname);
350
+ if (!name) {
351
+ APP_ERROR("Failed to convert filename to UTF-8");
352
+ continue;
353
+ }
354
+
355
+ char *subPath = JoinPath(path, name);
356
+ free(name);
357
+ if (!subPath) {
358
+ APP_ERROR("Failed to build delete file path");
359
+ break;
360
+ }
361
+
362
+ wchar_t *wsubPath = utf8_to_utf16(subPath);
363
+ if (!wsubPath) {
364
+ APP_ERROR("Failed to convert subpath to UTF-16");
365
+ free(subPath);
366
+ continue;
367
+ }
368
+
369
+ if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
370
+ DeleteRecursively(subPath);
371
+ } else if (!DeleteFileW(wsubPath)) {
372
+ DWORD err = GetLastError();
373
+ APP_ERROR("Failed to delete file, Error=%lu", err);
374
+ MoveFileExW(wsubPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
375
+ }
376
+
377
+ free(wsubPath);
378
+ free(subPath);
379
+ } while (FindNextFileW(handle, &findData));
380
+ FindClose(handle);
381
+ }
382
+
383
+ wchar_t *wpath = utf8_to_utf16(path);
384
+ if (!wpath) {
385
+ APP_ERROR("Failed to convert path to UTF-16");
386
+ return false;
387
+ }
388
+
389
+ if (!RemoveDirectoryW(wpath)) {
390
+ DWORD err = GetLastError();
391
+ APP_ERROR("Failed to delete directory, Error=%lu", err);
392
+ MoveFileExW(wpath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
393
+ free(wpath);
394
+ return false;
395
+ }
396
+ free(wpath);
397
+ return true;
398
+ }
399
+
400
+ static bool generate_unique_name(char *buffer, size_t buffer_size)
401
+ {
402
+ char base32[] = "0123456789ABCDEF"
403
+ "GHIJKLMNOPQRSTUV"
404
+ ;
405
+
406
+ NTSTATUS b = BCryptGenRandom(
407
+ 0,
408
+ (PUCHAR)buffer,
409
+ buffer_size,
410
+ BCRYPT_USE_SYSTEM_PREFERRED_RNG
411
+ );
412
+ if (!NT_SUCCESS(b)) {
413
+ return false;
414
+ }
415
+
416
+ for (size_t i = 0; i < buffer_size; i++) {
417
+ buffer[i] = base32[buffer[i] & 0x1F];
418
+ }
419
+ return true;
420
+ }
421
+
422
+ // Defines the maximum number of attempts to create a unique directory.
423
+ #define MAX_RETRY_CREATE_UNIQUE_DIR 20U
424
+
425
+ // Generates a unique directory within a specified base path using a prefix.
426
+ char *CreateUniqueDirectory(char *tmpl)
427
+ {
428
+ if (!tmpl || !*tmpl) {
429
+ APP_ERROR("template is NULL or empty");
430
+ return NULL;
431
+ }
432
+
433
+ size_t tmpl_len = strlen(tmpl);
434
+ char *tmpl_end = tmpl + tmpl_len;
435
+ char *x_str = tmpl_end;
436
+ size_t x_len = 0;
437
+ while (x_str > tmpl && *--x_str == 'X') {
438
+ x_len++;
439
+ }
440
+ if (x_len < 6) {
441
+ APP_ERROR("Template must end with at least six 'X's");
442
+ return NULL;
443
+ }
444
+ char *x_head = tmpl_end - x_len;
445
+
446
+ for (size_t retry = 0; retry < MAX_RETRY_CREATE_UNIQUE_DIR; retry++) {
447
+ if (!generate_unique_name(x_head, x_len)) {
448
+ APP_ERROR("Failed to construct a unique directory path");
449
+ return NULL;
450
+ }
451
+
452
+ wchar_t *wtmpl = utf8_to_utf16(tmpl);
453
+ if (!wtmpl) {
454
+ APP_ERROR("Failed to convert template path to UTF-16");
455
+ return NULL;
456
+ }
457
+
458
+ BOOL created = CreateDirectoryW(wtmpl, NULL);
459
+ DWORD err = GetLastError();
460
+ free(wtmpl);
461
+
462
+ if (created) {
463
+ return tmpl;
464
+ }
465
+ if (err != ERROR_ALREADY_EXISTS) {
466
+ APP_ERROR("Failed to create a unique directory, Error=%lu", err);
467
+ return NULL;
468
+ }
469
+ }
470
+
471
+ APP_ERROR(
472
+ "Failed to create a unique directory after %u retries",
473
+ MAX_RETRY_CREATE_UNIQUE_DIR
474
+ );
475
+ return NULL;
476
+ }
477
+
478
+ // Maximum path length in Windows (32,767 chars).
479
+ #define MAX_LONG_PATH 32767U
480
+
481
+ // Retrieves the full path to the executable file of the current process
482
+ char *GetImagePath(void)
483
+ {
484
+ char *image_path = NULL;
485
+
486
+ wchar_t *wimage_path = calloc(MAX_LONG_PATH, sizeof(*wimage_path));
487
+ if (!wimage_path) {
488
+ APP_ERROR("Memory allocation failed for image path");
489
+
490
+ goto cleanup;
491
+ }
492
+
493
+ DWORD copied = GetModuleFileNameW(NULL, wimage_path, MAX_LONG_PATH);
494
+ if (copied == 0) {
495
+ DWORD err = GetLastError();
496
+ APP_ERROR("GetModuleFileNameW failed, Error=%lu", err);
497
+
498
+ goto cleanup;
499
+ }
500
+ if (copied == MAX_LONG_PATH
501
+ && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
502
+ APP_ERROR("Image path truncated; buffer too small");
503
+
504
+ goto cleanup;
505
+ }
506
+
507
+ image_path = utf16_to_utf8(wimage_path);
508
+ if (!image_path) {
509
+ APP_ERROR("Failed to convert image path to UTF-8");
510
+
511
+ goto cleanup;
512
+ }
513
+
514
+ cleanup:
515
+ if (wimage_path) {
516
+ free(wimage_path);
517
+ }
518
+ return image_path;
519
+ }
520
+
521
+ // Retrieves the path to the temporary directory for the current user.
522
+ char *GetTempDirectoryPath(void)
523
+ {
524
+ wchar_t *wtemp_dir = calloc(MAX_PATH, sizeof(*wtemp_dir));
525
+ if (!wtemp_dir) {
526
+ APP_ERROR("Memory allocation failed for temp directory");
527
+ return NULL;
528
+ }
529
+
530
+ if (!GetTempPathW(MAX_PATH, wtemp_dir)) {
531
+ DWORD err = GetLastError();
532
+ APP_ERROR("Failed to get temp path, Error=%lu", err);
533
+ free(wtemp_dir);
534
+ return NULL;
535
+ }
536
+
537
+ char *temp_dir = utf16_to_utf8(wtemp_dir);
538
+ free(wtemp_dir);
539
+ if (!temp_dir) {
540
+ APP_ERROR("Failed to convert temp path to UTF-8");
541
+ return NULL;
542
+ }
543
+ return temp_dir;
544
+ }
545
+
546
+ bool ExportFile(const char *path, const void *buffer, size_t buffer_size)
547
+ {
548
+ bool result = false;
549
+ char *parent = NULL;
550
+ wchar_t *wpath = NULL;
551
+ HANDLE hFile = INVALID_HANDLE_VALUE;
552
+ DWORD written = 0;
553
+
554
+ if (buffer_size > MAXDWORD) {
555
+ APP_ERROR(
556
+ "ExportFile: Write length %zu exceeds maximum DWORD",
557
+ buffer_size
558
+ );
559
+
560
+ goto cleanup;
561
+ }
562
+
563
+ parent = GetParentPath(path);
564
+ if (!parent) {
565
+ APP_ERROR("Failed to get parent path");
566
+
567
+ goto cleanup;
568
+ }
569
+
570
+ if (!CreateDirectoriesRecursively(parent)) {
571
+ APP_ERROR("ExportFile: Failed to create parent directory for %s", path);
572
+
573
+ goto cleanup;
574
+ }
575
+
576
+ // Convert UTF-8 path to UTF-16 for proper multibyte support
577
+ wpath = utf8_to_utf16(path);
578
+ if (!wpath) {
579
+ APP_ERROR("ExportFile: Failed to convert path to UTF-16");
580
+ goto cleanup;
581
+ }
582
+
583
+ hFile = CreateFileW(
584
+ wpath, // file path (UTF-16)
585
+ GENERIC_WRITE, // write-only access (like O_WRONLY)
586
+ 0, // no sharing (exclusive)
587
+ NULL, // default security
588
+ CREATE_ALWAYS, // create or overwrite (O_CREAT|O_TRUNC)
589
+ FILE_ATTRIBUTE_NORMAL, // normal file (no special flags)
590
+ NULL // no template file
591
+ );
592
+ if (hFile == INVALID_HANDLE_VALUE) {
593
+ DWORD err = GetLastError();
594
+ APP_ERROR("ExportFile: CreateFileW failed, Error=%u", err);
595
+
596
+ goto cleanup;
597
+ }
598
+
599
+ if (!WriteFile(hFile, buffer, (DWORD)buffer_size, &written, NULL)) {
600
+ DWORD err = GetLastError();
601
+ APP_ERROR("ExportFile: WriteFile failed, Error=%u", err);
602
+
603
+ goto cleanup;
604
+ }
605
+
606
+ if (written != (DWORD)buffer_size) {
607
+ APP_ERROR(
608
+ "ExportFile: Write size mismatch, expected %zu, wrote %u",
609
+ buffer_size, written
610
+ );
611
+
612
+ goto cleanup;
613
+ }
614
+
615
+ result = true;
616
+
617
+ cleanup:
618
+ if (parent) {
619
+ free(parent);
620
+ }
621
+ if (wpath) {
622
+ free(wpath);
623
+ }
624
+ if (hFile != INVALID_HANDLE_VALUE) {
625
+ CloseHandle(hFile);
626
+ }
627
+ return result;
628
+ }
629
+
630
+ struct MemoryMap {
631
+ void *base; // Base address of the mapping
632
+ size_t size; // Length of the mapping
633
+ };
634
+
635
+ MemoryMap *CreateMemoryMap(const char *path)
636
+ {
637
+ if (!path) {
638
+ APP_ERROR("CreateMemoryMap: path is NULL");
639
+ return NULL;
640
+ }
641
+
642
+ MemoryMap *map = malloc(sizeof(*map));
643
+ if (!map) {
644
+ APP_ERROR("CreateMemoryMap: Memory allocation failed for map");
645
+ return NULL;
646
+ }
647
+
648
+ HANDLE hFile = INVALID_HANDLE_VALUE;
649
+ HANDLE hMapping = NULL; // section object handle for mapping
650
+ LPVOID base = NULL;
651
+ wchar_t *wpath = NULL;
652
+
653
+ /*
654
+ * Open the target file for memory mapping:
655
+ * - Read-only access; write and delete operations are denied.
656
+ * - Allows other processes to open the file for read-only access.
657
+ * - Fails if the file does not exist.
658
+ * - Sets the file's attribute to read-only.
659
+ * - Hints OS to optimize for sequential access after initial random read.
660
+ */
661
+
662
+ // Convert UTF-8 path to UTF-16 for proper multibyte support
663
+ wpath = utf8_to_utf16(path);
664
+ if (!wpath) {
665
+ APP_ERROR("CreateMemoryMap: Failed to convert path to UTF-16");
666
+ goto cleanup;
667
+ }
668
+
669
+ hFile = CreateFileW(
670
+ wpath, // path to existing file (UTF-16)
671
+ GENERIC_READ, // read-only access
672
+ FILE_SHARE_READ, // share read, deny write/delete
673
+ NULL, // default security
674
+ OPEN_EXISTING, // open only if file exists
675
+ FILE_ATTRIBUTE_READONLY // read-only attribute
676
+ | FILE_FLAG_SEQUENTIAL_SCAN, // then optimize for sequential access
677
+ NULL // no template file
678
+ );
679
+ if (hFile == INVALID_HANDLE_VALUE) {
680
+ DWORD err = GetLastError();
681
+ APP_ERROR(
682
+ "CreateMemoryMap: CreateFileW(\"%s\") failed, Error=%lu",
683
+ path, err
684
+ );
685
+
686
+ goto cleanup;
687
+ }
688
+
689
+ LARGE_INTEGER fileSize;
690
+ if (!GetFileSizeEx(hFile, &fileSize)) {
691
+ DWORD err = GetLastError();
692
+ APP_ERROR(
693
+ "CreateMemoryMap: GetFileSizeEx failed, Error=%lu",
694
+ err
695
+ );
696
+
697
+ goto cleanup;
698
+ }
699
+
700
+ /*
701
+ * Verify that the file size obtained at runtime does not exceed the
702
+ * maximum value representable by size_t on this platform. This check
703
+ * prevents an overflow when casting the 64-bit file size to size_t,
704
+ * which may be 32 bits on some environments.
705
+ */
706
+
707
+ if (fileSize.QuadPart < 0
708
+ || (ULONGLONG)fileSize.QuadPart > (ULONGLONG)SIZE_MAX) {
709
+ APP_ERROR(
710
+ "CreateMemoryMap: file too large (%lld bytes)",
711
+ fileSize.QuadPart
712
+ );
713
+
714
+ goto cleanup;
715
+ }
716
+
717
+ map->size = (size_t)fileSize.QuadPart;
718
+
719
+ hMapping = CreateFileMapping(
720
+ hFile, // read-only file handle
721
+ NULL, // default security attributes
722
+ PAGE_READONLY, // read-only mapping protection
723
+ 0, // max size high 32-bit (0 = full file)
724
+ 0, // max size low 32-bit (0 = full file)
725
+ NULL // unnamed mapping (private)
726
+ );
727
+ if (!hMapping) {
728
+ DWORD err = GetLastError();
729
+ APP_ERROR(
730
+ "CreateMemoryMap: CreateFileMapping(hFile) failed, Error=%lu",
731
+ err
732
+ );
733
+
734
+ goto cleanup;
735
+ }
736
+
737
+ CloseHandle(hFile);
738
+ hFile = INVALID_HANDLE_VALUE;
739
+
740
+ base = MapViewOfFile(
741
+ hMapping, // handle returned by CreateFileMapping
742
+ FILE_MAP_READ, // read-only access to mapped view
743
+ 0, // file offset high 32 bits (start at 0)
744
+ 0, // file offset low 32 bits (start at 0)
745
+ 0 // number of bytes to map (0 = entire file)
746
+ );
747
+ if (!base) {
748
+ DWORD err = GetLastError();
749
+ APP_ERROR(
750
+ "CreateMemoryMap: MapViewOfFile(hMapping) failed, Error=%lu",
751
+ err
752
+ );
753
+
754
+ goto cleanup;
755
+ }
756
+
757
+ CloseHandle(hMapping);
758
+ hMapping = NULL;
759
+
760
+ map->base = base;
761
+ if (wpath) {
762
+ free(wpath);
763
+ }
764
+ return map;
765
+
766
+ cleanup:
767
+ if (base) {
768
+ UnmapViewOfFile(base);
769
+ }
770
+ if (hMapping) {
771
+ CloseHandle(hMapping);
772
+ }
773
+ if (hFile != INVALID_HANDLE_VALUE) {
774
+ CloseHandle(hFile);
775
+ }
776
+ if (wpath) {
777
+ free(wpath);
778
+ }
779
+ free(map);
780
+ return NULL;
781
+ }
782
+
783
+ void DestroyMemoryMap(MemoryMap *map)
784
+ {
785
+ if (!map) {
786
+ APP_ERROR("DestroyMemoryMap: map is NULL");
787
+ return;
788
+ }
789
+
790
+ if (map->base) {
791
+ if (!UnmapViewOfFile(map->base)) {
792
+ DWORD err = GetLastError();
793
+ APP_ERROR(
794
+ "DestroyMemoryMap: UnmapViewOfFile failed, Error=%lu",
795
+ err
796
+ );
797
+ }
798
+ map->base = NULL;
799
+ }
800
+
801
+ free(map);
802
+ }
803
+
804
+ void *GetMemoryMapBase(const MemoryMap *map)
805
+ {
806
+ if (!map) {
807
+ APP_ERROR("GetMemoryMapBase: map is NULL");
808
+ return NULL;
809
+ }
810
+ return map->base;
811
+ }
812
+
813
+ size_t GetMemoryMapSize(const MemoryMap *map)
814
+ {
815
+ if (!map) {
816
+ APP_ERROR("GetMemoryMapSize: map is NULL");
817
+ return 0;
818
+ }
819
+ return map->size;
820
+ }
821
+
822
+ /**
823
+ * @brief Handle console control events in the parent process.
824
+ *
825
+ * This handler ignores all console control events (Ctrl+C, Ctrl+Break, etc.)
826
+ * in the parent process so it can complete cleanup without interruption.
827
+ * Child processes (e.g., Ruby) receive these events and exit quickly,
828
+ * allowing the parent to perform final cleanup tasks.
829
+ *
830
+ * @param dwCtrlType The type of console control event received.
831
+ * @return TRUE to indicate the event was handled and should be ignored.
832
+ */
833
+ static BOOL WINAPI ConsoleHandleRoutine(DWORD dwCtrlType)
834
+ {
835
+ return TRUE;
836
+ }
837
+
838
+ bool InitializeSignalHandling(void)
839
+ {
840
+ if (!SetConsoleCtrlHandler(ConsoleHandleRoutine, TRUE)) {
841
+ DWORD err = GetLastError();
842
+ APP_ERROR("Failed to set console control handler, Error=%lu", err);
843
+ return false;
844
+ }
845
+ return true;
846
+ }
847
+
848
+ bool SetEnvVar(const char *name, const char *value)
849
+ {
850
+ if (!name) {
851
+ APP_ERROR("name is NULL");
852
+ return false;
853
+ }
854
+
855
+ if (!SetEnvironmentVariable(name, value)) {
856
+ DWORD err = GetLastError();
857
+ APP_ERROR("Failed to set environment variable, Error=%lu", err);
858
+ return false;
859
+ }
860
+ return true;
861
+ }
862
+
863
+ static size_t quoted_arg(char *quoted, const char* arg)
864
+ {
865
+ size_t count = 0;
866
+
867
+ count++;
868
+ if (quoted) {
869
+ *quoted++ = '"';
870
+ }
871
+
872
+ for (const char *p = arg; *p; p++) {
873
+ switch (*p) {
874
+ case '\\': {
875
+ size_t trail = 1;
876
+ while (*++p == '\\') {
877
+ trail++;
878
+ }
879
+ if (*p == '"' || *p == '\0') {
880
+ trail *= 2;
881
+ }
882
+ count += trail;
883
+ if (quoted) {
884
+ while (trail--) *quoted++ = '\\';
885
+ }
886
+ p--;
887
+ break;
888
+ }
889
+ case '"':
890
+ count += 2;
891
+ if (quoted) {
892
+ *quoted++ = '\\';
893
+ *quoted++ = '"';
894
+ }
895
+ break;
896
+ default:
897
+ count++;
898
+ if (quoted) {
899
+ *quoted++ = *p;
900
+ }
901
+ break;
902
+ }
903
+ }
904
+
905
+ count++;
906
+ if (quoted) {
907
+ *quoted++ = '"';
908
+ *quoted = '\0';
909
+ }
910
+ return count;
911
+ }
912
+
913
+ static size_t quoted_args(char *args, char *argv[])
914
+ {
915
+ size_t args_len = 0;
916
+
917
+ for (char **p = argv; *p; p++) {
918
+ if (args_len > 0) {
919
+ if (args) {
920
+ args[args_len] = ' ';
921
+ }
922
+ args_len++;
923
+ }
924
+ if (args) {
925
+ args_len += quoted_arg(&args[args_len], *p);
926
+ } else {
927
+ args_len += quoted_arg(NULL, *p);
928
+ }
929
+ }
930
+
931
+ if (args) {
932
+ args[args_len] = '\0';
933
+ }
934
+ return args_len;
935
+ }
936
+
937
+ bool CreateAndWaitForProcess(const char *app_name, char *argv[], int *exit_code)
938
+ {
939
+ PROCESS_INFORMATION pi = { 0 };
940
+ STARTUPINFOW si = { .cb = sizeof(si) };
941
+ bool result = false;
942
+ char *cmd_line = NULL;
943
+ wchar_t *wapp_name = NULL;
944
+ wchar_t *wcmd_line = NULL;
945
+
946
+ cmd_line = calloc(quoted_args(NULL, argv) + 1, sizeof(*cmd_line));
947
+ if (!cmd_line) {
948
+ APP_ERROR("Failed to build command line for CreateProcessW()");
949
+ goto cleanup;
950
+ }
951
+ quoted_args(cmd_line, argv);
952
+
953
+ DEBUG("ApplicationName=%s", app_name);
954
+ DEBUG("CommandLine=%s", cmd_line);
955
+
956
+ wapp_name = utf8_to_utf16(app_name);
957
+ if (!wapp_name) {
958
+ APP_ERROR("Failed to convert application name to UTF-16");
959
+ goto cleanup;
960
+ }
961
+
962
+ wcmd_line = utf8_to_utf16(cmd_line);
963
+ if (!wcmd_line) {
964
+ APP_ERROR("Failed to convert command line to UTF-16");
965
+ goto cleanup;
966
+ }
967
+
968
+ if (!CreateProcessW(wapp_name, wcmd_line, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
969
+ APP_ERROR("Failed to create process (%lu)", GetLastError());
970
+ goto cleanup;
971
+ }
972
+
973
+ if (WaitForSingleObject(pi.hProcess, INFINITE) != WAIT_OBJECT_0) {
974
+ APP_ERROR("Failed to wait script process (%lu)", GetLastError());
975
+ goto cleanup;
976
+ }
977
+
978
+ if (!GetExitCodeProcess(pi.hProcess, (LPDWORD)exit_code)) {
979
+ APP_ERROR("Failed to get exit status (%lu)", GetLastError());
980
+ goto cleanup;
981
+ }
982
+
983
+ result = true;
984
+
985
+ cleanup:
986
+ if (cmd_line) {
987
+ free(cmd_line);
988
+ }
989
+ if (wapp_name) {
990
+ free(wapp_name);
991
+ }
992
+ if (wcmd_line) {
993
+ free(wcmd_line);
994
+ }
995
+ if (pi.hProcess && pi.hProcess != INVALID_HANDLE_VALUE) {
996
+ CloseHandle(pi.hProcess);
997
+ }
998
+ if (pi.hThread && pi.hThread != INVALID_HANDLE_VALUE) {
999
+ CloseHandle(pi.hThread);
1000
+ }
1001
+ return result;
1002
+ }