ocran 1.4.6 → 1.5.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.txt +82 -0
  3. data/README.md +519 -15
  4. data/ext/extconf.rb +5 -1
  5. data/lib/ocran/aot_toolchain.rb +154 -0
  6. data/lib/ocran/build_helper.rb +7 -0
  7. data/lib/ocran/command_output.rb +8 -1
  8. data/lib/ocran/compatibility_scan.rb +414 -0
  9. data/lib/ocran/cosmo_toolchain.rb +29 -7
  10. data/lib/ocran/dir_builder.rb +88 -38
  11. data/lib/ocran/direction.rb +292 -44
  12. data/lib/ocran/ed_resource.rb +113 -0
  13. data/lib/ocran/ed_resource_builder.rb +199 -0
  14. data/lib/ocran/env_hook.rb +98 -0
  15. data/lib/ocran/file_path_set.rb +9 -5
  16. data/lib/ocran/gem_spec_queryable.rb +13 -5
  17. data/lib/ocran/host_config_helper.rb +11 -1
  18. data/lib/ocran/inno_setup_script_builder.rb +24 -14
  19. data/lib/ocran/launcher_batch_builder.rb +8 -11
  20. data/lib/ocran/npm_package.rb +106 -0
  21. data/lib/ocran/option.rb +465 -35
  22. data/lib/ocran/pe_imports.rb +96 -0
  23. data/lib/ocran/pe_resources.rb +226 -0
  24. data/lib/ocran/refine_pathname.rb +29 -32
  25. data/lib/ocran/roundhouse_builder.rb +316 -0
  26. data/lib/ocran/runner.rb +52 -11
  27. data/lib/ocran/runtime_sdk.rb +259 -0
  28. data/lib/ocran/spinel_builder.rb +183 -0
  29. data/lib/ocran/spinel_compatibility.rb +149 -0
  30. data/lib/ocran/static_runtime/boot.rb +443 -0
  31. data/lib/ocran/static_runtime/build_static_ext.rb +86 -0
  32. data/lib/ocran/static_runtime_builder.rb +814 -0
  33. data/lib/ocran/stub_builder.rb +70 -25
  34. data/lib/ocran/version.rb +1 -1
  35. data/lib/ocran/wasm_builder.rb +570 -0
  36. data/lib/ocran/wasm_compatibility.rb +198 -0
  37. data/lib/ocran/windows_command_escaping.rb +21 -0
  38. data/lib/ocran/zip_payload_builder.rb +18 -9
  39. data/lib/ocran/zip_writer.rb +48 -9
  40. data/src/Makefile +18 -2
  41. data/src/Makefile.mswin +65 -0
  42. data/src/inst_dir.c +34 -6
  43. data/src/path_utils.c +144 -0
  44. data/src/script_info.c +5 -0
  45. data/src/stub.c +77 -19
  46. data/src/stub.rc +1 -1
  47. data/src/system_utils.c +276 -155
  48. data/src/system_utils.h +46 -3
  49. data/src/system_utils_posix.c +243 -148
  50. data/src/unpack.c +9 -0
  51. metadata +25 -4
