@scriptc/cli-darwin-x64 0.2.1 → 0.2.3

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 (198) hide show
  1. package/dist/THIRD_PARTY_NOTICES +1 -1
  2. package/dist/bin/scriptc +0 -0
  3. package/dist/bin/scriptc.json +1 -1
  4. package/dist/lib/declarations/scriptc-node-fallback.d.ts +158 -31
  5. package/dist/lib/llvm/bin/scriptc-llvm-codegen +0 -0
  6. package/dist/lib/llvm/package.json +1 -1
  7. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_array.o +0 -0
  8. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_assert.o +0 -0
  9. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_async.o +0 -0
  10. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_async_dyn.o +0 -0
  11. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_bytes.o +0 -0
  12. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_bytes_io.o +0 -0
  13. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_crypto_async.o +0 -0
  14. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_dyn_invoke.o +0 -0
  15. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_events.o +0 -0
  16. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_exception.o +0 -0
  17. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_file_handle.o +0 -0
  18. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_json.o +0 -0
  19. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_lib.o +0 -0
  20. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_map.o +0 -0
  21. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_node_builtin.o +0 -0
  22. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_qs.o +0 -0
  23. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_regex.o +0 -0
  24. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_symbol.o +0 -0
  25. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_url.o +0 -0
  26. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_url_params.o +0 -0
  27. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_util_compare.o +0 -0
  28. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_util_style.o +0 -0
  29. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_array.o +0 -0
  30. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_assert.o +0 -0
  31. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_async.o +0 -0
  32. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_async_dyn.o +0 -0
  33. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_bytes.o +0 -0
  34. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_bytes_io.o +0 -0
  35. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_crypto_async.o +0 -0
  36. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_dyn_invoke.o +0 -0
  37. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_events.o +0 -0
  38. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_exception.o +0 -0
  39. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_file_handle.o +0 -0
  40. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_island.o +0 -0
  41. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_json.o +0 -0
  42. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_lib.o +0 -0
  43. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_map.o +0 -0
  44. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_node_builtin.o +0 -0
  45. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_qs.o +0 -0
  46. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_regex.o +0 -0
  47. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_symbol.o +0 -0
  48. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_url.o +0 -0
  49. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_url_params.o +0 -0
  50. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_util_compare.o +0 -0
  51. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_util_style.o +0 -0
  52. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_web.o +0 -0
  53. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic-text-decoder-legacy/scr_bytes.o +0 -0
  54. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  55. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_array.o +0 -0
  56. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_assert.o +0 -0
  57. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_bytes.o +0 -0
  58. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_bytes_io.o +0 -0
  59. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_dyn_invoke.o +0 -0
  60. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_exception.o +0 -0
  61. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_json.o +0 -0
  62. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_lib.o +0 -0
  63. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_map.o +0 -0
  64. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_node_builtin.o +0 -0
  65. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_regex.o +0 -0
  66. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_symbol.o +0 -0
  67. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_url.o +0 -0
  68. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_url_params.o +0 -0
  69. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  70. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_array.o +0 -0
  71. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_assert.o +0 -0
  72. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_bytes.o +0 -0
  73. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_bytes_io.o +0 -0
  74. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_dyn_invoke.o +0 -0
  75. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_exception.o +0 -0
  76. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_json.o +0 -0
  77. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_lib.o +0 -0
  78. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_map.o +0 -0
  79. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_node_builtin.o +0 -0
  80. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_regex.o +0 -0
  81. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_symbol.o +0 -0
  82. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_url.o +0 -0
  83. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_url_params.o +0 -0
  84. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  85. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_array.o +0 -0
  86. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_assert.o +0 -0
  87. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_bytes.o +0 -0
  88. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_bytes_io.o +0 -0
  89. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_dyn_invoke.o +0 -0
  90. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_exception.o +0 -0
  91. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_json.o +0 -0
  92. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_lib.o +0 -0
  93. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_map.o +0 -0
  94. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_node_builtin.o +0 -0
  95. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_regex.o +0 -0
  96. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_symbol.o +0 -0
  97. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_url.o +0 -0
  98. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_url_params.o +0 -0
  99. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  100. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_array.o +0 -0
  101. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_assert.o +0 -0
  102. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_bytes.o +0 -0
  103. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_bytes_io.o +0 -0
  104. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_dyn_invoke.o +0 -0
  105. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_exception.o +0 -0
  106. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_json.o +0 -0
  107. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_lib.o +0 -0
  108. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_map.o +0 -0
  109. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_node_builtin.o +0 -0
  110. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_regex.o +0 -0
  111. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_symbol.o +0 -0
  112. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_url.o +0 -0
  113. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_url_params.o +0 -0
  114. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  115. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_array.o +0 -0
  116. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_assert.o +0 -0
  117. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_async.o +0 -0
  118. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_async_dyn.o +0 -0
  119. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_bytes.o +0 -0
  120. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_bytes_io.o +0 -0
  121. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_crypto_async.o +0 -0
  122. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_dyn_invoke.o +0 -0
  123. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_events.o +0 -0
  124. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_exception.o +0 -0
  125. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_file_handle.o +0 -0
  126. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_json.o +0 -0
  127. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_lib.o +0 -0
  128. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_map.o +0 -0
  129. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_node_builtin.o +0 -0
  130. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_qs.o +0 -0
  131. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_regex.o +0 -0
  132. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_symbol.o +0 -0
  133. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_url.o +0 -0
  134. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_url_params.o +0 -0
  135. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_util_compare.o +0 -0
  136. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_util_style.o +0 -0
  137. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_array.o +0 -0
  138. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_assert.o +0 -0
  139. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_async.o +0 -0
  140. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_async_dyn.o +0 -0
  141. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_bytes.o +0 -0
  142. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_bytes_io.o +0 -0
  143. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_crypto_async.o +0 -0
  144. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_dyn_invoke.o +0 -0
  145. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_events.o +0 -0
  146. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_exception.o +0 -0
  147. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_file_handle.o +0 -0
  148. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_island.o +0 -0
  149. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_json.o +0 -0
  150. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_lib.o +0 -0
  151. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_map.o +0 -0
  152. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_node_builtin.o +0 -0
  153. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_qs.o +0 -0
  154. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_regex.o +0 -0
  155. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_symbol.o +0 -0
  156. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_url.o +0 -0
  157. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_url_params.o +0 -0
  158. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_util_compare.o +0 -0
  159. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_util_style.o +0 -0
  160. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_web.o +0 -0
  161. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic-text-decoder-legacy/scr_bytes.o +0 -0
  162. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  163. package/dist/lib/runtime-darwin-x64/package.json +1 -1
  164. package/dist/lib/runtime-darwin-x64/runtime-pack.json +423 -297
  165. package/dist/lib/runtime-sources/package.json +1 -1
  166. package/dist/lib/runtime-sources/src/scr_abort.c +5 -0
  167. package/dist/lib/runtime-sources/src/scr_array.c +5 -0
  168. package/dist/lib/runtime-sources/src/scr_assert.c +26 -0
  169. package/dist/lib/runtime-sources/src/scr_async.c +56 -4
  170. package/dist/lib/runtime-sources/src/scr_async_dyn.c +118 -24
  171. package/dist/lib/runtime-sources/src/scr_bytes.c +226 -0
  172. package/dist/lib/runtime-sources/src/scr_bytes_io.c +355 -51
  173. package/dist/lib/runtime-sources/src/scr_crypto_async.c +22 -2
  174. package/dist/lib/runtime-sources/src/scr_dyn_invoke.c +181 -43
  175. package/dist/lib/runtime-sources/src/scr_events.c +9 -0
  176. package/dist/lib/runtime-sources/src/scr_exception.c +1 -0
  177. package/dist/lib/runtime-sources/src/scr_fetch.c +45 -1
  178. package/dist/lib/runtime-sources/src/scr_file_handle.c +42 -0
  179. package/dist/lib/runtime-sources/src/scr_island.c +2166 -786
  180. package/dist/lib/runtime-sources/src/scr_island_manifest.h +19 -13
  181. package/dist/lib/runtime-sources/src/scr_json.c +584 -54
  182. package/dist/lib/runtime-sources/src/scr_json.test.ts +1 -0
  183. package/dist/lib/runtime-sources/src/scr_lib.c +1192 -43
  184. package/dist/lib/runtime-sources/src/scr_map.c +8 -0
  185. package/dist/lib/runtime-sources/src/scr_node_builtin.c +78 -0
  186. package/dist/lib/runtime-sources/src/scr_qs.c +10 -0
  187. package/dist/lib/runtime-sources/src/scr_regex.c +107 -5
  188. package/dist/lib/runtime-sources/src/scr_runtime.h +136 -9
  189. package/dist/lib/runtime-sources/src/scr_symbol.c +5 -0
  190. package/dist/lib/runtime-sources/src/scr_system_errors.h +573 -0
  191. package/dist/lib/runtime-sources/src/scr_text_decoder_labels.h +12 -12
  192. package/dist/lib/runtime-sources/src/scr_url.c +547 -64
  193. package/dist/lib/runtime-sources/src/scr_url_internal.h +6 -0
  194. package/dist/lib/runtime-sources/src/scr_url_params.c +10 -0
  195. package/dist/lib/runtime-sources/src/scr_util_compare.c +222 -0
  196. package/dist/lib/runtime-sources/src/scr_util_style.c +337 -0
  197. package/dist/lib/runtime-sources/src/scr_web.c +849 -264
  198. package/package.json +1 -1
@@ -27,6 +27,13 @@
27
27
  #include <stdlib.h>
28
28
  #include <string.h>
29
29
  #include <sys/stat.h>
30
+ #if defined(__linux__)
31
+ #include <sys/vfs.h>
32
+ #elif defined(__APPLE__) || defined(__FreeBSD__)
33
+ #include <sys/mount.h>
34
+ #elif !defined(_WIN32) && !defined(__wasi__)
35
+ #include <sys/statvfs.h>
36
+ #endif
30
37
  #include <time.h>
31
38
 
32
39
  #ifdef _WIN32
@@ -55,6 +62,8 @@
55
62
 
56
63
  static WCHAR *scr_fs_win_wide(const ScrStr *path);
57
64
  static int scr_fs_win_errno(DWORD error);
65
+ static bool scr_stats_is_link_tag(DWORD tag);
66
+ static bool scr_stats_link_target(HANDLE h, DWORD tag, double *size_out, ScrStr **text_out);
58
67
 
59
68
  static ScrStr *scr_fs_win_utf8(const WCHAR *text, size_t length) {
60
69
  if (length > INT_MAX) { SetLastError(ERROR_FILENAME_EXCED_RANGE); return NULL; }
@@ -729,6 +738,239 @@ void scr_env_unset(const ScrStr *name) {
729
738
  #endif
730
739
  }
731
740
 
741
+ static ScrStr *scr_fs_read_file_core(ScrStr *path, bool env_file);
742
+
743
+ /* Node's dotenv parser trims ASCII space/tab/newline and removes every CR,
744
+ * including CR inside quoted values. Quoted values can span lines; only
745
+ * double quotes expand backslash-n. Preserve the pinned parser's ordering
746
+ * of empty-value and export-prefix handling, including its unusual forms. */
747
+ static bool scr_env_space(char c) { return c == ' ' || c == '\t' || c == '\n'; }
748
+
749
+ static void scr_env_trim(const char *text, size_t *start, size_t *end) {
750
+ while (*start < *end && scr_env_space(text[*start])) (*start)++;
751
+ while (*end > *start && scr_env_space(text[*end - 1])) (*end)--;
752
+ }
753
+
754
+ static void scr_env_store(ScrDyn *out, const char *text, size_t ks, size_t ke,
755
+ size_t vs, size_t ve, bool expand) {
756
+ ScrJsonBuf value;
757
+ scr_jb_init(&value);
758
+ for (size_t i = vs; i < ve; i++) {
759
+ if (expand && text[i] == '\\' && i + 1 < ve && text[i + 1] == 'n') {
760
+ scr_jb_putc(&value, '\n');
761
+ i++;
762
+ } else scr_jb_putc(&value, text[i]);
763
+ }
764
+ ScrStr *s = scr_jb_finish(&value);
765
+ scr_dyn_obj_set(out, text + ks, ke - ks, scr_dyn_new_str(s));
766
+ scr_str_release(s);
767
+ }
768
+
769
+ static int scr_env_entry_order(const void *left, const void *right) {
770
+ const ScrDynEntry *a = *(const ScrDynEntry *const *)left;
771
+ const ScrDynEntry *b = *(const ScrDynEntry *const *)right;
772
+ size_t n = a->key_len < b->key_len ? a->key_len : b->key_len;
773
+ int cmp = memcmp(a->key, b->key, n);
774
+ return cmp ? cmp : (a->key_len > b->key_len) - (a->key_len < b->key_len);
775
+ }
776
+
777
+ static ScrDyn *scr_env_parse_content(const ScrStr *input, bool ordinary) {
778
+ char *text = malloc(input->len + 1);
779
+ if (!text) scr_trap("scriptc: out of memory\n");
780
+ size_t end = 0;
781
+ for (size_t i = 0; i < input->len; i++) if (input->data[i] != '\r') text[end++] = input->data[i];
782
+ text[end] = '\0';
783
+ size_t pos = 0;
784
+ scr_env_trim(text, &pos, &end);
785
+ ScrDyn *entries = scr_dyn_new_obj_null_proto();
786
+ while (pos < end) {
787
+ if (text[pos] == '\n' || text[pos] == '#') {
788
+ while (pos < end && text[pos] != '\n') pos++;
789
+ if (pos < end) pos++;
790
+ continue;
791
+ }
792
+ size_t equal = pos;
793
+ while (equal < end && text[equal] != '=' && text[equal] != '\n') equal++;
794
+ if (equal == end) break;
795
+ if (text[equal] == '\n') {
796
+ pos = equal + 1;
797
+ scr_env_trim(text, &pos, &end);
798
+ continue;
799
+ }
800
+ size_t ks = pos, ke = equal;
801
+ scr_env_trim(text, &ks, &ke);
802
+ pos = equal + 1;
803
+ if (pos == end || text[pos] == '\n') {
804
+ scr_env_store(entries, text, ks, ke, pos, pos, false);
805
+ continue;
806
+ }
807
+ scr_env_trim(text, &pos, &end);
808
+ if (ks == ke) continue;
809
+ if (ke - ks >= 7 && memcmp(text + ks, "export ", 7) == 0) {
810
+ ks += 7;
811
+ scr_env_trim(text, &ks, &ke);
812
+ }
813
+ if (pos == end) {
814
+ scr_env_store(entries, text, ks, ke, pos, pos, false);
815
+ break;
816
+ }
817
+ char quote = text[pos];
818
+ if (quote == '\'' || quote == '"' || quote == '`') {
819
+ size_t closing = pos + 1;
820
+ while (closing < end && text[closing] != quote) closing++;
821
+ if (closing < end) {
822
+ scr_env_store(entries, text, ks, ke, pos + 1, closing, quote == '"');
823
+ pos = closing + 1;
824
+ while (pos < end && text[pos] != '\n') pos++;
825
+ if (pos < end) pos++;
826
+ continue;
827
+ }
828
+ size_t newline = pos;
829
+ while (newline < end && text[newline] != '\n') newline++;
830
+ scr_env_store(entries, text, ks, ke, pos, newline, false);
831
+ pos = newline < end ? newline + 1 : end;
832
+ } else {
833
+ size_t newline = pos;
834
+ while (newline < end && text[newline] != '\n') newline++;
835
+ size_t vs = pos, ve = pos;
836
+ while (ve < newline && text[ve] != '#') ve++;
837
+ scr_env_trim(text, &vs, &ve);
838
+ scr_env_store(entries, text, ks, ke, vs, ve, false);
839
+ pos = newline < end ? newline + 1 : end;
840
+ }
841
+ scr_env_trim(text, &pos, &end);
842
+ }
843
+ free(text);
844
+ /* Node's std::map orders UTF-8 keys before installing object properties.
845
+ * Ordinary objects ignore string-valued __proto__ assignments; loading
846
+ * the native environment retains that name as an ordinary variable. */
847
+ size_t count = entries->v.obj.len;
848
+ const ScrDynEntry **sorted = count ? malloc(count * sizeof(*sorted)) : NULL;
849
+ if (count && !sorted) scr_trap("scriptc: out of memory\n");
850
+ for (size_t i = 0; i < count; i++) sorted[i] = &entries->v.obj.entries[i];
851
+ if (count > 1) qsort(sorted, count, sizeof(*sorted), scr_env_entry_order);
852
+ ScrDyn *result = ordinary ? scr_dyn_new_obj() : scr_dyn_new_obj_null_proto();
853
+ for (size_t i = 0; i < count; i++) {
854
+ const ScrDynEntry *entry = sorted[i];
855
+ if (ordinary && entry->key_len == 9 && memcmp(entry->key, "__proto__", 9) == 0) continue;
856
+ if (!ordinary) {
857
+ scr_dyn_obj_set(result, entry->key, entry->key_len, scr_dyn_retain(entry->value));
858
+ continue;
859
+ }
860
+ /* Convert sorted entries to JS strings after parsing. */
861
+ ScrStr *key = scr_str_from_utf8_lossy((const uint8_t *)entry->key, entry->key_len);
862
+ const ScrStr *raw = entry->value->v.str;
863
+ ScrStr *value = scr_str_from_utf8_lossy((const uint8_t *)raw->data, raw->len);
864
+ scr_dyn_obj_set(result, key->data, key->len, scr_dyn_new_str(value));
865
+ scr_str_release(key);
866
+ scr_str_release(value);
867
+ }
868
+ free(sorted);
869
+ scr_dyn_release(entries);
870
+ return result;
871
+ }
872
+
873
+ ScrDyn *scr_util_parse_env(const ScrDyn *content) {
874
+ if (content->kind != SCR_DYN_STR) {
875
+ scr_dyn_arg_type_fail("content", "of type string", content);
876
+ return NULL;
877
+ }
878
+ return scr_env_parse_content(content->v.str, true);
879
+ }
880
+
881
+ void scr_fs_invalid_named_path(const ScrDyn *value, const ScrStr *path, const char *name) {
882
+ ScrJsonBuf b;
883
+ scr_jb_init(&b);
884
+ scr_jb_puts(&b, "The argument '");
885
+ scr_jb_puts(&b, name);
886
+ scr_jb_puts(&b, "' must be a string, Uint8Array, or URL without null bytes. Received ");
887
+ if (value->kind == SCR_DYN_BYTES) {
888
+ const ScrBytes *bytes = value->v.bytes;
889
+ if (value->buffer) {
890
+ scr_jb_puts(&b, "<Buffer");
891
+ for (size_t i = 0; i < bytes->len; i++) {
892
+ char hex[4];
893
+ snprintf(hex, sizeof hex, " %02x", bytes->data[i]);
894
+ scr_jb_puts(&b, hex);
895
+ }
896
+ scr_jb_putc(&b, '>');
897
+ } else {
898
+ char length[48];
899
+ snprintf(length, sizeof length, "Uint8Array(%zu) [ ", bytes->len);
900
+ scr_jb_puts(&b, length);
901
+ for (size_t i = 0; i < bytes->len; i++) {
902
+ char number[8];
903
+ snprintf(number, sizeof number, "%s%u", i ? ", " : "", bytes->data[i]);
904
+ scr_jb_puts(&b, number);
905
+ }
906
+ scr_jb_puts(&b, " ]");
907
+ }
908
+ } else {
909
+ char quote = memchr(path->data, '\'', path->len) ? '"' : '\'';
910
+ scr_jb_putc(&b, quote);
911
+ for (size_t i = 0; i < path->len; i++) {
912
+ unsigned char c = (unsigned char)path->data[i];
913
+ if (c == (unsigned char)quote || c == '\\') { scr_jb_putc(&b, '\\'); scr_jb_putc(&b, (char)c); }
914
+ else if (c == '\n') scr_jb_puts(&b, "\\n");
915
+ else if (c == '\r') scr_jb_puts(&b, "\\r");
916
+ else if (c == '\t') scr_jb_puts(&b, "\\t");
917
+ else if (c < 0x20 || c == 0x7f) {
918
+ char escape[5];
919
+ snprintf(escape, sizeof escape, "\\x%02X", c);
920
+ scr_jb_puts(&b, escape);
921
+ } else scr_jb_putc(&b, (char)c);
922
+ }
923
+ scr_jb_putc(&b, quote);
924
+ }
925
+ ScrStr *message = scr_jb_finish(&b);
926
+ scr_throw_error_msg_code(SCR_ERR_TYPE, message->data, message->len, "ERR_INVALID_ARG_VALUE");
927
+ scr_str_release(message);
928
+ }
929
+
930
+ void scr_fs_invalid_path(const ScrDyn *value, const ScrStr *path) {
931
+ scr_fs_invalid_named_path(value, path, "path");
932
+ }
933
+
934
+ void scr_process_load_env_file(const ScrDyn *value) {
935
+ ScrStr *path;
936
+ if (value->kind == SCR_DYN_UNDEF || value->kind == SCR_DYN_NULL) path = scr_str_new("./.env", 6);
937
+ else if (value->kind == SCR_DYN_STR) path = scr_str_retain(value->v.str);
938
+ else if (scr_dyn_native_url_is(value)) path = scr_url_to_path(value->v.handle.ptr);
939
+ else if (scr_dyn_bytes_is(value, SCR_BYTES_U8)) path = scr_str_new((const char *)value->v.bytes->data, value->v.bytes->len);
940
+ else {
941
+ scr_dyn_arg_type_fail("path", "of type string or an instance of Buffer or URL", value);
942
+ return;
943
+ }
944
+ if (!path) return;
945
+ if (memchr(path->data, 0, path->len)) {
946
+ scr_fs_invalid_path(value, path);
947
+ scr_str_release(path);
948
+ return;
949
+ }
950
+ ScrStr *content = scr_fs_read_file_core(path, true);
951
+ scr_str_release(path);
952
+ if (!content) return;
953
+ ScrDyn *entries = scr_env_parse_content(content, false);
954
+ scr_str_release(content);
955
+ for (size_t i = 0; i < entries->v.obj.len; i++) {
956
+ const ScrDynEntry *entry = &entries->v.obj.entries[i];
957
+ /* Node normalizes each raw sorted entry immediately before checking
958
+ * the environment. Distinct invalid byte keys can normalize alike;
959
+ * the first absent name wins, rather than the last parsed entry. */
960
+ ScrStr *key = scr_str_from_utf8_lossy((const uint8_t *)entry->key, entry->key_len);
961
+ ScrStr *existing = scr_env_get(key);
962
+ if (!existing) {
963
+ const ScrStr *raw = entry->value->v.str;
964
+ ScrStr *value = scr_str_from_utf8_lossy((const uint8_t *)raw->data, raw->len);
965
+ scr_env_set(key, value);
966
+ scr_str_release(value);
967
+ }
968
+ scr_str_release(existing);
969
+ scr_str_release(key);
970
+ }
971
+ scr_dyn_release(entries);
972
+ }
973
+
732
974
  /* The whole environment as one fresh string[] of alternating
733
975
  * [k0, v0, k1, v1, ...] entries in environ order — the raw material of the
734
976
  * compiler's process.env snapshot record (insertion order = environ order,
@@ -2019,13 +2261,15 @@ void scr_fs_throw(int e, const char *op, const ScrStr *path) {
2019
2261
  const char *name = scr_errno_name(e, namebuf, sizeof namebuf);
2020
2262
  const char *text = scr_errno_text(e);
2021
2263
  char pathbuf[PATH_MAX];
2022
- const char *shown = scr_fs_err_path(path, pathbuf);
2264
+ const char *shown = path ? scr_fs_err_path(path, pathbuf) : "";
2023
2265
  size_t cap = strlen(name) + strlen(text) + strlen(op) + strlen(shown) + 8;
2024
2266
  char *msg = malloc(cap);
2025
2267
  if (!msg) {
2026
2268
  scr_trap("scriptc: out of memory\n");
2027
2269
  }
2028
- int len = snprintf(msg, cap, "%s: %s, %s '%s'", name, text, op, shown);
2270
+ int len = path
2271
+ ? snprintf(msg, cap, "%s: %s, %s '%s'", name, text, op, shown)
2272
+ : snprintf(msg, cap, "%s: %s, %s", name, text, op);
2029
2273
  /* A real Error instance (name "Error", message = Node's text) — what a
2030
2274
  * typed catch's `e instanceof Error` + `e.message` observes in Node —
2031
2275
  * with `code` stamped to the errno name (the exotic-errno fallback
@@ -2055,7 +2299,7 @@ static FILE *scr_fs_fopen(const ScrStr *path, const char *mode) {
2055
2299
  #endif
2056
2300
  }
2057
2301
 
2058
- ScrStr *scr_fs_read_file(ScrStr *path) {
2302
+ static ScrStr *scr_fs_read_file_core(ScrStr *path, bool env_file) {
2059
2303
  FILE *f = scr_fs_fopen(path, "rb");
2060
2304
  if (!f) {
2061
2305
  scr_fs_throw(errno, "open", path);
@@ -2083,7 +2327,16 @@ ScrStr *scr_fs_read_file(ScrStr *path) {
2083
2327
  int e = errno;
2084
2328
  fclose(f);
2085
2329
  free(buf);
2086
- scr_fs_throw(e, "read", path);
2330
+ if (env_file) {
2331
+ ScrJsonBuf b;
2332
+ scr_jb_init(&b);
2333
+ scr_jb_puts(&b, "Contents of '");
2334
+ scr_jb_put_str(&b, path);
2335
+ scr_jb_puts(&b, "' should be a valid string.");
2336
+ ScrStr *message = scr_jb_finish(&b);
2337
+ scr_throw_error_msg_code(SCR_ERR_TYPE, message->data, message->len, "ERR_INVALID_ARG_TYPE");
2338
+ scr_str_release(message);
2339
+ } else scr_fs_throw(e, "read", path);
2087
2340
  return NULL;
2088
2341
  }
2089
2342
  fclose(f);
@@ -2092,6 +2345,11 @@ ScrStr *scr_fs_read_file(ScrStr *path) {
2092
2345
  return s;
2093
2346
  }
2094
2347
 
2348
+ ScrStr *scr_fs_read_file(ScrStr *path) {
2349
+ return scr_fs_read_file_core(path, false);
2350
+ }
2351
+
2352
+
2095
2353
  static ScrStr *scr_fs_realpath_common(ScrStr *path, const char *op) {
2096
2354
  #ifdef _WIN32
2097
2355
  WCHAR *wide = scr_fs_win_wide(path);
@@ -2258,7 +2516,45 @@ void scr_fs_mkdir_mode(ScrStr *path, double mode) {
2258
2516
  if (scr_sys_mkdir(path->data, (mode_t)mode) != 0) scr_fs_throw(errno, "mkdir", path);
2259
2517
  }
2260
2518
 
2519
+ #ifdef _WIN32
2520
+ /* The CRT's stat follows junctions. Recognize real link reparse records
2521
+ * before unlink/rm dispatch so a tree walk never descends into their targets.
2522
+ * Return 1 for a removed link, 0 for an ordinary entry, -1 on failure. */
2523
+ static int scr_fs_win_remove_link(const char *path, size_t len) {
2524
+ ScrStr *text = scr_str_new(path, len);
2525
+ WCHAR *wide = scr_fs_win_wide(text);
2526
+ scr_str_release(text);
2527
+ if (!wide) { errno = scr_fs_win_errno(GetLastError()); return -1; }
2528
+ DWORD attributes = GetFileAttributesW(wide);
2529
+ if (attributes == INVALID_FILE_ATTRIBUTES || !(attributes & FILE_ATTRIBUTE_REPARSE_POINT)) { free(wide); return 0; }
2530
+ HANDLE h = CreateFileW(wide, FILE_READ_ATTRIBUTES,
2531
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
2532
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
2533
+ if (h == INVALID_HANDLE_VALUE) { DWORD error = GetLastError(); free(wide); errno = scr_fs_win_errno(error); return -1; }
2534
+ FILE_ATTRIBUTE_TAG_INFO tagged;
2535
+ if (!GetFileInformationByHandleEx(h, FileAttributeTagInfo, &tagged, sizeof tagged)) {
2536
+ DWORD error = GetLastError(); CloseHandle(h); free(wide); errno = scr_fs_win_errno(error); return -1;
2537
+ }
2538
+ double size;
2539
+ bool link = scr_stats_is_link_tag(tagged.ReparseTag);
2540
+ // Mount-point tags also represent mounted volumes. Only junction payloads
2541
+ // are links; keep the existing ordinary-directory behavior for volumes.
2542
+ if (link && tagged.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) link = scr_stats_link_target(h, tagged.ReparseTag, &size, NULL);
2543
+ CloseHandle(h);
2544
+ if (!link) { free(wide); return 0; }
2545
+ BOOL ok = attributes & FILE_ATTRIBUTE_DIRECTORY ? RemoveDirectoryW(wide) : DeleteFileW(wide);
2546
+ DWORD error = ok ? 0 : GetLastError();
2547
+ free(wide);
2548
+ if (!ok) { errno = scr_fs_win_errno(error); return -1; }
2549
+ return 1;
2550
+ }
2551
+ #endif
2552
+
2261
2553
  void scr_fs_unlink(ScrStr *path) {
2554
+ #ifdef _WIN32
2555
+ int link = scr_fs_win_remove_link(path->data, path->len);
2556
+ if (link != 0) { if (link < 0) scr_fs_throw(errno, "unlink", path); return; }
2557
+ #endif
2262
2558
  if (unlink(path->data) != 0) scr_fs_throw(errno, "unlink", path);
2263
2559
  }
2264
2560
 
@@ -2353,6 +2649,20 @@ double scr_fs_open_numeric(ScrStr *path, double flags, double mode) {
2353
2649
  }
2354
2650
 
2355
2651
  static void scr_fs_throw_nopath(int e, const char *op);
2652
+ static bool scr_fs_write_fd_valid(double fd);
2653
+
2654
+ double scr_fs_open_native(ScrStr *path, double flags, double mode) {
2655
+ #ifdef _WIN32
2656
+ WCHAR *wide = scr_fs_win_wide(path);
2657
+ if (!wide) { scr_fs_throw(scr_fs_win_errno(GetLastError()), "open", path); return 0; }
2658
+ int fd = _wopen(wide, (int)flags | O_BINARY, (int)mode);
2659
+ free(wide);
2660
+ #else
2661
+ int fd = open(path->data, (int)flags | O_BINARY, (mode_t)mode);
2662
+ #endif
2663
+ if (fd < 0) { scr_fs_throw(errno, "open", path); return 0; }
2664
+ return (double)fd;
2665
+ }
2356
2666
 
2357
2667
  void scr_fs_fchmod(double fd, double mode) {
2358
2668
  #ifdef _WIN32
@@ -2374,6 +2684,114 @@ void scr_fs_fsync(double fd) {
2374
2684
  #endif
2375
2685
  }
2376
2686
 
2687
+ void scr_fs_fdatasync(double fd) {
2688
+ if (!scr_fs_write_fd_valid(fd)) return;
2689
+ #if defined(_WIN32)
2690
+ if (_commit((int)fd) != 0) scr_fs_throw_nopath(errno, "fdatasync");
2691
+ #elif defined(__APPLE__)
2692
+ /* macOS/libuv implements fdatasync with fsync. */
2693
+ if (fsync((int)fd) != 0) scr_fs_throw_nopath(errno, "fdatasync");
2694
+ #else
2695
+ if (fdatasync((int)fd) != 0) scr_fs_throw_nopath(errno, "fdatasync");
2696
+ #endif
2697
+ }
2698
+
2699
+ void scr_fs_ftruncate(double fd, double length) {
2700
+ if (!scr_fs_write_fd_valid(fd)) return;
2701
+ char received[48], msg[192];
2702
+ scr_num_received(length, received);
2703
+ if (!(isfinite(length) && trunc(length) == length) ||
2704
+ length < -9007199254740991.0 || length > 9007199254740991.0) {
2705
+ const char *bound = isfinite(length) && trunc(length) == length
2706
+ ? ">= -9007199254740991 && <= 9007199254740991" : "an integer";
2707
+ int n = snprintf(msg, sizeof msg,
2708
+ "The value of \"len\" is out of range. It must be %s. Received %s", bound, received);
2709
+ scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)n, "ERR_OUT_OF_RANGE");
2710
+ return;
2711
+ }
2712
+ if (length < 0) length = 0;
2713
+ #ifdef _WIN32
2714
+ int error = (int)_chsize_s((int)fd, (__int64)length);
2715
+ if (error) scr_fs_throw_nopath(error, "ftruncate");
2716
+ #else
2717
+ if (ftruncate((int)fd, (off_t)length) != 0) scr_fs_throw_nopath(errno, "ftruncate");
2718
+ #endif
2719
+ }
2720
+
2721
+ #ifdef _WIN32
2722
+ static bool scr_fs_set_times(HANDLE handle, double access, double modified) {
2723
+ FILETIME atime, mtime, now;
2724
+ if (isinf(access) || isinf(modified)) GetSystemTimeAsFileTime(&now);
2725
+ double values[2] = { access, modified };
2726
+ FILETIME *files[2] = { &atime, &mtime };
2727
+ for (size_t i = 0; i < 2; i++) {
2728
+ if (isnan(values[i])) { files[i] = NULL; continue; }
2729
+ if (isinf(values[i])) { *files[i] = now; continue; }
2730
+ double ticks = values[i] * 10000000.0 + 116444736000000000.0;
2731
+ if (ticks < -9223372036854775808.0 || ticks >= 9223372036854775808.0) {
2732
+ SetLastError(ERROR_INVALID_PARAMETER);
2733
+ return false;
2734
+ }
2735
+ int64_t time = (int64_t)ticks;
2736
+ files[i]->dwLowDateTime = (DWORD)(uint64_t)time;
2737
+ files[i]->dwHighDateTime = (DWORD)((uint64_t)time >> 32);
2738
+ }
2739
+ return SetFileTime(handle, NULL, files[0], files[1]) != 0;
2740
+ }
2741
+
2742
+ static DWORD scr_fs_set_path_times(WCHAR *path, double atime, double mtime, bool nofollow) {
2743
+ DWORD flags = FILE_FLAG_BACKUP_SEMANTICS | (nofollow ? FILE_FLAG_OPEN_REPARSE_POINT : 0);
2744
+ HANDLE handle = CreateFileW(path, FILE_WRITE_ATTRIBUTES,
2745
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, flags, NULL);
2746
+ if (handle == INVALID_HANDLE_VALUE) return GetLastError();
2747
+ DWORD error = scr_fs_set_times(handle, atime, mtime) ? 0 : GetLastError();
2748
+ CloseHandle(handle);
2749
+ return error;
2750
+ }
2751
+ #else
2752
+ /* Match the pinned libuv conversion: microsecond truncation, normalized
2753
+ * negative fractions, and the special current/unchanged time sentinels. */
2754
+ static bool scr_fs_timespec(double time, struct timespec *out) {
2755
+ if (isinf(time)) { *out = (struct timespec){ 0, UTIME_NOW }; return true; }
2756
+ if (isnan(time)) { *out = (struct timespec){ 0, UTIME_OMIT }; return true; }
2757
+ if (time < -9223372036854775808.0 || time >= 9223372036854775808.0) return false;
2758
+ out->tv_sec = (time_t)time;
2759
+ out->tv_nsec = (long)((time - (double)out->tv_sec) * 1000000000.0);
2760
+ out->tv_nsec -= out->tv_nsec % 1000;
2761
+ if (out->tv_nsec < 0) { out->tv_nsec += 1000000000; out->tv_sec--; }
2762
+ return true;
2763
+ }
2764
+ #endif
2765
+
2766
+ void scr_fs_utimes(ScrStr *path, double atime, double mtime, bool nofollow) {
2767
+ const char *op = nofollow ? "lutime" : "utime";
2768
+ #ifdef _WIN32
2769
+ WCHAR *wide = scr_fs_win_wide(path);
2770
+ if (!wide) { scr_fs_throw(scr_fs_win_errno(GetLastError()), op, path); return; }
2771
+ DWORD error = scr_fs_set_path_times(wide, atime, mtime, nofollow);
2772
+ if (nofollow && (error == ERROR_SYMLINK_NOT_SUPPORTED || error == ERROR_NOT_A_REPARSE_POINT))
2773
+ error = scr_fs_set_path_times(wide, atime, mtime, false);
2774
+ free(wide);
2775
+ if (error) scr_fs_throw(scr_fs_win_errno(error), op, path);
2776
+ #else
2777
+ struct timespec times[2];
2778
+ if (!scr_fs_timespec(atime, &times[0]) || !scr_fs_timespec(mtime, &times[1])) { scr_fs_throw(EINVAL, op, path); return; }
2779
+ if (utimensat(AT_FDCWD, path->data, times, nofollow ? AT_SYMLINK_NOFOLLOW : 0) != 0) scr_fs_throw(errno, op, path);
2780
+ #endif
2781
+ }
2782
+
2783
+ void scr_fs_futimes(double fd, double atime, double mtime) {
2784
+ #ifdef _WIN32
2785
+ HANDLE handle = (HANDLE)_get_osfhandle((int)fd);
2786
+ if (handle == INVALID_HANDLE_VALUE) { scr_fs_throw_nopath(EBADF, "futime"); return; }
2787
+ if (!scr_fs_set_times(handle, atime, mtime)) scr_fs_throw_nopath(scr_fs_win_errno(GetLastError()), "futime");
2788
+ #else
2789
+ struct timespec times[2];
2790
+ if (!scr_fs_timespec(atime, &times[0]) || !scr_fs_timespec(mtime, &times[1])) { scr_fs_throw_nopath(EINVAL, "futime"); return; }
2791
+ if (futimens((int)fd, times) != 0) scr_fs_throw_nopath(errno, "futime");
2792
+ #endif
2793
+ }
2794
+
2377
2795
  /* fs.closeSync(fd) — close(2); failure throws Node's path-less fs error
2378
2796
  * shape ("EBADF: bad file descriptor, close"). */
2379
2797
 
@@ -2771,6 +3189,73 @@ double scr_fs_read_sync(double fd, ScrBytes *buf, double offset, double length,
2771
3189
  return (double)n;
2772
3190
  }
2773
3191
 
3192
+ /* Gather/scatter one contiguous window around ONE kernel operation. Multiple
3193
+ * scalar calls would change short I/O and pipe atomicity. This portable seam
3194
+ * also preserves positioned I/O on targets without preadv/pwritev. */
3195
+ double scr_fs_read_zero(double fd, double position) {
3196
+ if (!scr_fs_write_fd_valid(fd)) return 0;
3197
+ uint8_t dummy;
3198
+ ssize_t n = position < 0 ? read((int)fd, &dummy, 0)
3199
+ : scr_fs_pread((int)fd, &dummy, 0, position);
3200
+ if (n < 0) scr_fs_throw_nopath(errno, "read");
3201
+ return 0;
3202
+ }
3203
+
3204
+ static double scr_fs_vector_sync(double fd, ScrArr *buffers, double position,
3205
+ bool writing) {
3206
+ size_t count = (size_t)scr_arr_len(buffers);
3207
+ if (writing && count == 0) return 0;
3208
+ if (!scr_fs_write_fd_valid(fd)) return 0;
3209
+ if (count == 0) { scr_fs_throw_nopath(EINVAL, "read"); return 0; }
3210
+ size_t length = 0;
3211
+ for (size_t i = 0; i < count; i++) {
3212
+ ScrBytes *buf = scr_arr_get_ref(buffers, (double)i);
3213
+ size_t n = buf->len;
3214
+ scr_bytes_release(buf);
3215
+ if (n > SIZE_MAX - length) scr_trap("scriptc: vector I/O size overflow\n");
3216
+ length += n;
3217
+ }
3218
+ uint8_t *data = malloc(length ? length : 1);
3219
+ if (!data) scr_trap("scriptc: out of memory\n");
3220
+ if (writing) {
3221
+ size_t at = 0;
3222
+ for (size_t i = 0; i < count; i++) {
3223
+ ScrBytes *buf = scr_arr_get_ref(buffers, (double)i);
3224
+ if (buf->len) memcpy(data + at, buf->data, buf->len);
3225
+ at += buf->len;
3226
+ scr_bytes_release(buf);
3227
+ }
3228
+ }
3229
+ ScrBytes window = {SIZE_MAX, length, SCR_BYTES_U8, data, NULL};
3230
+ position = isfinite(position) && position >= 0 && position <= 9007199254740991.0
3231
+ ? trunc(position) : -1;
3232
+ double transferred = writing
3233
+ ? scr_fs_write_sync(fd, &window, 0, (double)length, position)
3234
+ : length == 0 ? scr_fs_read_zero(fd, position)
3235
+ : scr_fs_read_sync(fd, &window, 0, (double)length, position);
3236
+ if (!writing && !scr_exc_pending()) {
3237
+ size_t at = 0, remaining = (size_t)transferred;
3238
+ for (size_t i = 0; i < count && remaining; i++) {
3239
+ ScrBytes *buf = scr_arr_get_ref(buffers, (double)i);
3240
+ size_t n = buf->len < remaining ? buf->len : remaining;
3241
+ if (n) memcpy(buf->data, data + at, n);
3242
+ at += n;
3243
+ remaining -= n;
3244
+ scr_bytes_release(buf);
3245
+ }
3246
+ }
3247
+ free(data);
3248
+ return transferred;
3249
+ }
3250
+
3251
+ double scr_fs_readv_sync(double fd, ScrArr *buffers, double position) {
3252
+ return scr_fs_vector_sync(fd, buffers, position, false);
3253
+ }
3254
+
3255
+ double scr_fs_writev_sync(double fd, ScrArr *buffers, double position) {
3256
+ return scr_fs_vector_sync(fd, buffers, position, true);
3257
+ }
3258
+
2774
3259
  void scr_fs_close(double fd) {
2775
3260
  if (close((int)fd) != 0) {
2776
3261
  int e = errno;
@@ -2807,18 +3292,15 @@ void scr_fs_link(ScrStr *source, ScrStr *dest) {
2807
3292
  WCHAR *from = scr_fs_win_wide(source);
2808
3293
  WCHAR *to = scr_fs_win_wide(dest);
2809
3294
  if (!from || !to) {
3295
+ DWORD error = GetLastError();
2810
3296
  free(from); free(to);
2811
- scr_fs_throw2(EINVAL, "link", source, dest);
3297
+ scr_fs_throw2(scr_fs_win_errno(error), "link", source, dest);
2812
3298
  return;
2813
3299
  }
2814
3300
  BOOL ok = CreateHardLinkW(to, from, NULL);
2815
3301
  DWORD error = ok ? 0 : GetLastError();
2816
3302
  free(from); free(to);
2817
- if (!ok) {
2818
- int code = error == ERROR_ALREADY_EXISTS || error == ERROR_FILE_EXISTS ? EEXIST :
2819
- error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND ? ENOENT : EACCES;
2820
- scr_fs_throw2(code, "link", source, dest);
2821
- }
3303
+ if (!ok) scr_fs_throw2(scr_fs_win_errno(error), "link", source, dest);
2822
3304
  #else
2823
3305
  if (link(source->data, dest->data) != 0) scr_fs_throw2(errno, "link", source, dest);
2824
3306
  #endif
@@ -2989,6 +3471,10 @@ void scr_fs_rename(ScrStr *oldpath, ScrStr *newpath) {
2989
3471
  static int scr_rm_unlink(const char *path, size_t len);
2990
3472
 
2991
3473
  void scr_fs_rm(ScrStr *path) {
3474
+ #ifdef _WIN32
3475
+ int link = scr_fs_win_remove_link(path->data, path->len);
3476
+ if (link != 0) { if (link < 0) scr_fs_throw(errno, "unlink", path); return; }
3477
+ #endif
2992
3478
  /* Node's rmSync: lstat first (a missing path reports the lstat syscall),
2993
3479
  * refuse directories (Node requires `recursive`, which the scriptc
2994
3480
  * surface doesn't declare — the message wording diverges from Node's
@@ -3143,6 +3629,10 @@ static int scr_rm_unlink(const char *path, size_t len) {
3143
3629
  /* Post-order tree removal for rmSync's recursive form. Stops at (and
3144
3630
  * records) the first failure, with the failing path and syscall name. */
3145
3631
  static void scr_rm_tree_e(const char *path, size_t len, ScrRmFail *f) {
3632
+ #ifdef _WIN32
3633
+ int link = scr_fs_win_remove_link(path, len);
3634
+ if (link != 0) { if (link < 0) scr_rm_fail_set(f, errno, "unlink", path, len); return; }
3635
+ #endif
3146
3636
  struct stat st;
3147
3637
  if (lstat(path, &st) != 0) {
3148
3638
  scr_rm_fail_set(f, errno, "lstat", path, len);
@@ -3183,6 +3673,10 @@ static void scr_rm_tree_e(const char *path, size_t len, ScrRmFail *f) {
3183
3673
  * force's ENOENT swallow, the non-recursive directory rejection, and the
3184
3674
  * tree walk — failures recorded in `f`, never thrown. */
3185
3675
  static void scr_fs_rm_attempt(ScrStr *path, bool recursive, bool force, ScrRmFail *f) {
3676
+ #ifdef _WIN32
3677
+ int link = scr_fs_win_remove_link(path->data, path->len);
3678
+ if (link != 0) { if (link < 0) scr_rm_fail_set(f, errno, "unlink", path->data, path->len); return; }
3679
+ #endif
3186
3680
  struct stat st;
3187
3681
  if (lstat(path->data, &st) != 0) {
3188
3682
  if (force && errno == ENOENT) return; /* Node: force swallows ENOENT */
@@ -3636,7 +4130,7 @@ typedef struct {
3636
4130
  /* Node/libuv reports a Windows link's target-text length as st_size. The
3637
4131
  * ordinary handle information does not carry that value, so read the reparse
3638
4132
  * payload while the no-follow handle is live. */
3639
- static bool scr_stats_link_size(HANDLE h, DWORD tag, double *size_out) {
4133
+ static bool scr_stats_link_target(HANDLE h, DWORD tag, double *size_out, ScrStr **text_out) {
3640
4134
  union {
3641
4135
  ScrStatsReparseData align;
3642
4136
  unsigned char bytes[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
@@ -3681,6 +4175,7 @@ static bool scr_stats_link_size(HANDLE h, DWORD tag, double *size_out) {
3681
4175
  /* WSL's LX symlink payload is a version word followed by UTF-8 bytes. */
3682
4176
  if (data->data_len < sizeof(ULONG)) return false;
3683
4177
  *size_out = (double)(data->data_len - sizeof(ULONG));
4178
+ if (text_out) *text_out = scr_str_new((const char *)data->body.generic.bytes + sizeof(ULONG), data->data_len - sizeof(ULONG));
3684
4179
  return true;
3685
4180
  } else {
3686
4181
  /* App execution links carry a counted UTF-16 string list; the third
@@ -3709,6 +4204,10 @@ static bool scr_stats_link_size(HANDLE h, DWORD tag, double *size_out) {
3709
4204
  double size = scr_stats_utf16_len(target, len);
3710
4205
  if (size < 0) return false;
3711
4206
  *size_out = size;
4207
+ if (text_out) {
4208
+ *text_out = scr_fs_win_utf8(target, len);
4209
+ if (!*text_out) return false;
4210
+ }
3712
4211
  return true;
3713
4212
  }
3714
4213
 
@@ -3734,6 +4233,10 @@ static bool scr_stats_link_size(HANDLE h, DWORD tag, double *size_out) {
3734
4233
  double size = scr_stats_utf16_len(target, len);
3735
4234
  if (size < 0) return false;
3736
4235
  *size_out = size;
4236
+ if (text_out) {
4237
+ *text_out = scr_fs_win_utf8(target, len);
4238
+ if (!*text_out) return false;
4239
+ }
3737
4240
  return true;
3738
4241
  }
3739
4242
 
@@ -3747,8 +4250,21 @@ static ScrStats *scr_stats_of_path(const ScrStr *path, const char *op,
3747
4250
  HANDLE h = CreateFileW(wide, FILE_READ_ATTRIBUTES,
3748
4251
  FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
3749
4252
  NULL, OPEN_EXISTING, flags, NULL);
4253
+ if (h == INVALID_HANDLE_VALUE) {
4254
+ DWORD error = GetLastError();
4255
+ DWORD attributes = GetFileAttributesW(wide);
4256
+ free(wide);
4257
+ /* CRT stat can report the link entry when its target cannot be opened,
4258
+ * hiding dangling links and Windows file/directory type mismatches.
4259
+ * libuv preserves the failed follow operation for reparse points. */
4260
+ if (!no_follow && attributes != INVALID_FILE_ATTRIBUTES &&
4261
+ (attributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
4262
+ scr_fs_throw(scr_fs_win_errno(error), op, path);
4263
+ return NULL;
4264
+ }
4265
+ return scr_stats_crt_fallback(path, op);
4266
+ }
3750
4267
  free(wide);
3751
- if (h == INVALID_HANDLE_VALUE) return scr_stats_crt_fallback(path, op);
3752
4268
 
3753
4269
  DWORD file_type = GetFileType(h);
3754
4270
  if (file_type != FILE_TYPE_DISK) {
@@ -3772,7 +4288,7 @@ static ScrStats *scr_stats_of_path(const ScrStr *path, const char *op,
3772
4288
  CloseHandle(h);
3773
4289
  return scr_stats_of_path(path, op, false);
3774
4290
  }
3775
- if (!scr_stats_link_size(h, tagged.ReparseTag, &link_size)) {
4291
+ if (!scr_stats_link_target(h, tagged.ReparseTag, &link_size, NULL)) {
3776
4292
  /* A mount-point tag may name a mounted volume rather than a junction.
3777
4293
  * libuv treats those (and malformed/unsupported link payloads) as
3778
4294
  * ordinary reparse points and retries with the final component
@@ -3849,6 +4365,215 @@ static ScrStats *scr_stats_of(const struct stat *st) {
3849
4365
  }
3850
4366
  #endif
3851
4367
 
4368
+ /* Symbolic link targets are stored verbatim on POSIX. Windows junctions
4369
+ * instead carry an absolute NT path in a bounded mount-point reparse record. */
4370
+ #ifdef _WIN32
4371
+ static DWORD scr_fs_win_junction(const WCHAR *target, const WCHAR *destination) {
4372
+ size_t len = wcslen(target);
4373
+ /* libuv permits drive-absolute junctions, not UNC mount targets. */
4374
+ size_t skip = len >= 4 && !wcsncmp(target, L"\\\\?\\", 4) ? 4 : 0;
4375
+ const WCHAR *plain = target + skip;
4376
+ size_t chars = len - skip;
4377
+ if (chars < 3 || !((plain[0] >= L'A' && plain[0] <= L'Z') || (plain[0] >= L'a' && plain[0] <= L'z')) || plain[1] != L':' || plain[2] != L'\\') return ERROR_INVALID_PARAMETER;
4378
+ size_t fixed = offsetof(ScrStatsReparseData, body.mount.path);
4379
+ size_t total = fixed + (4 + chars + 1 + chars + 1) * sizeof(WCHAR);
4380
+ if (total > MAXIMUM_REPARSE_DATA_BUFFER_SIZE) return ERROR_FILENAME_EXCED_RANGE;
4381
+ ScrStatsReparseData *data = calloc(1, total);
4382
+ if (!data) return ERROR_NOT_ENOUGH_MEMORY;
4383
+ data->tag = IO_REPARSE_TAG_MOUNT_POINT;
4384
+ data->data_len = (USHORT)(total - offsetof(ScrStatsReparseData, body));
4385
+ data->body.mount.substitute_offset = 0;
4386
+ data->body.mount.substitute_len = (USHORT)((4 + chars) * sizeof(WCHAR));
4387
+ data->body.mount.print_offset = (USHORT)((4 + chars + 1) * sizeof(WCHAR));
4388
+ data->body.mount.print_len = (USHORT)(chars * sizeof(WCHAR));
4389
+ memcpy(data->body.mount.path, L"\\??\\", 4 * sizeof(WCHAR));
4390
+ memcpy(data->body.mount.path + 4, plain, chars * sizeof(WCHAR));
4391
+ memcpy(data->body.mount.path + 4 + chars + 1, plain, chars * sizeof(WCHAR));
4392
+ DWORD error = 0, used;
4393
+ bool created = CreateDirectoryW(destination, NULL) != 0;
4394
+ HANDLE h = INVALID_HANDLE_VALUE;
4395
+ if (!created) error = GetLastError();
4396
+ else {
4397
+ h = CreateFileW(destination, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
4398
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
4399
+ if (h == INVALID_HANDLE_VALUE) error = GetLastError();
4400
+ else if (!DeviceIoControl(h, FSCTL_SET_REPARSE_POINT, data, (DWORD)total, NULL, 0, &used, NULL)) error = GetLastError();
4401
+ }
4402
+ if (h != INVALID_HANDLE_VALUE) CloseHandle(h);
4403
+ if (error && created) RemoveDirectoryW(destination);
4404
+ free(data);
4405
+ return error;
4406
+ }
4407
+ #endif
4408
+
4409
+ #ifdef _WIN32
4410
+ /* libuv keeps this fallback for later calls once Windows rejects the flag. */
4411
+ static DWORD scr_fs_symlink_usermode_flag = 0x2; /* ALLOW_UNPRIVILEGED_CREATE */
4412
+ #endif
4413
+
4414
+ int scr_fs_symlink_infer(ScrStr *absolute) {
4415
+ #ifdef _WIN32
4416
+ WCHAR *probe = scr_fs_win_wide(absolute);
4417
+ DWORD attributes = probe ? GetFileAttributesW(probe) : INVALID_FILE_ATTRIBUTES;
4418
+ free(probe);
4419
+ return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 0;
4420
+ #else
4421
+ (void)absolute;
4422
+ return 0;
4423
+ #endif
4424
+ }
4425
+
4426
+ void scr_fs_symlink(ScrStr *target, ScrStr *destination, int kind) {
4427
+ #ifdef _WIN32
4428
+ ScrArr *parts = scr_arr_new_ref(scr_str_retain_v, scr_str_release_v, NULL, 3);
4429
+ scr_arr_push_ref(parts, scr_str_retain(destination));
4430
+ scr_arr_push_ref(parts, scr_str_new("..", 2));
4431
+ scr_arr_push_ref(parts, scr_str_retain(target));
4432
+ ScrStr *absolute = scr_path_win32_resolve(parts);
4433
+ scr_arr_release(parts);
4434
+ if (kind == -1) kind = scr_fs_symlink_infer(absolute);
4435
+ ScrStr *stored;
4436
+ if (kind == 2) stored = scr_path_win32_to_namespaced_path(absolute);
4437
+ else if (scr_path_win32_is_absolute(target)) stored = scr_path_win32_to_namespaced_path(target);
4438
+ else {
4439
+ char *text = malloc(target->len ? target->len : 1);
4440
+ if (!text) scr_trap("scriptc: out of memory\n");
4441
+ for (size_t i = 0; i < target->len; i++) text[i] = target->data[i] == '/' ? '\\' : target->data[i];
4442
+ stored = scr_str_new(text, target->len);
4443
+ free(text);
4444
+ }
4445
+ scr_str_release(absolute);
4446
+ /* Node's binding namespaces the link path before passing it to libuv. */
4447
+ ScrStr *link_path = scr_path_win32_to_namespaced_path(destination);
4448
+ WCHAR *from = scr_fs_win_wide(stored), *to = scr_fs_win_wide(link_path);
4449
+ DWORD error = !from || !to ? GetLastError() : 0;
4450
+ if (!error && kind == 2) error = scr_fs_win_junction(from, to);
4451
+ else if (!error) {
4452
+ DWORD flags = (kind == 1 ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0) | scr_fs_symlink_usermode_flag;
4453
+ if (!CreateSymbolicLinkW(to, from, flags)) {
4454
+ error = GetLastError();
4455
+ if (error == ERROR_INVALID_PARAMETER && scr_fs_symlink_usermode_flag) {
4456
+ scr_fs_symlink_usermode_flag = 0;
4457
+ error = CreateSymbolicLinkW(to, from, flags & ~0x2u) ? 0 : GetLastError();
4458
+ }
4459
+ }
4460
+ }
4461
+ free(from); free(to); scr_str_release(stored); scr_str_release(link_path);
4462
+ if (error) scr_fs_throw2(scr_fs_win_errno(error), "symlink", target, destination);
4463
+ #else
4464
+ (void)kind;
4465
+ if (symlink(target->data, destination->data) != 0) scr_fs_throw2(errno, "symlink", target, destination);
4466
+ #endif
4467
+ }
4468
+
4469
+ /* Keep the unsigned libuv statfs representation before selecting Number or
4470
+ * BigInt. Free-space counts are live snapshots, not cached volume metadata. */
4471
+ ScrDyn *scr_fs_statfs(ScrStr *path, bool bigint) {
4472
+ uint64_t fields[7];
4473
+ #if defined(_WIN32)
4474
+ ScrStr *syscall_path = scr_path_win32_to_namespaced_path(path);
4475
+ WCHAR *wide = scr_fs_win_wide(syscall_path);
4476
+ scr_str_release(syscall_path);
4477
+ if (!wide) { scr_fs_throw(scr_fs_win_errno(GetLastError()), "statfs", path); return NULL; }
4478
+ DWORD sectors, bytes, free_clusters, total_clusters;
4479
+ BOOL ok = GetDiskFreeSpaceW(wide, &sectors, &bytes, &free_clusters, &total_clusters);
4480
+ DWORD error = ok ? 0 : GetLastError();
4481
+ if (!ok && error == ERROR_DIRECTORY) {
4482
+ /* libuv retries a file path's parent once, preserving the original path
4483
+ * in errors. GetFullPathName also expands relative and drive paths. */
4484
+ DWORD size = GetFullPathNameW(wide, 0, NULL, NULL);
4485
+ WCHAR *absolute = size ? malloc((size_t)size * sizeof *absolute) : NULL;
4486
+ if (size && !absolute) scr_trap("scriptc: out of memory\n");
4487
+ WCHAR *part = NULL;
4488
+ DWORD length = absolute ? GetFullPathNameW(wide, size, absolute, &part) : 0;
4489
+ if (length && length < size) {
4490
+ if (part) *part = L'\0';
4491
+ ok = GetDiskFreeSpaceW(absolute, &sectors, &bytes, &free_clusters, &total_clusters);
4492
+ if (!ok) error = GetLastError();
4493
+ }
4494
+ free(absolute);
4495
+ }
4496
+ free(wide);
4497
+ if (!ok) { scr_fs_throw(scr_fs_win_errno(error), "statfs", path); return NULL; }
4498
+ fields[0] = 0;
4499
+ fields[1] = (DWORD)(bytes * sectors);
4500
+ fields[2] = total_clusters;
4501
+ fields[3] = fields[4] = free_clusters;
4502
+ fields[5] = fields[6] = 0;
4503
+ #elif defined(__wasi__)
4504
+ (void)path; (void)bigint;
4505
+ static const char message[] = "Filesystem capacity queries have no native WASI syscall";
4506
+ scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
4507
+ return NULL;
4508
+ #else
4509
+ #if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
4510
+ struct statfs snapshot;
4511
+ if (statfs(path->data, &snapshot) != 0) { scr_fs_throw(errno, "statfs", path); return NULL; }
4512
+ fields[0] = (uint64_t)snapshot.f_type;
4513
+ #else
4514
+ struct statvfs snapshot;
4515
+ if (statvfs(path->data, &snapshot) != 0) { scr_fs_throw(errno, "statfs", path); return NULL; }
4516
+ fields[0] = 0;
4517
+ #endif
4518
+ fields[1] = (uint64_t)snapshot.f_bsize;
4519
+ fields[2] = (uint64_t)snapshot.f_blocks;
4520
+ fields[3] = (uint64_t)snapshot.f_bfree;
4521
+ fields[4] = (uint64_t)snapshot.f_bavail;
4522
+ fields[5] = (uint64_t)snapshot.f_files;
4523
+ fields[6] = (uint64_t)snapshot.f_ffree;
4524
+ #endif
4525
+ static const char *names[] = { "type", "bsize", "blocks", "bfree", "bavail", "files", "ffree" };
4526
+ ScrDyn *result = scr_dyn_new_obj();
4527
+ for (size_t i = 0; i < 7; i++) {
4528
+ ScrDyn *value;
4529
+ if (bigint) {
4530
+ ScrBigInt *number = scr_bigint_from_u64(fields[i]);
4531
+ value = scr_dyn_new_bigint(number);
4532
+ scr_bigint_release(number);
4533
+ } else value = scr_dyn_new_num((double)fields[i]);
4534
+ scr_dyn_obj_set(result, names[i], strlen(names[i]), value);
4535
+ }
4536
+ return result;
4537
+ }
4538
+
4539
+ ScrBytes *scr_fs_readlink_bytes(ScrStr *path) {
4540
+ #ifdef _WIN32
4541
+ WCHAR *wide = scr_fs_win_wide(path);
4542
+ if (!wide) { scr_fs_throw(scr_fs_win_errno(GetLastError()), "readlink", path); return NULL; }
4543
+ HANDLE h = CreateFileW(wide, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
4544
+ FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
4545
+ free(wide);
4546
+ if (h == INVALID_HANDLE_VALUE) { scr_fs_throw(scr_fs_win_errno(GetLastError()), "readlink", path); return NULL; }
4547
+ FILE_ATTRIBUTE_TAG_INFO tagged;
4548
+ ScrStr *text = NULL;
4549
+ double size;
4550
+ bool ok = GetFileInformationByHandleEx(h, FileAttributeTagInfo, &tagged, sizeof tagged) &&
4551
+ scr_stats_link_target(h, tagged.ReparseTag, &size, &text);
4552
+ CloseHandle(h);
4553
+ if (!ok || !text) { scr_str_release(text); scr_fs_throw(EINVAL, "readlink", path); return NULL; }
4554
+ ScrBytes *bytes = scr_bytes_from_data((const uint8_t *)text->data, text->len);
4555
+ scr_str_release(text);
4556
+ return bytes;
4557
+ #else
4558
+ size_t capacity = 256;
4559
+ uint8_t *text = NULL;
4560
+ for (;;) {
4561
+ uint8_t *grown = realloc(text, capacity);
4562
+ if (!grown) { free(text); scr_trap("scriptc: out of memory\n"); }
4563
+ text = grown;
4564
+ ssize_t size = readlink(path->data, (char *)text, capacity);
4565
+ if (size < 0) { int error = errno; free(text); scr_fs_throw(error, "readlink", path); return NULL; }
4566
+ if ((size_t)size < capacity) {
4567
+ ScrBytes *bytes = scr_bytes_from_data(text, (size_t)size);
4568
+ free(text);
4569
+ return bytes;
4570
+ }
4571
+ if (capacity > SIZE_MAX / 2) { free(text); scr_fs_throw(ENAMETOOLONG, "readlink", path); return NULL; }
4572
+ capacity *= 2;
4573
+ }
4574
+ #endif
4575
+ }
4576
+
3852
4577
  ScrStats *scr_fs_stat(ScrStr *path) {
3853
4578
  #ifdef _WIN32
3854
4579
  return scr_stats_of_path(path, "stat", false);
@@ -4256,7 +4981,7 @@ ScrStr *scr_crypto_random_string(double n, ScrStr *enc) {
4256
4981
  return out;
4257
4982
  }
4258
4983
 
4259
- /* ── incremental MD5/SHA-1/SHA-256 and HMAC ──────────────────────────
4984
+ /* ── incremental MD5/SHA-1/SHA-2 and HMAC ──────────────────────────
4260
4985
  * The first-class Hash/Hmac handles, fused one-shot paths, island bridge,
4261
4986
  * and PBKDF2 all share these contexts. Hash.copy() is a context snapshot;
4262
4987
  * update never buffers the full input. Differential tests pin every
@@ -4462,7 +5187,7 @@ static size_t scr_sha1_digest(const unsigned char *data, size_t len, unsigned ch
4462
5187
  static ScrStr *scr_digest_encode(const unsigned char *d, size_t n, const ScrStr *enc) {
4463
5188
  if (enc->len == 3 && memcmp(enc->data, "hex", 3) == 0) {
4464
5189
  static const char hex[] = "0123456789abcdef";
4465
- char buf[64];
5190
+ char buf[128];
4466
5191
  for (size_t i = 0; i < n; i++) {
4467
5192
  buf[i * 2] = hex[d[i] >> 4];
4468
5193
  buf[i * 2 + 1] = hex[d[i] & 0x0f];
@@ -4471,7 +5196,7 @@ static ScrStr *scr_digest_encode(const unsigned char *d, size_t n, const ScrStr
4471
5196
  }
4472
5197
  /* base64, standard alphabet, '=' padded — Buffer.toString("base64"). */
4473
5198
  static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4474
- char buf[48];
5199
+ char buf[88];
4475
5200
  size_t o = 0;
4476
5201
  for (size_t i = 0; i < n; i += 3) {
4477
5202
  unsigned v = (unsigned)d[i] << 16;
@@ -4586,10 +5311,140 @@ static size_t scr_md5_final(const ScrMd5Ctx *source, unsigned char out[32]) {
4586
5311
  return 16;
4587
5312
  }
4588
5313
 
5314
+ /* SHA-384/SHA-512, FIPS 180-4. The 128-byte blocks and HMAC pads differ
5315
+ * from SHA-256; the digest dispatcher keeps that distinction explicit. */
5316
+ typedef struct ScrSha512Ctx {
5317
+ uint64_t h[8];
5318
+ uint64_t bytes;
5319
+ unsigned char tail[128];
5320
+ size_t tail_len;
5321
+ } ScrSha512Ctx;
5322
+
5323
+ static const uint64_t scr_sha512_k[80] = {
5324
+ UINT64_C(0x428a2f98d728ae22), UINT64_C(0x7137449123ef65cd), UINT64_C(0xb5c0fbcfec4d3b2f),
5325
+ UINT64_C(0xe9b5dba58189dbbc), UINT64_C(0x3956c25bf348b538), UINT64_C(0x59f111f1b605d019),
5326
+ UINT64_C(0x923f82a4af194f9b), UINT64_C(0xab1c5ed5da6d8118), UINT64_C(0xd807aa98a3030242),
5327
+ UINT64_C(0x12835b0145706fbe), UINT64_C(0x243185be4ee4b28c), UINT64_C(0x550c7dc3d5ffb4e2),
5328
+ UINT64_C(0x72be5d74f27b896f), UINT64_C(0x80deb1fe3b1696b1), UINT64_C(0x9bdc06a725c71235),
5329
+ UINT64_C(0xc19bf174cf692694), UINT64_C(0xe49b69c19ef14ad2), UINT64_C(0xefbe4786384f25e3),
5330
+ UINT64_C(0x0fc19dc68b8cd5b5), UINT64_C(0x240ca1cc77ac9c65), UINT64_C(0x2de92c6f592b0275),
5331
+ UINT64_C(0x4a7484aa6ea6e483), UINT64_C(0x5cb0a9dcbd41fbd4), UINT64_C(0x76f988da831153b5),
5332
+ UINT64_C(0x983e5152ee66dfab), UINT64_C(0xa831c66d2db43210), UINT64_C(0xb00327c898fb213f),
5333
+ UINT64_C(0xbf597fc7beef0ee4), UINT64_C(0xc6e00bf33da88fc2), UINT64_C(0xd5a79147930aa725),
5334
+ UINT64_C(0x06ca6351e003826f), UINT64_C(0x142929670a0e6e70), UINT64_C(0x27b70a8546d22ffc),
5335
+ UINT64_C(0x2e1b21385c26c926), UINT64_C(0x4d2c6dfc5ac42aed), UINT64_C(0x53380d139d95b3df),
5336
+ UINT64_C(0x650a73548baf63de), UINT64_C(0x766a0abb3c77b2a8), UINT64_C(0x81c2c92e47edaee6),
5337
+ UINT64_C(0x92722c851482353b), UINT64_C(0xa2bfe8a14cf10364), UINT64_C(0xa81a664bbc423001),
5338
+ UINT64_C(0xc24b8b70d0f89791), UINT64_C(0xc76c51a30654be30), UINT64_C(0xd192e819d6ef5218),
5339
+ UINT64_C(0xd69906245565a910), UINT64_C(0xf40e35855771202a), UINT64_C(0x106aa07032bbd1b8),
5340
+ UINT64_C(0x19a4c116b8d2d0c8), UINT64_C(0x1e376c085141ab53), UINT64_C(0x2748774cdf8eeb99),
5341
+ UINT64_C(0x34b0bcb5e19b48a8), UINT64_C(0x391c0cb3c5c95a63), UINT64_C(0x4ed8aa4ae3418acb),
5342
+ UINT64_C(0x5b9cca4f7763e373), UINT64_C(0x682e6ff3d6b2b8a3), UINT64_C(0x748f82ee5defb2fc),
5343
+ UINT64_C(0x78a5636f43172f60), UINT64_C(0x84c87814a1f0ab72), UINT64_C(0x8cc702081a6439ec),
5344
+ UINT64_C(0x90befffa23631e28), UINT64_C(0xa4506cebde82bde9), UINT64_C(0xbef9a3f7b2c67915),
5345
+ UINT64_C(0xc67178f2e372532b), UINT64_C(0xca273eceea26619c), UINT64_C(0xd186b8c721c0c207),
5346
+ UINT64_C(0xeada7dd6cde0eb1e), UINT64_C(0xf57d4f7fee6ed178), UINT64_C(0x06f067aa72176fba),
5347
+ UINT64_C(0x0a637dc5a2c898a6), UINT64_C(0x113f9804bef90dae), UINT64_C(0x1b710b35131c471b),
5348
+ UINT64_C(0x28db77f523047d84), UINT64_C(0x32caab7b40c72493), UINT64_C(0x3c9ebe0a15c9bebc),
5349
+ UINT64_C(0x431d67c49c100d4c), UINT64_C(0x4cc5d4becb3e42b6), UINT64_C(0x597f299cfc657e2a),
5350
+ UINT64_C(0x5fcb6fab3ad6faec), UINT64_C(0x6c44198c4a475817),
5351
+ };
5352
+
5353
+ static uint64_t scr_sha512_rotr(uint64_t x, unsigned n) {
5354
+ return (x >> n) | (x << (64 - n));
5355
+ }
5356
+
5357
+ static void scr_sha512_block(uint64_t h[8], const unsigned char *data) {
5358
+ uint64_t w[80];
5359
+ for (size_t i = 0; i < 16; i++) {
5360
+ w[i] = 0;
5361
+ for (size_t b = 0; b < 8; b++) w[i] = (w[i] << 8) | data[i * 8 + b];
5362
+ }
5363
+ for (size_t i = 16; i < 80; i++) {
5364
+ uint64_t s0 = scr_sha512_rotr(w[i - 15], 1) ^ scr_sha512_rotr(w[i - 15], 8) ^ (w[i - 15] >> 7);
5365
+ uint64_t s1 = scr_sha512_rotr(w[i - 2], 19) ^ scr_sha512_rotr(w[i - 2], 61) ^ (w[i - 2] >> 6);
5366
+ w[i] = w[i - 16] + s0 + w[i - 7] + s1;
5367
+ }
5368
+ uint64_t a = h[0], b = h[1], c = h[2], d = h[3];
5369
+ uint64_t e = h[4], f = h[5], g = h[6], v = h[7];
5370
+ for (size_t i = 0; i < 80; i++) {
5371
+ uint64_t s1 = scr_sha512_rotr(e, 14) ^ scr_sha512_rotr(e, 18) ^ scr_sha512_rotr(e, 41);
5372
+ uint64_t t1 = v + s1 + ((e & f) ^ (~e & g)) + scr_sha512_k[i] + w[i];
5373
+ uint64_t s0 = scr_sha512_rotr(a, 28) ^ scr_sha512_rotr(a, 34) ^ scr_sha512_rotr(a, 39);
5374
+ uint64_t t2 = s0 + ((a & b) ^ (a & c) ^ (b & c));
5375
+ v = g; g = f; f = e; e = d + t1;
5376
+ d = c; c = b; b = a; a = t1 + t2;
5377
+ }
5378
+ h[0] += a; h[1] += b; h[2] += c; h[3] += d;
5379
+ h[4] += e; h[5] += f; h[6] += g; h[7] += v;
5380
+ }
5381
+
5382
+ static void scr_sha512_init(ScrSha512Ctx *ctx, bool sha384) {
5383
+ static const uint64_t initial512[8] = {
5384
+ UINT64_C(0x6a09e667f3bcc908), UINT64_C(0xbb67ae8584caa73b),
5385
+ UINT64_C(0x3c6ef372fe94f82b), UINT64_C(0xa54ff53a5f1d36f1),
5386
+ UINT64_C(0x510e527fade682d1), UINT64_C(0x9b05688c2b3e6c1f),
5387
+ UINT64_C(0x1f83d9abfb41bd6b), UINT64_C(0x5be0cd19137e2179)};
5388
+ static const uint64_t initial384[8] = {
5389
+ UINT64_C(0xcbbb9d5dc1059ed8), UINT64_C(0x629a292a367cd507),
5390
+ UINT64_C(0x9159015a3070dd17), UINT64_C(0x152fecd8f70e5939),
5391
+ UINT64_C(0x67332667ffc00b31), UINT64_C(0x8eb44a8768581511),
5392
+ UINT64_C(0xdb0c2e0d64f98fa7), UINT64_C(0x47b5481dbefa4fa4)};
5393
+ memcpy(ctx->h, sha384 ? initial384 : initial512, sizeof ctx->h);
5394
+ ctx->bytes = 0;
5395
+ ctx->tail_len = 0;
5396
+ }
5397
+
5398
+ static void scr_sha512_update(ScrSha512Ctx *ctx, const unsigned char *data, size_t len) {
5399
+ ctx->bytes += len;
5400
+ if (ctx->tail_len != 0) {
5401
+ size_t take = 128 - ctx->tail_len;
5402
+ if (take > len) take = len;
5403
+ memcpy(ctx->tail + ctx->tail_len, data, take);
5404
+ ctx->tail_len += take;
5405
+ data += take;
5406
+ len -= take;
5407
+ if (ctx->tail_len == 128) {
5408
+ scr_sha512_block(ctx->h, ctx->tail);
5409
+ ctx->tail_len = 0;
5410
+ }
5411
+ }
5412
+ while (len >= 128) {
5413
+ scr_sha512_block(ctx->h, data);
5414
+ data += 128;
5415
+ len -= 128;
5416
+ }
5417
+ if (len != 0) {
5418
+ memcpy(ctx->tail, data, len);
5419
+ ctx->tail_len = len;
5420
+ }
5421
+ }
5422
+
5423
+ static size_t scr_sha512_final(const ScrSha512Ctx *source, unsigned char out[64], bool sha384) {
5424
+ ScrSha512Ctx ctx = *source;
5425
+ unsigned char tail[256] = {0};
5426
+ memcpy(tail, ctx.tail, ctx.tail_len);
5427
+ tail[ctx.tail_len] = 0x80;
5428
+ size_t pad = ctx.tail_len < 112 ? 128 : 256;
5429
+ uint64_t low = ctx.bytes << 3, high = ctx.bytes >> 61;
5430
+ for (size_t b = 0; b < 8; b++) {
5431
+ tail[pad - 1 - b] = (unsigned char)(low >> (8 * b));
5432
+ tail[pad - 9 - b] = (unsigned char)(high >> (8 * b));
5433
+ }
5434
+ scr_sha512_block(ctx.h, tail);
5435
+ if (pad == 256) scr_sha512_block(ctx.h, tail + 128);
5436
+ size_t len = sha384 ? 48 : 64;
5437
+ for (size_t i = 0; i < len; i++) out[i] = (unsigned char)(ctx.h[i / 8] >> (56 - 8 * (i % 8)));
5438
+ return len;
5439
+ }
5440
+
4589
5441
  typedef enum ScrDigestAlg {
4590
5442
  SCR_DIGEST_MD5,
4591
5443
  SCR_DIGEST_SHA1,
5444
+ SCR_DIGEST_SHA224,
4592
5445
  SCR_DIGEST_SHA256,
5446
+ SCR_DIGEST_SHA384,
5447
+ SCR_DIGEST_SHA512,
4593
5448
  } ScrDigestAlg;
4594
5449
 
4595
5450
  typedef struct ScrDigestCtx {
@@ -4598,6 +5453,7 @@ typedef struct ScrDigestCtx {
4598
5453
  ScrMd5Ctx md5;
4599
5454
  ScrSha1Ctx sha1;
4600
5455
  ScrSha256Ctx sha256;
5456
+ ScrSha512Ctx sha512;
4601
5457
  } state;
4602
5458
  } ScrDigestCtx;
4603
5459
 
@@ -4617,7 +5473,10 @@ static bool scr_digest_name_is(const char *name, size_t len, const char *literal
4617
5473
  static bool scr_digest_alg(const char *name, size_t len, ScrDigestAlg *out) {
4618
5474
  if (scr_digest_name_is(name, len, "md5")) *out = SCR_DIGEST_MD5;
4619
5475
  else if (scr_digest_name_is(name, len, "sha1")) *out = SCR_DIGEST_SHA1;
5476
+ else if (scr_digest_name_is(name, len, "sha224")) *out = SCR_DIGEST_SHA224;
4620
5477
  else if (scr_digest_name_is(name, len, "sha256")) *out = SCR_DIGEST_SHA256;
5478
+ else if (scr_digest_name_is(name, len, "sha384")) *out = SCR_DIGEST_SHA384;
5479
+ else if (scr_digest_name_is(name, len, "sha512")) *out = SCR_DIGEST_SHA512;
4621
5480
  else return false;
4622
5481
  return true;
4623
5482
  }
@@ -4627,7 +5486,17 @@ static void scr_digest_init(ScrDigestCtx *ctx, ScrDigestAlg alg) {
4627
5486
  switch (alg) {
4628
5487
  case SCR_DIGEST_MD5: scr_md5_init(&ctx->state.md5); break;
4629
5488
  case SCR_DIGEST_SHA1: scr_sha1_init(&ctx->state.sha1); break;
5489
+ case SCR_DIGEST_SHA224: {
5490
+ static const uint32_t initial[8] = {
5491
+ 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939,
5492
+ 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4};
5493
+ scr_sha256_init(&ctx->state.sha256);
5494
+ memcpy(ctx->state.sha256.h, initial, sizeof initial);
5495
+ break;
5496
+ }
4630
5497
  case SCR_DIGEST_SHA256: scr_sha256_init(&ctx->state.sha256); break;
5498
+ case SCR_DIGEST_SHA384: scr_sha512_init(&ctx->state.sha512, true); break;
5499
+ case SCR_DIGEST_SHA512: scr_sha512_init(&ctx->state.sha512, false); break;
4631
5500
  }
4632
5501
  }
4633
5502
 
@@ -4635,15 +5504,21 @@ static void scr_digest_update(ScrDigestCtx *ctx, const unsigned char *data, size
4635
5504
  switch (ctx->alg) {
4636
5505
  case SCR_DIGEST_MD5: scr_md5_update(&ctx->state.md5, data, len); break;
4637
5506
  case SCR_DIGEST_SHA1: scr_sha1_update(&ctx->state.sha1, data, len); break;
5507
+ case SCR_DIGEST_SHA224:
4638
5508
  case SCR_DIGEST_SHA256: scr_sha256_update(&ctx->state.sha256, data, len); break;
5509
+ case SCR_DIGEST_SHA384:
5510
+ case SCR_DIGEST_SHA512: scr_sha512_update(&ctx->state.sha512, data, len); break;
4639
5511
  }
4640
5512
  }
4641
5513
 
4642
- static size_t scr_digest_final(const ScrDigestCtx *ctx, unsigned char out[32]) {
5514
+ static size_t scr_digest_final(const ScrDigestCtx *ctx, unsigned char out[64]) {
4643
5515
  switch (ctx->alg) {
4644
5516
  case SCR_DIGEST_MD5: return scr_md5_final(&ctx->state.md5, out);
4645
5517
  case SCR_DIGEST_SHA1: return scr_sha1_final(&ctx->state.sha1, out);
5518
+ case SCR_DIGEST_SHA224: scr_sha256_final(&ctx->state.sha256, out); return 28;
4646
5519
  case SCR_DIGEST_SHA256: return scr_sha256_final(&ctx->state.sha256, out);
5520
+ case SCR_DIGEST_SHA384: return scr_sha512_final(&ctx->state.sha512, out, true);
5521
+ case SCR_DIGEST_SHA512: return scr_sha512_final(&ctx->state.sha512, out, false);
4647
5522
  }
4648
5523
  return 0;
4649
5524
  }
@@ -4655,9 +5530,10 @@ static void scr_crypto_zero(void *ptr, size_t len) {
4655
5530
 
4656
5531
  static void scr_hmac_init(ScrDigestCtx *inner, ScrDigestCtx *outer, ScrDigestAlg alg,
4657
5532
  const unsigned char *key, size_t keylen) {
4658
- unsigned char kblock[64] = {0};
4659
- unsigned char kd[32];
4660
- if (keylen > sizeof kblock) {
5533
+ unsigned char kblock[128] = {0};
5534
+ size_t block_len = alg == SCR_DIGEST_SHA384 || alg == SCR_DIGEST_SHA512 ? 128 : 64;
5535
+ unsigned char kd[64];
5536
+ if (keylen > block_len) {
4661
5537
  ScrDigestCtx key_hash;
4662
5538
  scr_digest_init(&key_hash, alg);
4663
5539
  scr_digest_update(&key_hash, key, keylen);
@@ -4668,24 +5544,24 @@ static void scr_hmac_init(ScrDigestCtx *inner, ScrDigestCtx *outer, ScrDigestAlg
4668
5544
  } else if (keylen != 0) {
4669
5545
  memcpy(kblock, key, keylen);
4670
5546
  }
4671
- unsigned char ipad[64];
4672
- unsigned char opad[64];
4673
- for (size_t i = 0; i < 64; i++) {
5547
+ unsigned char ipad[128];
5548
+ unsigned char opad[128];
5549
+ for (size_t i = 0; i < block_len; i++) {
4674
5550
  ipad[i] = (unsigned char)(kblock[i] ^ 0x36);
4675
5551
  opad[i] = (unsigned char)(kblock[i] ^ 0x5c);
4676
5552
  }
4677
5553
  scr_digest_init(inner, alg);
4678
- scr_digest_update(inner, ipad, sizeof ipad);
5554
+ scr_digest_update(inner, ipad, block_len);
4679
5555
  scr_digest_init(outer, alg);
4680
- scr_digest_update(outer, opad, sizeof opad);
5556
+ scr_digest_update(outer, opad, block_len);
4681
5557
  scr_crypto_zero(kblock, sizeof kblock);
4682
- scr_crypto_zero(ipad, sizeof ipad);
4683
- scr_crypto_zero(opad, sizeof opad);
5558
+ scr_crypto_zero(ipad, block_len);
5559
+ scr_crypto_zero(opad, block_len);
4684
5560
  }
4685
5561
 
4686
5562
  static size_t scr_hmac_final(const ScrDigestCtx *inner, const ScrDigestCtx *outer,
4687
- unsigned char out[32]) {
4688
- unsigned char inner_digest[32];
5563
+ unsigned char out[64]) {
5564
+ unsigned char inner_digest[64];
4689
5565
  size_t inner_len = scr_digest_final(inner, inner_digest);
4690
5566
  ScrDigestCtx final_outer = *outer;
4691
5567
  scr_digest_update(&final_outer, inner_digest, inner_len);
@@ -4699,7 +5575,7 @@ static size_t scr_hmac_final(const ScrDigestCtx *inner, const ScrDigestCtx *oute
4699
5575
  * helpers both use the incremental core. Returns zero for an unknown
4700
5576
  * algorithm, preserving the island's unsupported-digest probe. */
4701
5577
  size_t scr_crypto_digest_raw(const char *alg, const unsigned char *data, size_t len,
4702
- unsigned char out[32]) {
5578
+ unsigned char out[64]) {
4703
5579
  ScrDigestAlg kind;
4704
5580
  if (!scr_digest_alg(alg, strlen(alg), &kind)) return 0;
4705
5581
  ScrDigestCtx ctx;
@@ -4709,7 +5585,7 @@ size_t scr_crypto_digest_raw(const char *alg, const unsigned char *data, size_t
4709
5585
  }
4710
5586
 
4711
5587
  size_t scr_crypto_hmac_raw(const char *alg, const unsigned char *key, size_t keylen,
4712
- const unsigned char *data, size_t len, unsigned char out[32]) {
5588
+ const unsigned char *data, size_t len, unsigned char out[64]) {
4713
5589
  ScrDigestAlg kind;
4714
5590
  if (!scr_digest_alg(alg, strlen(alg), &kind)) return 0;
4715
5591
  ScrDigestCtx inner, outer;
@@ -4859,7 +5735,7 @@ ScrCryptoHash *scr_crypto_hash_copy(ScrCryptoHash *hash) {
4859
5735
  return copy;
4860
5736
  }
4861
5737
 
4862
- static size_t scr_crypto_hash_finish(ScrCryptoHash *hash, unsigned char out[32]) {
5738
+ static size_t scr_crypto_hash_finish(ScrCryptoHash *hash, unsigned char out[64]) {
4863
5739
  if (hash->finalized) {
4864
5740
  if (hash->hmac) return 0; /* Node's repeated Hmac.digest() is empty. */
4865
5741
  scr_crypto_hash_finalized();
@@ -4872,7 +5748,7 @@ static size_t scr_crypto_hash_finish(ScrCryptoHash *hash, unsigned char out[32])
4872
5748
  }
4873
5749
 
4874
5750
  ScrStr *scr_crypto_hash_digest_string(ScrCryptoHash *hash, ScrStr *enc) {
4875
- unsigned char digest[32];
5751
+ unsigned char digest[64];
4876
5752
  size_t n = scr_crypto_hash_finish(hash, digest);
4877
5753
  if (n == 0) return scr_exc_pending() ? NULL : scr_str_new("", 0);
4878
5754
  ScrStr *result = scr_digest_encode(digest, n, enc);
@@ -4881,7 +5757,7 @@ ScrStr *scr_crypto_hash_digest_string(ScrCryptoHash *hash, ScrStr *enc) {
4881
5757
  }
4882
5758
 
4883
5759
  ScrBytes *scr_crypto_hash_digest_buffer(ScrCryptoHash *hash) {
4884
- unsigned char digest[32];
5760
+ unsigned char digest[64];
4885
5761
  size_t n = scr_crypto_hash_finish(hash, digest);
4886
5762
  if (n == 0) return scr_exc_pending() ? NULL : scr_bytes_new(SCR_BYTES_U8, 0);
4887
5763
  ScrBytes *result = scr_bytes_new(SCR_BYTES_U8, (double)n);
@@ -4897,7 +5773,7 @@ static ScrStr *scr_hash_digest_raw(const ScrStr *alg, const unsigned char *data,
4897
5773
  scr_crypto_hash_digest_unsupported(alg);
4898
5774
  return NULL;
4899
5775
  }
4900
- unsigned char d[32];
5776
+ unsigned char d[64];
4901
5777
  ScrDigestCtx ctx;
4902
5778
  scr_digest_init(&ctx, kind);
4903
5779
  scr_digest_update(&ctx, data, len);
@@ -4934,7 +5810,7 @@ bool scr_crypto_timing_safe_equal(ScrBytes *left, ScrBytes *right) {
4934
5810
  ScrBytes *scr_crypto_random_fill(ScrBytes *bytes, double offset, double size) {
4935
5811
  double length = (double)(bytes->len * scr_bytes_elem_size(bytes->elem));
4936
5812
  if (!isfinite(offset) || floor(offset) != offset || offset < 0 || offset > length) {
4937
- char value[32], msg[160];
5813
+ char value[64], msg[160];
4938
5814
  size_t value_len = scr_f64_to_str(offset, value);
4939
5815
  int n = snprintf(msg, sizeof msg,
4940
5816
  "The value of \"offset\" is out of range. It must be >= 0 && <= %.0f. Received %.*s",
@@ -4944,7 +5820,7 @@ ScrBytes *scr_crypto_random_fill(ScrBytes *bytes, double offset, double size) {
4944
5820
  }
4945
5821
  double available = length - offset;
4946
5822
  if (!isfinite(size) || floor(size) != size || size < 0 || size > 2147483647.0) {
4947
- char value[32], msg[160];
5823
+ char value[64], msg[160];
4948
5824
  size_t value_len = scr_f64_to_str(size, value);
4949
5825
  int n = snprintf(msg, sizeof msg,
4950
5826
  "The value of \"size\" is out of range. It must be >= 0 && <= 2147483647. Received %.*s",
@@ -4953,7 +5829,7 @@ ScrBytes *scr_crypto_random_fill(ScrBytes *bytes, double offset, double size) {
4953
5829
  return NULL;
4954
5830
  }
4955
5831
  if (size > available) {
4956
- char value[32], msg[160];
5832
+ char value[64], msg[160];
4957
5833
  double total = size + offset;
4958
5834
  size_t value_len = scr_f64_to_str(total, value);
4959
5835
  int n = snprintf(msg, sizeof msg,
@@ -4983,7 +5859,7 @@ double scr_crypto_random_int(double min, double max) {
4983
5859
  return 0;
4984
5860
  }
4985
5861
  if (max <= min) {
4986
- char min_text[32], max_text[32], msg[192];
5862
+ char min_text[64], max_text[64], msg[192];
4987
5863
  size_t min_len = scr_f64_to_str(min, min_text);
4988
5864
  size_t max_len = scr_f64_to_str(max, max_text);
4989
5865
  int n = snprintf(msg, sizeof msg,
@@ -5015,7 +5891,7 @@ static ScrBytes *scr_crypto_pbkdf2_raw(const unsigned char *password, size_t pas
5015
5891
  uint32_t iterations, size_t keylen,
5016
5892
  ScrDigestAlg alg) {
5017
5893
  ScrDigestCtx empty;
5018
- unsigned char empty_digest[32];
5894
+ unsigned char empty_digest[64];
5019
5895
  scr_digest_init(&empty, alg);
5020
5896
  size_t digest_len = scr_digest_final(&empty, empty_digest);
5021
5897
  scr_crypto_zero(empty_digest, sizeof empty_digest);
@@ -5030,7 +5906,7 @@ static ScrBytes *scr_crypto_pbkdf2_raw(const unsigned char *password, size_t pas
5030
5906
  block[salt_len + 2] = (unsigned char)(index >> 8);
5031
5907
  block[salt_len + 3] = (unsigned char)index;
5032
5908
  ScrDigestCtx inner, outer;
5033
- unsigned char u[32], accum[32];
5909
+ unsigned char u[64], accum[64];
5034
5910
  scr_hmac_init(&inner, &outer, alg, password, password_len);
5035
5911
  scr_digest_update(&inner, block, salt_len + 4);
5036
5912
  scr_hmac_final(&inner, &outer, u);
@@ -5058,7 +5934,7 @@ ScrBytes *scr_crypto_pbkdf2(ScrBytes *password, ScrBytes *salt,
5058
5934
  double iterations, double keylen, ScrStr *digest) {
5059
5935
  if (!isfinite(iterations) || floor(iterations) != iterations ||
5060
5936
  iterations < 1 || iterations > 2147483647.0) {
5061
- char value[32], msg[176];
5937
+ char value[64], msg[176];
5062
5938
  size_t value_len = scr_f64_to_str(iterations, value);
5063
5939
  int n = snprintf(msg, sizeof msg,
5064
5940
  "The value of \"iterations\" is out of range. It must be >= 1 && <= 2147483647. Received %.*s",
@@ -5067,7 +5943,7 @@ ScrBytes *scr_crypto_pbkdf2(ScrBytes *password, ScrBytes *salt,
5067
5943
  return NULL;
5068
5944
  }
5069
5945
  if (!isfinite(keylen) || floor(keylen) != keylen || keylen < 0 || keylen > 2147483647.0) {
5070
- char value[32], msg[176];
5946
+ char value[64], msg[176];
5071
5947
  size_t value_len = scr_f64_to_str(keylen, value);
5072
5948
  int n = snprintf(msg, sizeof msg,
5073
5949
  "The value of \"keylen\" is out of range. It must be >= 0 && <= 2147483647. Received %.*s",
@@ -5086,6 +5962,279 @@ ScrBytes *scr_crypto_pbkdf2(ScrBytes *password, ScrBytes *salt,
5086
5962
  (size_t)keylen, alg);
5087
5963
  }
5088
5964
 
5965
+ /* RFC 5869 HKDF and RFC 7914 scrypt share the digest core in both
5966
+ * execution tiers. Sensitive scratch storage is cleared before release. */
5967
+ static bool scr_crypto_integer(double value, const char *name, double max) {
5968
+ if (isfinite(value) && floor(value) == value && value >= 0 && value <= max) return true;
5969
+ char received[64], requirement[80], message[256];
5970
+ size_t n = scr_f64_to_str(value, received);
5971
+ received[n] = '\0';
5972
+ if (!isfinite(value) || floor(value) != value) {
5973
+ snprintf(requirement, sizeof requirement, "an integer");
5974
+ } else {
5975
+ snprintf(requirement, sizeof requirement, ">= 0 && <= %.0f", max);
5976
+ if (fabs(value) > 4294967296.0) {
5977
+ char digits[40];
5978
+ snprintf(digits, sizeof digits, "%.0f", value);
5979
+ size_t len = strlen(digits), out = 0, start = digits[0] == '-' ? 1 : 0;
5980
+ for (size_t i = 0; i < len; i++) {
5981
+ if (i > start && (len - i) % 3 == 0) received[out++] = '_';
5982
+ received[out++] = digits[i];
5983
+ }
5984
+ received[out] = '\0';
5985
+ }
5986
+ }
5987
+ int len = snprintf(message, sizeof message,
5988
+ "The value of \"%s\" is out of range. It must be %s. Received %s", name, requirement, received);
5989
+ scr_throw_error_msg_code(SCR_ERR_RANGE, message, (size_t)len, "ERR_OUT_OF_RANGE");
5990
+ return false;
5991
+ }
5992
+
5993
+ ScrBytes *scr_crypto_hkdf_bytes(ScrStr *digest, ScrBytes *ikm, ScrBytes *salt,
5994
+ ScrBytes *info, double keylen) {
5995
+ if (!scr_crypto_integer(keylen, "length", 9007199254740991.0)) return NULL;
5996
+ size_t info_len = info->len * scr_bytes_elem_size(info->elem);
5997
+ if (info_len > 1024) {
5998
+ char message[192];
5999
+ int len = snprintf(message, sizeof message,
6000
+ "The value of \"info\" is out of range. It must be must not contain more than 1024 bytes. Received %zu", info_len);
6001
+ scr_throw_error_msg_code(SCR_ERR_RANGE, message, (size_t)len, "ERR_OUT_OF_RANGE");
6002
+ return NULL;
6003
+ }
6004
+ ScrDigestAlg alg;
6005
+ if (!scr_digest_alg(digest->data, digest->len, &alg)) {
6006
+ scr_crypto_invalid_digest(digest);
6007
+ return NULL;
6008
+ }
6009
+ ScrDigestCtx inner, outer;
6010
+ unsigned char prk[64], t[64];
6011
+ scr_hmac_init(&inner, &outer, alg, salt->data, salt->len * scr_bytes_elem_size(salt->elem));
6012
+ scr_digest_update(&inner, ikm->data, ikm->len * scr_bytes_elem_size(ikm->elem));
6013
+ size_t hash_len = scr_hmac_final(&inner, &outer, prk);
6014
+ if (keylen > 255 * hash_len) {
6015
+ scr_crypto_zero(prk, sizeof prk);
6016
+ scr_crypto_zero(&inner, sizeof inner);
6017
+ scr_crypto_zero(&outer, sizeof outer);
6018
+ scr_throw_error_msg_code(SCR_ERR_RANGE, "Invalid key length", 18, "ERR_CRYPTO_INVALID_KEYLEN");
6019
+ return NULL;
6020
+ }
6021
+ ScrBytes *result = scr_bytes_new(SCR_BYTES_U8, keylen);
6022
+ size_t offset = 0, previous_len = 0;
6023
+ for (unsigned counter = 1; offset < (size_t)keylen; counter++) {
6024
+ scr_hmac_init(&inner, &outer, alg, prk, hash_len);
6025
+ scr_digest_update(&inner, t, previous_len);
6026
+ scr_digest_update(&inner, info->data, info_len);
6027
+ unsigned char byte = (unsigned char)counter;
6028
+ scr_digest_update(&inner, &byte, 1);
6029
+ scr_hmac_final(&inner, &outer, t);
6030
+ size_t take = (size_t)keylen - offset;
6031
+ if (take > hash_len) take = hash_len;
6032
+ memcpy(result->data + offset, t, take);
6033
+ offset += take;
6034
+ previous_len = hash_len;
6035
+ }
6036
+ scr_crypto_zero(prk, sizeof prk);
6037
+ scr_crypto_zero(t, sizeof t);
6038
+ scr_crypto_zero(&inner, sizeof inner);
6039
+ scr_crypto_zero(&outer, sizeof outer);
6040
+ return result;
6041
+ }
6042
+
6043
+ ScrDyn *scr_crypto_hkdf(ScrStr *digest, ScrBytes *ikm, ScrBytes *salt,
6044
+ ScrBytes *info, double keylen) {
6045
+ ScrBytes *bytes = scr_crypto_hkdf_bytes(digest, ikm, salt, info, keylen);
6046
+ if (!bytes) return NULL;
6047
+ if (bytes->len == 0) {
6048
+ scr_bytes_release(bytes);
6049
+ scr_throw_error_msg(SCR_ERR_ERROR, "Deriving bits failed", 20);
6050
+ return NULL;
6051
+ }
6052
+ ScrDyn *result = scr_array_buffer_from_bytes(bytes);
6053
+ scr_bytes_release(bytes);
6054
+ return result;
6055
+ }
6056
+
6057
+ static uint32_t scr_scrypt_rotl(uint32_t x, unsigned n) {
6058
+ return (x << n) | (x >> (32 - n));
6059
+ }
6060
+
6061
+ /* Salsa20/8 core, RFC 7914 section 3. Unsigned arithmetic supplies the
6062
+ * specified reduction modulo 2^32 without signed-overflow UB. */
6063
+ static void scr_scrypt_salsa(uint32_t block[16]) {
6064
+ uint32_t x[16];
6065
+ memcpy(x, block, sizeof x);
6066
+ for (unsigned round = 0; round < 8; round += 2) {
6067
+ #define SCR_SALSA(a, b, c, n) x[a] ^= scr_scrypt_rotl(x[b] + x[c], n)
6068
+ SCR_SALSA(4,0,12,7); SCR_SALSA(8,4,0,9); SCR_SALSA(12,8,4,13); SCR_SALSA(0,12,8,18);
6069
+ SCR_SALSA(9,5,1,7); SCR_SALSA(13,9,5,9); SCR_SALSA(1,13,9,13); SCR_SALSA(5,1,13,18);
6070
+ SCR_SALSA(14,10,6,7); SCR_SALSA(2,14,10,9); SCR_SALSA(6,2,14,13); SCR_SALSA(10,6,2,18);
6071
+ SCR_SALSA(3,15,11,7); SCR_SALSA(7,3,15,9); SCR_SALSA(11,7,3,13); SCR_SALSA(15,11,7,18);
6072
+ SCR_SALSA(1,0,3,7); SCR_SALSA(2,1,0,9); SCR_SALSA(3,2,1,13); SCR_SALSA(0,3,2,18);
6073
+ SCR_SALSA(6,5,4,7); SCR_SALSA(7,6,5,9); SCR_SALSA(4,7,6,13); SCR_SALSA(5,4,7,18);
6074
+ SCR_SALSA(11,10,9,7); SCR_SALSA(8,11,10,9); SCR_SALSA(9,8,11,13); SCR_SALSA(10,9,8,18);
6075
+ SCR_SALSA(12,15,14,7); SCR_SALSA(13,12,15,9); SCR_SALSA(14,13,12,13); SCR_SALSA(15,14,13,18);
6076
+ #undef SCR_SALSA
6077
+ }
6078
+ for (size_t i = 0; i < 16; i++) block[i] += x[i];
6079
+ scr_crypto_zero(x, sizeof x);
6080
+ }
6081
+
6082
+ static void scr_scrypt_blockmix(uint32_t *x, uint32_t *y, size_t r) {
6083
+ uint32_t block[16];
6084
+ memcpy(block, x + (2 * r - 1) * 16, sizeof block);
6085
+ for (size_t i = 0; i < 2 * r; i++) {
6086
+ for (size_t k = 0; k < 16; k++) block[k] ^= x[i * 16 + k];
6087
+ scr_scrypt_salsa(block);
6088
+ size_t dest = (i / 2 + (i % 2) * r) * 16;
6089
+ memcpy(y + dest, block, sizeof block);
6090
+ }
6091
+ memcpy(x, y, 128 * r);
6092
+ scr_crypto_zero(block, sizeof block);
6093
+ }
6094
+
6095
+ static bool scr_scrypt_params(double n, double r, double p, double maxmem, size_t *memory) {
6096
+ uint64_t N = (uint64_t)n, R = (uint64_t)r, P = (uint64_t)p;
6097
+ if (N < 2 || (N & (N - 1)) != 0) {
6098
+ scr_throw_error_msg_code(SCR_ERR_RANGE, "Invalid scrypt params", 21, "ERR_CRYPTO_INVALID_SCRYPT_PARAMS");
6099
+ return false;
6100
+ }
6101
+ /* Match Node's OpenSSL 3 scrypt guard, including X/Y working buffers
6102
+ * and the initial PBKDF2 output; check every product before allocation. */
6103
+ bool overflow = R == 0 || P == 0 || P > UINT64_C(0x3fffffff) / R;
6104
+ if (!overflow && R <= 3 && N >= (UINT64_C(1) << (16 * R))) overflow = true;
6105
+ uint64_t b = overflow ? 0 : 128 * R * P;
6106
+ if (b > INT32_MAX || R > UINT64_MAX / 128 / (N + 2)) overflow = true;
6107
+ uint64_t v = overflow ? 0 : 128 * R * (N + 2);
6108
+ if (overflow || b > UINT64_MAX - v || b + v > (uint64_t)maxmem || b + v > SIZE_MAX) {
6109
+ static const char message[] = "Invalid scrypt params: error:030000AC:digital envelope routines::memory limit exceeded";
6110
+ scr_throw_error_msg_code(SCR_ERR_RANGE, message, sizeof message - 1, "ERR_CRYPTO_INVALID_SCRYPT_PARAMS");
6111
+ return false;
6112
+ }
6113
+ *memory = (size_t)(b + v);
6114
+ return true;
6115
+ }
6116
+
6117
+ ScrBytes *scr_crypto_scrypt_derive(ScrBytes *password, ScrBytes *salt, double keylen,
6118
+ double n, double r, double p, double maxmem) {
6119
+ if (!scr_crypto_integer(keylen, "keylen", 2147483647.0) ||
6120
+ !scr_crypto_integer(n, "N", 4294967295.0) || !scr_crypto_integer(r, "r", 4294967295.0) ||
6121
+ !scr_crypto_integer(p, "p", 4294967295.0) || !scr_crypto_integer(maxmem, "maxmem", 9007199254740991.0)) return NULL;
6122
+ if (n == 0) n = 16384;
6123
+ if (r == 0) r = 8;
6124
+ if (p == 0) p = 1;
6125
+ if (maxmem == 0) maxmem = 33554432;
6126
+ size_t memory;
6127
+ if (!scr_scrypt_params(n, r, p, maxmem, &memory)) return NULL;
6128
+ if (keylen == 0) return scr_bytes_new(SCR_BYTES_U8, 0);
6129
+ size_t words = 32 * (size_t)r, block_len = words * sizeof(uint32_t);
6130
+ size_t b_len = block_len * (size_t)p;
6131
+ ScrBytes *b = scr_crypto_pbkdf2_raw(password->data, password->len * scr_bytes_elem_size(password->elem),
6132
+ salt->data, salt->len * scr_bytes_elem_size(salt->elem), 1, b_len, SCR_DIGEST_SHA256);
6133
+ uint32_t *work = malloc(memory - b_len);
6134
+ if (!work) { scr_bytes_release(b); scr_trap("scriptc: out of memory\n"); }
6135
+ uint32_t *x = work, *y = x + words, *v = y + words;
6136
+ for (size_t part = 0; part < (size_t)p; part++) {
6137
+ unsigned char *bytes = b->data + part * block_len;
6138
+ for (size_t i = 0; i < words; i++) {
6139
+ x[i] = (uint32_t)bytes[i * 4] | ((uint32_t)bytes[i * 4 + 1] << 8) |
6140
+ ((uint32_t)bytes[i * 4 + 2] << 16) | ((uint32_t)bytes[i * 4 + 3] << 24);
6141
+ }
6142
+ for (size_t i = 0; i < (size_t)n; i++) {
6143
+ memcpy(v + i * words, x, block_len);
6144
+ scr_scrypt_blockmix(x, y, (size_t)r);
6145
+ }
6146
+ for (size_t i = 0; i < (size_t)n; i++) {
6147
+ size_t j = (size_t)x[words - 16] & ((size_t)n - 1);
6148
+ for (size_t k = 0; k < words; k++) x[k] ^= v[j * words + k];
6149
+ scr_scrypt_blockmix(x, y, (size_t)r);
6150
+ }
6151
+ for (size_t i = 0; i < words; i++) {
6152
+ for (size_t k = 0; k < 4; k++) bytes[i * 4 + k] = (unsigned char)(x[i] >> (8 * k));
6153
+ }
6154
+ }
6155
+ ScrBytes *result = scr_crypto_pbkdf2_raw(password->data, password->len * scr_bytes_elem_size(password->elem),
6156
+ b->data, b_len, 1, (size_t)keylen, SCR_DIGEST_SHA256);
6157
+ scr_crypto_zero(b->data, b_len);
6158
+ scr_bytes_release(b);
6159
+ scr_crypto_zero(work, memory - b_len);
6160
+ free(work);
6161
+ return result;
6162
+ }
6163
+
6164
+ static ScrDyn *scr_scrypt_read_option(ScrDyn *options, const char *name) {
6165
+ if (options->kind != SCR_DYN_TYPED_REF) return scr_dyn_obj_read(options, name, strlen(name));
6166
+ ScrDyn *view = scr_dyn_typed_ref_materialize(options);
6167
+ if (!view) return NULL;
6168
+ ScrDyn *value = scr_dyn_obj_read(view, name, strlen(name));
6169
+ scr_dyn_release(view);
6170
+ return value;
6171
+ }
6172
+
6173
+ static bool scr_scrypt_option(ScrDyn *options, const char *name, double max, double *out, bool *present) {
6174
+ ScrDyn *value = scr_scrypt_read_option(options, name);
6175
+ if (scr_exc_pending()) return false;
6176
+ *present = value && value->kind != SCR_DYN_UNDEF;
6177
+ if (*present) {
6178
+ scr_dyn_release(value);
6179
+ value = scr_scrypt_read_option(options, name);
6180
+ if (scr_exc_pending()) return false;
6181
+ if (value->kind != SCR_DYN_NUM) {
6182
+ scr_dyn_arg_type_fail(name, "number", value);
6183
+ scr_dyn_release(value);
6184
+ return false;
6185
+ }
6186
+ *out = value->v.num;
6187
+ bool valid = scr_crypto_integer(*out, name, max);
6188
+ scr_dyn_release(value);
6189
+ return valid;
6190
+ }
6191
+ scr_dyn_release(value);
6192
+ return true;
6193
+ }
6194
+
6195
+ ScrBytes *scr_crypto_scrypt(ScrBytes *password, ScrBytes *salt, double keylen, ScrDyn *options) {
6196
+ if (!scr_crypto_integer(keylen, "keylen", 2147483647.0)) return NULL;
6197
+ double values[4] = {16384, 8, 1, 33554432};
6198
+ if (options && scr_dyn_truthy(options)) {
6199
+ const char *names[3] = {"N", "r", "p"};
6200
+ const char *aliases[3] = {"cost", "blockSize", "parallelization"};
6201
+ for (size_t i = 0; i < 3; i++) {
6202
+ bool primary, alias;
6203
+ if (!scr_scrypt_option(options, names[i], 4294967295.0, &values[i], &primary)) return NULL;
6204
+ // Node reads the alias to test presence before checking the conflict.
6205
+ ScrDyn *alias_value = scr_scrypt_read_option(options, aliases[i]);
6206
+ if (scr_exc_pending()) return NULL;
6207
+ alias = alias_value && alias_value->kind != SCR_DYN_UNDEF;
6208
+ if (primary && alias) {
6209
+ char message[160];
6210
+ int len = snprintf(message, sizeof message, "Option \"%s\" cannot be used in combination with option \"%s\"", names[i], aliases[i]);
6211
+ scr_dyn_release(alias_value);
6212
+ scr_throw_error_msg_code(SCR_ERR_TYPE, message, (size_t)len, "ERR_INCOMPATIBLE_OPTION_PAIR");
6213
+ return NULL;
6214
+ }
6215
+ if (alias) {
6216
+ scr_dyn_release(alias_value);
6217
+ alias_value = scr_scrypt_read_option(options, aliases[i]);
6218
+ if (scr_exc_pending()) return NULL;
6219
+ if (alias_value->kind != SCR_DYN_NUM) {
6220
+ scr_dyn_arg_type_fail(aliases[i], "number", alias_value);
6221
+ scr_dyn_release(alias_value);
6222
+ return NULL;
6223
+ }
6224
+ values[i] = alias_value->v.num;
6225
+ if (!scr_crypto_integer(values[i], aliases[i], 4294967295.0)) {
6226
+ scr_dyn_release(alias_value);
6227
+ return NULL;
6228
+ }
6229
+ }
6230
+ scr_dyn_release(alias_value);
6231
+ }
6232
+ bool present;
6233
+ if (!scr_scrypt_option(options, "maxmem", 9007199254740991.0, &values[3], &present)) return NULL;
6234
+ }
6235
+ return scr_crypto_scrypt_derive(password, salt, keylen, values[0], values[1], values[2], values[3]);
6236
+ }
6237
+
5089
6238
  /* The composed `new crypto.X509Certificate(data).fingerprint` read, fused
5090
6239
  * by the compiler (no certificate handle exists). Node's .fingerprint IS
5091
6240
  * the SHA-1 of the certificate's DER bytes, uppercase colon-separated —