data/src/path_utils.c ADDED
@@ -0,0 +1,144 @@
1
+ /*
2
+ * Path string helpers shared by the Windows (system_utils.c) and POSIX
3
+ * (system_utils_posix.c) builds. Pure string manipulation: nothing here
4
+ * touches the file system.
5
+ */
6
+
7
+ #include <stdbool.h>
8
+ #include <stdlib.h>
9
+ #include <string.h>
10
+ #ifdef __COSMOPOLITAN__
11
+ #include <cosmo.h> /* IsWindows() */
12
+ #endif
13
+ #include "error.h"
14
+ #include "system_utils.h"
15
+
16
+ bool IsCleanRelativePath(const char *path)
17
+ {
18
+ if (!path || !*path) {
19
+ return false;
20
+ }
21
+
22
+ #ifdef _WIN32
23
+ /* Forbid Windows drive specification (e.g. "C:\") */
24
+ if (((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z'))
25
+ && path[1] == ':'
26
+ && is_path_separator(path[2])) {
27
+ return false;
28
+ }
29
+ #endif
30
+
31
+ /* Forbid absolute path (leading '/' or '\') */
32
+ if (is_path_separator(*path)) {
33
+ return false;
34
+ }
35
+
36
+ #ifdef __COSMOPOLITAN__
37
+ /* A cosmopolitan stub splits paths at '/' only, but on Windows the
38
+ path ends up with the Win32 API, which splits at '\' as well: a
39
+ name like "..\x" would pass below as a single segment and then
40
+ climb out of the extraction directory. The packer never emits a
41
+ backslash in a name, so refuse all of them there. */
42
+ if (IsWindows() && strchr(path, '\\')) {
43
+ return false;
44
+ }
45
+ #endif
46
+
47
+ /* Validate each path segment */
48
+ const char *p = path;
49
+ while (*p) {
50
+ const char *start = p;
51
+
52
+ /* Advance until next separator or end-of-string */
53
+ while (*p && !is_path_separator(*p)) {
54
+ p++;
55
+ }
56
+
57
+ size_t len = p - start;
58
+
59
+ /* Reject empty, "." or ".." segments */
60
+ if (len == 0
61
+ || (len == 1 && start[0] == '.')
62
+ || (len == 2 && start[0] == '.' && start[1] == '.')) {
63
+ return false;
64
+ }
65
+
66
+ /* Skip over the separator */
67
+ if (*p) {
68
+ p++;
69
+ }
70
+ }
71
+
72
+ return true;
73
+ }
74
+
75
+ // Combines two file path components into a single path, handling path separators.
76
+ char *JoinPath(const char *p1, const char *p2)
77
+ {
78
+ if (p1 == NULL || *p1 == '\0') {
79
+ APP_ERROR("p1 is null or empty");
80
+ return NULL;
81
+ }
82
+
83
+ if (p2 == NULL || *p2 == '\0') {
84
+ APP_ERROR("p2 is null or empty");
85
+ return NULL;
86
+ }
87
+
88
+ size_t p1_len = strlen(p1);
89
+ if (is_path_separator(p1[p1_len - 1])) { p1_len--; }
90
+
91
+ size_t p2_len = strlen(p2);
92
+ const char *p2_start = p2;
93
+ if (is_path_separator(*p2_start)) { p2_start++; p2_len--; }
94
+
95
+ size_t joined_len = p1_len + 1 + p2_len;
96
+ char *joined_path = calloc(1, joined_len + 1);
97
+ if (!joined_path) {
98
+ APP_ERROR("Failed to allocate buffer for join path");
99
+ return NULL;
100
+ }
101
+ memcpy(joined_path, p1, p1_len);
102
+ joined_path[p1_len] = PATH_SEPARATOR;
103
+ memcpy(joined_path + p1_len + 1, p2_start, p2_len);
104
+ joined_path[joined_len] = '\0';
105
+
106
+ return joined_path;
107
+ }
108
+
109
+ char *GetParentPath(const char *path)
110
+ {
111
+ if (!path) {
112
+ APP_ERROR("path is NULL");
113
+ return NULL;
114
+ }
115
+
116
+ size_t root = path_root_length(path);
117
+ size_t i = strlen(path);
118
+
119
+ /* Skip any trailing separators */
120
+ while (i > root && is_path_separator(path[i - 1])) {
121
+ i--;
122
+ }
123
+
124
+ /* Skip the last segment's characters */
125
+ while (i > root && !is_path_separator(path[i - 1])) {
126
+ i--;
127
+ }
128
+
129
+ /* Skip the separators before it, keeping the root ("/" or "C:\") */
130
+ while (i > root && is_path_separator(path[i - 1])) {
131
+ i--;
132
+ }
133
+
134
+ /* i==0 ⇒ empty parent (relative path with a single segment) */
135
+
136
+ char *out = malloc(i + 1);
137
+ if (!out) {
138
+ APP_ERROR("Memory allocation failed for parent path");
139
+ return NULL;
140
+ }
141
+ memcpy(out, path, i);
142
+ out[i] = '\0';
143
+ return out;
144
+ }
data/src/script_info.c CHANGED
@@ -183,6 +183,11 @@ bool RunScript(char *argv[], bool is_chdir_to_script_dir,
183
183
  char **merged_argv = NULL;
184
184
  char *script_dir = NULL;
185
185
 
186
+ if (!script_info) {
187
+ APP_ERROR("Failed to copy the script info");
188
+ goto cleanup;
189
+ }
190
+
186
191
  app_name = ExpandInstDirPath(script_info[0]);
187
192
  if (!app_name) {
188
193
  APP_ERROR("Failed to expand application name to installation directory");
data/src/stub.c CHANGED
@@ -18,11 +18,48 @@
18
18
  #include "script_info.h"
19
19
  #include "unpack.h"
20
20
 
21
+ /* Operation modes from the header of the packed data; 0 until read. */
22
+ static OperationModes OpModes = 0;
23
+
24
+ /*
25
+ Deletes the extraction directory when AUTO_CLEAN_INST_DIR is set. Runs
26
+ once, through RunCleanupRoutine(): at the end of main(), or on Windows
27
+ from the console control handler when the console window is closed or
28
+ the system shuts down - possibly on another thread, at any point of
29
+ main().
30
+ */
31
+ static void delete_extraction_dir(void)
32
+ {
33
+ /* Never delete in RUN_IN_EXE_DIR mode: the "installation directory"
34
+ is the real application directory, not a temporary extraction dir. */
35
+ if (!IsAutoCleanInstDir(OpModes) || IsRunInExeDir(OpModes)) {
36
+ return;
37
+ }
38
+
39
+ const char *dir = GetInstDir();
40
+ if (!dir) {
41
+ return;
42
+ }
43
+
44
+ DEBUG("Deleting extraction directory: %s", dir);
45
+ if (!DeleteInstDir()) {
46
+ DEBUG("Failed to delete extraction directory");
47
+ }
48
+ }
49
+
50
+
51
+ /* Whether an environment variable asks for something: set, and neither
52
+ empty nor "0". OCRAN_DEBUG=0 used to switch debug output on. */
53
+ static bool IsEnvFlagSet(const char *name)
54
+ {
55
+ const char *value = getenv(name);
56
+ return value != NULL && value[0] != '\0' && strcmp(value, "0") != 0;
57
+ }
58
+
21
59
  int main(int argc, char *argv[])
22
60
  {
23
61
  int status = EXIT_CODE_FAILURE;
24
62
  UnpackContext *unpack_ctx = NULL;
25
- OperationModes op_modes = 0;
26
63
  const char *extract_dir = NULL;
27
64
  char *image_path = NULL;
28
65
  char *exe_dir = NULL;
@@ -34,6 +71,7 @@ int main(int argc, char *argv[])
34
71
  Child processes (e.g., Ruby) handle their own signals independently,
35
72
  ensuring the parent can finalize cleanup without premature termination.
36
73
  */
74
+ SetCleanupRoutine(delete_extraction_dir);
37
75
  if (!InitializeSignalHandling()) {
38
76
  FATAL("Failed to initialize system controls");
39
77
  goto cleanup;
@@ -54,17 +92,18 @@ int main(int argc, char *argv[])
54
92
  }
55
93
 
56
94
  /* Read header of packed data */
57
- op_modes = GetOperationModes(unpack_ctx);
95
+ OpModes = GetOperationModes(unpack_ctx);
58
96
 
59
- /* Enable debug mode when the flag is set or OCRAN_DEBUG env var is set */
60
- if (IsDebugMode(op_modes) || getenv("OCRAN_DEBUG")) {
97
+ /* Enable debug mode when the flag is set or OCRAN_DEBUG is set to
98
+ anything but "0" or the empty string. */
99
+ if (IsDebugMode(OpModes) || IsEnvFlagSet("OCRAN_DEBUG")) {
61
100
  EnableDebugMode();
62
101
  DEBUG("Ocran stub running in debug mode");
63
102
  }
64
103
 
65
104
  /* Create extraction directory, or run in place next to the executable
66
105
  (installer/wrapper mode, see RUN_IN_EXE_DIR) */
67
- if (IsRunInExeDir(op_modes)) {
106
+ if (IsRunInExeDir(OpModes)) {
68
107
  extract_dir = SetInstDirToExeDir();
69
108
  if (!extract_dir) {
70
109
  FATAL("Failed to resolve the executable directory");
@@ -72,7 +111,7 @@ int main(int argc, char *argv[])
72
111
  }
73
112
  DEBUG("Running in executable directory: %s", extract_dir);
74
113
  } else {
75
- extract_dir = CreateInstDir(IsExtractToExeDir(op_modes));
114
+ extract_dir = CreateInstDir(IsExtractToExeDir(OpModes));
76
115
  if (!extract_dir) {
77
116
  FATAL("Failed to create extraction directory");
78
117
  goto cleanup;
@@ -103,7 +142,7 @@ int main(int argc, char *argv[])
103
142
 
104
143
  /* Resolve the executable's directory when the script should start
105
144
  with its working directory next to the .exe (--chdir-exe-dir). */
106
- if (IsChdirToExeDir(op_modes)) {
145
+ if (IsChdirToExeDir(OpModes)) {
107
146
  exe_dir = GetParentPath(image_path);
108
147
  if (!exe_dir) {
109
148
  FATAL("Failed to resolve the executable directory");
@@ -135,10 +174,33 @@ int main(int argc, char *argv[])
135
174
  and then overwrites it with the external script’s return code.
136
175
  */
137
176
  DEBUG("Run application script");
138
- if (!RunScript(argv, IsChdirBeforeScript(op_modes), exe_dir, &status)) {
177
+ if (!RunScript(argv, IsChdirBeforeScript(OpModes), exe_dir, &status)) {
139
178
  FATAL("Failed to run script");
140
179
  goto cleanup;
141
180
  }
181
+ #ifdef _WIN32
182
+ /*
183
+ The loader failed before the interpreter ran a single line, so
184
+ nothing else would say why the application did nothing. Reported
185
+ here rather than in system_utils.c: FATAL is a message box in the
186
+ windowed stub only, and this file is compiled for each variant.
187
+ */
188
+ switch ((unsigned long)status) {
189
+ case 0xC0000135UL: /* STATUS_DLL_NOT_FOUND */
190
+ FATAL("The Ruby interpreter could not start: a DLL it needs was not "
191
+ "found (0x%08lX). An mswin Ruby also needs the Visual C++ "
192
+ "runtime (vcruntime140.dll).", (unsigned long)status);
193
+ break;
194
+ case 0xC0000138UL: /* STATUS_ORDINAL_NOT_FOUND */
195
+ case 0xC0000139UL: /* STATUS_ENTRYPOINT_NOT_FOUND */
196
+ case 0xC0000142UL: /* STATUS_DLL_INIT_FAILED */
197
+ case 0xC000007BUL: /* STATUS_INVALID_IMAGE_FORMAT */
198
+ FATAL("The Ruby interpreter could not start: a DLL it loads is "
199
+ "incompatible or failed to initialize (0x%08lX)",
200
+ (unsigned long)status);
201
+ break;
202
+ }
203
+ #endif
142
204
  /*
143
205
  If the script executes successfully, its return code is stored in status.
144
206
  */
@@ -164,21 +226,17 @@ cleanup:
164
226
 
165
227
  FreeScriptInfo();
166
228
 
167
- /*
168
- If AUTO_CLEAN_INST_DIR is set, delete the extraction directory.
169
- */
170
- /* Never delete in RUN_IN_EXE_DIR mode: the "installation directory"
171
- is the real application directory, not a temporary extraction dir. */
172
- if (IsAutoCleanInstDir(op_modes) && !IsRunInExeDir(op_modes)) {
173
- DEBUG("Deleting extraction directory: %s", extract_dir);
174
- if (!DeleteInstDir()) {
175
- DEBUG("Failed to delete extraction directory");
176
- }
177
- }
229
+ /* Delete the extraction directory (see delete_extraction_dir), unless
230
+ the console control handler has done so already. */
231
+ RunCleanupRoutine();
178
232
 
179
233
  FreeInstDir();
180
234
  extract_dir = NULL;
181
235
 
236
+ /* A child killed by a signal kills the stub the same way, now that the
237
+ extraction directory is gone. Returns if the signal did not. */
238
+ ReraiseChildSignal();
239
+
182
240
  #ifdef __COSMOPOLITAN__
183
241
  /* On Windows, Cosmopolitan Libc's exit path encodes the full wait
184
242
  status into the process exit code (code << 8) so that cosmo
data/src/stub.rc CHANGED
@@ -1,3 +1,3 @@
1
1
  #include <winresrc.h>
2
2
  CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "stub.manifest"
3
- 101 ICON vit-ruby.ico
3
+ 101 ICON "vit-ruby.ico"