@scriptc/runtime 0.0.0 → 0.0.2
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.
- package/LICENSE +202 -0
- package/package.json +16 -6
- package/src/scr_array.c +488 -0
- package/src/scr_assert.c +1393 -0
- package/src/scr_async.c +2593 -0
- package/src/scr_async_dyn.c +607 -0
- package/src/scr_bytes.c +1425 -0
- package/src/scr_bytes_io.c +161 -0
- package/src/scr_child.c +3587 -0
- package/src/scr_closure.c +214 -0
- package/src/scr_console.c +143 -0
- package/src/scr_cycle.c +218 -0
- package/src/scr_dc.c +805 -0
- package/src/scr_dgram.c +1007 -0
- package/src/scr_dyn_handle.c +154 -0
- package/src/scr_dyn_invoke.c +644 -0
- package/src/scr_error.c +332 -0
- package/src/scr_events.c +714 -0
- package/src/scr_events_emitter.c +699 -0
- package/src/scr_exception.c +242 -0
- package/src/scr_fetch.c +1130 -0
- package/src/scr_fetch_curl.c +654 -0
- package/src/scr_http.c +3482 -0
- package/src/scr_http2.c +3030 -0
- package/src/scr_inspect.c +803 -0
- package/src/scr_inspect_island.c +51 -0
- package/src/scr_island.c +9363 -0
- package/src/scr_json.c +2151 -0
- package/src/scr_lib.c +3612 -0
- package/src/scr_loop_epoll.c +241 -0
- package/src/scr_loop_kqueue.c +121 -0
- package/src/scr_loop_wsapoll.c +208 -0
- package/src/scr_map.c +591 -0
- package/src/scr_net.c +3017 -0
- package/src/scr_net_island.c +458 -0
- package/src/scr_number.c +115 -0
- package/src/scr_object.c +25 -0
- package/src/scr_path.c +1266 -0
- package/src/scr_platform.h +119 -0
- package/src/scr_readline.c +287 -0
- package/src/scr_regex.c +1080 -0
- package/src/scr_runtime.h +5046 -0
- package/src/scr_stream.c +2877 -0
- package/src/scr_string.c +1264 -0
- package/src/scr_symbol.c +132 -0
- package/src/scr_test.c +662 -0
- package/src/scr_tls.c +1520 -0
- package/src/scr_union.c +105 -0
- package/src/scr_url.c +911 -0
- package/src/scr_url_internal.h +29 -0
- package/src/scr_url_params.c +561 -0
- package/src/scr_watch.c +568 -0
- package/src/scr_web.c +1778 -0
- package/src/scr_win.c +76 -0
- package/src/scr_zlib.c +197 -0
- package/src/scr_zlib_island.c +15 -0
- package/vendor/README.md +52 -0
- package/vendor/curl/COPYRIGHT +534 -0
- package/vendor/curl/include/curl/curl.h +3214 -0
- package/vendor/curl/include/curl/curlver.h +79 -0
- package/vendor/curl/include/curl/easy.h +125 -0
- package/vendor/curl/include/curl/header.h +74 -0
- package/vendor/curl/include/curl/mprintf.h +52 -0
- package/vendor/curl/include/curl/multi.h +460 -0
- package/vendor/curl/include/curl/options.h +70 -0
- package/vendor/curl/include/curl/stdcheaders.h +35 -0
- package/vendor/curl/include/curl/system.h +508 -0
- package/vendor/curl/include/curl/typecheck-gcc.h +716 -0
- package/vendor/curl/include/curl/urlapi.h +149 -0
- package/vendor/curl/include/curl/websockets.h +84 -0
- package/vendor/mbedtls/LICENSE +553 -0
- package/vendor/mbedtls/include/CMakeLists.txt +22 -0
- package/vendor/mbedtls/include/mbedtls/aes.h +631 -0
- package/vendor/mbedtls/include/mbedtls/aria.h +343 -0
- package/vendor/mbedtls/include/mbedtls/asn1.h +642 -0
- package/vendor/mbedtls/include/mbedtls/asn1write.h +390 -0
- package/vendor/mbedtls/include/mbedtls/base64.h +82 -0
- package/vendor/mbedtls/include/mbedtls/bignum.h +1088 -0
- package/vendor/mbedtls/include/mbedtls/block_cipher.h +76 -0
- package/vendor/mbedtls/include/mbedtls/build_info.h +194 -0
- package/vendor/mbedtls/include/mbedtls/camellia.h +305 -0
- package/vendor/mbedtls/include/mbedtls/ccm.h +526 -0
- package/vendor/mbedtls/include/mbedtls/chacha20.h +209 -0
- package/vendor/mbedtls/include/mbedtls/chachapoly.h +351 -0
- package/vendor/mbedtls/include/mbedtls/check_config.h +1149 -0
- package/vendor/mbedtls/include/mbedtls/cipher.h +1250 -0
- package/vendor/mbedtls/include/mbedtls/cmac.h +246 -0
- package/vendor/mbedtls/include/mbedtls/compat-2.x.h +46 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h +578 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h +873 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h +359 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h +145 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_ssl.h +91 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_x509.h +35 -0
- package/vendor/mbedtls/include/mbedtls/config_psa.h +61 -0
- package/vendor/mbedtls/include/mbedtls/constant_time.h +36 -0
- package/vendor/mbedtls/include/mbedtls/ctr_drbg.h +596 -0
- package/vendor/mbedtls/include/mbedtls/debug.h +156 -0
- package/vendor/mbedtls/include/mbedtls/des.h +385 -0
- package/vendor/mbedtls/include/mbedtls/dhm.h +972 -0
- package/vendor/mbedtls/include/mbedtls/ecdh.h +455 -0
- package/vendor/mbedtls/include/mbedtls/ecdsa.h +674 -0
- package/vendor/mbedtls/include/mbedtls/ecjpake.h +298 -0
- package/vendor/mbedtls/include/mbedtls/ecp.h +1517 -0
- package/vendor/mbedtls/include/mbedtls/entropy.h +274 -0
- package/vendor/mbedtls/include/mbedtls/error.h +201 -0
- package/vendor/mbedtls/include/mbedtls/gcm.h +387 -0
- package/vendor/mbedtls/include/mbedtls/hkdf.h +124 -0
- package/vendor/mbedtls/include/mbedtls/hmac_drbg.h +434 -0
- package/vendor/mbedtls/include/mbedtls/lms.h +440 -0
- package/vendor/mbedtls/include/mbedtls/mbedtls_config.h +4446 -0
- package/vendor/mbedtls/include/mbedtls/md.h +526 -0
- package/vendor/mbedtls/include/mbedtls/md5.h +190 -0
- package/vendor/mbedtls/include/mbedtls/memory_buffer_alloc.h +142 -0
- package/vendor/mbedtls/include/mbedtls/net_sockets.h +299 -0
- package/vendor/mbedtls/include/mbedtls/nist_kw.h +166 -0
- package/vendor/mbedtls/include/mbedtls/oid.h +727 -0
- package/vendor/mbedtls/include/mbedtls/pem.h +160 -0
- package/vendor/mbedtls/include/mbedtls/pk.h +1303 -0
- package/vendor/mbedtls/include/mbedtls/pkcs12.h +186 -0
- package/vendor/mbedtls/include/mbedtls/pkcs5.h +198 -0
- package/vendor/mbedtls/include/mbedtls/pkcs7.h +252 -0
- package/vendor/mbedtls/include/mbedtls/platform.h +516 -0
- package/vendor/mbedtls/include/mbedtls/platform_time.h +79 -0
- package/vendor/mbedtls/include/mbedtls/platform_util.h +247 -0
- package/vendor/mbedtls/include/mbedtls/poly1305.h +168 -0
- package/vendor/mbedtls/include/mbedtls/private_access.h +20 -0
- package/vendor/mbedtls/include/mbedtls/psa_util.h +207 -0
- package/vendor/mbedtls/include/mbedtls/ripemd160.h +136 -0
- package/vendor/mbedtls/include/mbedtls/rsa.h +1170 -0
- package/vendor/mbedtls/include/mbedtls/sha1.h +219 -0
- package/vendor/mbedtls/include/mbedtls/sha256.h +200 -0
- package/vendor/mbedtls/include/mbedtls/sha3.h +172 -0
- package/vendor/mbedtls/include/mbedtls/sha512.h +208 -0
- package/vendor/mbedtls/include/mbedtls/ssl.h +5887 -0
- package/vendor/mbedtls/include/mbedtls/ssl_cache.h +187 -0
- package/vendor/mbedtls/include/mbedtls/ssl_ciphersuites.h +482 -0
- package/vendor/mbedtls/include/mbedtls/ssl_cookie.h +106 -0
- package/vendor/mbedtls/include/mbedtls/ssl_ticket.h +199 -0
- package/vendor/mbedtls/include/mbedtls/threading.h +167 -0
- package/vendor/mbedtls/include/mbedtls/timing.h +94 -0
- package/vendor/mbedtls/include/mbedtls/version.h +78 -0
- package/vendor/mbedtls/include/mbedtls/x509.h +500 -0
- package/vendor/mbedtls/include/mbedtls/x509_crl.h +184 -0
- package/vendor/mbedtls/include/mbedtls/x509_crt.h +1208 -0
- package/vendor/mbedtls/include/mbedtls/x509_csr.h +382 -0
- package/vendor/mbedtls/include/psa/build_info.h +20 -0
- package/vendor/mbedtls/include/psa/crypto.h +4998 -0
- package/vendor/mbedtls/include/psa/crypto_adjust_auto_enabled.h +31 -0
- package/vendor/mbedtls/include/psa/crypto_adjust_config_dependencies.h +51 -0
- package/vendor/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h +101 -0
- package/vendor/mbedtls/include/psa/crypto_adjust_config_synonyms.h +49 -0
- package/vendor/mbedtls/include/psa/crypto_builtin_composites.h +214 -0
- package/vendor/mbedtls/include/psa/crypto_builtin_key_derivation.h +118 -0
- package/vendor/mbedtls/include/psa/crypto_builtin_primitives.h +114 -0
- package/vendor/mbedtls/include/psa/crypto_compat.h +230 -0
- package/vendor/mbedtls/include/psa/crypto_config.h +145 -0
- package/vendor/mbedtls/include/psa/crypto_driver_common.h +44 -0
- package/vendor/mbedtls/include/psa/crypto_driver_contexts_composites.h +151 -0
- package/vendor/mbedtls/include/psa/crypto_driver_contexts_key_derivation.h +52 -0
- package/vendor/mbedtls/include/psa/crypto_driver_contexts_primitives.h +105 -0
- package/vendor/mbedtls/include/psa/crypto_extra.h +2145 -0
- package/vendor/mbedtls/include/psa/crypto_legacy.h +88 -0
- package/vendor/mbedtls/include/psa/crypto_platform.h +102 -0
- package/vendor/mbedtls/include/psa/crypto_se_driver.h +1383 -0
- package/vendor/mbedtls/include/psa/crypto_sizes.h +1319 -0
- package/vendor/mbedtls/include/psa/crypto_struct.h +527 -0
- package/vendor/mbedtls/include/psa/crypto_types.h +508 -0
- package/vendor/mbedtls/include/psa/crypto_values.h +2782 -0
- package/vendor/mbedtls/library/aes.c +2294 -0
- package/vendor/mbedtls/library/aesce.c +624 -0
- package/vendor/mbedtls/library/aesce.h +136 -0
- package/vendor/mbedtls/library/aesni.c +846 -0
- package/vendor/mbedtls/library/aesni.h +162 -0
- package/vendor/mbedtls/library/alignment.h +704 -0
- package/vendor/mbedtls/library/aria.c +969 -0
- package/vendor/mbedtls/library/asn1parse.c +468 -0
- package/vendor/mbedtls/library/asn1write.c +440 -0
- package/vendor/mbedtls/library/base64.c +322 -0
- package/vendor/mbedtls/library/base64_internal.h +45 -0
- package/vendor/mbedtls/library/bignum.c +2583 -0
- package/vendor/mbedtls/library/bignum_core.c +1240 -0
- package/vendor/mbedtls/library/bignum_core.h +872 -0
- package/vendor/mbedtls/library/bignum_core_invasive.h +38 -0
- package/vendor/mbedtls/library/bignum_internal.h +122 -0
- package/vendor/mbedtls/library/bignum_mod.c +394 -0
- package/vendor/mbedtls/library/bignum_mod.h +452 -0
- package/vendor/mbedtls/library/bignum_mod_raw.c +276 -0
- package/vendor/mbedtls/library/bignum_mod_raw.h +416 -0
- package/vendor/mbedtls/library/bignum_mod_raw_invasive.h +34 -0
- package/vendor/mbedtls/library/block_cipher.c +207 -0
- package/vendor/mbedtls/library/block_cipher_internal.h +99 -0
- package/vendor/mbedtls/library/bn_mul.h +1094 -0
- package/vendor/mbedtls/library/camellia.c +1058 -0
- package/vendor/mbedtls/library/ccm.c +777 -0
- package/vendor/mbedtls/library/chacha20.c +563 -0
- package/vendor/mbedtls/library/chacha20_internal.h +23 -0
- package/vendor/mbedtls/library/chachapoly.c +487 -0
- package/vendor/mbedtls/library/check_crypto_config.h +136 -0
- package/vendor/mbedtls/library/cipher.c +1712 -0
- package/vendor/mbedtls/library/cipher_invasive.h +28 -0
- package/vendor/mbedtls/library/cipher_wrap.c +2482 -0
- package/vendor/mbedtls/library/cipher_wrap.h +178 -0
- package/vendor/mbedtls/library/cmac.c +1067 -0
- package/vendor/mbedtls/library/common.h +487 -0
- package/vendor/mbedtls/library/constant_time.c +247 -0
- package/vendor/mbedtls/library/constant_time_impl.h +541 -0
- package/vendor/mbedtls/library/constant_time_internal.h +579 -0
- package/vendor/mbedtls/library/ctr.h +35 -0
- package/vendor/mbedtls/library/ctr_drbg.c +1016 -0
- package/vendor/mbedtls/library/debug.c +475 -0
- package/vendor/mbedtls/library/debug_internal.h +185 -0
- package/vendor/mbedtls/library/des.c +1042 -0
- package/vendor/mbedtls/library/dhm.c +700 -0
- package/vendor/mbedtls/library/ecdh.c +696 -0
- package/vendor/mbedtls/library/ecdsa.c +858 -0
- package/vendor/mbedtls/library/ecjpake.c +1216 -0
- package/vendor/mbedtls/library/ecp.c +3674 -0
- package/vendor/mbedtls/library/ecp_curves.c +6125 -0
- package/vendor/mbedtls/library/ecp_curves_new.c +9 -0
- package/vendor/mbedtls/library/ecp_internal_alt.h +287 -0
- package/vendor/mbedtls/library/ecp_invasive.h +305 -0
- package/vendor/mbedtls/library/entropy.c +680 -0
- package/vendor/mbedtls/library/entropy_poll.c +233 -0
- package/vendor/mbedtls/library/entropy_poll.h +64 -0
- package/vendor/mbedtls/library/error.c +878 -0
- package/vendor/mbedtls/library/gcm.c +1330 -0
- package/vendor/mbedtls/library/hkdf.c +161 -0
- package/vendor/mbedtls/library/hmac_drbg.c +633 -0
- package/vendor/mbedtls/library/lmots.c +789 -0
- package/vendor/mbedtls/library/lmots.h +288 -0
- package/vendor/mbedtls/library/lms.c +778 -0
- package/vendor/mbedtls/library/md.c +1108 -0
- package/vendor/mbedtls/library/md5.c +426 -0
- package/vendor/mbedtls/library/md_psa.h +26 -0
- package/vendor/mbedtls/library/md_wrap.h +46 -0
- package/vendor/mbedtls/library/memory_buffer_alloc.c +751 -0
- package/vendor/mbedtls/library/mps_common.h +181 -0
- package/vendor/mbedtls/library/mps_error.h +89 -0
- package/vendor/mbedtls/library/mps_reader.c +538 -0
- package/vendor/mbedtls/library/mps_reader.h +366 -0
- package/vendor/mbedtls/library/mps_trace.c +112 -0
- package/vendor/mbedtls/library/mps_trace.h +154 -0
- package/vendor/mbedtls/library/net_sockets.c +694 -0
- package/vendor/mbedtls/library/nist_kw.c +729 -0
- package/vendor/mbedtls/library/oid.c +1166 -0
- package/vendor/mbedtls/library/padlock.c +157 -0
- package/vendor/mbedtls/library/padlock.h +111 -0
- package/vendor/mbedtls/library/pem.c +554 -0
- package/vendor/mbedtls/library/pk.c +1602 -0
- package/vendor/mbedtls/library/pk_ecc.c +261 -0
- package/vendor/mbedtls/library/pk_internal.h +241 -0
- package/vendor/mbedtls/library/pk_wrap.c +1618 -0
- package/vendor/mbedtls/library/pk_wrap.h +138 -0
- package/vendor/mbedtls/library/pkcs12.c +437 -0
- package/vendor/mbedtls/library/pkcs5.c +500 -0
- package/vendor/mbedtls/library/pkcs7.c +787 -0
- package/vendor/mbedtls/library/pkparse.c +1392 -0
- package/vendor/mbedtls/library/pkwrite.c +631 -0
- package/vendor/mbedtls/library/pkwrite.h +121 -0
- package/vendor/mbedtls/library/platform.c +402 -0
- package/vendor/mbedtls/library/platform_util.c +258 -0
- package/vendor/mbedtls/library/poly1305.c +492 -0
- package/vendor/mbedtls/library/psa_crypto.c +9532 -0
- package/vendor/mbedtls/library/psa_crypto_aead.c +646 -0
- package/vendor/mbedtls/library/psa_crypto_aead.h +499 -0
- package/vendor/mbedtls/library/psa_crypto_cipher.c +747 -0
- package/vendor/mbedtls/library/psa_crypto_cipher.h +316 -0
- package/vendor/mbedtls/library/psa_crypto_client.c +22 -0
- package/vendor/mbedtls/library/psa_crypto_core.h +983 -0
- package/vendor/mbedtls/library/psa_crypto_core_common.h +52 -0
- package/vendor/mbedtls/library/psa_crypto_driver_wrappers.h +2896 -0
- package/vendor/mbedtls/library/psa_crypto_driver_wrappers_no_static.c +256 -0
- package/vendor/mbedtls/library/psa_crypto_driver_wrappers_no_static.h +31 -0
- package/vendor/mbedtls/library/psa_crypto_ecp.c +594 -0
- package/vendor/mbedtls/library/psa_crypto_ecp.h +267 -0
- package/vendor/mbedtls/library/psa_crypto_ffdh.c +361 -0
- package/vendor/mbedtls/library/psa_crypto_ffdh.h +131 -0
- package/vendor/mbedtls/library/psa_crypto_hash.c +470 -0
- package/vendor/mbedtls/library/psa_crypto_hash.h +211 -0
- package/vendor/mbedtls/library/psa_crypto_invasive.h +92 -0
- package/vendor/mbedtls/library/psa_crypto_its.h +131 -0
- package/vendor/mbedtls/library/psa_crypto_mac.c +505 -0
- package/vendor/mbedtls/library/psa_crypto_mac.h +264 -0
- package/vendor/mbedtls/library/psa_crypto_pake.c +571 -0
- package/vendor/mbedtls/library/psa_crypto_pake.h +159 -0
- package/vendor/mbedtls/library/psa_crypto_random.c +181 -0
- package/vendor/mbedtls/library/psa_crypto_random.h +72 -0
- package/vendor/mbedtls/library/psa_crypto_random_impl.h +200 -0
- package/vendor/mbedtls/library/psa_crypto_rsa.c +714 -0
- package/vendor/mbedtls/library/psa_crypto_rsa.h +321 -0
- package/vendor/mbedtls/library/psa_crypto_se.c +373 -0
- package/vendor/mbedtls/library/psa_crypto_se.h +192 -0
- package/vendor/mbedtls/library/psa_crypto_slot_management.c +1137 -0
- package/vendor/mbedtls/library/psa_crypto_slot_management.h +344 -0
- package/vendor/mbedtls/library/psa_crypto_storage.c +481 -0
- package/vendor/mbedtls/library/psa_crypto_storage.h +392 -0
- package/vendor/mbedtls/library/psa_its_file.c +254 -0
- package/vendor/mbedtls/library/psa_util.c +614 -0
- package/vendor/mbedtls/library/psa_util_internal.h +100 -0
- package/vendor/mbedtls/library/ripemd160.c +490 -0
- package/vendor/mbedtls/library/rsa.c +3121 -0
- package/vendor/mbedtls/library/rsa_alt_helpers.c +455 -0
- package/vendor/mbedtls/library/rsa_alt_helpers.h +209 -0
- package/vendor/mbedtls/library/rsa_internal.h +188 -0
- package/vendor/mbedtls/library/sha1.c +480 -0
- package/vendor/mbedtls/library/sha256.c +983 -0
- package/vendor/mbedtls/library/sha3.c +721 -0
- package/vendor/mbedtls/library/sha512.c +1115 -0
- package/vendor/mbedtls/library/ssl_cache.c +410 -0
- package/vendor/mbedtls/library/ssl_ciphersuites.c +2050 -0
- package/vendor/mbedtls/library/ssl_ciphersuites_internal.h +154 -0
- package/vendor/mbedtls/library/ssl_client.c +1023 -0
- package/vendor/mbedtls/library/ssl_client.h +22 -0
- package/vendor/mbedtls/library/ssl_cookie.c +384 -0
- package/vendor/mbedtls/library/ssl_debug_helpers.h +85 -0
- package/vendor/mbedtls/library/ssl_debug_helpers_generated.c +251 -0
- package/vendor/mbedtls/library/ssl_misc.h +3198 -0
- package/vendor/mbedtls/library/ssl_msg.c +6619 -0
- package/vendor/mbedtls/library/ssl_ticket.c +556 -0
- package/vendor/mbedtls/library/ssl_tls.c +10234 -0
- package/vendor/mbedtls/library/ssl_tls12_client.c +3688 -0
- package/vendor/mbedtls/library/ssl_tls12_server.c +4311 -0
- package/vendor/mbedtls/library/ssl_tls13_client.c +3208 -0
- package/vendor/mbedtls/library/ssl_tls13_generic.c +1793 -0
- package/vendor/mbedtls/library/ssl_tls13_invasive.h +23 -0
- package/vendor/mbedtls/library/ssl_tls13_keys.c +1918 -0
- package/vendor/mbedtls/library/ssl_tls13_keys.h +668 -0
- package/vendor/mbedtls/library/ssl_tls13_server.c +3624 -0
- package/vendor/mbedtls/library/threading.c +193 -0
- package/vendor/mbedtls/library/threading_internal.h +28 -0
- package/vendor/mbedtls/library/timing.c +152 -0
- package/vendor/mbedtls/library/version.c +32 -0
- package/vendor/mbedtls/library/version_features.c +856 -0
- package/vendor/mbedtls/library/x509.c +1776 -0
- package/vendor/mbedtls/library/x509_create.c +570 -0
- package/vendor/mbedtls/library/x509_crl.c +708 -0
- package/vendor/mbedtls/library/x509_crt.c +3311 -0
- package/vendor/mbedtls/library/x509_csr.c +648 -0
- package/vendor/mbedtls/library/x509_internal.h +101 -0
- package/vendor/mbedtls/library/x509write.c +174 -0
- package/vendor/mbedtls/library/x509write_crt.c +688 -0
- package/vendor/mbedtls/library/x509write_csr.c +336 -0
- package/vendor/quickjs-ng/CMakeLists.txt +566 -0
- package/vendor/quickjs-ng/LICENSE +24 -0
- package/vendor/quickjs-ng/README.md +24 -0
- package/vendor/quickjs-ng/api-test.c +1195 -0
- package/vendor/quickjs-ng/builtin-array-fromasync.h +119 -0
- package/vendor/quickjs-ng/builtin-iterator-zip-keyed.h +332 -0
- package/vendor/quickjs-ng/builtin-iterator-zip.h +337 -0
- package/vendor/quickjs-ng/cutils.h +1998 -0
- package/vendor/quickjs-ng/dtoa.c +1619 -0
- package/vendor/quickjs-ng/dtoa.h +87 -0
- package/vendor/quickjs-ng/gen/function_source.c +81 -0
- package/vendor/quickjs-ng/gen/hello.c +53 -0
- package/vendor/quickjs-ng/gen/hello_module.c +106 -0
- package/vendor/quickjs-ng/gen/repl.c +3036 -0
- package/vendor/quickjs-ng/gen/standalone.c +323 -0
- package/vendor/quickjs-ng/gen/test_fib.c +81 -0
- package/vendor/quickjs-ng/libregexp-opcode.h +73 -0
- package/vendor/quickjs-ng/libregexp.c +3474 -0
- package/vendor/quickjs-ng/libregexp.h +101 -0
- package/vendor/quickjs-ng/libunicode-table.h +5173 -0
- package/vendor/quickjs-ng/libunicode.c +2069 -0
- package/vendor/quickjs-ng/libunicode.h +172 -0
- package/vendor/quickjs-ng/list.h +107 -0
- package/vendor/quickjs-ng/lre-test.c +52 -0
- package/vendor/quickjs-ng/qjs.c +762 -0
- package/vendor/quickjs-ng/qjsc.c +673 -0
- package/vendor/quickjs-ng/quickjs-atom.h +280 -0
- package/vendor/quickjs-ng/quickjs-c-atomics.h +54 -0
- package/vendor/quickjs-ng/quickjs-libc.c +5037 -0
- package/vendor/quickjs-ng/quickjs-libc.h +87 -0
- package/vendor/quickjs-ng/quickjs-opcode.h +377 -0
- package/vendor/quickjs-ng/quickjs.c +64041 -0
- package/vendor/quickjs-ng/quickjs.h +1447 -0
- package/vendor/quickjs-ng/run-test262.c +2374 -0
- package/vendor/quickjs-ng/unicode_gen.c +3121 -0
- package/vendor/quickjs-ng/unicode_gen_def.h +310 -0
- package/vendor/ryu/LICENSE-Boost +23 -0
- package/vendor/ryu/common.h +114 -0
- package/vendor/ryu/d2s.c +509 -0
- package/vendor/ryu/d2s_full_table.h +367 -0
- package/vendor/ryu/d2s_intrinsics.h +357 -0
- package/vendor/ryu/d2s_small_table.h +186 -0
- package/vendor/ryu/digit_table.h +35 -0
- package/vendor/ryu/ryu.h +46 -0
- package/vendor/zlib/LICENSE +22 -0
- package/vendor/zlib/adler32.c +164 -0
- package/vendor/zlib/compress.c +75 -0
- package/vendor/zlib/crc32.c +1049 -0
- package/vendor/zlib/crc32.h +9446 -0
- package/vendor/zlib/deflate.c +2139 -0
- package/vendor/zlib/deflate.h +377 -0
- package/vendor/zlib/gzclose.c +23 -0
- package/vendor/zlib/gzguts.h +214 -0
- package/vendor/zlib/gzlib.c +582 -0
- package/vendor/zlib/gzread.c +602 -0
- package/vendor/zlib/gzwrite.c +631 -0
- package/vendor/zlib/infback.c +628 -0
- package/vendor/zlib/inffast.c +320 -0
- package/vendor/zlib/inffast.h +11 -0
- package/vendor/zlib/inffixed.h +94 -0
- package/vendor/zlib/inflate.c +1526 -0
- package/vendor/zlib/inflate.h +126 -0
- package/vendor/zlib/inftrees.c +299 -0
- package/vendor/zlib/inftrees.h +62 -0
- package/vendor/zlib/trees.c +1117 -0
- package/vendor/zlib/trees.h +128 -0
- package/vendor/zlib/uncompr.c +85 -0
- package/vendor/zlib/zconf.h +543 -0
- package/vendor/zlib/zlib.h +1938 -0
- package/vendor/zlib/zutil.c +299 -0
- package/vendor/zlib/zutil.h +254 -0
- package/README.md +0 -3
- package/index.js +0 -2
package/src/scr_lib.c
ADDED
|
@@ -0,0 +1,3612 @@
|
|
|
1
|
+
/* Standard library: the process global + synchronous node:fs (scr_runtime.h
|
|
2
|
+
* has the API contract). Everything here is called through compiler-emitted
|
|
3
|
+
* `libCall` IR.
|
|
4
|
+
*
|
|
5
|
+
* - process.argv is ONE interned array, built lazily on first read and
|
|
6
|
+
* retained per read — identity (`process.argv === process.argv`) and
|
|
7
|
+
* mutation persistence match Node's stable process.argv. The atexit
|
|
8
|
+
* cleanup registered by scr_lib_init releases the interned values before
|
|
9
|
+
* the RC audit runs (atexit is LIFO; scr_init registered the audit
|
|
10
|
+
* first), so they never count as leaks.
|
|
11
|
+
* - fs failures THROW through the exception cell (scr_throw_error — the
|
|
12
|
+
* payload is a catchable Error instance whose message is shaped like
|
|
13
|
+
* Node's fs error messages) and return a dummy; the compiler emits
|
|
14
|
+
* pending checks after every fs call (the MAY_THROW_LIB_FNS seed).
|
|
15
|
+
* scr_fs_exists never throws, like Node's existsSync.
|
|
16
|
+
*/
|
|
17
|
+
#include "scr_runtime.h"
|
|
18
|
+
|
|
19
|
+
#include <ctype.h>
|
|
20
|
+
#include <dirent.h>
|
|
21
|
+
#include <errno.h>
|
|
22
|
+
#include <fcntl.h>
|
|
23
|
+
#include <limits.h>
|
|
24
|
+
#include <math.h>
|
|
25
|
+
#include <signal.h>
|
|
26
|
+
#include <stdio.h>
|
|
27
|
+
#include <stdlib.h>
|
|
28
|
+
#include <string.h>
|
|
29
|
+
#include <sys/stat.h>
|
|
30
|
+
#include <time.h>
|
|
31
|
+
|
|
32
|
+
#ifdef _WIN32
|
|
33
|
+
/* ── the Windows arm's system surface ─────────────────────────────────
|
|
34
|
+
* mingw-w64's CRT covers most of sync fs (open/read/write/stat/dirent —
|
|
35
|
+
* the seams below note what it lacks: d_type, lstat, two-arg mkdir,
|
|
36
|
+
* mkdtemp, O_SYNC) and the Win32 API covers the process/os surface
|
|
37
|
+
* (GetTempPathA, GetUserNameA, RtlGetVersion, GetConsoleScreenBufferInfo).
|
|
38
|
+
* What has NO arm yet is stubbed honestly at its seam: process.kill and
|
|
39
|
+
* getuid/getgid (needs-design: OpenProcess/TerminateProcess vs Node's
|
|
40
|
+
* uv_kill; no uids exist on Windows — Node omits the members there),
|
|
41
|
+
* setRawMode's raw arm (mechanical: SetConsoleMode).
|
|
42
|
+
* os.networkInterfaces HAS its arm: GetAdaptersAddresses below, libuv's
|
|
43
|
+
* exact row selection. */
|
|
44
|
+
#include <direct.h> /* _mkdir */
|
|
45
|
+
#include <io.h> /* _isatty, _access, open/read/write/close */
|
|
46
|
+
#include <process.h> /* getpid */
|
|
47
|
+
#include <unistd.h> /* mingw-w64 ships one: getcwd, access, isatty, ... */
|
|
48
|
+
#include <winsock2.h> /* BEFORE windows.h (which pulls winsock 1 otherwise) */
|
|
49
|
+
#include <ws2tcpip.h> /* inet_ntop, sockaddr_in6 */
|
|
50
|
+
#include <iphlpapi.h> /* GetAdaptersAddresses (os.networkInterfaces) */
|
|
51
|
+
#include <windows.h>
|
|
52
|
+
#include <lmcons.h> /* UNLEN for GetUserNameA */
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
/* CRT stat has no symlink view — lstat degrades to stat (divergence: a
|
|
56
|
+
* compiled binary never reports isSymbolicLink() true on Windows; Node
|
|
57
|
+
* does for real symlinks/junctions). Mechanical fix: GetFileAttributesW +
|
|
58
|
+
* FILE_ATTRIBUTE_REPARSE_POINT. */
|
|
59
|
+
#define lstat stat
|
|
60
|
+
|
|
61
|
+
/* Windows has no directory mode bits; the CRT mkdir takes one argument. */
|
|
62
|
+
#define scr_sys_mkdir(p, m) ((void)(m), mkdir(p))
|
|
63
|
+
|
|
64
|
+
/* openSync's "rs"/"sa" flags: no O_SYNC on the CRT — degrade to non-sync
|
|
65
|
+
* opens (Node on Windows maps O_SYNC to FILE_FLAG_WRITE_THROUGH; the
|
|
66
|
+
* difference is durability, not observable output). */
|
|
67
|
+
#define O_SYNC 0
|
|
68
|
+
|
|
69
|
+
#ifndef INET6_ADDRSTRLEN
|
|
70
|
+
#define INET6_ADDRSTRLEN 46 /* ws2tcpip.h's value; only sizes the row bufs */
|
|
71
|
+
#endif
|
|
72
|
+
|
|
73
|
+
#else /* !_WIN32 */
|
|
74
|
+
|
|
75
|
+
#include <arpa/inet.h>
|
|
76
|
+
#include <ifaddrs.h>
|
|
77
|
+
#include <net/if.h>
|
|
78
|
+
#include <netinet/in.h>
|
|
79
|
+
#include <pwd.h>
|
|
80
|
+
#include <sys/ioctl.h>
|
|
81
|
+
#include <sys/socket.h>
|
|
82
|
+
#include <sys/utsname.h>
|
|
83
|
+
#include <termios.h>
|
|
84
|
+
#include <unistd.h>
|
|
85
|
+
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
|
86
|
+
#include <net/if_dl.h>
|
|
87
|
+
#elif defined(__linux__)
|
|
88
|
+
#include <netpacket/packet.h>
|
|
89
|
+
#endif
|
|
90
|
+
|
|
91
|
+
#define scr_sys_mkdir(p, m) mkdir((p), (m))
|
|
92
|
+
|
|
93
|
+
extern char **environ; /* env snapshot (scr_env_pairs) */
|
|
94
|
+
|
|
95
|
+
#endif /* _WIN32 */
|
|
96
|
+
|
|
97
|
+
/* O_BINARY: Windows-only (CRT text mode would translate \n on fd writes);
|
|
98
|
+
* zero elsewhere so the POSIX open flags are unchanged. */
|
|
99
|
+
#ifndef O_BINARY
|
|
100
|
+
#define O_BINARY 0
|
|
101
|
+
#endif
|
|
102
|
+
|
|
103
|
+
/* ── process ─────────────────────────────────────────────────────────── */
|
|
104
|
+
|
|
105
|
+
static int scr_lib_argc = 0;
|
|
106
|
+
static char **scr_lib_argv = NULL;
|
|
107
|
+
static ScrArr *scr_argv_arr = NULL; /* interned process.argv */
|
|
108
|
+
static ScrStr *scr_platform_str = NULL; /* interned process.platform */
|
|
109
|
+
static ScrStr *scr_exec_path_str = NULL; /* interned process.execPath */
|
|
110
|
+
static ScrStr *scr_arch_str = NULL; /* interned process.arch */
|
|
111
|
+
static ScrStr *scr_versions_node_str = NULL; /* interned process.versions.node */
|
|
112
|
+
static ScrStr *scr_versions_openssl_str = NULL; /* interned process.versions.openssl */
|
|
113
|
+
|
|
114
|
+
static void scr_lib_cleanup(void) {
|
|
115
|
+
scr_arr_release(scr_argv_arr);
|
|
116
|
+
scr_argv_arr = NULL;
|
|
117
|
+
scr_str_release(scr_platform_str);
|
|
118
|
+
scr_platform_str = NULL;
|
|
119
|
+
scr_str_release(scr_exec_path_str);
|
|
120
|
+
scr_exec_path_str = NULL;
|
|
121
|
+
scr_str_release(scr_arch_str);
|
|
122
|
+
scr_arch_str = NULL;
|
|
123
|
+
scr_str_release(scr_versions_node_str);
|
|
124
|
+
scr_versions_node_str = NULL;
|
|
125
|
+
scr_str_release(scr_versions_openssl_str);
|
|
126
|
+
scr_versions_openssl_str = NULL;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
void scr_lib_init(int argc, char **argv) {
|
|
130
|
+
scr_lib_argc = argc;
|
|
131
|
+
scr_lib_argv = argv;
|
|
132
|
+
atexit(scr_lib_cleanup);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* Raw argv accessors for the island's process shim (scr_island.c): the
|
|
136
|
+
* island's process.argv must match the static world's ["scriptc",
|
|
137
|
+
* argv[0], ...] shape exactly, so both build from the same stash. */
|
|
138
|
+
int scr_lib_arg_count(void) { return scr_lib_argc; }
|
|
139
|
+
const char *scr_lib_arg(int i) { return scr_lib_argv[i]; }
|
|
140
|
+
|
|
141
|
+
ScrArr *scr_process_argv(void) {
|
|
142
|
+
if (!scr_argv_arr) {
|
|
143
|
+
/* ["scriptc", argv[0], argv[1], ...]: positions and length line up
|
|
144
|
+
* with Node's [node-path, script-path, ...args]; the argv[0]/argv[1]
|
|
145
|
+
* VALUES diverge (SEMANTICS.md). */
|
|
146
|
+
scr_argv_arr = scr_arr_new(SCR_ELEM_STR, (size_t)scr_lib_argc + 1);
|
|
147
|
+
scr_arr_push_ref(scr_argv_arr, scr_str_new("scriptc", 7));
|
|
148
|
+
for (int i = 0; i < scr_lib_argc; i++) {
|
|
149
|
+
const char *a = scr_lib_argv[i];
|
|
150
|
+
scr_arr_push_ref(scr_argv_arr, scr_str_new(a, strlen(a)));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return scr_arr_retain(scr_argv_arr);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
ScrStr *scr_process_platform(void) {
|
|
157
|
+
if (!scr_platform_str) {
|
|
158
|
+
#if defined(__APPLE__)
|
|
159
|
+
scr_platform_str = scr_str_new("darwin", 6);
|
|
160
|
+
#elif defined(__linux__)
|
|
161
|
+
scr_platform_str = scr_str_new("linux", 5);
|
|
162
|
+
#elif defined(_WIN32)
|
|
163
|
+
scr_platform_str = scr_str_new("win32", 5);
|
|
164
|
+
#else
|
|
165
|
+
scr_platform_str = scr_str_new("unknown", 7);
|
|
166
|
+
#endif
|
|
167
|
+
}
|
|
168
|
+
return scr_str_retain(scr_platform_str);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* process.arch — the compiled binary's OWN architecture, spelled the way
|
|
172
|
+
* Node spells its own build's arch. Interned like process.platform. */
|
|
173
|
+
ScrStr *scr_process_arch(void) {
|
|
174
|
+
if (!scr_arch_str) {
|
|
175
|
+
#if defined(__aarch64__) || defined(_M_ARM64)
|
|
176
|
+
scr_arch_str = scr_str_new("arm64", 5);
|
|
177
|
+
#elif defined(__x86_64__) || defined(_M_X64)
|
|
178
|
+
scr_arch_str = scr_str_new("x64", 3);
|
|
179
|
+
#else
|
|
180
|
+
scr_arch_str = scr_str_new("unknown", 7);
|
|
181
|
+
#endif
|
|
182
|
+
}
|
|
183
|
+
return scr_str_retain(scr_arch_str);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* process.versions.node — the runtime's Node COMPATIBILITY TARGET. There
|
|
187
|
+
* is no Node under a compiled binary; this is the version whose semantics
|
|
188
|
+
* SEMANTICS.md verifies the runtime against (divergence 60, the execPath
|
|
189
|
+
* stance: answer for the world that actually exists). */
|
|
190
|
+
#define SCR_NODE_COMPAT_VERSION "24.0.0"
|
|
191
|
+
ScrStr *scr_process_versions_node(void) {
|
|
192
|
+
if (!scr_versions_node_str) {
|
|
193
|
+
scr_versions_node_str =
|
|
194
|
+
scr_str_new(SCR_NODE_COMPAT_VERSION, sizeof(SCR_NODE_COMPAT_VERSION) - 1);
|
|
195
|
+
}
|
|
196
|
+
return scr_str_retain(scr_versions_node_str);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* process.versions.openssl — the compat target's crypto-provider version
|
|
200
|
+
* string (the versions.node stance): Boolean(process.versions.openssl) is
|
|
201
|
+
* Node's own "is crypto available" idiom, and the runtime DOES ship a
|
|
202
|
+
* crypto module (mbedTLS-backed; unsupported members fence at their call
|
|
203
|
+
* sites). Reading undefined here made every hasCrypto test self-skip —
|
|
204
|
+
* measuring nothing (SEMANTICS.md; the divergence entry documents that
|
|
205
|
+
* the string names the compat target's OpenSSL, not a linked library). */
|
|
206
|
+
#define SCR_OPENSSL_COMPAT_VERSION "3.5.5"
|
|
207
|
+
ScrStr *scr_process_versions_openssl(void) {
|
|
208
|
+
if (!scr_versions_openssl_str) {
|
|
209
|
+
scr_versions_openssl_str =
|
|
210
|
+
scr_str_new(SCR_OPENSSL_COMPAT_VERSION, sizeof(SCR_OPENSSL_COMPAT_VERSION) - 1);
|
|
211
|
+
}
|
|
212
|
+
return scr_str_retain(scr_versions_openssl_str);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* process.execPath — the running binary's own resolved absolute path,
|
|
216
|
+
* exactly what Node computes for ITS executable (uv_exepath + realpath):
|
|
217
|
+
* _NSGetExecutablePath on macOS, /proc/self/exe on Linux, argv[0] as the
|
|
218
|
+
* last resort. Interned like process.platform; +1 per read. */
|
|
219
|
+
ScrStr *scr_process_exec_path(void) {
|
|
220
|
+
if (!scr_exec_path_str) {
|
|
221
|
+
char raw[4096];
|
|
222
|
+
raw[0] = '\0';
|
|
223
|
+
#if defined(__APPLE__)
|
|
224
|
+
uint32_t size = sizeof(raw);
|
|
225
|
+
extern int _NSGetExecutablePath(char *buf, uint32_t *bufsize);
|
|
226
|
+
if (_NSGetExecutablePath(raw, &size) != 0) raw[0] = '\0';
|
|
227
|
+
#elif defined(__linux__)
|
|
228
|
+
ssize_t got = readlink("/proc/self/exe", raw, sizeof(raw) - 1);
|
|
229
|
+
if (got > 0) raw[got] = '\0';
|
|
230
|
+
else raw[0] = '\0';
|
|
231
|
+
#elif defined(_WIN32)
|
|
232
|
+
/* uv_exepath's source of truth on Windows; already absolute, and
|
|
233
|
+
* spelled with backslashes like Node's own execPath there. */
|
|
234
|
+
DWORD got = GetModuleFileNameA(NULL, raw, sizeof(raw) - 1);
|
|
235
|
+
raw[got < sizeof(raw) ? got : 0] = '\0';
|
|
236
|
+
#endif
|
|
237
|
+
if (raw[0] == '\0' && scr_lib_argc > 0) {
|
|
238
|
+
snprintf(raw, sizeof(raw), "%s", scr_lib_argv[0]);
|
|
239
|
+
}
|
|
240
|
+
char resolved[PATH_MAX];
|
|
241
|
+
#ifdef _WIN32
|
|
242
|
+
const char *use = _fullpath(resolved, raw, sizeof resolved) != NULL ? resolved : raw;
|
|
243
|
+
#else
|
|
244
|
+
const char *use = realpath(raw, resolved) != NULL ? resolved : raw;
|
|
245
|
+
#endif
|
|
246
|
+
scr_exec_path_str = scr_str_new(use, strlen(use));
|
|
247
|
+
}
|
|
248
|
+
return scr_str_retain(scr_exec_path_str);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
ScrStr *scr_env_get(const ScrStr *name) {
|
|
252
|
+
/* ScrStr data is NUL-terminated (like the fs paths below). A fresh copy
|
|
253
|
+
* per read: getenv's buffer is not ours to alias, and Node's process.env
|
|
254
|
+
* reads snapshot the value too. Absent → NULL (the compiler's undefined
|
|
255
|
+
* arm), never a throw. */
|
|
256
|
+
#ifdef _WIN32
|
|
257
|
+
/* The WIN32 environment, not the CRT's startup snapshot — libuv's choice
|
|
258
|
+
* too, and the one CreateProcess children inherit. Case-insensitive,
|
|
259
|
+
* like Node's process.env on Windows. */
|
|
260
|
+
DWORD need = GetEnvironmentVariableA(name->data, NULL, 0);
|
|
261
|
+
if (need == 0) return NULL; /* absent (an empty value still needs its NUL) */
|
|
262
|
+
char *buf = malloc(need);
|
|
263
|
+
if (!buf) {
|
|
264
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
265
|
+
abort();
|
|
266
|
+
}
|
|
267
|
+
DWORD got = GetEnvironmentVariableA(name->data, buf, need);
|
|
268
|
+
ScrStr *s = scr_str_new(buf, got);
|
|
269
|
+
free(buf);
|
|
270
|
+
return s;
|
|
271
|
+
#else
|
|
272
|
+
const char *v = getenv(name->data);
|
|
273
|
+
return v ? scr_str_new(v, strlen(v)) : NULL;
|
|
274
|
+
#endif
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/* process.env.NAME = v — setenv(3): later scr_env_get reads and spawned
|
|
278
|
+
* children (posix_spawn inherits environ) observe the write, like Node.
|
|
279
|
+
* Both args borrowed (NUL-terminated ScrStr data); setenv copies. */
|
|
280
|
+
void scr_env_set(const ScrStr *name, const ScrStr *value) {
|
|
281
|
+
#ifdef _WIN32
|
|
282
|
+
/* The WIN32 environment (see scr_env_get): an empty value stays a
|
|
283
|
+
* present-but-empty variable, and children inherit the write. */
|
|
284
|
+
SetEnvironmentVariableA(name->data, value->data);
|
|
285
|
+
#else
|
|
286
|
+
setenv(name->data, value->data, 1);
|
|
287
|
+
#endif
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/* `delete process.env.NAME` — unsetenv(3): later reads answer absent and
|
|
291
|
+
* spawned children lose the variable, like Node. Borrowed. */
|
|
292
|
+
void scr_env_unset(const ScrStr *name) {
|
|
293
|
+
#ifdef _WIN32
|
|
294
|
+
SetEnvironmentVariableA(name->data, NULL);
|
|
295
|
+
#else
|
|
296
|
+
unsetenv(name->data);
|
|
297
|
+
#endif
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/* The whole environment as one fresh string[] of alternating
|
|
301
|
+
* [k0, v0, k1, v1, ...] entries in environ order — the raw material of the
|
|
302
|
+
* compiler's process.env snapshot record (insertion order = environ order,
|
|
303
|
+
* which is Node's own Object.keys(process.env) order). Entries without '='
|
|
304
|
+
* (not producible by setenv) are skipped; the value is everything after
|
|
305
|
+
* the FIRST '='. +1 array. */
|
|
306
|
+
ScrArr *scr_env_pairs(void) {
|
|
307
|
+
ScrArr *out = scr_arr_new(SCR_ELEM_STR, 0);
|
|
308
|
+
#ifdef _WIN32
|
|
309
|
+
/* The WIN32 environment block (see scr_env_get): NUL-separated
|
|
310
|
+
* "K=V" entries, double-NUL terminated. Entries whose first byte is
|
|
311
|
+
* '=' are the hidden per-drive cwd variables ("=C:=..."), which libuv
|
|
312
|
+
* (and so Node's process.env) also skips. */
|
|
313
|
+
char *block = GetEnvironmentStringsA();
|
|
314
|
+
if (block != NULL) {
|
|
315
|
+
for (char *e = block; *e != '\0'; e += strlen(e) + 1) {
|
|
316
|
+
const char *eq = strchr(e, '=');
|
|
317
|
+
if (!eq || eq == e) continue;
|
|
318
|
+
scr_arr_push_ref(out, scr_str_new(e, (size_t)(eq - e)));
|
|
319
|
+
scr_arr_push_ref(out, scr_str_new(eq + 1, strlen(eq + 1)));
|
|
320
|
+
}
|
|
321
|
+
FreeEnvironmentStringsA(block);
|
|
322
|
+
}
|
|
323
|
+
#else
|
|
324
|
+
for (char **e = environ; *e != NULL; e++) {
|
|
325
|
+
const char *eq = strchr(*e, '=');
|
|
326
|
+
if (!eq || eq == *e) continue;
|
|
327
|
+
scr_arr_push_ref(out, scr_str_new(*e, (size_t)(eq - *e)));
|
|
328
|
+
scr_arr_push_ref(out, scr_str_new(eq + 1, strlen(eq + 1)));
|
|
329
|
+
}
|
|
330
|
+
#endif
|
|
331
|
+
return out;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/* ── process.pid / process.getuid / process.kill ─────────────────────── */
|
|
335
|
+
|
|
336
|
+
double scr_process_pid(void) { return (double)getpid(); }
|
|
337
|
+
|
|
338
|
+
#ifdef _WIN32
|
|
339
|
+
/* No uids/gids exist on Windows: Node's process object simply has no
|
|
340
|
+
* getuid/getgid members there, so a call is the property-access TypeError
|
|
341
|
+
* below — thrown catchably, exactly what `process.getuid()` does under
|
|
342
|
+
* Windows Node. */
|
|
343
|
+
double scr_process_getuid(void) {
|
|
344
|
+
scr_throw_error_msg(SCR_ERR_TYPE, "process.getuid is not a function", 32);
|
|
345
|
+
return 0;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
double scr_process_getgid(void) {
|
|
349
|
+
scr_throw_error_msg(SCR_ERR_TYPE, "process.getgid is not a function", 32);
|
|
350
|
+
return 0;
|
|
351
|
+
}
|
|
352
|
+
#else
|
|
353
|
+
double scr_process_getuid(void) { return (double)getuid(); }
|
|
354
|
+
|
|
355
|
+
double scr_process_getgid(void) { return (double)getgid(); }
|
|
356
|
+
#endif
|
|
357
|
+
|
|
358
|
+
/* Node's signal-name table (the names uv exposes), resolved to the HOST's
|
|
359
|
+
* numbers via the POSIX constants. Node accepts names with the SIG prefix
|
|
360
|
+
* only. On Windows the C runtime defines only the ANSI six (INT, ILL,
|
|
361
|
+
* ABRT, FPE, SEGV, TERM) plus SIGBREAK — libuv fills in the numbers below
|
|
362
|
+
* for the handful more it emulates, and Node's os.constants.signals shows
|
|
363
|
+
* exactly that union, so the same defines keep the two tables' Windows
|
|
364
|
+
* rows Node-identical; every other row #ifdefs away like SIGIO/SIGINFO
|
|
365
|
+
* always did. */
|
|
366
|
+
#ifdef _WIN32
|
|
367
|
+
#define SIGHUP 1 /* uv's emulated numbers (uv-win.h) */
|
|
368
|
+
#define SIGQUIT 3
|
|
369
|
+
#define SIGKILL 9
|
|
370
|
+
#define SIGWINCH 28
|
|
371
|
+
#endif
|
|
372
|
+
|
|
373
|
+
static int scr_signal_by_name(const char *name) {
|
|
374
|
+
static const struct { const char *name; int sig; } SIGS[] = {
|
|
375
|
+
{"SIGHUP", SIGHUP}, {"SIGINT", SIGINT}, {"SIGQUIT", SIGQUIT},
|
|
376
|
+
{"SIGILL", SIGILL}, {"SIGABRT", SIGABRT},
|
|
377
|
+
{"SIGIOT", SIGABRT}, {"SIGFPE", SIGFPE},
|
|
378
|
+
{"SIGKILL", SIGKILL}, {"SIGSEGV", SIGSEGV},
|
|
379
|
+
{"SIGTERM", SIGTERM}, {"SIGWINCH", SIGWINCH},
|
|
380
|
+
#ifdef SIGTRAP
|
|
381
|
+
{"SIGTRAP", SIGTRAP},
|
|
382
|
+
#endif
|
|
383
|
+
#ifdef SIGBUS
|
|
384
|
+
{"SIGBUS", SIGBUS},
|
|
385
|
+
#endif
|
|
386
|
+
#ifdef SIGUSR1
|
|
387
|
+
{"SIGUSR1", SIGUSR1}, {"SIGUSR2", SIGUSR2},
|
|
388
|
+
#endif
|
|
389
|
+
#ifdef SIGPIPE
|
|
390
|
+
{"SIGPIPE", SIGPIPE},
|
|
391
|
+
#endif
|
|
392
|
+
#ifdef SIGALRM
|
|
393
|
+
{"SIGALRM", SIGALRM},
|
|
394
|
+
#endif
|
|
395
|
+
#ifdef SIGCHLD
|
|
396
|
+
{"SIGCHLD", SIGCHLD}, {"SIGCONT", SIGCONT},
|
|
397
|
+
{"SIGSTOP", SIGSTOP}, {"SIGTSTP", SIGTSTP}, {"SIGTTIN", SIGTTIN},
|
|
398
|
+
{"SIGTTOU", SIGTTOU}, {"SIGURG", SIGURG}, {"SIGXCPU", SIGXCPU},
|
|
399
|
+
{"SIGXFSZ", SIGXFSZ}, {"SIGVTALRM", SIGVTALRM}, {"SIGPROF", SIGPROF},
|
|
400
|
+
{"SIGSYS", SIGSYS},
|
|
401
|
+
#endif
|
|
402
|
+
#ifdef SIGBREAK
|
|
403
|
+
{"SIGBREAK", SIGBREAK},
|
|
404
|
+
#endif
|
|
405
|
+
#ifdef SIGIO
|
|
406
|
+
{"SIGIO", SIGIO},
|
|
407
|
+
#endif
|
|
408
|
+
#ifdef SIGINFO
|
|
409
|
+
{"SIGINFO", SIGINFO},
|
|
410
|
+
#endif
|
|
411
|
+
};
|
|
412
|
+
for (size_t i = 0; i < sizeof SIGS / sizeof SIGS[0]; i++) {
|
|
413
|
+
if (strcmp(SIGS[i].name, name) == 0) return SIGS[i].sig;
|
|
414
|
+
}
|
|
415
|
+
return -1;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/* The table above for other units (scr_child.c's child.kill shares Node's
|
|
419
|
+
* one signal-name story): the resolved number, or -1 for unknown names. */
|
|
420
|
+
int scr_signal_from_name(const ScrStr *signal) {
|
|
421
|
+
return scr_signal_by_name(signal->data);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/* The reverse walk, for spawnSync's result.signal: the FIRST name with
|
|
425
|
+
* the number wins (SIGABRT precedes its SIGIOT alias — Node's spelling),
|
|
426
|
+
* NULL for numbers outside the table. Static storage; never freed. */
|
|
427
|
+
const char *scr_signal_name(int sig) {
|
|
428
|
+
static const struct { const char *name; int signo; } SIGS[] = {
|
|
429
|
+
{"SIGHUP", SIGHUP}, {"SIGINT", SIGINT}, {"SIGQUIT", SIGQUIT},
|
|
430
|
+
{"SIGILL", SIGILL},
|
|
431
|
+
#ifdef SIGTRAP
|
|
432
|
+
{"SIGTRAP", SIGTRAP},
|
|
433
|
+
#endif
|
|
434
|
+
{"SIGABRT", SIGABRT},
|
|
435
|
+
#ifdef SIGBUS
|
|
436
|
+
{"SIGBUS", SIGBUS},
|
|
437
|
+
#endif
|
|
438
|
+
{"SIGFPE", SIGFPE}, {"SIGKILL", SIGKILL},
|
|
439
|
+
#ifdef SIGUSR1
|
|
440
|
+
{"SIGUSR1", SIGUSR1},
|
|
441
|
+
#endif
|
|
442
|
+
{"SIGSEGV", SIGSEGV},
|
|
443
|
+
#ifdef SIGUSR2
|
|
444
|
+
{"SIGUSR2", SIGUSR2},
|
|
445
|
+
#endif
|
|
446
|
+
#ifdef SIGPIPE
|
|
447
|
+
{"SIGPIPE", SIGPIPE},
|
|
448
|
+
#endif
|
|
449
|
+
#ifdef SIGALRM
|
|
450
|
+
{"SIGALRM", SIGALRM},
|
|
451
|
+
#endif
|
|
452
|
+
{"SIGTERM", SIGTERM},
|
|
453
|
+
#ifdef SIGCHLD
|
|
454
|
+
{"SIGCHLD", SIGCHLD}, {"SIGCONT", SIGCONT}, {"SIGSTOP", SIGSTOP},
|
|
455
|
+
{"SIGTSTP", SIGTSTP}, {"SIGTTIN", SIGTTIN}, {"SIGTTOU", SIGTTOU},
|
|
456
|
+
{"SIGURG", SIGURG}, {"SIGXCPU", SIGXCPU}, {"SIGXFSZ", SIGXFSZ},
|
|
457
|
+
{"SIGVTALRM", SIGVTALRM}, {"SIGPROF", SIGPROF},
|
|
458
|
+
#endif
|
|
459
|
+
{"SIGWINCH", SIGWINCH},
|
|
460
|
+
#ifdef SIGSYS
|
|
461
|
+
{"SIGSYS", SIGSYS},
|
|
462
|
+
#endif
|
|
463
|
+
#ifdef SIGBREAK
|
|
464
|
+
{"SIGBREAK", SIGBREAK},
|
|
465
|
+
#endif
|
|
466
|
+
#ifdef SIGIO
|
|
467
|
+
{"SIGIO", SIGIO},
|
|
468
|
+
#endif
|
|
469
|
+
#ifdef SIGINFO
|
|
470
|
+
{"SIGINFO", SIGINFO},
|
|
471
|
+
#endif
|
|
472
|
+
};
|
|
473
|
+
for (size_t i = 0; i < sizeof SIGS / sizeof SIGS[0]; i++) {
|
|
474
|
+
if (SIGS[i].signo == sig) return SIGS[i].name;
|
|
475
|
+
}
|
|
476
|
+
return NULL;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/* Node validates the pid as an int32 BEFORE kill(2) and throws the
|
|
480
|
+
* ERR_INVALID_ARG_TYPE TypeError with this exact (odd) wording. */
|
|
481
|
+
static bool scr_kill_pid_check(double pid) {
|
|
482
|
+
if (pid >= -2147483648.0 && pid <= 2147483647.0 && pid == (double)(long long)pid) {
|
|
483
|
+
return true; /* NaN fails both range comparisons */
|
|
484
|
+
}
|
|
485
|
+
char num[32];
|
|
486
|
+
scr_f64_to_str(pid, num);
|
|
487
|
+
char msg[96];
|
|
488
|
+
int len = snprintf(msg, sizeof msg,
|
|
489
|
+
"The \"pid\" argument must be of type number. "
|
|
490
|
+
"Received type number (%s)",
|
|
491
|
+
num);
|
|
492
|
+
scr_throw_error_msg(SCR_ERR_TYPE, msg, (size_t)len);
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/* The shared kill(2) tail: signal 0 probes; failure throws Node's terse
|
|
497
|
+
* `kill ESRCH` / `kill EPERM` Error. Returns Node's constant true.
|
|
498
|
+
* Windows: uv_kill's behavior — signal 0 opens the process to probe
|
|
499
|
+
* liveness, anything else is TerminateProcess (no signal exists to
|
|
500
|
+
* deliver; the target dies with exit code 1, exactly Node-on-Windows's
|
|
501
|
+
* process.kill). ESRCH/EPERM map from the open failure. */
|
|
502
|
+
#ifdef _WIN32
|
|
503
|
+
static int scr_win_kill(int pid, int sig) {
|
|
504
|
+
HANDLE h = OpenProcess(
|
|
505
|
+
sig == 0 ? PROCESS_QUERY_LIMITED_INFORMATION : PROCESS_TERMINATE,
|
|
506
|
+
FALSE, (DWORD)pid);
|
|
507
|
+
if (h == NULL) {
|
|
508
|
+
errno = GetLastError() == ERROR_ACCESS_DENIED ? EPERM : ESRCH;
|
|
509
|
+
return -1;
|
|
510
|
+
}
|
|
511
|
+
BOOL ok = sig == 0 ? TRUE : TerminateProcess(h, 1);
|
|
512
|
+
CloseHandle(h);
|
|
513
|
+
if (!ok) {
|
|
514
|
+
errno = EPERM;
|
|
515
|
+
return -1;
|
|
516
|
+
}
|
|
517
|
+
return 0;
|
|
518
|
+
}
|
|
519
|
+
#define scr_sys_kill(pid, sig) scr_win_kill((int)(pid), (sig))
|
|
520
|
+
#else
|
|
521
|
+
#define scr_sys_kill(pid, sig) kill((pid_t)(pid), (sig))
|
|
522
|
+
#endif
|
|
523
|
+
|
|
524
|
+
static bool scr_kill_send(int pid, int sig) {
|
|
525
|
+
if (scr_sys_kill(pid, sig) == 0) return true;
|
|
526
|
+
const char *name = errno == ESRCH ? "ESRCH"
|
|
527
|
+
: errno == EPERM ? "EPERM"
|
|
528
|
+
: errno == EINVAL ? "EINVAL"
|
|
529
|
+
: NULL;
|
|
530
|
+
char msg[32];
|
|
531
|
+
int len;
|
|
532
|
+
if (name) {
|
|
533
|
+
len = snprintf(msg, sizeof msg, "kill %s", name);
|
|
534
|
+
/* Node's errnoException carries code = the errno name. */
|
|
535
|
+
scr_throw_error_msg_code(SCR_ERR_ERROR, msg, (size_t)len, name);
|
|
536
|
+
} else {
|
|
537
|
+
len = snprintf(msg, sizeof msg, "kill E%d", errno);
|
|
538
|
+
scr_throw_error_msg(SCR_ERR_ERROR, msg, (size_t)len);
|
|
539
|
+
}
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
bool scr_process_kill(double pid, double signum) {
|
|
544
|
+
if (!scr_kill_pid_check(pid)) return false;
|
|
545
|
+
return scr_kill_send((int)pid, (int)signum);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
bool scr_process_kill_named(double pid, const ScrStr *signal) {
|
|
549
|
+
if (!scr_kill_pid_check(pid)) return false;
|
|
550
|
+
int sig = scr_signal_by_name(signal->data);
|
|
551
|
+
if (sig < 0) {
|
|
552
|
+
/* Node's ERR_UNKNOWN_SIGNAL TypeError. */
|
|
553
|
+
size_t cap = 16 + signal->len + 1;
|
|
554
|
+
char *msg = malloc(cap);
|
|
555
|
+
if (!msg) {
|
|
556
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
557
|
+
abort();
|
|
558
|
+
}
|
|
559
|
+
int len = snprintf(msg, cap, "Unknown signal: %s", signal->data);
|
|
560
|
+
scr_throw_error_msg(SCR_ERR_TYPE, msg, (size_t)len);
|
|
561
|
+
free(msg);
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
return scr_kill_send((int)pid, sig);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
ScrStr *scr_process_cwd(void) {
|
|
568
|
+
char buf[4096];
|
|
569
|
+
if (!getcwd(buf, sizeof buf)) {
|
|
570
|
+
fputs("scriptc: process.cwd() failed\n", stderr);
|
|
571
|
+
abort();
|
|
572
|
+
}
|
|
573
|
+
return scr_str_new(buf, strlen(buf));
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/* The raw byte writes. stdout goes through the SAME stdio stream and
|
|
577
|
+
* buffer console.log uses (scr_init's setvbuf), so interleaved writes
|
|
578
|
+
* keep source order; stderr is unbuffered, like Node's, and FLUSHES
|
|
579
|
+
* stdout first (the console.error convention) so merged 2>&1 output
|
|
580
|
+
* keeps source order despite stdout's full buffering. The boolean is
|
|
581
|
+
* Node's backpressure signal — a synchronous fwrite has no backpressure,
|
|
582
|
+
* so it is constantly true. */
|
|
583
|
+
bool scr_process_stdout_write(const ScrStr *data) {
|
|
584
|
+
fwrite(data->data, 1, data->len, stdout);
|
|
585
|
+
return true;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
bool scr_process_stderr_write(const ScrStr *data) {
|
|
589
|
+
fflush(stdout);
|
|
590
|
+
fwrite(data->data, 1, data->len, stderr);
|
|
591
|
+
return true;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/* The FIRST-CLASS stream write (`output.write(line)` where output is a
|
|
595
|
+
* WritableStream-typed value — the prefixStream idiom): the value is the
|
|
596
|
+
* stream's fd (1 or 2, minted by the process.stdout/stderr reads), and
|
|
597
|
+
* the write dispatches onto the exact stdout/stderr paths above so
|
|
598
|
+
* buffering and ordering stay identical. */
|
|
599
|
+
bool scr_proc_stream_write(double fd, const ScrStr *data) {
|
|
600
|
+
return (int)fd == 2 ? scr_process_stderr_write(data) : scr_process_stdout_write(data);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/* ── os ──────────────────────────────────────────────────────────────
|
|
604
|
+
* os.platform() lowers to scr_process_platform (one implementation).
|
|
605
|
+
* homedir/tmpdir follow libuv's POSIX rules, which Node delegates to.
|
|
606
|
+
*/
|
|
607
|
+
|
|
608
|
+
#ifdef _WIN32
|
|
609
|
+
/* uv_os_homedir on Windows: %USERPROFILE% first, then the profile
|
|
610
|
+
* directory API. The env var covers every real session; abort matches
|
|
611
|
+
* the POSIX arm's stance on the unreachable failure. */
|
|
612
|
+
ScrStr *scr_os_homedir(void) {
|
|
613
|
+
const char *home = getenv("USERPROFILE");
|
|
614
|
+
if (home && home[0] != '\0') return scr_str_new(home, strlen(home));
|
|
615
|
+
fputs("scriptc: os.homedir() failed\n", stderr);
|
|
616
|
+
abort();
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
ScrStr *scr_os_user_name(void) {
|
|
620
|
+
char buf[UNLEN + 1];
|
|
621
|
+
DWORD n = sizeof buf;
|
|
622
|
+
if (!GetUserNameA(buf, &n) || n == 0) {
|
|
623
|
+
fputs("scriptc: os.userInfo() failed\n", stderr);
|
|
624
|
+
abort();
|
|
625
|
+
}
|
|
626
|
+
return scr_str_new(buf, n - 1); /* n counts the NUL */
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
ScrStr *scr_os_user_shell(void) {
|
|
630
|
+
/* Node's userInfo().shell is null on Windows; the scriptc surface types
|
|
631
|
+
* it string, so the closest honest spelling is "" (divergence noted in
|
|
632
|
+
* the windows report). */
|
|
633
|
+
return scr_str_new("", 0);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
ScrStr *scr_os_user_homedir(void) {
|
|
637
|
+
return scr_os_homedir(); /* uv_os_get_passwd reuses uv_os_homedir on win */
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
ScrStr *scr_os_release(void) {
|
|
641
|
+
/* uv_os_uname on Windows: RtlGetVersion (the un-lied-to GetVersionEx),
|
|
642
|
+
* rendered "major.minor.build" — Node answers e.g. "10.0.26100". */
|
|
643
|
+
typedef LONG(WINAPI * RtlGetVersionFn)(PRTL_OSVERSIONINFOW);
|
|
644
|
+
RTL_OSVERSIONINFOW info;
|
|
645
|
+
memset(&info, 0, sizeof info);
|
|
646
|
+
info.dwOSVersionInfoSize = sizeof info;
|
|
647
|
+
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
|
|
648
|
+
RtlGetVersionFn fn =
|
|
649
|
+
ntdll ? (RtlGetVersionFn)(void *)GetProcAddress(ntdll, "RtlGetVersion") : NULL;
|
|
650
|
+
if (fn == NULL || fn(&info) != 0) {
|
|
651
|
+
fputs("scriptc: os.release() failed\n", stderr);
|
|
652
|
+
abort();
|
|
653
|
+
}
|
|
654
|
+
char buf[64];
|
|
655
|
+
int len = snprintf(buf, sizeof buf, "%lu.%lu.%lu", (unsigned long)info.dwMajorVersion,
|
|
656
|
+
(unsigned long)info.dwMinorVersion, (unsigned long)info.dwBuildNumber);
|
|
657
|
+
return scr_str_new(buf, (size_t)len);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
ScrStr *scr_os_type(void) {
|
|
661
|
+
/* uv_os_uname's sysname on Windows is the constant "Windows_NT". */
|
|
662
|
+
return scr_str_new("Windows_NT", 10);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
double scr_os_totalmem(void) {
|
|
666
|
+
MEMORYSTATUSEX ms;
|
|
667
|
+
memset(&ms, 0, sizeof ms);
|
|
668
|
+
ms.dwLength = sizeof ms;
|
|
669
|
+
if (!GlobalMemoryStatusEx(&ms)) return 0;
|
|
670
|
+
return (double)ms.ullTotalPhys;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
ScrStr *scr_os_tmpdir(void) {
|
|
674
|
+
/* GetTempPathA is libuv's source (TMP → TEMP → USERPROFILE → windir),
|
|
675
|
+
* with Node's one-trailing-separator trim. */
|
|
676
|
+
char buf[MAX_PATH + 2];
|
|
677
|
+
DWORD n = GetTempPathA(sizeof buf, buf);
|
|
678
|
+
if (n == 0 || n >= sizeof buf) {
|
|
679
|
+
fputs("scriptc: os.tmpdir() failed\n", stderr);
|
|
680
|
+
abort();
|
|
681
|
+
}
|
|
682
|
+
size_t len = n;
|
|
683
|
+
if (len > 1 && (buf[len - 1] == '\\' || buf[len - 1] == '/')) len--;
|
|
684
|
+
return scr_str_new(buf, len);
|
|
685
|
+
}
|
|
686
|
+
#else
|
|
687
|
+
ScrStr *scr_os_homedir(void) {
|
|
688
|
+
/* uv_os_homedir: $HOME when set (even empty is "set" only if non-NULL;
|
|
689
|
+
* libuv requires non-empty), else the passwd entry. */
|
|
690
|
+
const char *home = getenv("HOME");
|
|
691
|
+
if (home && home[0] != '\0') return scr_str_new(home, strlen(home));
|
|
692
|
+
struct passwd pw;
|
|
693
|
+
struct passwd *result = NULL;
|
|
694
|
+
char buf[8192];
|
|
695
|
+
if (getpwuid_r(getuid(), &pw, buf, sizeof buf, &result) != 0 || !result || !result->pw_dir) {
|
|
696
|
+
fputs("scriptc: os.homedir() failed\n", stderr);
|
|
697
|
+
abort();
|
|
698
|
+
}
|
|
699
|
+
return scr_str_new(result->pw_dir, strlen(result->pw_dir));
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/* The os.userInfo() field trio — uv_os_get_passwd's slices. One passwd
|
|
703
|
+
* lookup per call (three calls per userInfo record — cheap, no caching
|
|
704
|
+
* to invalidate). Failure aborts: Node throws a system error there, but
|
|
705
|
+
* no compiled program path reaches it for the running uid. */
|
|
706
|
+
static const struct passwd *scr_os_passwd(char *buf, size_t cap, struct passwd *pw) {
|
|
707
|
+
struct passwd *result = NULL;
|
|
708
|
+
if (getpwuid_r(getuid(), pw, buf, cap, &result) != 0 || !result) {
|
|
709
|
+
fputs("scriptc: os.userInfo() failed\n", stderr);
|
|
710
|
+
abort();
|
|
711
|
+
}
|
|
712
|
+
return result;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
ScrStr *scr_os_user_name(void) {
|
|
716
|
+
struct passwd pw;
|
|
717
|
+
char buf[8192];
|
|
718
|
+
const struct passwd *r = scr_os_passwd(buf, sizeof buf, &pw);
|
|
719
|
+
return scr_str_new(r->pw_name, strlen(r->pw_name));
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
ScrStr *scr_os_user_shell(void) {
|
|
723
|
+
struct passwd pw;
|
|
724
|
+
char buf[8192];
|
|
725
|
+
const struct passwd *r = scr_os_passwd(buf, sizeof buf, &pw);
|
|
726
|
+
const char *sh = r->pw_shell ? r->pw_shell : "";
|
|
727
|
+
return scr_str_new(sh, strlen(sh));
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
ScrStr *scr_os_user_homedir(void) {
|
|
731
|
+
/* The PASSWD home (pw_dir) — Node's userInfo().homedir, distinct from
|
|
732
|
+
* os.homedir()'s $HOME-first cascade. */
|
|
733
|
+
struct passwd pw;
|
|
734
|
+
char buf[8192];
|
|
735
|
+
const struct passwd *r = scr_os_passwd(buf, sizeof buf, &pw);
|
|
736
|
+
return scr_str_new(r->pw_dir, strlen(r->pw_dir));
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
ScrStr *scr_os_release(void) {
|
|
740
|
+
/* uname(2)'s release field — Node's uv_os_uname()-backed answer. */
|
|
741
|
+
struct utsname u;
|
|
742
|
+
if (uname(&u) != 0) {
|
|
743
|
+
fputs("scriptc: os.release() failed\n", stderr);
|
|
744
|
+
abort();
|
|
745
|
+
}
|
|
746
|
+
return scr_str_new(u.release, strlen(u.release));
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
ScrStr *scr_os_type(void) {
|
|
750
|
+
/* uname(2)'s sysname field ("Darwin", "Linux") — Node's os.type(). */
|
|
751
|
+
struct utsname u;
|
|
752
|
+
if (uname(&u) != 0) {
|
|
753
|
+
fputs("scriptc: os.type() failed\n", stderr);
|
|
754
|
+
abort();
|
|
755
|
+
}
|
|
756
|
+
return scr_str_new(u.sysname, strlen(u.sysname));
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
double scr_os_totalmem(void) {
|
|
760
|
+
/* Total physical memory in bytes (sysconf pages × page size — Darwin
|
|
761
|
+
* and Linux both answer _SC_PHYS_PAGES). */
|
|
762
|
+
long pages = sysconf(_SC_PHYS_PAGES);
|
|
763
|
+
long psize = sysconf(_SC_PAGE_SIZE);
|
|
764
|
+
if (pages <= 0 || psize <= 0) return 0;
|
|
765
|
+
return (double)pages * (double)psize;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
ScrStr *scr_os_tmpdir(void) {
|
|
769
|
+
/* Node's env cascade, with ONE trailing slash trimmed (never down to
|
|
770
|
+
* nothing: "/" stays "/"). */
|
|
771
|
+
const char *dir = getenv("TMPDIR");
|
|
772
|
+
if (!dir || dir[0] == '\0') dir = getenv("TMP");
|
|
773
|
+
if (!dir || dir[0] == '\0') dir = getenv("TEMP");
|
|
774
|
+
if (!dir || dir[0] == '\0') dir = "/tmp";
|
|
775
|
+
size_t len = strlen(dir);
|
|
776
|
+
if (len > 1 && dir[len - 1] == '/') len--;
|
|
777
|
+
return scr_str_new(dir, len);
|
|
778
|
+
}
|
|
779
|
+
#endif /* _WIN32 */
|
|
780
|
+
|
|
781
|
+
/* ── os.networkInterfaces(): the getifaddrs(3) snapshot ────────────────
|
|
782
|
+
* Row selection and field semantics follow libuv (src/unix/bsd-ifaddrs.c),
|
|
783
|
+
* which Node delegates to: an entry contributes a row iff its interface is
|
|
784
|
+
* IFF_UP && IFF_RUNNING, its address is present, and its family is
|
|
785
|
+
* AF_INET/AF_INET6; `internal` is IFF_LOOPBACK; MACs come from the
|
|
786
|
+
* interface's link-level sibling entry (AF_LINK/AF_PACKET, matched by
|
|
787
|
+
* name), all-zeros when there is none; cidr is Node's lib/os.js
|
|
788
|
+
* computation — address/<contiguous netmask prefix>, null when the netmask
|
|
789
|
+
* is non-contiguous (never in practice). Row order is getifaddrs
|
|
790
|
+
* enumeration order, which is also Node's — but Node guarantees no order,
|
|
791
|
+
* so consumers should compare structurally. The emitter walks the snapshot
|
|
792
|
+
* through the accessors below and builds the typed record inline; a
|
|
793
|
+
* getifaddrs failure (effectively unreachable) yields an empty snapshot
|
|
794
|
+
* where Node would throw ERR_SYSTEM_ERROR. */
|
|
795
|
+
|
|
796
|
+
typedef struct ScrIfaddrRow {
|
|
797
|
+
char name[64];
|
|
798
|
+
char address[INET6_ADDRSTRLEN];
|
|
799
|
+
char netmask[INET6_ADDRSTRLEN];
|
|
800
|
+
char cidr[INET6_ADDRSTRLEN + 5]; /* address + "/128" */
|
|
801
|
+
bool has_cidr;
|
|
802
|
+
char mac[18]; /* "aa:bb:cc:dd:ee:ff" */
|
|
803
|
+
bool internal;
|
|
804
|
+
bool ipv6;
|
|
805
|
+
double scopeid;
|
|
806
|
+
} ScrIfaddrRow;
|
|
807
|
+
|
|
808
|
+
struct ScrIfaddrs {
|
|
809
|
+
size_t n;
|
|
810
|
+
ScrIfaddrRow *rows;
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
#ifdef _WIN32
|
|
814
|
+
/* The Windows arm mirrors libuv's uv_interface_addresses (src/win/util.c),
|
|
815
|
+
* which Node delegates to: GetAdaptersAddresses(AF_UNSPEC, INCLUDE_PREFIX
|
|
816
|
+
* + the SKIP_* flags libuv passes), an adapter contributes rows iff
|
|
817
|
+
* OperStatus == IfOperStatusUp and it has a unicast address, the row name
|
|
818
|
+
* is the adapter's FriendlyName (UTF-8), `internal` is the software-
|
|
819
|
+
* loopback interface type, MAC comes from PhysicalAddress (all-zeros when
|
|
820
|
+
* absent — the loopback), the netmask is built from each unicast
|
|
821
|
+
* address's OnLinkPrefixLength, and scopeid is the v6 sockaddr's. cidr is
|
|
822
|
+
* the shared prefix computation below (always contiguous here by
|
|
823
|
+
* construction). A snapshot failure yields the empty dict, the historical
|
|
824
|
+
* stance. */
|
|
825
|
+
ScrIfaddrs *scr_os_ifaddrs(void) {
|
|
826
|
+
ScrIfaddrs *s = calloc(1, sizeof *s);
|
|
827
|
+
if (!s) abort();
|
|
828
|
+
ULONG flags = GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST |
|
|
829
|
+
GAA_FLAG_SKIP_DNS_SERVER;
|
|
830
|
+
ULONG size = 16 * 1024;
|
|
831
|
+
IP_ADAPTER_ADDRESSES *adapters = NULL;
|
|
832
|
+
for (int tries = 0; tries < 4; tries++) {
|
|
833
|
+
adapters = realloc(adapters, size);
|
|
834
|
+
if (!adapters) abort();
|
|
835
|
+
ULONG rc = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, adapters, &size);
|
|
836
|
+
if (rc == ERROR_SUCCESS) break;
|
|
837
|
+
if (rc != ERROR_BUFFER_OVERFLOW) {
|
|
838
|
+
free(adapters);
|
|
839
|
+
s->rows = calloc(1, sizeof *s->rows);
|
|
840
|
+
if (!s->rows) abort();
|
|
841
|
+
return s; /* empty snapshot */
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
size_t count = 0;
|
|
845
|
+
for (IP_ADAPTER_ADDRESSES *a = adapters; a != NULL; a = a->Next) {
|
|
846
|
+
if (a->OperStatus != IfOperStatusUp || a->FirstUnicastAddress == NULL) continue;
|
|
847
|
+
for (IP_ADAPTER_UNICAST_ADDRESS *u = a->FirstUnicastAddress; u != NULL; u = u->Next) {
|
|
848
|
+
int fam = u->Address.lpSockaddr->sa_family;
|
|
849
|
+
if (fam == AF_INET || fam == AF_INET6) count++;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
s->rows = calloc(count ? count : 1, sizeof *s->rows);
|
|
853
|
+
if (!s->rows) abort();
|
|
854
|
+
for (IP_ADAPTER_ADDRESSES *a = adapters; a != NULL; a = a->Next) {
|
|
855
|
+
if (a->OperStatus != IfOperStatusUp || a->FirstUnicastAddress == NULL) continue;
|
|
856
|
+
char name[64] = "";
|
|
857
|
+
WideCharToMultiByte(CP_UTF8, 0, a->FriendlyName, -1, name, sizeof name - 1, NULL, NULL);
|
|
858
|
+
char mac[18];
|
|
859
|
+
if (a->PhysicalAddressLength == 6) {
|
|
860
|
+
snprintf(mac, sizeof mac, "%02x:%02x:%02x:%02x:%02x:%02x", a->PhysicalAddress[0],
|
|
861
|
+
a->PhysicalAddress[1], a->PhysicalAddress[2], a->PhysicalAddress[3],
|
|
862
|
+
a->PhysicalAddress[4], a->PhysicalAddress[5]);
|
|
863
|
+
} else {
|
|
864
|
+
snprintf(mac, sizeof mac, "00:00:00:00:00:00");
|
|
865
|
+
}
|
|
866
|
+
bool internal = a->IfType == IF_TYPE_SOFTWARE_LOOPBACK;
|
|
867
|
+
for (IP_ADAPTER_UNICAST_ADDRESS *u = a->FirstUnicastAddress; u != NULL; u = u->Next) {
|
|
868
|
+
int fam = u->Address.lpSockaddr->sa_family;
|
|
869
|
+
if (fam != AF_INET && fam != AF_INET6) continue;
|
|
870
|
+
if (s->n == count) break; /* the topology raced the two passes */
|
|
871
|
+
ScrIfaddrRow *row = &s->rows[s->n++];
|
|
872
|
+
snprintf(row->name, sizeof row->name, "%s", name);
|
|
873
|
+
memcpy(row->mac, mac, sizeof mac);
|
|
874
|
+
row->internal = internal;
|
|
875
|
+
unsigned prefix = u->OnLinkPrefixLength;
|
|
876
|
+
if (fam == AF_INET6) {
|
|
877
|
+
const struct sockaddr_in6 *sa = (const struct sockaddr_in6 *)u->Address.lpSockaddr;
|
|
878
|
+
row->ipv6 = true;
|
|
879
|
+
row->scopeid = (double)sa->sin6_scope_id;
|
|
880
|
+
inet_ntop(AF_INET6, (void *)&sa->sin6_addr, row->address, sizeof row->address);
|
|
881
|
+
unsigned char mask[16];
|
|
882
|
+
if (prefix > 128) prefix = 128;
|
|
883
|
+
for (size_t i = 0; i < 16; i++) {
|
|
884
|
+
unsigned bits = prefix > 8 * i ? prefix - 8 * i : 0;
|
|
885
|
+
mask[i] = bits >= 8 ? 0xff : (unsigned char)(0xff00 >> bits);
|
|
886
|
+
}
|
|
887
|
+
inet_ntop(AF_INET6, mask, row->netmask, sizeof row->netmask);
|
|
888
|
+
row->has_cidr = true;
|
|
889
|
+
snprintf(row->cidr, sizeof row->cidr, "%s/%u", row->address, prefix);
|
|
890
|
+
} else {
|
|
891
|
+
const struct sockaddr_in *sa = (const struct sockaddr_in *)u->Address.lpSockaddr;
|
|
892
|
+
inet_ntop(AF_INET, (void *)&sa->sin_addr, row->address, sizeof row->address);
|
|
893
|
+
if (prefix > 32) prefix = 32;
|
|
894
|
+
uint32_t mask = prefix == 0 ? 0 : 0xffffffffu << (32 - prefix);
|
|
895
|
+
struct in_addr m;
|
|
896
|
+
m.s_addr = htonl(mask);
|
|
897
|
+
inet_ntop(AF_INET, (void *)&m, row->netmask, sizeof row->netmask);
|
|
898
|
+
row->has_cidr = true;
|
|
899
|
+
snprintf(row->cidr, sizeof row->cidr, "%s/%u", row->address, prefix);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
free(adapters);
|
|
904
|
+
return s;
|
|
905
|
+
}
|
|
906
|
+
#else
|
|
907
|
+
/* libuv's uv__ifaddr_exclude for the address pass, plus the explicit
|
|
908
|
+
* INET/INET6 family filter (the only families Node's binding reports). */
|
|
909
|
+
static bool scr_ifaddr_row_ok(const struct ifaddrs *ent) {
|
|
910
|
+
if (!((ent->ifa_flags & IFF_UP) && (ent->ifa_flags & IFF_RUNNING))) return false;
|
|
911
|
+
if (ent->ifa_addr == NULL) return false;
|
|
912
|
+
int fam = ent->ifa_addr->sa_family;
|
|
913
|
+
return fam == AF_INET || fam == AF_INET6;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/* Node's getCIDR (lib/os.js): the netmask's contiguous 1-bit prefix.
|
|
917
|
+
* Returns -1 for a non-contiguous mask (cidr is then null). */
|
|
918
|
+
static int scr_netmask_prefix(const unsigned char *bytes, size_t len) {
|
|
919
|
+
int ones = 0;
|
|
920
|
+
bool zero_seen = false;
|
|
921
|
+
for (size_t i = 0; i < len; i++) {
|
|
922
|
+
unsigned char b = bytes[i];
|
|
923
|
+
for (int bit = 7; bit >= 0; bit--) {
|
|
924
|
+
if (b & (1u << bit)) {
|
|
925
|
+
if (zero_seen) return -1; /* a 1 after a 0: split mask */
|
|
926
|
+
ones++;
|
|
927
|
+
} else {
|
|
928
|
+
zero_seen = true;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
return ones;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
ScrIfaddrs *scr_os_ifaddrs(void) {
|
|
936
|
+
ScrIfaddrs *s = calloc(1, sizeof *s);
|
|
937
|
+
if (!s) abort();
|
|
938
|
+
struct ifaddrs *addrs = NULL;
|
|
939
|
+
if (getifaddrs(&addrs) != 0) return s;
|
|
940
|
+
size_t count = 0;
|
|
941
|
+
for (struct ifaddrs *ent = addrs; ent != NULL; ent = ent->ifa_next) {
|
|
942
|
+
if (scr_ifaddr_row_ok(ent)) count++;
|
|
943
|
+
}
|
|
944
|
+
s->rows = calloc(count ? count : 1, sizeof *s->rows);
|
|
945
|
+
if (!s->rows) abort();
|
|
946
|
+
for (struct ifaddrs *ent = addrs; ent != NULL; ent = ent->ifa_next) {
|
|
947
|
+
if (!scr_ifaddr_row_ok(ent)) continue;
|
|
948
|
+
ScrIfaddrRow *row = &s->rows[s->n++];
|
|
949
|
+
snprintf(row->name, sizeof row->name, "%s", ent->ifa_name);
|
|
950
|
+
snprintf(row->mac, sizeof row->mac, "00:00:00:00:00:00");
|
|
951
|
+
row->internal = (ent->ifa_flags & IFF_LOOPBACK) != 0;
|
|
952
|
+
unsigned char maskbytes[16];
|
|
953
|
+
size_t masklen = 0;
|
|
954
|
+
if (ent->ifa_addr->sa_family == AF_INET6) {
|
|
955
|
+
const struct sockaddr_in6 *sa = (const struct sockaddr_in6 *)ent->ifa_addr;
|
|
956
|
+
row->ipv6 = true;
|
|
957
|
+
row->scopeid = (double)sa->sin6_scope_id;
|
|
958
|
+
inet_ntop(AF_INET6, &sa->sin6_addr, row->address, sizeof row->address);
|
|
959
|
+
/* A NULL netmask stays zeroed, exactly libuv's memset — "::"/0. */
|
|
960
|
+
struct in6_addr mask;
|
|
961
|
+
memset(&mask, 0, sizeof mask);
|
|
962
|
+
if (ent->ifa_netmask != NULL) {
|
|
963
|
+
mask = ((const struct sockaddr_in6 *)ent->ifa_netmask)->sin6_addr;
|
|
964
|
+
}
|
|
965
|
+
inet_ntop(AF_INET6, &mask, row->netmask, sizeof row->netmask);
|
|
966
|
+
memcpy(maskbytes, &mask, 16);
|
|
967
|
+
masklen = 16;
|
|
968
|
+
} else {
|
|
969
|
+
const struct sockaddr_in *sa = (const struct sockaddr_in *)ent->ifa_addr;
|
|
970
|
+
inet_ntop(AF_INET, &sa->sin_addr, row->address, sizeof row->address);
|
|
971
|
+
struct in_addr mask;
|
|
972
|
+
memset(&mask, 0, sizeof mask);
|
|
973
|
+
if (ent->ifa_netmask != NULL) {
|
|
974
|
+
mask = ((const struct sockaddr_in *)ent->ifa_netmask)->sin_addr;
|
|
975
|
+
}
|
|
976
|
+
inet_ntop(AF_INET, &mask, row->netmask, sizeof row->netmask);
|
|
977
|
+
memcpy(maskbytes, &mask, 4);
|
|
978
|
+
masklen = 4;
|
|
979
|
+
}
|
|
980
|
+
int prefix = scr_netmask_prefix(maskbytes, masklen);
|
|
981
|
+
if (prefix >= 0) {
|
|
982
|
+
row->has_cidr = true;
|
|
983
|
+
snprintf(row->cidr, sizeof row->cidr, "%s/%d", row->address, prefix);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
/* MAC pass: the link-level sibling entry, matched by interface name —
|
|
987
|
+
* every row of that interface gets its physical address. */
|
|
988
|
+
for (struct ifaddrs *ent = addrs; ent != NULL; ent = ent->ifa_next) {
|
|
989
|
+
if (!((ent->ifa_flags & IFF_UP) && (ent->ifa_flags & IFF_RUNNING))) continue;
|
|
990
|
+
if (ent->ifa_addr == NULL) continue;
|
|
991
|
+
const unsigned char *phys = NULL;
|
|
992
|
+
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
|
993
|
+
if (ent->ifa_addr->sa_family == AF_LINK) {
|
|
994
|
+
const struct sockaddr_dl *sdl = (const struct sockaddr_dl *)ent->ifa_addr;
|
|
995
|
+
if (sdl->sdl_alen == 6) phys = (const unsigned char *)LLADDR(sdl);
|
|
996
|
+
}
|
|
997
|
+
#elif defined(__linux__)
|
|
998
|
+
if (ent->ifa_addr->sa_family == AF_PACKET) {
|
|
999
|
+
const struct sockaddr_ll *sll = (const struct sockaddr_ll *)ent->ifa_addr;
|
|
1000
|
+
if (sll->sll_halen == 6) phys = sll->sll_addr;
|
|
1001
|
+
}
|
|
1002
|
+
#endif
|
|
1003
|
+
if (!phys) continue;
|
|
1004
|
+
for (size_t i = 0; i < s->n; i++) {
|
|
1005
|
+
if (strcmp(s->rows[i].name, ent->ifa_name) != 0) continue;
|
|
1006
|
+
snprintf(s->rows[i].mac, sizeof s->rows[i].mac, "%02x:%02x:%02x:%02x:%02x:%02x",
|
|
1007
|
+
phys[0], phys[1], phys[2], phys[3], phys[4], phys[5]);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
freeifaddrs(addrs);
|
|
1011
|
+
return s;
|
|
1012
|
+
}
|
|
1013
|
+
#endif /* _WIN32 */
|
|
1014
|
+
|
|
1015
|
+
size_t scr_os_ifaddrs_count(const ScrIfaddrs *s) { return s->n; }
|
|
1016
|
+
ScrStr *scr_os_ifaddrs_name(const ScrIfaddrs *s, size_t i) {
|
|
1017
|
+
return scr_str_new(s->rows[i].name, strlen(s->rows[i].name));
|
|
1018
|
+
}
|
|
1019
|
+
ScrStr *scr_os_ifaddrs_address(const ScrIfaddrs *s, size_t i) {
|
|
1020
|
+
return scr_str_new(s->rows[i].address, strlen(s->rows[i].address));
|
|
1021
|
+
}
|
|
1022
|
+
ScrStr *scr_os_ifaddrs_netmask(const ScrIfaddrs *s, size_t i) {
|
|
1023
|
+
return scr_str_new(s->rows[i].netmask, strlen(s->rows[i].netmask));
|
|
1024
|
+
}
|
|
1025
|
+
ScrStr *scr_os_ifaddrs_family(const ScrIfaddrs *s, size_t i) {
|
|
1026
|
+
return s->rows[i].ipv6 ? scr_str_new("IPv6", 4) : scr_str_new("IPv4", 4);
|
|
1027
|
+
}
|
|
1028
|
+
ScrStr *scr_os_ifaddrs_mac(const ScrIfaddrs *s, size_t i) {
|
|
1029
|
+
return scr_str_new(s->rows[i].mac, strlen(s->rows[i].mac));
|
|
1030
|
+
}
|
|
1031
|
+
bool scr_os_ifaddrs_internal(const ScrIfaddrs *s, size_t i) { return s->rows[i].internal; }
|
|
1032
|
+
bool scr_os_ifaddrs_ipv6(const ScrIfaddrs *s, size_t i) { return s->rows[i].ipv6; }
|
|
1033
|
+
/* +1 cidr string, or NULL for the null arm (split netmask). */
|
|
1034
|
+
ScrStr *scr_os_ifaddrs_cidr(const ScrIfaddrs *s, size_t i) {
|
|
1035
|
+
if (!s->rows[i].has_cidr) return NULL;
|
|
1036
|
+
return scr_str_new(s->rows[i].cidr, strlen(s->rows[i].cidr));
|
|
1037
|
+
}
|
|
1038
|
+
double scr_os_ifaddrs_scopeid(const ScrIfaddrs *s, size_t i) { return s->rows[i].scopeid; }
|
|
1039
|
+
void scr_os_ifaddrs_free(ScrIfaddrs *s) {
|
|
1040
|
+
free(s->rows);
|
|
1041
|
+
free(s);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/* The events-unit hooks (scr_events.c fills them at install; NULL in
|
|
1045
|
+
* event-free binaries and in the standalone runtime C tests). */
|
|
1046
|
+
void (*scr_process_exit_hook)(double code) = NULL;
|
|
1047
|
+
void (*scr_stdin_destroy_hook)(void) = NULL;
|
|
1048
|
+
|
|
1049
|
+
void scr_process_exit(double code) {
|
|
1050
|
+
/* Node runs 'exit' listeners on explicit process.exit() too — they run
|
|
1051
|
+
* HERE, synchronously, before the teardown-free exit below. The hook is
|
|
1052
|
+
* non-NULL only when the events unit is linked (scr_events.c). */
|
|
1053
|
+
scr_process_in_exit = true;
|
|
1054
|
+
if (scr_process_exit_hook != NULL) scr_process_exit_hook(code);
|
|
1055
|
+
/* _Exit skips atexit handlers on purpose: no further code runs (matching
|
|
1056
|
+
* Node), and the RC audit is meaningless mid-program (live values are
|
|
1057
|
+
* expected). scr_init's flush-at-exit is also skipped — flush here. */
|
|
1058
|
+
fflush(stdout);
|
|
1059
|
+
_Exit((int)code);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/* ── the process introspection statics ────────────────────────────────
|
|
1063
|
+
* process.uptime/cpuUsage/threadCpuUsage/resourceUsage/availableMemory/
|
|
1064
|
+
* constrainedMemory — plain reads of the process's own clocks and
|
|
1065
|
+
* counters in Node's units. uptime anchors at load time (a constructor-
|
|
1066
|
+
* attribute monotonic stamp — the binary's own start, which is what
|
|
1067
|
+
* "the current Node.js process" means for a compiled program). */
|
|
1068
|
+
#ifdef _WIN32
|
|
1069
|
+
static double scr_uptime_t0_ms;
|
|
1070
|
+
static void scr_uptime_anchor_init(void) { scr_uptime_t0_ms = (double)GetTickCount64(); }
|
|
1071
|
+
static double scr_uptime_now_ms(void) { return (double)GetTickCount64(); }
|
|
1072
|
+
#else
|
|
1073
|
+
#include <sys/resource.h>
|
|
1074
|
+
#include <sys/time.h>
|
|
1075
|
+
#ifdef __APPLE__
|
|
1076
|
+
#include <mach/mach.h>
|
|
1077
|
+
#endif
|
|
1078
|
+
static double scr_uptime_t0_ms;
|
|
1079
|
+
static double scr_uptime_now_ms(void) {
|
|
1080
|
+
struct timespec ts;
|
|
1081
|
+
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
1082
|
+
return (double)ts.tv_sec * 1000.0 + (double)ts.tv_nsec / 1e6;
|
|
1083
|
+
}
|
|
1084
|
+
__attribute__((constructor)) static void scr_uptime_anchor_init(void) {
|
|
1085
|
+
scr_uptime_t0_ms = scr_uptime_now_ms();
|
|
1086
|
+
}
|
|
1087
|
+
#endif
|
|
1088
|
+
|
|
1089
|
+
double scr_process_uptime(void) {
|
|
1090
|
+
#ifdef _WIN32
|
|
1091
|
+
if (scr_uptime_t0_ms == 0) scr_uptime_anchor_init();
|
|
1092
|
+
#endif
|
|
1093
|
+
return (scr_uptime_now_ms() - scr_uptime_t0_ms) / 1000.0;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
#ifdef _WIN32
|
|
1097
|
+
/* GetProcessTimes/GetThreadTimes answer 100ns units; Node reports µs. */
|
|
1098
|
+
static double scr_filetime_us(FILETIME ft) {
|
|
1099
|
+
ULARGE_INTEGER v;
|
|
1100
|
+
v.LowPart = ft.dwLowDateTime;
|
|
1101
|
+
v.HighPart = ft.dwHighDateTime;
|
|
1102
|
+
return (double)(v.QuadPart / 10);
|
|
1103
|
+
}
|
|
1104
|
+
double scr_cpu_user(void) {
|
|
1105
|
+
FILETIME c, e, k, u;
|
|
1106
|
+
if (!GetProcessTimes(GetCurrentProcess(), &c, &e, &k, &u)) return 0;
|
|
1107
|
+
return scr_filetime_us(u);
|
|
1108
|
+
}
|
|
1109
|
+
double scr_cpu_system(void) {
|
|
1110
|
+
FILETIME c, e, k, u;
|
|
1111
|
+
if (!GetProcessTimes(GetCurrentProcess(), &c, &e, &k, &u)) return 0;
|
|
1112
|
+
return scr_filetime_us(k);
|
|
1113
|
+
}
|
|
1114
|
+
double scr_thread_cpu_user(void) {
|
|
1115
|
+
FILETIME c, e, k, u;
|
|
1116
|
+
if (!GetThreadTimes(GetCurrentThread(), &c, &e, &k, &u)) return 0;
|
|
1117
|
+
return scr_filetime_us(u);
|
|
1118
|
+
}
|
|
1119
|
+
double scr_thread_cpu_system(void) {
|
|
1120
|
+
FILETIME c, e, k, u;
|
|
1121
|
+
if (!GetThreadTimes(GetCurrentThread(), &c, &e, &k, &u)) return 0;
|
|
1122
|
+
return scr_filetime_us(k);
|
|
1123
|
+
}
|
|
1124
|
+
#else
|
|
1125
|
+
static double scr_tv_us(struct timeval tv) {
|
|
1126
|
+
return (double)tv.tv_sec * 1e6 + (double)tv.tv_usec;
|
|
1127
|
+
}
|
|
1128
|
+
double scr_cpu_user(void) {
|
|
1129
|
+
struct rusage ru;
|
|
1130
|
+
if (getrusage(RUSAGE_SELF, &ru) != 0) return 0;
|
|
1131
|
+
return scr_tv_us(ru.ru_utime);
|
|
1132
|
+
}
|
|
1133
|
+
double scr_cpu_system(void) {
|
|
1134
|
+
struct rusage ru;
|
|
1135
|
+
if (getrusage(RUSAGE_SELF, &ru) != 0) return 0;
|
|
1136
|
+
return scr_tv_us(ru.ru_stime);
|
|
1137
|
+
}
|
|
1138
|
+
#if defined(RUSAGE_THREAD)
|
|
1139
|
+
double scr_thread_cpu_user(void) {
|
|
1140
|
+
struct rusage ru;
|
|
1141
|
+
if (getrusage(RUSAGE_THREAD, &ru) != 0) return 0;
|
|
1142
|
+
return scr_tv_us(ru.ru_utime);
|
|
1143
|
+
}
|
|
1144
|
+
double scr_thread_cpu_system(void) {
|
|
1145
|
+
struct rusage ru;
|
|
1146
|
+
if (getrusage(RUSAGE_THREAD, &ru) != 0) return 0;
|
|
1147
|
+
return scr_tv_us(ru.ru_stime);
|
|
1148
|
+
}
|
|
1149
|
+
#elif defined(__APPLE__)
|
|
1150
|
+
double scr_thread_cpu_user(void) {
|
|
1151
|
+
thread_basic_info_data_t info;
|
|
1152
|
+
mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
|
|
1153
|
+
if (thread_info(mach_thread_self(), THREAD_BASIC_INFO, (thread_info_t)&info, &count) != KERN_SUCCESS) return 0;
|
|
1154
|
+
return (double)info.user_time.seconds * 1e6 + (double)info.user_time.microseconds;
|
|
1155
|
+
}
|
|
1156
|
+
double scr_thread_cpu_system(void) {
|
|
1157
|
+
thread_basic_info_data_t info;
|
|
1158
|
+
mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
|
|
1159
|
+
if (thread_info(mach_thread_self(), THREAD_BASIC_INFO, (thread_info_t)&info, &count) != KERN_SUCCESS) return 0;
|
|
1160
|
+
return (double)info.system_time.seconds * 1e6 + (double)info.system_time.microseconds;
|
|
1161
|
+
}
|
|
1162
|
+
#else
|
|
1163
|
+
/* No per-thread clock on this platform: the process clocks stand in (a
|
|
1164
|
+
* single-threaded binary's thread IS the process). */
|
|
1165
|
+
double scr_thread_cpu_user(void) { return scr_cpu_user(); }
|
|
1166
|
+
double scr_thread_cpu_system(void) { return scr_cpu_system(); }
|
|
1167
|
+
#endif
|
|
1168
|
+
#endif
|
|
1169
|
+
|
|
1170
|
+
/* The prev-argument validation: Node checks prevValue.user then
|
|
1171
|
+
* prevValue.system and throws the ERR_INVALID_ARG_VALUE RangeError with
|
|
1172
|
+
* the received number, catchably. (The frontend guarantees numbers —
|
|
1173
|
+
* non-number shapes keep compile fences.) */
|
|
1174
|
+
static void scr_cpu_prev_check_field(const char *name, double v) {
|
|
1175
|
+
if (v >= 0 && v <= 1.7976931348623157e308 && v == v) return; /* finite, non-negative */
|
|
1176
|
+
char num[32];
|
|
1177
|
+
size_t nlen = scr_f64_to_str(v, num);
|
|
1178
|
+
num[nlen] = 0;
|
|
1179
|
+
char msg[128];
|
|
1180
|
+
int len = snprintf(msg, sizeof msg, "The property 'prevValue.%s' is invalid. Received %s", name, num);
|
|
1181
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_INVALID_ARG_VALUE");
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
void scr_cpu_prev_validate(double user, double system) {
|
|
1185
|
+
scr_cpu_prev_check_field("user", user);
|
|
1186
|
+
if (scr_exc_pending()) return;
|
|
1187
|
+
scr_cpu_prev_check_field("system", system);
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
double scr_cpu_user_diff(double prev) { return scr_cpu_user() - prev; }
|
|
1191
|
+
double scr_cpu_system_diff(double prev) { return scr_cpu_system() - prev; }
|
|
1192
|
+
double scr_thread_cpu_user_diff(double prev) { return scr_thread_cpu_user() - prev; }
|
|
1193
|
+
double scr_thread_cpu_system_diff(double prev) { return scr_thread_cpu_system() - prev; }
|
|
1194
|
+
|
|
1195
|
+
/* process.resourceUsage(): one field by canonical index — Node's names,
|
|
1196
|
+
* order, and units (CPU times in µs; maxRSS in kilobytes — uv divides
|
|
1197
|
+
* Darwin's bytes by 1024; the rest are getrusage's own counters, zero
|
|
1198
|
+
* where the platform never fills them). */
|
|
1199
|
+
double scr_process_rusage(double idx) {
|
|
1200
|
+
#ifdef _WIN32
|
|
1201
|
+
/* uv fills only the CPU times and page-fault/maxRSS slice on Windows;
|
|
1202
|
+
* everything else answers 0 — Node's own shape there. */
|
|
1203
|
+
switch ((int)idx) {
|
|
1204
|
+
case 0: return scr_cpu_user();
|
|
1205
|
+
case 1: return scr_cpu_system();
|
|
1206
|
+
default: return 0;
|
|
1207
|
+
}
|
|
1208
|
+
#else
|
|
1209
|
+
struct rusage ru;
|
|
1210
|
+
if (getrusage(RUSAGE_SELF, &ru) != 0) return 0;
|
|
1211
|
+
switch ((int)idx) {
|
|
1212
|
+
case 0: return scr_tv_us(ru.ru_utime); /* userCPUTime */
|
|
1213
|
+
case 1: return scr_tv_us(ru.ru_stime); /* systemCPUTime */
|
|
1214
|
+
case 2: /* maxRSS (kilobytes) */
|
|
1215
|
+
#ifdef __APPLE__
|
|
1216
|
+
return (double)(ru.ru_maxrss / 1024);
|
|
1217
|
+
#else
|
|
1218
|
+
return (double)ru.ru_maxrss;
|
|
1219
|
+
#endif
|
|
1220
|
+
case 3: return (double)ru.ru_ixrss; /* sharedMemorySize */
|
|
1221
|
+
case 4: return (double)ru.ru_idrss; /* unsharedDataSize */
|
|
1222
|
+
case 5: return (double)ru.ru_isrss; /* unsharedStackSize */
|
|
1223
|
+
case 6: return (double)ru.ru_minflt; /* minorPageFault */
|
|
1224
|
+
case 7: return (double)ru.ru_majflt; /* majorPageFault */
|
|
1225
|
+
case 8: return (double)ru.ru_nswap; /* swappedOut */
|
|
1226
|
+
case 9: return (double)ru.ru_inblock; /* fsRead */
|
|
1227
|
+
case 10: return (double)ru.ru_oublock; /* fsWrite */
|
|
1228
|
+
case 11: return (double)ru.ru_msgsnd; /* ipcSent */
|
|
1229
|
+
case 12: return (double)ru.ru_msgrcv; /* ipcReceived */
|
|
1230
|
+
case 13: return (double)ru.ru_nsignals; /* signalsCount */
|
|
1231
|
+
case 14: return (double)ru.ru_nvcsw; /* voluntaryContextSwitches */
|
|
1232
|
+
case 15: return (double)ru.ru_nivcsw; /* involuntaryContextSwitches */
|
|
1233
|
+
default: return 0;
|
|
1234
|
+
}
|
|
1235
|
+
#endif
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/* process.availableMemory()/constrainedMemory() — libuv's numbers: the
|
|
1239
|
+
* constrained form answers the cgroup cap where one exists (Linux) and 0
|
|
1240
|
+
* everywhere else; available is the free-ish byte count. */
|
|
1241
|
+
double scr_constrained_memory(void) {
|
|
1242
|
+
#if defined(__linux__)
|
|
1243
|
+
FILE *f = fopen("/sys/fs/cgroup/memory.max", "r"); /* cgroup v2 */
|
|
1244
|
+
if (f == NULL) f = fopen("/sys/fs/cgroup/memory/memory.limit_in_bytes", "r"); /* v1 */
|
|
1245
|
+
if (f == NULL) return 0;
|
|
1246
|
+
char buf[64];
|
|
1247
|
+
size_t n = fread(buf, 1, sizeof buf - 1, f);
|
|
1248
|
+
fclose(f);
|
|
1249
|
+
buf[n] = 0;
|
|
1250
|
+
if (n == 0 || buf[0] == 'm') return 0; /* "max" = unconstrained */
|
|
1251
|
+
double v = strtod(buf, NULL);
|
|
1252
|
+
return v > 0 ? v : 0;
|
|
1253
|
+
#else
|
|
1254
|
+
return 0;
|
|
1255
|
+
#endif
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
double scr_available_memory(void) {
|
|
1259
|
+
#if defined(_WIN32)
|
|
1260
|
+
MEMORYSTATUSEX ms;
|
|
1261
|
+
memset(&ms, 0, sizeof ms);
|
|
1262
|
+
ms.dwLength = sizeof ms;
|
|
1263
|
+
if (!GlobalMemoryStatusEx(&ms)) return 0;
|
|
1264
|
+
return (double)ms.ullAvailPhys;
|
|
1265
|
+
#elif defined(__APPLE__)
|
|
1266
|
+
/* uv_get_available_memory falls back to the free-memory number on
|
|
1267
|
+
* Darwin (vm_statistics' free pages). */
|
|
1268
|
+
vm_statistics64_data_t vm;
|
|
1269
|
+
mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
|
|
1270
|
+
if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)&vm, &count) != KERN_SUCCESS) return 0;
|
|
1271
|
+
return (double)vm.free_count * (double)vm_page_size;
|
|
1272
|
+
#elif defined(__linux__)
|
|
1273
|
+
/* /proc/meminfo's MemAvailable — the kernel's own availability estimate
|
|
1274
|
+
* (what uv reads via sysinfo lacks reclaimable cache). */
|
|
1275
|
+
FILE *f = fopen("/proc/meminfo", "r");
|
|
1276
|
+
if (f == NULL) return 0;
|
|
1277
|
+
char line[128];
|
|
1278
|
+
double kb = 0;
|
|
1279
|
+
while (fgets(line, sizeof line, f) != NULL) {
|
|
1280
|
+
if (sscanf(line, "MemAvailable: %lf kB", &kb) == 1) break;
|
|
1281
|
+
}
|
|
1282
|
+
fclose(f);
|
|
1283
|
+
return kb * 1024.0;
|
|
1284
|
+
#else
|
|
1285
|
+
return 0;
|
|
1286
|
+
#endif
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
/* process._exiting — true once the exit sequence began (set above and by
|
|
1290
|
+
* scr_run_exit_listeners in scr_events.c; the flag lives HERE so reading
|
|
1291
|
+
* it never forces the events unit into the link). */
|
|
1292
|
+
bool scr_process_in_exit = false;
|
|
1293
|
+
|
|
1294
|
+
bool scr_process_exiting(void) { return scr_process_in_exit; }
|
|
1295
|
+
|
|
1296
|
+
/* umask(2): mask < 0 reads without setting (set 0, restore — umask has no
|
|
1297
|
+
* read-only form); otherwise sets and answers the previous mask. */
|
|
1298
|
+
double scr_process_umask(double mask) {
|
|
1299
|
+
#ifdef _WIN32
|
|
1300
|
+
/* Node on Windows accepts umask() calls; only the low bits matter. */
|
|
1301
|
+
int prev;
|
|
1302
|
+
if (mask < 0) {
|
|
1303
|
+
_umask_s(0, &prev);
|
|
1304
|
+
int ignored;
|
|
1305
|
+
_umask_s(prev, &ignored);
|
|
1306
|
+
} else {
|
|
1307
|
+
_umask_s((int)mask, &prev);
|
|
1308
|
+
}
|
|
1309
|
+
return (double)prev;
|
|
1310
|
+
#else
|
|
1311
|
+
mode_t prev;
|
|
1312
|
+
if (mask < 0) {
|
|
1313
|
+
prev = umask(0);
|
|
1314
|
+
umask(prev);
|
|
1315
|
+
} else {
|
|
1316
|
+
prev = umask((mode_t)mask);
|
|
1317
|
+
}
|
|
1318
|
+
return (double)prev;
|
|
1319
|
+
#endif
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
void scr_process_chdir(ScrStr *dir) {
|
|
1323
|
+
#ifdef _WIN32
|
|
1324
|
+
if (_chdir(dir->data) != 0) scr_fs_throw(errno, "chdir", dir);
|
|
1325
|
+
#else
|
|
1326
|
+
if (chdir(dir->data) != 0) scr_fs_throw(errno, "chdir", dir);
|
|
1327
|
+
#endif
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
/* net's process-wide happy-eyeballs attempt budget (Node's
|
|
1331
|
+
* getDefaultAutoSelectFamilyAttemptTimeout pair, default 250ms). Lives in
|
|
1332
|
+
* the core unit so the knob never forces scr_net.c into the link. */
|
|
1333
|
+
static double scr_net_autosel_timeout_ms = 250;
|
|
1334
|
+
|
|
1335
|
+
double scr_net_get_autosel_timeout(void) { return scr_net_autosel_timeout_ms; }
|
|
1336
|
+
|
|
1337
|
+
void scr_net_set_autosel_timeout(double ms) { scr_net_autosel_timeout_ms = ms; }
|
|
1338
|
+
|
|
1339
|
+
/* ── fs error formatting ─────────────────────────────────────────────
|
|
1340
|
+
* Node's fs errors read "<ERRNO>: <text>, <syscall> '<path>'"
|
|
1341
|
+
* ("ENOENT: no such file or directory, open 'x'"). The common errnos get
|
|
1342
|
+
* Node's (libuv's) exact lowercase text; anything exotic falls back to
|
|
1343
|
+
* "E<num>: <strerror>, <op> '<path>'" — close enough, and the corpus can
|
|
1344
|
+
* only observe messages through the runtime C tests anyway (the supported
|
|
1345
|
+
* catch form is bindingless).
|
|
1346
|
+
*/
|
|
1347
|
+
|
|
1348
|
+
static const char *scr_errno_name(int e, char *fallback, size_t cap) {
|
|
1349
|
+
switch (e) {
|
|
1350
|
+
case ENOENT: return "ENOENT";
|
|
1351
|
+
case EEXIST: return "EEXIST";
|
|
1352
|
+
case EACCES: return "EACCES";
|
|
1353
|
+
case ENOTDIR: return "ENOTDIR";
|
|
1354
|
+
case EISDIR: return "EISDIR";
|
|
1355
|
+
case ENOTEMPTY: return "ENOTEMPTY";
|
|
1356
|
+
case EPERM: return "EPERM";
|
|
1357
|
+
case EBADF: return "EBADF";
|
|
1358
|
+
default:
|
|
1359
|
+
snprintf(fallback, cap, "E%d", e);
|
|
1360
|
+
return fallback;
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
static const char *scr_errno_text(int e) {
|
|
1365
|
+
switch (e) {
|
|
1366
|
+
case ENOENT: return "no such file or directory";
|
|
1367
|
+
case EEXIST: return "file already exists";
|
|
1368
|
+
case EACCES: return "permission denied";
|
|
1369
|
+
case ENOTDIR: return "not a directory";
|
|
1370
|
+
case EISDIR: return "illegal operation on a directory";
|
|
1371
|
+
case ENOTEMPTY: return "directory not empty";
|
|
1372
|
+
case EPERM: return "operation not permitted";
|
|
1373
|
+
case EBADF: return "bad file descriptor";
|
|
1374
|
+
default: return strerror(e);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
/* Node on WINDOWS reports fs error paths ABSOLUTIZED and backslashed
|
|
1379
|
+
* (its fs binding hands the Windows API namespaced absolute paths and the
|
|
1380
|
+
* error keeps that spelling): open("no.bin") fails with "... open
|
|
1381
|
+
* 'C:\cwd\no.bin'". _fullpath reproduces exactly that resolution. POSIX
|
|
1382
|
+
* Node reports the path as given — the passthrough arm. */
|
|
1383
|
+
#ifdef _WIN32
|
|
1384
|
+
static const char *scr_fs_err_path(const ScrStr *path, char buf[PATH_MAX]) {
|
|
1385
|
+
return _fullpath(buf, path->data, PATH_MAX) != NULL ? buf : path->data;
|
|
1386
|
+
}
|
|
1387
|
+
#else
|
|
1388
|
+
static const char *scr_fs_err_path(const ScrStr *path, char buf[PATH_MAX]) {
|
|
1389
|
+
(void)buf;
|
|
1390
|
+
return path->data;
|
|
1391
|
+
}
|
|
1392
|
+
#endif
|
|
1393
|
+
|
|
1394
|
+
/* Exported (scr_runtime.h): scr_bytes.c's fs Buffer forms share it. */
|
|
1395
|
+
void scr_fs_throw(int e, const char *op, const ScrStr *path) {
|
|
1396
|
+
#ifdef _WIN32
|
|
1397
|
+
/* The CRT lands ERROR_ACCESS_DENIED in errno as EACCES; libuv's
|
|
1398
|
+
* uv_translate_sys_error maps the same Win32 error to EPERM, so that
|
|
1399
|
+
* is the code Node throws (a read-only file's write open, chmod on a
|
|
1400
|
+
* held file). Translate at the throw seam so every fs op agrees with
|
|
1401
|
+
* the Windows oracle. */
|
|
1402
|
+
if (e == EACCES) e = EPERM;
|
|
1403
|
+
#endif
|
|
1404
|
+
char namebuf[16];
|
|
1405
|
+
const char *name = scr_errno_name(e, namebuf, sizeof namebuf);
|
|
1406
|
+
const char *text = scr_errno_text(e);
|
|
1407
|
+
char pathbuf[PATH_MAX];
|
|
1408
|
+
const char *shown = scr_fs_err_path(path, pathbuf);
|
|
1409
|
+
size_t cap = strlen(name) + strlen(text) + strlen(op) + strlen(shown) + 8;
|
|
1410
|
+
char *msg = malloc(cap);
|
|
1411
|
+
if (!msg) {
|
|
1412
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
1413
|
+
abort();
|
|
1414
|
+
}
|
|
1415
|
+
int len = snprintf(msg, cap, "%s: %s, %s '%s'", name, text, op, shown);
|
|
1416
|
+
/* A real Error instance (name "Error", message = Node's text) — what a
|
|
1417
|
+
* typed catch's `e instanceof Error` + `e.message` observes in Node —
|
|
1418
|
+
* with `code` stamped to the errno name (the exotic-errno fallback
|
|
1419
|
+
* stamps its "E<num>" spelling; Node would carry the uv name there).
|
|
1420
|
+
* errno/syscall/path stay unrepresented (SEMANTICS.md divergence 13). */
|
|
1421
|
+
scr_throw_error_msg_code(SCR_ERR_ERROR, msg, (size_t)len, name);
|
|
1422
|
+
free(msg);
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
/* ── fs operations ───────────────────────────────────────────────────── */
|
|
1426
|
+
|
|
1427
|
+
ScrStr *scr_fs_read_file(ScrStr *path) {
|
|
1428
|
+
FILE *f = fopen(path->data, "rb");
|
|
1429
|
+
if (!f) {
|
|
1430
|
+
scr_fs_throw(errno, "open", path);
|
|
1431
|
+
return NULL;
|
|
1432
|
+
}
|
|
1433
|
+
size_t cap = 4096, len = 0;
|
|
1434
|
+
char *buf = malloc(cap);
|
|
1435
|
+
if (!buf) {
|
|
1436
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
1437
|
+
abort();
|
|
1438
|
+
}
|
|
1439
|
+
for (;;) {
|
|
1440
|
+
if (cap - len < 2048) {
|
|
1441
|
+
cap *= 2;
|
|
1442
|
+
char *grown = realloc(buf, cap);
|
|
1443
|
+
if (!grown) {
|
|
1444
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
1445
|
+
abort();
|
|
1446
|
+
}
|
|
1447
|
+
buf = grown;
|
|
1448
|
+
}
|
|
1449
|
+
size_t n = fread(buf + len, 1, cap - len, f);
|
|
1450
|
+
len += n;
|
|
1451
|
+
if (n == 0) break;
|
|
1452
|
+
}
|
|
1453
|
+
if (ferror(f)) {
|
|
1454
|
+
int e = errno;
|
|
1455
|
+
fclose(f);
|
|
1456
|
+
free(buf);
|
|
1457
|
+
scr_fs_throw(e, "read", path);
|
|
1458
|
+
return NULL;
|
|
1459
|
+
}
|
|
1460
|
+
fclose(f);
|
|
1461
|
+
ScrStr *s = scr_str_new(buf, len);
|
|
1462
|
+
free(buf);
|
|
1463
|
+
return s;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
ScrStr *scr_fs_realpath(ScrStr *path) {
|
|
1467
|
+
#ifdef _WIN32
|
|
1468
|
+
/* _fullpath resolves . / .. and drive-relative forms (symlink-free —
|
|
1469
|
+
* the honest Windows approximation); a missing path throws Node's
|
|
1470
|
+
* lstat-spelled ENOENT like the POSIX arm. */
|
|
1471
|
+
char buf[PATH_MAX];
|
|
1472
|
+
if (_fullpath(buf, path->data, sizeof buf) == NULL) {
|
|
1473
|
+
scr_fs_throw(errno ? errno : ENOENT, "lstat", path);
|
|
1474
|
+
return NULL;
|
|
1475
|
+
}
|
|
1476
|
+
if (GetFileAttributesA(buf) == INVALID_FILE_ATTRIBUTES) {
|
|
1477
|
+
scr_fs_throw(ENOENT, "lstat", path);
|
|
1478
|
+
return NULL;
|
|
1479
|
+
}
|
|
1480
|
+
return scr_str_new(buf, strlen(buf));
|
|
1481
|
+
#else
|
|
1482
|
+
/* realpath(3); Node's realpathSync reports failures with the "lstat"
|
|
1483
|
+
* syscall in the message ("ENOENT: no such file or directory, lstat
|
|
1484
|
+
* 'x'") — its own resolution walks lstat by component. */
|
|
1485
|
+
char buf[PATH_MAX];
|
|
1486
|
+
if (realpath(path->data, buf) == NULL) {
|
|
1487
|
+
scr_fs_throw(errno, "lstat", path);
|
|
1488
|
+
return NULL;
|
|
1489
|
+
}
|
|
1490
|
+
return scr_str_new(buf, strlen(buf));
|
|
1491
|
+
#endif
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
static void scr_fs_write_common(ScrStr *path, ScrStr *data, const char *mode) {
|
|
1495
|
+
FILE *f = fopen(path->data, mode);
|
|
1496
|
+
if (!f) {
|
|
1497
|
+
scr_fs_throw(errno, "open", path);
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
if (data->len > 0 && fwrite(data->data, 1, data->len, f) != data->len) {
|
|
1501
|
+
int e = errno;
|
|
1502
|
+
fclose(f);
|
|
1503
|
+
scr_fs_throw(e, "write", path);
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1506
|
+
if (fclose(f) != 0) scr_fs_throw(errno, "close", path);
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
void scr_fs_write_file(ScrStr *path, ScrStr *data) {
|
|
1510
|
+
scr_fs_write_common(path, data, "wb");
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
/* writeFileSync(p, data, { mode }): the mode is open(2)'s O_CREAT
|
|
1514
|
+
* argument — it applies at CREATION only (umask applying), and an
|
|
1515
|
+
* existing file keeps its permissions, exactly Node (which never chmods
|
|
1516
|
+
* here). Same error shapes as the plain form. */
|
|
1517
|
+
void scr_fs_write_file_mode(ScrStr *path, ScrStr *data, double mode) {
|
|
1518
|
+
/* O_BINARY: zero on POSIX; on Windows it keeps the CRT from translating
|
|
1519
|
+
* \n in these byte-exact writes (fopen's "wb" path already does). */
|
|
1520
|
+
int fd = open(path->data, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, (mode_t)mode);
|
|
1521
|
+
if (fd < 0) {
|
|
1522
|
+
scr_fs_throw(errno, "open", path);
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
size_t at = 0;
|
|
1526
|
+
while (at < data->len) {
|
|
1527
|
+
ssize_t wrote = write(fd, data->data + at, data->len - at);
|
|
1528
|
+
if (wrote < 0) {
|
|
1529
|
+
if (errno == EINTR) continue;
|
|
1530
|
+
int e = errno;
|
|
1531
|
+
close(fd);
|
|
1532
|
+
scr_fs_throw(e, "write", path);
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
at += (size_t)wrote;
|
|
1536
|
+
}
|
|
1537
|
+
if (close(fd) != 0) scr_fs_throw(errno, "close", path);
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
void scr_fs_append_file(ScrStr *path, ScrStr *data) {
|
|
1541
|
+
scr_fs_write_common(path, data, "ab");
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
bool scr_fs_exists(ScrStr *path) {
|
|
1545
|
+
/* Like Node's existsSync: any failure (missing, EACCES on a parent, ...)
|
|
1546
|
+
* is simply false — never a throw. */
|
|
1547
|
+
return access(path->data, F_OK) == 0;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
void scr_fs_mkdir(ScrStr *path) {
|
|
1551
|
+
if (scr_sys_mkdir(path->data, 0777) != 0) scr_fs_throw(errno, "mkdir", path);
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
/* mkdirSync(p, { mode }) non-recursive: mkdir(2) with the explicit mode
|
|
1555
|
+
* (umask applies, exactly as in Node — mode is the syscall argument;
|
|
1556
|
+
* Windows has no directory modes and drops it, like Node there). */
|
|
1557
|
+
void scr_fs_mkdir_mode(ScrStr *path, double mode) {
|
|
1558
|
+
if (scr_sys_mkdir(path->data, (mode_t)mode) != 0) scr_fs_throw(errno, "mkdir", path);
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
void scr_fs_unlink(ScrStr *path) {
|
|
1562
|
+
if (unlink(path->data) != 0) scr_fs_throw(errno, "unlink", path);
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
void scr_fs_chmod(ScrStr *path, double mode) {
|
|
1566
|
+
if (chmod(path->data, (mode_t)mode) != 0) scr_fs_throw(errno, "chmod", path);
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
void scr_fs_chown(ScrStr *path, double uid, double gid) {
|
|
1570
|
+
#ifdef _WIN32
|
|
1571
|
+
/* libuv's uv_fs_chown on Windows is an unconditional no-op success —
|
|
1572
|
+
* Node's chownSync "works" and changes nothing there; same here. */
|
|
1573
|
+
(void)path; (void)uid; (void)gid;
|
|
1574
|
+
#else
|
|
1575
|
+
/* Node passes the ids straight to chown(2); -1 is the POSIX "leave
|
|
1576
|
+
* unchanged" value and rides the same int cast. */
|
|
1577
|
+
if (chown(path->data, (uid_t)(int64_t)uid, (gid_t)(int64_t)gid) != 0) {
|
|
1578
|
+
scr_fs_throw(errno, "chown", path);
|
|
1579
|
+
}
|
|
1580
|
+
#endif
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
/* fs.openSync(path, flags) → the raw fd (as f64) — the pair behind
|
|
1584
|
+
* spawn's fd-stdio form (openSync → stdio: ["ignore", fd, fd] →
|
|
1585
|
+
* closeSync). flags is Node's string grammar; an unknown flag throws
|
|
1586
|
+
* Node's ERR_INVALID_ARG_VALUE TypeError text, an open(2) failure the
|
|
1587
|
+
* usual Node-shaped fs error. Mode is Node's 0666 default (the numeric
|
|
1588
|
+
* third argument is a compile fence). */
|
|
1589
|
+
double scr_fs_open(ScrStr *path, ScrStr *flags) {
|
|
1590
|
+
const char *f = flags->data;
|
|
1591
|
+
int of;
|
|
1592
|
+
if (strcmp(f, "r") == 0) of = O_RDONLY;
|
|
1593
|
+
else if (strcmp(f, "rs") == 0 || strcmp(f, "sr") == 0) of = O_RDONLY | O_SYNC;
|
|
1594
|
+
else if (strcmp(f, "r+") == 0) of = O_RDWR;
|
|
1595
|
+
else if (strcmp(f, "rs+") == 0 || strcmp(f, "sr+") == 0) of = O_RDWR | O_SYNC;
|
|
1596
|
+
else if (strcmp(f, "w") == 0) of = O_TRUNC | O_CREAT | O_WRONLY;
|
|
1597
|
+
else if (strcmp(f, "wx") == 0 || strcmp(f, "xw") == 0) of = O_TRUNC | O_CREAT | O_WRONLY | O_EXCL;
|
|
1598
|
+
else if (strcmp(f, "w+") == 0) of = O_TRUNC | O_CREAT | O_RDWR;
|
|
1599
|
+
else if (strcmp(f, "wx+") == 0 || strcmp(f, "xw+") == 0) of = O_TRUNC | O_CREAT | O_RDWR | O_EXCL;
|
|
1600
|
+
else if (strcmp(f, "a") == 0) of = O_APPEND | O_CREAT | O_WRONLY;
|
|
1601
|
+
else if (strcmp(f, "ax") == 0 || strcmp(f, "xa") == 0) of = O_APPEND | O_CREAT | O_WRONLY | O_EXCL;
|
|
1602
|
+
else if (strcmp(f, "as") == 0 || strcmp(f, "sa") == 0) of = O_APPEND | O_CREAT | O_WRONLY | O_SYNC;
|
|
1603
|
+
else if (strcmp(f, "a+") == 0) of = O_APPEND | O_CREAT | O_RDWR;
|
|
1604
|
+
else if (strcmp(f, "ax+") == 0 || strcmp(f, "xa+") == 0) of = O_APPEND | O_CREAT | O_RDWR | O_EXCL;
|
|
1605
|
+
else if (strcmp(f, "as+") == 0 || strcmp(f, "sa+") == 0) of = O_APPEND | O_CREAT | O_RDWR | O_SYNC;
|
|
1606
|
+
else {
|
|
1607
|
+
char msg[128];
|
|
1608
|
+
int len = snprintf(msg, sizeof msg, "The argument 'flags' is invalid. Received '%s'", f);
|
|
1609
|
+
scr_throw_error_msg(SCR_ERR_TYPE, msg, (size_t)len);
|
|
1610
|
+
return 0;
|
|
1611
|
+
}
|
|
1612
|
+
int fd = open(path->data, of | O_BINARY, 0666);
|
|
1613
|
+
if (fd < 0) {
|
|
1614
|
+
scr_fs_throw(errno, "open", path);
|
|
1615
|
+
return 0;
|
|
1616
|
+
}
|
|
1617
|
+
return (double)fd;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
/* fs.closeSync(fd) — close(2); failure throws Node's path-less fs error
|
|
1621
|
+
* shape ("EBADF: bad file descriptor, close"). */
|
|
1622
|
+
/* fs.readSync(fd, buffer, offset, length) — the 4-argument buffer form.
|
|
1623
|
+
* Node validates offset/length against the buffer before reading and
|
|
1624
|
+
* throws ERR_OUT_OF_RANGE; here the checks clamp to the same contract and
|
|
1625
|
+
* throw the RangeError shape. Returns the byte count read(2) reports;
|
|
1626
|
+
* errors carry the errno name like the other fd operations. */
|
|
1627
|
+
double scr_fs_read_sync(double fd, ScrBytes *buf, double offset, double length) {
|
|
1628
|
+
size_t bytelen = buf->len; /* u8 buffers: elem count == byte count */
|
|
1629
|
+
char msg[160];
|
|
1630
|
+
int mlen;
|
|
1631
|
+
/* Node's exact texts: offset validates against MAX_SAFE_INTEGER's range
|
|
1632
|
+
* form, length against the remaining window (validateOffset vs the
|
|
1633
|
+
* buffer-bounds check in fs.readSync). */
|
|
1634
|
+
char numbuf[40];
|
|
1635
|
+
if (offset < 0 || offset > (double)bytelen) {
|
|
1636
|
+
numbuf[scr_f64_to_str(offset, numbuf)] = 0;
|
|
1637
|
+
mlen = snprintf(msg, sizeof msg,
|
|
1638
|
+
"The value of \"offset\" is out of range. It must be >= 0 && <= 9007199254740991. Received %s",
|
|
1639
|
+
numbuf);
|
|
1640
|
+
scr_throw_error_msg(SCR_ERR_RANGE, msg, (size_t)mlen);
|
|
1641
|
+
return 0;
|
|
1642
|
+
}
|
|
1643
|
+
size_t off = (size_t)offset;
|
|
1644
|
+
if (length < 0 || (double)length > (double)(bytelen - off)) {
|
|
1645
|
+
numbuf[scr_f64_to_str(length, numbuf)] = 0;
|
|
1646
|
+
mlen = snprintf(msg, sizeof msg,
|
|
1647
|
+
"The value of \"length\" is out of range. It must be <= %zu. Received %s",
|
|
1648
|
+
bytelen - off, numbuf);
|
|
1649
|
+
scr_throw_error_msg(SCR_ERR_RANGE, msg, (size_t)mlen);
|
|
1650
|
+
return 0;
|
|
1651
|
+
}
|
|
1652
|
+
size_t want = (size_t)length;
|
|
1653
|
+
ssize_t n = read((int)fd, buf->data + off, want);
|
|
1654
|
+
if (n < 0) {
|
|
1655
|
+
int e = errno;
|
|
1656
|
+
char namebuf[16];
|
|
1657
|
+
const char *name = scr_errno_name(e, namebuf, sizeof namebuf);
|
|
1658
|
+
const char *text = scr_errno_text(e);
|
|
1659
|
+
char msg[160];
|
|
1660
|
+
int len = snprintf(msg, sizeof msg, "%s: %s, read", name, text);
|
|
1661
|
+
scr_throw_error_msg_code(SCR_ERR_ERROR, msg, (size_t)len, name);
|
|
1662
|
+
return 0;
|
|
1663
|
+
}
|
|
1664
|
+
return (double)n;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
void scr_fs_close(double fd) {
|
|
1668
|
+
if (close((int)fd) != 0) {
|
|
1669
|
+
int e = errno;
|
|
1670
|
+
char namebuf[16];
|
|
1671
|
+
const char *name = scr_errno_name(e, namebuf, sizeof namebuf);
|
|
1672
|
+
const char *text = scr_errno_text(e);
|
|
1673
|
+
char msg[160];
|
|
1674
|
+
int len = snprintf(msg, sizeof msg, "%s: %s, close", name, text);
|
|
1675
|
+
scr_throw_error_msg_code(SCR_ERR_ERROR, msg, (size_t)len, name);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
/* The two-path fs error shape — Node's copyfile errors quote both ends:
|
|
1680
|
+
* "ENOENT: no such file or directory, copyfile 'src' -> 'dest'". */
|
|
1681
|
+
static void scr_fs_throw2(int e, const char *op, const ScrStr *src, const ScrStr *dest) {
|
|
1682
|
+
char namebuf[16];
|
|
1683
|
+
const char *name = scr_errno_name(e, namebuf, sizeof namebuf);
|
|
1684
|
+
const char *text = scr_errno_text(e);
|
|
1685
|
+
char srcbuf[PATH_MAX], destbuf[PATH_MAX];
|
|
1686
|
+
const char *shown_src = scr_fs_err_path(src, srcbuf);
|
|
1687
|
+
const char *shown_dest = scr_fs_err_path(dest, destbuf);
|
|
1688
|
+
size_t cap = strlen(name) + strlen(text) + strlen(op) + strlen(shown_src) + strlen(shown_dest) + 16;
|
|
1689
|
+
char *msg = malloc(cap);
|
|
1690
|
+
if (!msg) {
|
|
1691
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
1692
|
+
abort();
|
|
1693
|
+
}
|
|
1694
|
+
int len = snprintf(msg, cap, "%s: %s, %s '%s' -> '%s'", name, text, op, shown_src, shown_dest);
|
|
1695
|
+
scr_throw_error_msg_code(SCR_ERR_ERROR, msg, (size_t)len, name);
|
|
1696
|
+
free(msg);
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
/* copyFileSync(src, dest): contents copied into a created-or-truncated
|
|
1700
|
+
* destination carrying the SOURCE's permission bits — libuv's
|
|
1701
|
+
* uv_fs_copyfile behavior behind Node's copyFileSync (umask applies at
|
|
1702
|
+
* creation, like any open(2)). Every failure throws catchably with the
|
|
1703
|
+
* two-path message and the syscall name Node reports ("copyfile"). */
|
|
1704
|
+
void scr_fs_copyfile(ScrStr *src, ScrStr *dest) {
|
|
1705
|
+
int in = open(src->data, O_RDONLY | O_BINARY);
|
|
1706
|
+
if (in < 0) {
|
|
1707
|
+
scr_fs_throw2(errno, "copyfile", src, dest);
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
struct stat st;
|
|
1711
|
+
if (fstat(in, &st) != 0) {
|
|
1712
|
+
int e = errno;
|
|
1713
|
+
close(in);
|
|
1714
|
+
scr_fs_throw2(e, "copyfile", src, dest);
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
int out = open(dest->data, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, st.st_mode & 07777);
|
|
1718
|
+
if (out < 0) {
|
|
1719
|
+
int e = errno;
|
|
1720
|
+
close(in);
|
|
1721
|
+
scr_fs_throw2(e, "copyfile", src, dest);
|
|
1722
|
+
return;
|
|
1723
|
+
}
|
|
1724
|
+
char buf[65536];
|
|
1725
|
+
for (;;) {
|
|
1726
|
+
ssize_t got = read(in, buf, sizeof buf);
|
|
1727
|
+
if (got < 0) {
|
|
1728
|
+
if (errno == EINTR) continue;
|
|
1729
|
+
int e = errno;
|
|
1730
|
+
close(in);
|
|
1731
|
+
close(out);
|
|
1732
|
+
scr_fs_throw2(e, "copyfile", src, dest);
|
|
1733
|
+
return;
|
|
1734
|
+
}
|
|
1735
|
+
if (got == 0) break;
|
|
1736
|
+
ssize_t at = 0;
|
|
1737
|
+
while (at < got) {
|
|
1738
|
+
ssize_t wrote = write(out, buf + at, (size_t)(got - at));
|
|
1739
|
+
if (wrote < 0) {
|
|
1740
|
+
if (errno == EINTR) continue;
|
|
1741
|
+
int e = errno;
|
|
1742
|
+
close(in);
|
|
1743
|
+
close(out);
|
|
1744
|
+
scr_fs_throw2(e, "copyfile", src, dest);
|
|
1745
|
+
return;
|
|
1746
|
+
}
|
|
1747
|
+
at += wrote;
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
close(in);
|
|
1751
|
+
if (close(out) != 0) scr_fs_throw2(errno, "copyfile", src, dest);
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
/* renameSync(old, new): rename(2), Node's two-path error shape ("ENOENT:
|
|
1755
|
+
* no such file or directory, rename 'a' -> 'b'"). */
|
|
1756
|
+
void scr_fs_rename(ScrStr *oldpath, ScrStr *newpath) {
|
|
1757
|
+
if (rename(oldpath->data, newpath->data) != 0) {
|
|
1758
|
+
scr_fs_throw2(errno, "rename", oldpath, newpath);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
void scr_fs_rm(ScrStr *path) {
|
|
1763
|
+
/* Node's rmSync: lstat first (a missing path reports the lstat syscall),
|
|
1764
|
+
* refuse directories (Node requires `recursive`, which the scriptc
|
|
1765
|
+
* surface doesn't declare — the message wording diverges from Node's
|
|
1766
|
+
* ERR_FS_EISDIR, see SEMANTICS.md), then unlink. */
|
|
1767
|
+
struct stat st;
|
|
1768
|
+
if (lstat(path->data, &st) != 0) {
|
|
1769
|
+
scr_fs_throw(errno, "lstat", path);
|
|
1770
|
+
return;
|
|
1771
|
+
}
|
|
1772
|
+
if (S_ISDIR(st.st_mode)) {
|
|
1773
|
+
scr_fs_throw(EISDIR, "rm", path);
|
|
1774
|
+
return;
|
|
1775
|
+
}
|
|
1776
|
+
if (unlink(path->data) != 0) scr_fs_throw(errno, "unlink", path);
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
void scr_fs_rmdir(ScrStr *path) {
|
|
1780
|
+
if (rmdir(path->data) != 0) scr_fs_throw(errno, "rmdir", path);
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
/* ── fs option forms ─────────────────────────────────────────────────
|
|
1784
|
+
* mkdirSync(p, { recursive: true }), rmSync(p, { recursive, force }),
|
|
1785
|
+
* mkdtempSync(prefix), accessSync(p, mode), and the readFileSync(fd)
|
|
1786
|
+
* forms — the slice real CLIs use. All throw catchably like the rest of
|
|
1787
|
+
* sync fs, with Node's errno/path shapes (verified against Node). */
|
|
1788
|
+
|
|
1789
|
+
/* Node's recursive mkdir algorithm: try mkdir; EEXIST is fine iff the
|
|
1790
|
+
* path is a directory (a file target throws EEXIST at that path); ENOENT
|
|
1791
|
+
* creates the parent first and retries. ENOTDIR past a file reports the
|
|
1792
|
+
* FULL requested path, like Node. `path` is a NUL-terminated mutable
|
|
1793
|
+
* buffer of `len` bytes. */
|
|
1794
|
+
/* The path separators the RECURSIVE walk recognizes: win32 targets take
|
|
1795
|
+
* both slashes (the path module hands out backslashed paths there);
|
|
1796
|
+
* POSIX '/' only — a backslash is an ordinary filename byte. */
|
|
1797
|
+
static bool scr_fs_sep(char c) {
|
|
1798
|
+
#ifdef _WIN32
|
|
1799
|
+
if (c == '\\') return true;
|
|
1800
|
+
#endif
|
|
1801
|
+
return c == '/';
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
static void scr_mkdir_rec(char *path, size_t len, mode_t mode) {
|
|
1805
|
+
if (scr_sys_mkdir(path, mode) == 0) return;
|
|
1806
|
+
int e = errno;
|
|
1807
|
+
struct stat st;
|
|
1808
|
+
if (e == EEXIST) {
|
|
1809
|
+
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) return;
|
|
1810
|
+
} else if (e == ENOENT) {
|
|
1811
|
+
/* Parent: trim trailing separators, the last component, then the
|
|
1812
|
+
* separator run before it (keep "/" itself). */
|
|
1813
|
+
size_t i = len;
|
|
1814
|
+
while (i > 0 && scr_fs_sep(path[i - 1])) i--;
|
|
1815
|
+
while (i > 0 && !scr_fs_sep(path[i - 1])) i--;
|
|
1816
|
+
while (i > 1 && scr_fs_sep(path[i - 1])) i--;
|
|
1817
|
+
if (i > 0 && i < len) {
|
|
1818
|
+
char saved = path[i];
|
|
1819
|
+
path[i] = 0;
|
|
1820
|
+
#ifdef _WIN32
|
|
1821
|
+
/* POSIX mkdir answers ENOTDIR itself when a path component is a
|
|
1822
|
+
* FILE; the CRT answers ENOENT for that too. Node on Windows still
|
|
1823
|
+
* reports ENOTDIR with the full requested path — recover the
|
|
1824
|
+
* distinction from the parent's stat before recursing. */
|
|
1825
|
+
if (stat(path, &st) == 0 && !S_ISDIR(st.st_mode)) {
|
|
1826
|
+
path[i] = saved;
|
|
1827
|
+
ScrStr *full = scr_str_new(path, len);
|
|
1828
|
+
scr_fs_throw(ENOTDIR, "mkdir", full);
|
|
1829
|
+
scr_str_release(full);
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
#endif
|
|
1833
|
+
scr_mkdir_rec(path, i, mode);
|
|
1834
|
+
path[i] = saved;
|
|
1835
|
+
if (scr_exc_pending()) return;
|
|
1836
|
+
if (scr_sys_mkdir(path, mode) == 0) return;
|
|
1837
|
+
e = errno;
|
|
1838
|
+
if (e == EEXIST && stat(path, &st) == 0 && S_ISDIR(st.st_mode)) return;
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
ScrStr *p = scr_str_new(path, len);
|
|
1842
|
+
scr_fs_throw(e, "mkdir", p);
|
|
1843
|
+
scr_str_release(p);
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
void scr_fs_mkdir_recursive(ScrStr *path) {
|
|
1847
|
+
char *buf = malloc(path->len + 1);
|
|
1848
|
+
if (!buf) {
|
|
1849
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
1850
|
+
abort();
|
|
1851
|
+
}
|
|
1852
|
+
memcpy(buf, path->data, path->len + 1); /* ScrStr data is NUL-terminated */
|
|
1853
|
+
scr_mkdir_rec(buf, path->len, 0777);
|
|
1854
|
+
free(buf);
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
/* mkdirSync(p, { recursive: true, mode }): Node passes the mode to every
|
|
1858
|
+
* directory the walk creates (existing ones keep theirs). */
|
|
1859
|
+
void scr_fs_mkdir_recursive_mode(ScrStr *path, double mode) {
|
|
1860
|
+
char *buf = malloc(path->len + 1);
|
|
1861
|
+
if (!buf) {
|
|
1862
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
1863
|
+
abort();
|
|
1864
|
+
}
|
|
1865
|
+
memcpy(buf, path->data, path->len + 1);
|
|
1866
|
+
scr_mkdir_rec(buf, path->len, (mode_t)mode);
|
|
1867
|
+
free(buf);
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
/* First failure of an rm walk, recorded instead of thrown so the retry
|
|
1871
|
+
* form can decide (retryable errno + attempts left → sleep and go again)
|
|
1872
|
+
* before anything reaches the exception cell. `path` is +1 when err != 0;
|
|
1873
|
+
* the throwers release it after scr_fs_throw. */
|
|
1874
|
+
typedef struct {
|
|
1875
|
+
int err;
|
|
1876
|
+
const char *op;
|
|
1877
|
+
ScrStr *path;
|
|
1878
|
+
} ScrRmFail;
|
|
1879
|
+
|
|
1880
|
+
static void scr_rm_fail_set(ScrRmFail *f, int err, const char *op, const char *path, size_t len) {
|
|
1881
|
+
if (f->err != 0) return; /* first failure wins, exactly like the old unwind */
|
|
1882
|
+
f->err = err;
|
|
1883
|
+
f->op = op;
|
|
1884
|
+
f->path = scr_str_new(path, len);
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
/* Post-order tree removal for rmSync's recursive form. Stops at (and
|
|
1888
|
+
* records) the first failure, with the failing path and syscall name. */
|
|
1889
|
+
static void scr_rm_tree_e(const char *path, size_t len, ScrRmFail *f) {
|
|
1890
|
+
struct stat st;
|
|
1891
|
+
if (lstat(path, &st) != 0) {
|
|
1892
|
+
scr_rm_fail_set(f, errno, "lstat", path, len);
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
if (!S_ISDIR(st.st_mode)) {
|
|
1896
|
+
if (unlink(path) != 0) scr_rm_fail_set(f, errno, "unlink", path, len);
|
|
1897
|
+
return;
|
|
1898
|
+
}
|
|
1899
|
+
DIR *d = opendir(path);
|
|
1900
|
+
if (!d) {
|
|
1901
|
+
scr_rm_fail_set(f, errno, "scandir", path, len);
|
|
1902
|
+
return;
|
|
1903
|
+
}
|
|
1904
|
+
const struct dirent *ent;
|
|
1905
|
+
while ((ent = readdir(d)) != NULL) {
|
|
1906
|
+
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
|
|
1907
|
+
size_t namelen = strlen(ent->d_name);
|
|
1908
|
+
char *child = malloc(len + 1 + namelen + 1);
|
|
1909
|
+
if (!child) {
|
|
1910
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
1911
|
+
abort();
|
|
1912
|
+
}
|
|
1913
|
+
memcpy(child, path, len);
|
|
1914
|
+
child[len] = '/';
|
|
1915
|
+
memcpy(child + len + 1, ent->d_name, namelen + 1);
|
|
1916
|
+
scr_rm_tree_e(child, len + 1 + namelen, f);
|
|
1917
|
+
free(child);
|
|
1918
|
+
if (f->err != 0) {
|
|
1919
|
+
closedir(d);
|
|
1920
|
+
return;
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
closedir(d);
|
|
1924
|
+
if (rmdir(path) != 0) scr_rm_fail_set(f, errno, "rmdir", path, len);
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
/* One rm attempt (the shared core of both option forms): lstat dispatch,
|
|
1928
|
+
* force's ENOENT swallow, the non-recursive directory rejection, and the
|
|
1929
|
+
* tree walk — failures recorded in `f`, never thrown. */
|
|
1930
|
+
static void scr_fs_rm_attempt(ScrStr *path, bool recursive, bool force, ScrRmFail *f) {
|
|
1931
|
+
struct stat st;
|
|
1932
|
+
if (lstat(path->data, &st) != 0) {
|
|
1933
|
+
if (force && errno == ENOENT) return; /* Node: force swallows ENOENT */
|
|
1934
|
+
scr_rm_fail_set(f, errno, "lstat", path->data, path->len);
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1937
|
+
if (S_ISDIR(st.st_mode)) {
|
|
1938
|
+
if (!recursive) {
|
|
1939
|
+
/* Node throws ERR_FS_EISDIR here; the EISDIR-prefixed wording is
|
|
1940
|
+
* divergence 13's documented difference. */
|
|
1941
|
+
scr_rm_fail_set(f, EISDIR, "rm", path->data, path->len);
|
|
1942
|
+
return;
|
|
1943
|
+
}
|
|
1944
|
+
scr_rm_tree_e(path->data, path->len, f);
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
if (unlink(path->data) != 0) scr_rm_fail_set(f, errno, "unlink", path->data, path->len);
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
void scr_fs_rm_opts(ScrStr *path, bool recursive, bool force) {
|
|
1951
|
+
ScrRmFail f = {0, NULL, NULL};
|
|
1952
|
+
scr_fs_rm_attempt(path, recursive, force, &f);
|
|
1953
|
+
if (f.err != 0) {
|
|
1954
|
+
scr_fs_throw(f.err, f.op, f.path);
|
|
1955
|
+
scr_str_release(f.path);
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
/* rmSync(p, { recursive, force, maxRetries, retryDelay }): Node retries
|
|
1960
|
+
* the operation on EBUSY/EMFILE/ENFILE/ENOTEMPTY/EPERM up to maxRetries
|
|
1961
|
+
* times, waiting retryDelay ms LONGER on each try (linear backoff — the
|
|
1962
|
+
* fs.rmSync documented semantics). Everything else throws immediately
|
|
1963
|
+
* with the failing path, exactly like the plain options form. */
|
|
1964
|
+
void scr_fs_rm_opts_retry(ScrStr *path, bool recursive, bool force, double max_retries, double retry_delay) {
|
|
1965
|
+
long tries = max_retries > 0 ? (long)max_retries : 0;
|
|
1966
|
+
double delay_ms = retry_delay > 0 ? retry_delay : 0;
|
|
1967
|
+
ScrRmFail f = {0, NULL, NULL};
|
|
1968
|
+
for (long attempt = 0;; attempt++) {
|
|
1969
|
+
f.err = 0;
|
|
1970
|
+
f.op = NULL;
|
|
1971
|
+
if (f.path) {
|
|
1972
|
+
scr_str_release(f.path);
|
|
1973
|
+
f.path = NULL;
|
|
1974
|
+
}
|
|
1975
|
+
scr_fs_rm_attempt(path, recursive, force, &f);
|
|
1976
|
+
if (f.err == 0) return;
|
|
1977
|
+
bool retryable = f.err == EBUSY || f.err == EMFILE || f.err == ENFILE ||
|
|
1978
|
+
f.err == ENOTEMPTY || f.err == EPERM;
|
|
1979
|
+
if (!retryable || attempt >= tries) break;
|
|
1980
|
+
double ms = delay_ms * (double)(attempt + 1);
|
|
1981
|
+
if (ms > 0) {
|
|
1982
|
+
struct timespec ts;
|
|
1983
|
+
ts.tv_sec = (time_t)(ms / 1000.0);
|
|
1984
|
+
ts.tv_nsec = (long)((ms - (double)ts.tv_sec * 1000.0) * 1000000.0);
|
|
1985
|
+
nanosleep(&ts, NULL);
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
scr_fs_throw(f.err, f.op, f.path);
|
|
1989
|
+
scr_str_release(f.path);
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
#ifdef _WIN32
|
|
1993
|
+
/* No mkdtemp in the CRT: libuv's own fallback shape — six random
|
|
1994
|
+
* [a-z0-9] name characters from the CSPRNG, retried on EEXIST. */
|
|
1995
|
+
static char *scr_win_mkdtemp(char *tmpl) {
|
|
1996
|
+
static const char cs[] = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
1997
|
+
size_t len = strlen(tmpl);
|
|
1998
|
+
for (int tries = 0; tries < 32; tries++) {
|
|
1999
|
+
unsigned char r[6];
|
|
2000
|
+
arc4random_buf(r, sizeof r);
|
|
2001
|
+
for (size_t i = 0; i < 6; i++) tmpl[len - 6 + i] = cs[r[i] % 36];
|
|
2002
|
+
if (mkdir(tmpl) == 0) return tmpl;
|
|
2003
|
+
if (errno != EEXIST) break;
|
|
2004
|
+
}
|
|
2005
|
+
memcpy(tmpl + len - 6, "XXXXXX", 6); /* the error message shows the template */
|
|
2006
|
+
return NULL;
|
|
2007
|
+
}
|
|
2008
|
+
#define mkdtemp scr_win_mkdtemp
|
|
2009
|
+
#endif
|
|
2010
|
+
|
|
2011
|
+
ScrStr *scr_fs_mkdtemp(ScrStr *prefix) {
|
|
2012
|
+
char *tmpl = malloc(prefix->len + 7);
|
|
2013
|
+
if (!tmpl) {
|
|
2014
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
2015
|
+
abort();
|
|
2016
|
+
}
|
|
2017
|
+
memcpy(tmpl, prefix->data, prefix->len);
|
|
2018
|
+
memcpy(tmpl + prefix->len, "XXXXXX", 7);
|
|
2019
|
+
if (!mkdtemp(tmpl)) {
|
|
2020
|
+
/* Node reports the template, X's included: mkdtemp '/nope/x-XXXXXX' */
|
|
2021
|
+
int e = errno;
|
|
2022
|
+
ScrStr *shown = scr_str_new(tmpl, prefix->len + 6);
|
|
2023
|
+
scr_fs_throw(e, "mkdtemp", shown);
|
|
2024
|
+
scr_str_release(shown);
|
|
2025
|
+
free(tmpl);
|
|
2026
|
+
return NULL;
|
|
2027
|
+
}
|
|
2028
|
+
ScrStr *out = scr_str_new(tmpl, prefix->len + 6);
|
|
2029
|
+
free(tmpl);
|
|
2030
|
+
return out;
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
void scr_fs_access(ScrStr *path, double mode) {
|
|
2034
|
+
int m = (int)mode;
|
|
2035
|
+
#ifdef _WIN32
|
|
2036
|
+
/* The CRT access() rejects X_OK (there is no execute bit); Node on
|
|
2037
|
+
* Windows treats X_OK as F_OK, so mask it down to the R/W bits. */
|
|
2038
|
+
m &= 6;
|
|
2039
|
+
#endif
|
|
2040
|
+
if (access(path->data, m) != 0) scr_fs_throw(errno, "access", path);
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
/* The no-path variant of the fs error shape — Node's fd reads report
|
|
2044
|
+
* "EBADF: bad file descriptor, read" with no quoted path. */
|
|
2045
|
+
static void scr_fs_throw_nopath(int e, const char *op) {
|
|
2046
|
+
char namebuf[16];
|
|
2047
|
+
const char *name = scr_errno_name(e, namebuf, sizeof namebuf);
|
|
2048
|
+
const char *text = scr_errno_text(e);
|
|
2049
|
+
char msg[256];
|
|
2050
|
+
int len = snprintf(msg, sizeof msg, "%s: %s, %s", name, text, op);
|
|
2051
|
+
scr_throw_error_msg(SCR_ERR_ERROR, msg, (size_t)len);
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
/* read(2) loop to EOF from the CURRENT position — Node's
|
|
2055
|
+
* readFileSync(fd) semantics for pipes and files alike (the stdin
|
|
2056
|
+
* pattern: readFileSync(0, "utf8")). Returns the malloc'd buffer and
|
|
2057
|
+
* its length, or NULL with the exception pending. */
|
|
2058
|
+
static char *scr_read_fd_all(double fd, size_t *out_len) {
|
|
2059
|
+
size_t cap = 4096, len = 0;
|
|
2060
|
+
char *buf = malloc(cap);
|
|
2061
|
+
if (!buf) {
|
|
2062
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
2063
|
+
abort();
|
|
2064
|
+
}
|
|
2065
|
+
for (;;) {
|
|
2066
|
+
if (cap - len < 2048) {
|
|
2067
|
+
cap *= 2;
|
|
2068
|
+
char *grown = realloc(buf, cap);
|
|
2069
|
+
if (!grown) {
|
|
2070
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
2071
|
+
abort();
|
|
2072
|
+
}
|
|
2073
|
+
buf = grown;
|
|
2074
|
+
}
|
|
2075
|
+
ssize_t n = read((int)fd, buf + len, cap - len);
|
|
2076
|
+
if (n < 0) {
|
|
2077
|
+
if (errno == EINTR) continue;
|
|
2078
|
+
int e = errno;
|
|
2079
|
+
free(buf);
|
|
2080
|
+
scr_fs_throw_nopath(e, "read");
|
|
2081
|
+
return NULL;
|
|
2082
|
+
}
|
|
2083
|
+
if (n == 0) break;
|
|
2084
|
+
len += (size_t)n;
|
|
2085
|
+
}
|
|
2086
|
+
*out_len = len;
|
|
2087
|
+
return buf;
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
ScrStr *scr_fs_read_fd(double fd) {
|
|
2091
|
+
size_t len;
|
|
2092
|
+
char *buf = scr_read_fd_all(fd, &len);
|
|
2093
|
+
if (!buf) return NULL;
|
|
2094
|
+
ScrStr *s = scr_str_new(buf, len);
|
|
2095
|
+
free(buf);
|
|
2096
|
+
return s;
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
ScrBytes *scr_fs_read_fd_bytes(double fd) {
|
|
2100
|
+
size_t len;
|
|
2101
|
+
char *buf = scr_read_fd_all(fd, &len);
|
|
2102
|
+
if (!buf) return NULL;
|
|
2103
|
+
ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)len);
|
|
2104
|
+
if (len > 0) memcpy(b->data, buf, len);
|
|
2105
|
+
free(buf);
|
|
2106
|
+
return b;
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
/* ── Atomics.wait: the synchronous-sleep idiom ───────────────────────
|
|
2110
|
+
* Atomics.wait(int32Array, idx, expected, timeoutMs). scriptc has no
|
|
2111
|
+
* threads: nothing can ever notify a waiter, so the spec's behavior for
|
|
2112
|
+
* every compilable program is exactly "compare, then sleep out the
|
|
2113
|
+
* timeout" — "not-equal" immediately when the element differs from
|
|
2114
|
+
* `expected`, "timed-out" after a real nanosleep otherwise ("ok" is
|
|
2115
|
+
* unreachable; the compiler requires the timeout argument, since an
|
|
2116
|
+
* infinite wait here would be a certain deadlock). The sleep resumes
|
|
2117
|
+
* across EINTR so signals don't shorten it. Timeout semantics follow the
|
|
2118
|
+
* spec: NaN/+Infinity would be infinite (compiler-fenced by requiring
|
|
2119
|
+
* the argument, but a runtime NaN clamps to 0 defensively), negatives
|
|
2120
|
+
* clamp to 0. */
|
|
2121
|
+
ScrStr *scr_atomics_wait(ScrBytes *arr, double idx, double expected, double timeout_ms) {
|
|
2122
|
+
double have = scr_bytes_get(arr, idx); /* traps out-of-range like every access */
|
|
2123
|
+
/* The comparison is on the stored int32 vs ToInt32(expected). */
|
|
2124
|
+
double t = expected;
|
|
2125
|
+
if (t != t || isinf(t)) t = 0;
|
|
2126
|
+
else {
|
|
2127
|
+
t = trunc(t);
|
|
2128
|
+
t = fmod(t, 4294967296.0);
|
|
2129
|
+
if (t < 0) t += 4294967296.0;
|
|
2130
|
+
if (t >= 2147483648.0) t -= 4294967296.0;
|
|
2131
|
+
}
|
|
2132
|
+
if (have != t) return scr_str_new("not-equal", 9);
|
|
2133
|
+
double ms = timeout_ms;
|
|
2134
|
+
if (ms != ms || ms < 0) ms = 0;
|
|
2135
|
+
if (ms > 0) {
|
|
2136
|
+
struct timespec left = {
|
|
2137
|
+
(time_t)(ms / 1000.0),
|
|
2138
|
+
(long)((ms - (double)(time_t)(ms / 1000.0) * 1000.0) * 1e6),
|
|
2139
|
+
};
|
|
2140
|
+
struct timespec rem;
|
|
2141
|
+
while (nanosleep(&left, &rem) != 0 && errno == EINTR) left = rem;
|
|
2142
|
+
}
|
|
2143
|
+
return scr_str_new("timed-out", 9);
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
/* ── the tty probes ──────────────────────────────────────────────────── */
|
|
2147
|
+
|
|
2148
|
+
bool scr_process_is_tty(double fd) { return isatty((int)fd) != 0; }
|
|
2149
|
+
|
|
2150
|
+
/* Terminal width for process.stdout/stderr.columns: ioctl(TIOCGWINSZ) on
|
|
2151
|
+
* the stream's fd, exactly Node's tty.WriteStream source of truth. A
|
|
2152
|
+
* non-TTY stream, or a terminal that refuses the ioctl, answers -1 and
|
|
2153
|
+
* the emitter's union construction turns that into the undefined arm —
|
|
2154
|
+
* Node's missing `.columns` on non-TTY streams. */
|
|
2155
|
+
double scr_process_columns(double fd) {
|
|
2156
|
+
if (!isatty((int)fd)) return -1;
|
|
2157
|
+
#ifdef _WIN32
|
|
2158
|
+
/* The console buffer's window width — libuv's uv_tty_get_winsize. */
|
|
2159
|
+
HANDLE h = (HANDLE)_get_osfhandle((int)fd);
|
|
2160
|
+
CONSOLE_SCREEN_BUFFER_INFO info;
|
|
2161
|
+
if (h == INVALID_HANDLE_VALUE || !GetConsoleScreenBufferInfo(h, &info)) return -1;
|
|
2162
|
+
return (double)(info.srWindow.Right - info.srWindow.Left + 1);
|
|
2163
|
+
#else
|
|
2164
|
+
struct winsize ws;
|
|
2165
|
+
if (ioctl((int)fd, TIOCGWINSZ, &ws) != 0) return -1;
|
|
2166
|
+
return (double)ws.ws_col;
|
|
2167
|
+
#endif
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
/* process.stdin.setRawMode(mode). TTY stdin: libuv's UV_TTY_MODE_RAW
|
|
2171
|
+
* termios flag set — exactly what Node's setRawMode(true) applies — and
|
|
2172
|
+
* setRawMode(false) restores the termios saved at the first raw entry
|
|
2173
|
+
* (libuv's orig_termios), a no-op when raw mode was never entered.
|
|
2174
|
+
* NON-TTY stdin: Node's process.stdin is a Socket with no setRawMode
|
|
2175
|
+
* member at all, so the call throws Node's exact catchable TypeError. */
|
|
2176
|
+
#ifdef _WIN32
|
|
2177
|
+
static DWORD scr_stdin_cooked;
|
|
2178
|
+
static bool scr_stdin_cooked_saved = false;
|
|
2179
|
+
|
|
2180
|
+
void scr_process_stdin_set_raw_mode(bool raw) {
|
|
2181
|
+
if (!isatty(0)) {
|
|
2182
|
+
const char msg[] = "process.stdin.setRawMode is not a function";
|
|
2183
|
+
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
2186
|
+
/* libuv's uv_tty_set_mode(UV_TTY_MODE_RAW) console half: drop line
|
|
2187
|
+
* buffering, echo, and Ctrl-C cooking; restore the entry mode on the
|
|
2188
|
+
* way back — the termios save/restore shape, translated. */
|
|
2189
|
+
HANDLE h = (HANDLE)_get_osfhandle(0);
|
|
2190
|
+
DWORD mode;
|
|
2191
|
+
if (h == INVALID_HANDLE_VALUE || !GetConsoleMode(h, &mode)) return;
|
|
2192
|
+
if (raw) {
|
|
2193
|
+
if (!scr_stdin_cooked_saved) {
|
|
2194
|
+
scr_stdin_cooked = mode;
|
|
2195
|
+
scr_stdin_cooked_saved = true;
|
|
2196
|
+
}
|
|
2197
|
+
mode &= ~(DWORD)(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
|
|
2198
|
+
(void)SetConsoleMode(h, mode);
|
|
2199
|
+
} else if (scr_stdin_cooked_saved) {
|
|
2200
|
+
(void)SetConsoleMode(h, scr_stdin_cooked);
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
#else
|
|
2204
|
+
static struct termios scr_stdin_cooked;
|
|
2205
|
+
static bool scr_stdin_cooked_saved = false;
|
|
2206
|
+
|
|
2207
|
+
void scr_process_stdin_set_raw_mode(bool raw) {
|
|
2208
|
+
if (!isatty(0)) {
|
|
2209
|
+
const char msg[] = "process.stdin.setRawMode is not a function";
|
|
2210
|
+
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
|
2211
|
+
return;
|
|
2212
|
+
}
|
|
2213
|
+
if (raw) {
|
|
2214
|
+
struct termios t;
|
|
2215
|
+
if (tcgetattr(0, &t) != 0) return;
|
|
2216
|
+
if (!scr_stdin_cooked_saved) {
|
|
2217
|
+
scr_stdin_cooked = t;
|
|
2218
|
+
scr_stdin_cooked_saved = true;
|
|
2219
|
+
}
|
|
2220
|
+
/* libuv uv__tty_make_raw (UV_TTY_MODE_RAW) */
|
|
2221
|
+
t.c_iflag &= (tcflag_t)~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
|
2222
|
+
t.c_oflag |= (tcflag_t)ONLCR;
|
|
2223
|
+
t.c_cflag |= (tcflag_t)CS8;
|
|
2224
|
+
t.c_lflag &= (tcflag_t)~(ECHO | ICANON | IEXTEN | ISIG);
|
|
2225
|
+
t.c_cc[VMIN] = 1;
|
|
2226
|
+
t.c_cc[VTIME] = 0;
|
|
2227
|
+
(void)tcsetattr(0, TCSADRAIN, &t);
|
|
2228
|
+
} else if (scr_stdin_cooked_saved) {
|
|
2229
|
+
(void)tcsetattr(0, TCSADRAIN, &scr_stdin_cooked);
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
#endif /* _WIN32 */
|
|
2233
|
+
|
|
2234
|
+
/* Node's destroy() tears down the stream: the events unit (scr_events.c)
|
|
2235
|
+
* drops every stdin listener, stops watching fd 0, and ends a running
|
|
2236
|
+
* for-await — nothing fires after, and the loop stops keeping the
|
|
2237
|
+
* process alive for stdin. With the events unit not linked there is
|
|
2238
|
+
* nothing to tear down and the call is a no-op, as before. */
|
|
2239
|
+
void scr_process_stdin_destroy(void) {
|
|
2240
|
+
if (scr_stdin_destroy_hook != NULL) scr_stdin_destroy_hook();
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
/* ── Stats values ────────────────────────────────────────────────────
|
|
2244
|
+
* An immutable snapshot of stat(2) results — the slice the lowered
|
|
2245
|
+
* surface exposes (isFile/isDirectory/size). statSync THROWS like the
|
|
2246
|
+
* other sync fs calls; the promise form rejects (see the fsp section). */
|
|
2247
|
+
|
|
2248
|
+
struct ScrStats {
|
|
2249
|
+
size_t rc;
|
|
2250
|
+
bool is_file;
|
|
2251
|
+
bool is_dir;
|
|
2252
|
+
bool is_symlink; /* lstat only — a followed stat never sees one */
|
|
2253
|
+
double size;
|
|
2254
|
+
double mtime_ms; /* milliseconds with the nanosecond fraction (Node) */
|
|
2255
|
+
};
|
|
2256
|
+
|
|
2257
|
+
ScrStats *scr_stats_retain(ScrStats *s) {
|
|
2258
|
+
if (s->rc != SIZE_MAX) s->rc++;
|
|
2259
|
+
return s;
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
void scr_stats_release(ScrStats *s) {
|
|
2263
|
+
if (!s || s->rc == SIZE_MAX) return;
|
|
2264
|
+
if (--s->rc == 0) free(s);
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
void *scr_stats_retain_v(void *p) { return scr_stats_retain(p); }
|
|
2268
|
+
void scr_stats_release_v(void *p) { scr_stats_release(p); }
|
|
2269
|
+
|
|
2270
|
+
bool scr_stats_is_file(ScrStats *s) { return s->is_file; }
|
|
2271
|
+
bool scr_stats_is_dir(ScrStats *s) { return s->is_dir; }
|
|
2272
|
+
bool scr_stats_is_symlink(ScrStats *s) { return s->is_symlink; }
|
|
2273
|
+
double scr_stats_size(ScrStats *s) { return s->size; }
|
|
2274
|
+
double scr_stats_mtime_ms(ScrStats *s) { return s->mtime_ms; }
|
|
2275
|
+
|
|
2276
|
+
static ScrStats *scr_stats_of(const struct stat *st) {
|
|
2277
|
+
ScrStats *s = malloc(sizeof(ScrStats));
|
|
2278
|
+
if (!s) {
|
|
2279
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
2280
|
+
abort();
|
|
2281
|
+
}
|
|
2282
|
+
s->rc = 1;
|
|
2283
|
+
s->is_file = S_ISREG(st->st_mode);
|
|
2284
|
+
s->is_dir = S_ISDIR(st->st_mode);
|
|
2285
|
+
#if defined(_WIN32)
|
|
2286
|
+
/* CRT stat has no symlink view (lstat above degrades to stat) and no
|
|
2287
|
+
* sub-second mtime — whole seconds where Node reads the FILETIME's
|
|
2288
|
+
* 100ns units (divergence: mtimeMs precision; mechanical fix is
|
|
2289
|
+
* GetFileAttributesEx). */
|
|
2290
|
+
s->is_symlink = false;
|
|
2291
|
+
s->mtime_ms = (double)st->st_mtime * 1000.0;
|
|
2292
|
+
#else
|
|
2293
|
+
s->is_symlink = S_ISLNK(st->st_mode);
|
|
2294
|
+
#if defined(__APPLE__)
|
|
2295
|
+
s->mtime_ms = (double)st->st_mtimespec.tv_sec * 1000.0 +
|
|
2296
|
+
(double)st->st_mtimespec.tv_nsec / 1e6;
|
|
2297
|
+
#else
|
|
2298
|
+
s->mtime_ms = (double)st->st_mtim.tv_sec * 1000.0 +
|
|
2299
|
+
(double)st->st_mtim.tv_nsec / 1e6;
|
|
2300
|
+
#endif
|
|
2301
|
+
#endif
|
|
2302
|
+
s->size = (double)st->st_size;
|
|
2303
|
+
return s;
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
ScrStats *scr_fs_stat(ScrStr *path) {
|
|
2307
|
+
struct stat st;
|
|
2308
|
+
if (stat(path->data, &st) != 0) { /* follows symlinks, like Node's statSync */
|
|
2309
|
+
scr_fs_throw(errno, "stat", path);
|
|
2310
|
+
return NULL;
|
|
2311
|
+
}
|
|
2312
|
+
return scr_stats_of(&st);
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
ScrStats *scr_fs_lstat(ScrStr *path) {
|
|
2316
|
+
struct stat st;
|
|
2317
|
+
if (lstat(path->data, &st) != 0) { /* no follow; Node reports lstat */
|
|
2318
|
+
scr_fs_throw(errno, "lstat", path);
|
|
2319
|
+
return NULL;
|
|
2320
|
+
}
|
|
2321
|
+
return scr_stats_of(&st);
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
ScrArr *scr_fs_readdir(ScrStr *path) {
|
|
2325
|
+
DIR *d = opendir(path->data);
|
|
2326
|
+
if (!d) {
|
|
2327
|
+
scr_fs_throw(errno, "scandir", path); /* Node reports scandir */
|
|
2328
|
+
return NULL;
|
|
2329
|
+
}
|
|
2330
|
+
ScrArr *arr = scr_arr_new(SCR_ELEM_STR, 8);
|
|
2331
|
+
const struct dirent *ent;
|
|
2332
|
+
while ((ent = readdir(d)) != NULL) {
|
|
2333
|
+
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
|
|
2334
|
+
scr_arr_push_ref(arr, scr_str_new(ent->d_name, strlen(ent->d_name)));
|
|
2335
|
+
}
|
|
2336
|
+
closedir(d);
|
|
2337
|
+
return arr; /* OS order, exactly like Node (unsorted) */
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
/* ── the withFileTypes scandir snapshot ──────────────────────────────
|
|
2341
|
+
* One readdir pass capturing name + entry kind for the emitter's Dirent
|
|
2342
|
+
* assembly (scr_runtime.h has the contract). Kinds are libuv's UV_DIRENT
|
|
2343
|
+
* encoding; DT_UNKNOWN (filesystems that don't fill d_type) falls back
|
|
2344
|
+
* to lstat(2) on the joined path — Node's own getDirents rule (a failed
|
|
2345
|
+
* lstat leaves 0/unknown: every is*() probe answers false, like Node's
|
|
2346
|
+
* Dirent over an UNKNOWN row it could not stat). */
|
|
2347
|
+
|
|
2348
|
+
struct ScrScandir {
|
|
2349
|
+
size_t len, cap;
|
|
2350
|
+
ScrStr **names;
|
|
2351
|
+
unsigned char *kinds;
|
|
2352
|
+
};
|
|
2353
|
+
|
|
2354
|
+
static unsigned char scr_dirent_kind_of_mode(mode_t m) {
|
|
2355
|
+
if (S_ISREG(m)) return 1;
|
|
2356
|
+
if (S_ISDIR(m)) return 2;
|
|
2357
|
+
#ifdef S_ISLNK /* no symlink/socket bits in the CRT's stat */
|
|
2358
|
+
if (S_ISLNK(m)) return 3;
|
|
2359
|
+
#endif
|
|
2360
|
+
if (S_ISFIFO(m)) return 4;
|
|
2361
|
+
#ifdef S_ISSOCK
|
|
2362
|
+
if (S_ISSOCK(m)) return 5;
|
|
2363
|
+
#endif
|
|
2364
|
+
if (S_ISCHR(m)) return 6;
|
|
2365
|
+
if (S_ISBLK(m)) return 7;
|
|
2366
|
+
return 0;
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
ScrScandir *scr_fs_scandir(ScrStr *path) {
|
|
2370
|
+
DIR *d = opendir(path->data);
|
|
2371
|
+
if (!d) {
|
|
2372
|
+
scr_fs_throw(errno, "scandir", path); /* Node reports scandir */
|
|
2373
|
+
return NULL;
|
|
2374
|
+
}
|
|
2375
|
+
ScrScandir *s = malloc(sizeof *s);
|
|
2376
|
+
if (!s) {
|
|
2377
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
2378
|
+
abort();
|
|
2379
|
+
}
|
|
2380
|
+
s->len = 0;
|
|
2381
|
+
s->cap = 8;
|
|
2382
|
+
s->names = malloc(s->cap * sizeof *s->names);
|
|
2383
|
+
s->kinds = malloc(s->cap);
|
|
2384
|
+
if (!s->names || !s->kinds) {
|
|
2385
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
2386
|
+
abort();
|
|
2387
|
+
}
|
|
2388
|
+
const struct dirent *ent;
|
|
2389
|
+
while ((ent = readdir(d)) != NULL) {
|
|
2390
|
+
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
|
|
2391
|
+
unsigned char kind;
|
|
2392
|
+
#ifdef DT_REG
|
|
2393
|
+
switch (ent->d_type) {
|
|
2394
|
+
case DT_REG: kind = 1; break;
|
|
2395
|
+
case DT_DIR: kind = 2; break;
|
|
2396
|
+
case DT_LNK: kind = 3; break;
|
|
2397
|
+
case DT_FIFO: kind = 4; break;
|
|
2398
|
+
case DT_SOCK: kind = 5; break;
|
|
2399
|
+
case DT_CHR: kind = 6; break;
|
|
2400
|
+
case DT_BLK: kind = 7; break;
|
|
2401
|
+
default: { /* DT_UNKNOWN: the lstat fallback */
|
|
2402
|
+
char buf[4096];
|
|
2403
|
+
int w = snprintf(buf, sizeof buf, "%s/%s", path->data, ent->d_name);
|
|
2404
|
+
struct stat st;
|
|
2405
|
+
kind = (w > 0 && (size_t)w < sizeof buf && lstat(buf, &st) == 0)
|
|
2406
|
+
? scr_dirent_kind_of_mode(st.st_mode)
|
|
2407
|
+
: 0;
|
|
2408
|
+
break;
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
#else
|
|
2412
|
+
{ /* mingw dirent has no d_type: every row takes the stat fallback
|
|
2413
|
+
* (lstat degrades to stat on Windows — see the include seam). */
|
|
2414
|
+
char buf[4096];
|
|
2415
|
+
int w = snprintf(buf, sizeof buf, "%s/%s", path->data, ent->d_name);
|
|
2416
|
+
struct stat st;
|
|
2417
|
+
kind = (w > 0 && (size_t)w < sizeof buf && lstat(buf, &st) == 0)
|
|
2418
|
+
? scr_dirent_kind_of_mode(st.st_mode)
|
|
2419
|
+
: 0;
|
|
2420
|
+
}
|
|
2421
|
+
#endif
|
|
2422
|
+
if (s->len == s->cap) {
|
|
2423
|
+
s->cap *= 2;
|
|
2424
|
+
s->names = realloc(s->names, s->cap * sizeof *s->names);
|
|
2425
|
+
s->kinds = realloc(s->kinds, s->cap);
|
|
2426
|
+
if (!s->names || !s->kinds) {
|
|
2427
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
2428
|
+
abort();
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
s->names[s->len] = scr_str_new(ent->d_name, strlen(ent->d_name));
|
|
2432
|
+
s->kinds[s->len] = kind;
|
|
2433
|
+
s->len++;
|
|
2434
|
+
}
|
|
2435
|
+
closedir(d);
|
|
2436
|
+
return s;
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
size_t scr_fs_scandir_count(const ScrScandir *s) { return s->len; }
|
|
2440
|
+
|
|
2441
|
+
ScrStr *scr_fs_scandir_name(const ScrScandir *s, size_t i) {
|
|
2442
|
+
return scr_str_retain(s->names[i]);
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2445
|
+
double scr_fs_scandir_type(const ScrScandir *s, size_t i) { return (double)s->kinds[i]; }
|
|
2446
|
+
|
|
2447
|
+
void scr_fs_scandir_free(ScrScandir *s) {
|
|
2448
|
+
for (size_t i = 0; i < s->len; i++) scr_str_release(s->names[i]);
|
|
2449
|
+
free(s->names);
|
|
2450
|
+
free(s->kinds);
|
|
2451
|
+
free(s);
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
/* ── node:crypto (the string-producing slice) ────────────────────────
|
|
2455
|
+
* Buffers aren't representable, so the lowered surface is exactly the
|
|
2456
|
+
* string-producing forms: randomUUID(), and the COMPOSED pattern
|
|
2457
|
+
* randomBytes(n).toString("hex"|"base64") — one libCall, the Buffer never
|
|
2458
|
+
* escapes. Randomness comes from arc4random_buf (the CSPRNG both macOS
|
|
2459
|
+
* and modern glibc provide). */
|
|
2460
|
+
|
|
2461
|
+
/* ── the scalar Math statics ─────────────────────────────────────────
|
|
2462
|
+
* Math.min/max at two arguments: the ECMA folds — C's fmin/fmax are NOT
|
|
2463
|
+
* these (they return the non-NaN operand where JS lets NaN poison, and
|
|
2464
|
+
* leave the ±0 order unspecified). Math.random(): a uniform double in
|
|
2465
|
+
* [0,1) at the spec's 53-bit granularity, drawn from arc4random_buf —
|
|
2466
|
+
* the same CSPRNG behind the crypto lowerings. Same distribution as
|
|
2467
|
+
* Node's, necessarily a different sequence (SEMANTICS.md 62); range and
|
|
2468
|
+
* granularity are pinned differentially by invariant, not by bytes. */
|
|
2469
|
+
|
|
2470
|
+
double scr_math_min(double a, double b) {
|
|
2471
|
+
if (isnan(a) || isnan(b)) return (double)NAN;
|
|
2472
|
+
if (a == 0.0 && b == 0.0) return signbit(a) ? a : b; /* -0 wins */
|
|
2473
|
+
return a < b ? a : b;
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
double scr_math_max(double a, double b) {
|
|
2477
|
+
if (isnan(a) || isnan(b)) return (double)NAN;
|
|
2478
|
+
if (a == 0.0 && b == 0.0) return signbit(a) ? b : a; /* +0 wins */
|
|
2479
|
+
return a > b ? a : b;
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
/* Math.round: ECMA half-toward-+Infinity. NOT C round() (half away from
|
|
2483
|
+
* zero: round(-1.5) is -2 where JS answers -1) and NOT floor(x + 0.5)
|
|
2484
|
+
* (the float ADD drifts at the epsilon boundary: 0.49999999999999994 +
|
|
2485
|
+
* 0.5 == 1.0 in doubles where the exact sum is below one — JS answers
|
|
2486
|
+
* 0). x - floor(x) is EXACT for doubles (Sterbenz), so the fraction
|
|
2487
|
+
* comparison decides losslessly; results in (-0.5, 0] keep the sign (JS:
|
|
2488
|
+
* Math.round(-0.3) is -0). */
|
|
2489
|
+
double scr_math_round(double x) {
|
|
2490
|
+
if (isnan(x) || isinf(x) || x == 0.0) return x;
|
|
2491
|
+
double f = floor(x);
|
|
2492
|
+
double diff = x - f;
|
|
2493
|
+
double r = diff < 0.5 ? f : f + 1.0;
|
|
2494
|
+
return (r == 0.0 && x < 0.0) ? -0.0 : r;
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
double scr_math_random(void) {
|
|
2498
|
+
uint64_t r;
|
|
2499
|
+
arc4random_buf(&r, sizeof r);
|
|
2500
|
+
/* The top 53 bits scaled by 2^-53: every representable k/2^53 in [0,1)
|
|
2501
|
+
* is equally likely — V8's own construction. */
|
|
2502
|
+
return (double)(r >> 11) * 0x1.0p-53;
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
ScrStr *scr_crypto_random_uuid(void) {
|
|
2506
|
+
unsigned char b[16];
|
|
2507
|
+
arc4random_buf(b, sizeof b);
|
|
2508
|
+
b[6] = (unsigned char)((b[6] & 0x0f) | 0x40); /* version 4 */
|
|
2509
|
+
b[8] = (unsigned char)((b[8] & 0x3f) | 0x80); /* variant 10xx */
|
|
2510
|
+
char out[37];
|
|
2511
|
+
static const char hex[] = "0123456789abcdef";
|
|
2512
|
+
size_t o = 0;
|
|
2513
|
+
for (size_t i = 0; i < 16; i++) {
|
|
2514
|
+
if (i == 4 || i == 6 || i == 8 || i == 10) out[o++] = '-';
|
|
2515
|
+
out[o++] = hex[b[i] >> 4];
|
|
2516
|
+
out[o++] = hex[b[i] & 0x0f];
|
|
2517
|
+
}
|
|
2518
|
+
return scr_str_new(out, 36);
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
/* randomBytes(n).toString(enc): n truncates like Node's (1.5 → 1 byte);
|
|
2522
|
+
* out-of-range n THROWS Node's RangeError verbatim. enc is "hex" or
|
|
2523
|
+
* "base64" (the compiler fences other encodings at the call site). */
|
|
2524
|
+
ScrStr *scr_crypto_random_string(double n, ScrStr *enc) {
|
|
2525
|
+
if (!(n >= 0 && n <= 2147483647)) {
|
|
2526
|
+
char num[32];
|
|
2527
|
+
size_t numlen = scr_f64_to_str(n, num);
|
|
2528
|
+
char msg[128];
|
|
2529
|
+
int mlen = snprintf(msg, sizeof msg,
|
|
2530
|
+
"The value of \"size\" is out of range. It must be >= 0 && <= 2147483647. Received %.*s",
|
|
2531
|
+
(int)numlen, num);
|
|
2532
|
+
scr_throw_error_msg(SCR_ERR_RANGE, msg, (size_t)mlen);
|
|
2533
|
+
return NULL;
|
|
2534
|
+
}
|
|
2535
|
+
size_t size = (size_t)n;
|
|
2536
|
+
unsigned char *bytes = malloc(size ? size : 1);
|
|
2537
|
+
if (!bytes) {
|
|
2538
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
2539
|
+
abort();
|
|
2540
|
+
}
|
|
2541
|
+
arc4random_buf(bytes, size);
|
|
2542
|
+
ScrStr *out;
|
|
2543
|
+
if (enc->len == 3 && memcmp(enc->data, "hex", 3) == 0) {
|
|
2544
|
+
static const char hex[] = "0123456789abcdef";
|
|
2545
|
+
char *buf = malloc(size * 2 + 1);
|
|
2546
|
+
if (!buf) {
|
|
2547
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
2548
|
+
abort();
|
|
2549
|
+
}
|
|
2550
|
+
for (size_t i = 0; i < size; i++) {
|
|
2551
|
+
buf[i * 2] = hex[bytes[i] >> 4];
|
|
2552
|
+
buf[i * 2 + 1] = hex[bytes[i] & 0x0f];
|
|
2553
|
+
}
|
|
2554
|
+
out = scr_str_new(buf, size * 2);
|
|
2555
|
+
free(buf);
|
|
2556
|
+
} else {
|
|
2557
|
+
/* base64, standard alphabet, '=' padded — Buffer.toString("base64"). */
|
|
2558
|
+
static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
2559
|
+
size_t outlen = (size + 2) / 3 * 4;
|
|
2560
|
+
char *buf = malloc(outlen + 1);
|
|
2561
|
+
if (!buf) {
|
|
2562
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
2563
|
+
abort();
|
|
2564
|
+
}
|
|
2565
|
+
size_t o = 0;
|
|
2566
|
+
for (size_t i = 0; i < size; i += 3) {
|
|
2567
|
+
unsigned v = (unsigned)bytes[i] << 16;
|
|
2568
|
+
if (i + 1 < size) v |= (unsigned)bytes[i + 1] << 8;
|
|
2569
|
+
if (i + 2 < size) v |= (unsigned)bytes[i + 2];
|
|
2570
|
+
buf[o++] = b64[(v >> 18) & 63];
|
|
2571
|
+
buf[o++] = b64[(v >> 12) & 63];
|
|
2572
|
+
buf[o++] = i + 1 < size ? b64[(v >> 6) & 63] : '=';
|
|
2573
|
+
buf[o++] = i + 2 < size ? b64[v & 63] : '=';
|
|
2574
|
+
}
|
|
2575
|
+
out = scr_str_new(buf, o);
|
|
2576
|
+
free(buf);
|
|
2577
|
+
}
|
|
2578
|
+
free(bytes);
|
|
2579
|
+
return out;
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
/* ── SHA-256 (FIPS 180-4) — the composed createHash chain ────────────
|
|
2583
|
+
* createHash("sha256").update(data).digest("hex") fuses into one call in
|
|
2584
|
+
* the compiler (the Hash handle never materializes), so the runtime
|
|
2585
|
+
* surface is just hash-these-bytes-to-hex. Straightforward FIPS 180-4
|
|
2586
|
+
* implementation; the differential corpus pins it against Node's own
|
|
2587
|
+
* digests. */
|
|
2588
|
+
|
|
2589
|
+
static const uint32_t scr_sha256_k[64] = {
|
|
2590
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
|
|
2591
|
+
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
|
2592
|
+
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
|
|
2593
|
+
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
2594
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
|
|
2595
|
+
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
|
2596
|
+
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
|
|
2597
|
+
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
2598
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
|
|
2599
|
+
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
|
2600
|
+
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
|
|
2601
|
+
|
|
2602
|
+
static uint32_t scr_sha256_rotr(uint32_t x, unsigned n) {
|
|
2603
|
+
return (x >> n) | (x << (32 - n));
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
static void scr_sha256_block(uint32_t h[8], const unsigned char *p) {
|
|
2607
|
+
uint32_t w[64];
|
|
2608
|
+
for (int i = 0; i < 16; i++) {
|
|
2609
|
+
w[i] = ((uint32_t)p[i * 4] << 24) | ((uint32_t)p[i * 4 + 1] << 16) |
|
|
2610
|
+
((uint32_t)p[i * 4 + 2] << 8) | (uint32_t)p[i * 4 + 3];
|
|
2611
|
+
}
|
|
2612
|
+
for (int i = 16; i < 64; i++) {
|
|
2613
|
+
uint32_t s0 = scr_sha256_rotr(w[i - 15], 7) ^ scr_sha256_rotr(w[i - 15], 18) ^ (w[i - 15] >> 3);
|
|
2614
|
+
uint32_t s1 = scr_sha256_rotr(w[i - 2], 17) ^ scr_sha256_rotr(w[i - 2], 19) ^ (w[i - 2] >> 10);
|
|
2615
|
+
w[i] = w[i - 16] + s0 + w[i - 7] + s1;
|
|
2616
|
+
}
|
|
2617
|
+
uint32_t a = h[0], b = h[1], c = h[2], d = h[3];
|
|
2618
|
+
uint32_t e = h[4], f = h[5], g = h[6], hh = h[7];
|
|
2619
|
+
for (int i = 0; i < 64; i++) {
|
|
2620
|
+
uint32_t s1 = scr_sha256_rotr(e, 6) ^ scr_sha256_rotr(e, 11) ^ scr_sha256_rotr(e, 25);
|
|
2621
|
+
uint32_t ch = (e & f) ^ (~e & g);
|
|
2622
|
+
uint32_t t1 = hh + s1 + ch + scr_sha256_k[i] + w[i];
|
|
2623
|
+
uint32_t s0 = scr_sha256_rotr(a, 2) ^ scr_sha256_rotr(a, 13) ^ scr_sha256_rotr(a, 22);
|
|
2624
|
+
uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
|
|
2625
|
+
uint32_t t2 = s0 + maj;
|
|
2626
|
+
hh = g; g = f; f = e; e = d + t1;
|
|
2627
|
+
d = c; c = b; b = a; a = t1 + t2;
|
|
2628
|
+
}
|
|
2629
|
+
h[0] += a; h[1] += b; h[2] += c; h[3] += d;
|
|
2630
|
+
h[4] += e; h[5] += f; h[6] += g; h[7] += hh;
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
/* Final block(s) shared shape: the 0x80 terminator, zero padding, 64-bit
|
|
2634
|
+
* big-endian bit length (FIPS 180-4 — SHA-1 and SHA-256 pad alike). */
|
|
2635
|
+
static size_t scr_sha256_digest(const unsigned char *data, size_t len, unsigned char out[32]) {
|
|
2636
|
+
uint32_t h[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
|
2637
|
+
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
|
|
2638
|
+
size_t i = 0;
|
|
2639
|
+
for (; i + 64 <= len; i += 64) scr_sha256_block(h, data + i);
|
|
2640
|
+
unsigned char tail[128];
|
|
2641
|
+
size_t rem = len - i;
|
|
2642
|
+
memcpy(tail, data + i, rem);
|
|
2643
|
+
tail[rem] = 0x80;
|
|
2644
|
+
size_t pad = (rem + 1 + 8 <= 64) ? 64 : 128;
|
|
2645
|
+
memset(tail + rem + 1, 0, pad - rem - 1 - 8);
|
|
2646
|
+
uint64_t bits = (uint64_t)len * 8;
|
|
2647
|
+
for (int b = 0; b < 8; b++) tail[pad - 1 - b] = (unsigned char)(bits >> (8 * b));
|
|
2648
|
+
scr_sha256_block(h, tail);
|
|
2649
|
+
if (pad == 128) scr_sha256_block(h, tail + 64);
|
|
2650
|
+
for (int j = 0; j < 8; j++) {
|
|
2651
|
+
for (int b = 0; b < 4; b++) out[j * 4 + b] = (unsigned char)(h[j] >> (24 - 8 * b));
|
|
2652
|
+
}
|
|
2653
|
+
return 32;
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
/* ── SHA-1 (FIPS 180-4) — the RFC 6455 Sec-WebSocket-Accept hash ────── */
|
|
2657
|
+
|
|
2658
|
+
static void scr_sha1_block(uint32_t h[5], const unsigned char *p) {
|
|
2659
|
+
uint32_t w[80];
|
|
2660
|
+
for (int i = 0; i < 16; i++) {
|
|
2661
|
+
w[i] = ((uint32_t)p[i * 4] << 24) | ((uint32_t)p[i * 4 + 1] << 16) |
|
|
2662
|
+
((uint32_t)p[i * 4 + 2] << 8) | (uint32_t)p[i * 4 + 3];
|
|
2663
|
+
}
|
|
2664
|
+
for (int i = 16; i < 80; i++) {
|
|
2665
|
+
uint32_t x = w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16];
|
|
2666
|
+
w[i] = (x << 1) | (x >> 31);
|
|
2667
|
+
}
|
|
2668
|
+
uint32_t a = h[0], b = h[1], c = h[2], d = h[3], e = h[4];
|
|
2669
|
+
for (int i = 0; i < 80; i++) {
|
|
2670
|
+
uint32_t f, k;
|
|
2671
|
+
if (i < 20) { f = (b & c) | (~b & d); k = 0x5a827999; }
|
|
2672
|
+
else if (i < 40) { f = b ^ c ^ d; k = 0x6ed9eba1; }
|
|
2673
|
+
else if (i < 60) { f = (b & c) | (b & d) | (c & d); k = 0x8f1bbcdc; }
|
|
2674
|
+
else { f = b ^ c ^ d; k = 0xca62c1d6; }
|
|
2675
|
+
uint32_t t = ((a << 5) | (a >> 27)) + f + e + k + w[i];
|
|
2676
|
+
e = d; d = c; c = ((b << 30) | (b >> 2)); b = a; a = t;
|
|
2677
|
+
}
|
|
2678
|
+
h[0] += a; h[1] += b; h[2] += c; h[3] += d; h[4] += e;
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
static size_t scr_sha1_digest(const unsigned char *data, size_t len, unsigned char out[32]) {
|
|
2682
|
+
uint32_t h[5] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0};
|
|
2683
|
+
size_t i = 0;
|
|
2684
|
+
for (; i + 64 <= len; i += 64) scr_sha1_block(h, data + i);
|
|
2685
|
+
unsigned char tail[128];
|
|
2686
|
+
size_t rem = len - i;
|
|
2687
|
+
memcpy(tail, data + i, rem);
|
|
2688
|
+
tail[rem] = 0x80;
|
|
2689
|
+
size_t pad = (rem + 1 + 8 <= 64) ? 64 : 128;
|
|
2690
|
+
memset(tail + rem + 1, 0, pad - rem - 1 - 8);
|
|
2691
|
+
uint64_t bits = (uint64_t)len * 8;
|
|
2692
|
+
for (int b = 0; b < 8; b++) tail[pad - 1 - b] = (unsigned char)(bits >> (8 * b));
|
|
2693
|
+
scr_sha1_block(h, tail);
|
|
2694
|
+
if (pad == 128) scr_sha1_block(h, tail + 64);
|
|
2695
|
+
for (int j = 0; j < 5; j++) {
|
|
2696
|
+
for (int b = 0; b < 4; b++) out[j * 4 + b] = (unsigned char)(h[j] >> (24 - 8 * b));
|
|
2697
|
+
}
|
|
2698
|
+
return 20;
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
/* The digest's encoding: "hex" or "base64" (compiler-fenced literals). */
|
|
2702
|
+
static ScrStr *scr_digest_encode(const unsigned char *d, size_t n, const ScrStr *enc) {
|
|
2703
|
+
if (enc->len == 3 && memcmp(enc->data, "hex", 3) == 0) {
|
|
2704
|
+
static const char hex[] = "0123456789abcdef";
|
|
2705
|
+
char buf[64];
|
|
2706
|
+
for (size_t i = 0; i < n; i++) {
|
|
2707
|
+
buf[i * 2] = hex[d[i] >> 4];
|
|
2708
|
+
buf[i * 2 + 1] = hex[d[i] & 0x0f];
|
|
2709
|
+
}
|
|
2710
|
+
return scr_str_new(buf, n * 2);
|
|
2711
|
+
}
|
|
2712
|
+
/* base64, standard alphabet, '=' padded — Buffer.toString("base64"). */
|
|
2713
|
+
static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
2714
|
+
char buf[48];
|
|
2715
|
+
size_t o = 0;
|
|
2716
|
+
for (size_t i = 0; i < n; i += 3) {
|
|
2717
|
+
unsigned v = (unsigned)d[i] << 16;
|
|
2718
|
+
if (i + 1 < n) v |= (unsigned)d[i + 1] << 8;
|
|
2719
|
+
if (i + 2 < n) v |= (unsigned)d[i + 2];
|
|
2720
|
+
buf[o++] = b64[(v >> 18) & 63];
|
|
2721
|
+
buf[o++] = b64[(v >> 12) & 63];
|
|
2722
|
+
buf[o++] = i + 1 < n ? b64[(v >> 6) & 63] : '=';
|
|
2723
|
+
buf[o++] = i + 2 < n ? b64[v & 63] : '=';
|
|
2724
|
+
}
|
|
2725
|
+
return scr_str_new(buf, o);
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
/* ── MD5 (RFC 1321) — island npm code only (the static frontend fences
|
|
2729
|
+
* every non-SHA algorithm literal; published packages hash cache keys and
|
|
2730
|
+
* etags with md5, so the island's createHash carries it). ─────────── */
|
|
2731
|
+
|
|
2732
|
+
static const uint32_t scr_md5_k[64] = {
|
|
2733
|
+
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a,
|
|
2734
|
+
0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
|
|
2735
|
+
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340,
|
|
2736
|
+
0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
|
|
2737
|
+
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8,
|
|
2738
|
+
0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
|
|
2739
|
+
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa,
|
|
2740
|
+
0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
|
|
2741
|
+
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92,
|
|
2742
|
+
0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
|
|
2743
|
+
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391};
|
|
2744
|
+
static const unsigned char scr_md5_r[64] = {
|
|
2745
|
+
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
|
|
2746
|
+
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
|
|
2747
|
+
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
|
|
2748
|
+
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21};
|
|
2749
|
+
|
|
2750
|
+
static void scr_md5_block(uint32_t h[4], const unsigned char *p) {
|
|
2751
|
+
uint32_t m[16];
|
|
2752
|
+
for (int i = 0; i < 16; i++) {
|
|
2753
|
+
m[i] = (uint32_t)p[i * 4] | ((uint32_t)p[i * 4 + 1] << 8) |
|
|
2754
|
+
((uint32_t)p[i * 4 + 2] << 16) | ((uint32_t)p[i * 4 + 3] << 24);
|
|
2755
|
+
}
|
|
2756
|
+
uint32_t a = h[0], b = h[1], c = h[2], d = h[3];
|
|
2757
|
+
for (int i = 0; i < 64; i++) {
|
|
2758
|
+
uint32_t f, g;
|
|
2759
|
+
if (i < 16) { f = (b & c) | (~b & d); g = (uint32_t)i; }
|
|
2760
|
+
else if (i < 32) { f = (d & b) | (~d & c); g = (5u * i + 1) & 15; }
|
|
2761
|
+
else if (i < 48) { f = b ^ c ^ d; g = (3u * i + 5) & 15; }
|
|
2762
|
+
else { f = c ^ (b | ~d); g = (7u * i) & 15; }
|
|
2763
|
+
uint32_t t = d;
|
|
2764
|
+
d = c;
|
|
2765
|
+
c = b;
|
|
2766
|
+
uint32_t x = a + f + scr_md5_k[i] + m[g];
|
|
2767
|
+
b = b + ((x << scr_md5_r[i]) | (x >> (32 - scr_md5_r[i])));
|
|
2768
|
+
a = t;
|
|
2769
|
+
}
|
|
2770
|
+
h[0] += a; h[1] += b; h[2] += c; h[3] += d;
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
static size_t scr_md5_digest(const unsigned char *data, size_t len, unsigned char out[32]) {
|
|
2774
|
+
uint32_t h[4] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476};
|
|
2775
|
+
size_t i = 0;
|
|
2776
|
+
for (; i + 64 <= len; i += 64) scr_md5_block(h, data + i);
|
|
2777
|
+
unsigned char tail[128];
|
|
2778
|
+
size_t rem = len - i;
|
|
2779
|
+
memcpy(tail, data + i, rem);
|
|
2780
|
+
tail[rem] = 0x80;
|
|
2781
|
+
size_t pad = (rem + 1 + 8 <= 64) ? 64 : 128;
|
|
2782
|
+
memset(tail + rem + 1, 0, pad - rem - 1 - 8);
|
|
2783
|
+
uint64_t bits = (uint64_t)len * 8;
|
|
2784
|
+
for (int b = 0; b < 8; b++) tail[pad - 8 + b] = (unsigned char)(bits >> (8 * b));
|
|
2785
|
+
scr_md5_block(h, tail);
|
|
2786
|
+
if (pad == 128) scr_md5_block(h, tail + 64);
|
|
2787
|
+
for (int j = 0; j < 4; j++) {
|
|
2788
|
+
for (int b = 0; b < 4; b++) out[j * 4 + b] = (unsigned char)(h[j] >> (8 * b));
|
|
2789
|
+
}
|
|
2790
|
+
return 16;
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
/* One-shot digest by algorithm name — the island crypto shim's bridge
|
|
2794
|
+
* (createHash concatenates its update() chunks JS-side). Returns the
|
|
2795
|
+
* digest length, 0 for an unknown algorithm. */
|
|
2796
|
+
size_t scr_crypto_digest_raw(const char *alg, const unsigned char *data, size_t len,
|
|
2797
|
+
unsigned char out[32]) {
|
|
2798
|
+
if (strcmp(alg, "sha256") == 0) return scr_sha256_digest(data, len, out);
|
|
2799
|
+
if (strcmp(alg, "sha1") == 0) return scr_sha1_digest(data, len, out);
|
|
2800
|
+
if (strcmp(alg, "md5") == 0) return scr_md5_digest(data, len, out);
|
|
2801
|
+
return 0;
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
/* HMAC (RFC 2104) over the same digests — block size 64 for all three. */
|
|
2805
|
+
size_t scr_crypto_hmac_raw(const char *alg, const unsigned char *key, size_t keylen,
|
|
2806
|
+
const unsigned char *data, size_t len, unsigned char out[32]) {
|
|
2807
|
+
unsigned char kblock[64];
|
|
2808
|
+
unsigned char kd[32];
|
|
2809
|
+
if (keylen > 64) {
|
|
2810
|
+
size_t kn = scr_crypto_digest_raw(alg, key, keylen, kd);
|
|
2811
|
+
if (kn == 0) return 0;
|
|
2812
|
+
memset(kblock, 0, 64);
|
|
2813
|
+
memcpy(kblock, kd, kn);
|
|
2814
|
+
} else {
|
|
2815
|
+
memset(kblock, 0, 64);
|
|
2816
|
+
memcpy(kblock, key, keylen);
|
|
2817
|
+
}
|
|
2818
|
+
unsigned char *inner = malloc(64 + len);
|
|
2819
|
+
if (!inner) return 0;
|
|
2820
|
+
for (int i = 0; i < 64; i++) inner[i] = kblock[i] ^ 0x36;
|
|
2821
|
+
memcpy(inner + 64, data, len);
|
|
2822
|
+
unsigned char ih[32];
|
|
2823
|
+
size_t in = scr_crypto_digest_raw(alg, inner, 64 + len, ih);
|
|
2824
|
+
free(inner);
|
|
2825
|
+
if (in == 0) return 0;
|
|
2826
|
+
unsigned char outer[96];
|
|
2827
|
+
for (int i = 0; i < 64; i++) outer[i] = kblock[i] ^ 0x5c;
|
|
2828
|
+
memcpy(outer + 64, ih, in);
|
|
2829
|
+
return scr_crypto_digest_raw(alg, outer, 64 + in, out);
|
|
2830
|
+
}
|
|
2831
|
+
|
|
2832
|
+
static ScrStr *scr_hash_digest_raw(const ScrStr *alg, const unsigned char *data, size_t len,
|
|
2833
|
+
const ScrStr *enc) {
|
|
2834
|
+
unsigned char d[32];
|
|
2835
|
+
/* sha1 or sha256 — the compiler fences every other algorithm literal. */
|
|
2836
|
+
size_t n = (alg->len == 4 && memcmp(alg->data, "sha1", 4) == 0)
|
|
2837
|
+
? scr_sha1_digest(data, len, d)
|
|
2838
|
+
: scr_sha256_digest(data, len, d);
|
|
2839
|
+
return scr_digest_encode(d, n, enc);
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
/* Strings hash their UTF-8 bytes (Node's default input encoding — ScrStr
|
|
2843
|
+
* storage IS utf8, so the bytes are the string's own). Borrowed; +1. */
|
|
2844
|
+
ScrStr *scr_crypto_hash_digest_str(ScrStr *alg, ScrStr *data, ScrStr *enc) {
|
|
2845
|
+
return scr_hash_digest_raw(alg, (const unsigned char *)data->data, data->len, enc);
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
ScrStr *scr_crypto_hash_digest_bytes(ScrStr *alg, ScrBytes *data, ScrStr *enc) {
|
|
2849
|
+
return scr_hash_digest_raw(alg, data->data, data->len * scr_bytes_elem_size(data->elem), enc);
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
/* The composed `new crypto.X509Certificate(data).fingerprint` read, fused
|
|
2853
|
+
* by the compiler (no certificate handle exists). Node's .fingerprint IS
|
|
2854
|
+
* the SHA-1 of the certificate's DER bytes, uppercase colon-separated —
|
|
2855
|
+
* pinned against Node. Accepts what Node's constructor accepts from the
|
|
2856
|
+
* fs.readFileSync idiom: PEM (the armor's base64 body decodes to DER,
|
|
2857
|
+
* Node-leniently — whitespace skipped) or raw DER (a leading SEQUENCE
|
|
2858
|
+
* tag). Anything else throws Node's exact PEM error (Error, code
|
|
2859
|
+
* ERR_OSSL_PEM_NO_START_LINE). Input borrowed; result +1. */
|
|
2860
|
+
static ScrStr *scr_x509_fingerprint_raw(const uint8_t *in, size_t n);
|
|
2861
|
+
|
|
2862
|
+
ScrStr *scr_crypto_x509_fingerprint(ScrBytes *data) {
|
|
2863
|
+
return scr_x509_fingerprint_raw(data->data, data->len * scr_bytes_elem_size(data->elem));
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
/* The string-input form (readFileSync(path, "utf-8") — PEM text; ScrStr
|
|
2867
|
+
* storage IS the bytes). */
|
|
2868
|
+
ScrStr *scr_crypto_x509_fingerprint_str(ScrStr *pem) {
|
|
2869
|
+
return scr_x509_fingerprint_raw((const uint8_t *)pem->data, pem->len);
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
/* PEM armor → DER (or raw-DER passthrough), the shared front half of
|
|
2873
|
+
* every X509Certificate member. On success `*der`/`*der_len` hold the
|
|
2874
|
+
* certificate bytes and `*decoded` the malloc'd base64 buffer to free
|
|
2875
|
+
* (NULL for raw-DER input); unparseable input throws Node's exact PEM
|
|
2876
|
+
* error and answers false with the exception pending. */
|
|
2877
|
+
static bool scr_x509_der(const uint8_t *in, size_t n, const uint8_t **der, size_t *der_len,
|
|
2878
|
+
uint8_t **decoded) {
|
|
2879
|
+
static const char begin[] = "-----BEGIN CERTIFICATE-----";
|
|
2880
|
+
static const char end[] = "-----END CERTIFICATE-----";
|
|
2881
|
+
*der = NULL;
|
|
2882
|
+
*der_len = 0;
|
|
2883
|
+
*decoded = NULL;
|
|
2884
|
+
/* PEM: base64-decode the armor's body. */
|
|
2885
|
+
const uint8_t *b = NULL;
|
|
2886
|
+
for (size_t i = 0; i + sizeof begin - 1 <= n; i++) {
|
|
2887
|
+
if (memcmp(in + i, begin, sizeof begin - 1) == 0) {
|
|
2888
|
+
b = in + i + sizeof begin - 1;
|
|
2889
|
+
break;
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
if (b != NULL) {
|
|
2893
|
+
const uint8_t *stop = in + n;
|
|
2894
|
+
for (const uint8_t *p = b; p + sizeof end - 1 <= in + n; p++) {
|
|
2895
|
+
if (memcmp(p, end, sizeof end - 1) == 0) {
|
|
2896
|
+
stop = p;
|
|
2897
|
+
break;
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
uint8_t *out = malloc(((size_t)(stop - b) / 4 + 1) * 3 + 3);
|
|
2901
|
+
if (!out) {
|
|
2902
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
2903
|
+
abort();
|
|
2904
|
+
}
|
|
2905
|
+
size_t o = 0;
|
|
2906
|
+
unsigned acc = 0;
|
|
2907
|
+
int have = 0;
|
|
2908
|
+
for (const uint8_t *p = b; p < stop; p++) {
|
|
2909
|
+
int v;
|
|
2910
|
+
uint8_t c = *p;
|
|
2911
|
+
if (c >= 'A' && c <= 'Z') v = c - 'A';
|
|
2912
|
+
else if (c >= 'a' && c <= 'z') v = c - 'a' + 26;
|
|
2913
|
+
else if (c >= '0' && c <= '9') v = c - '0' + 52;
|
|
2914
|
+
else if (c == '+') v = 62;
|
|
2915
|
+
else if (c == '/') v = 63;
|
|
2916
|
+
else continue; /* whitespace, '=' */
|
|
2917
|
+
acc = (acc << 6) | (unsigned)v;
|
|
2918
|
+
if (++have == 4) {
|
|
2919
|
+
out[o++] = (uint8_t)(acc >> 16);
|
|
2920
|
+
out[o++] = (uint8_t)(acc >> 8);
|
|
2921
|
+
out[o++] = (uint8_t)acc;
|
|
2922
|
+
acc = 0;
|
|
2923
|
+
have = 0;
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
if (have == 2) out[o++] = (uint8_t)(acc >> 4);
|
|
2927
|
+
else if (have == 3) {
|
|
2928
|
+
out[o++] = (uint8_t)(acc >> 10);
|
|
2929
|
+
out[o++] = (uint8_t)(acc >> 2);
|
|
2930
|
+
}
|
|
2931
|
+
*decoded = out;
|
|
2932
|
+
*der = out;
|
|
2933
|
+
*der_len = o;
|
|
2934
|
+
return true;
|
|
2935
|
+
}
|
|
2936
|
+
if (n > 0 && in[0] == 0x30) {
|
|
2937
|
+
/* Raw DER: the certificate SEQUENCE. */
|
|
2938
|
+
*der = in;
|
|
2939
|
+
*der_len = n;
|
|
2940
|
+
return true;
|
|
2941
|
+
}
|
|
2942
|
+
scr_throw_error_msg_code(SCR_ERR_ERROR,
|
|
2943
|
+
"error:0480006C:PEM routines::no start line",
|
|
2944
|
+
42, "ERR_OSSL_PEM_NO_START_LINE");
|
|
2945
|
+
return false; /* exception pending */
|
|
2946
|
+
}
|
|
2947
|
+
|
|
2948
|
+
static ScrStr *scr_x509_fingerprint_raw(const uint8_t *in, size_t n) {
|
|
2949
|
+
const uint8_t *der;
|
|
2950
|
+
size_t der_len;
|
|
2951
|
+
uint8_t *decoded;
|
|
2952
|
+
if (!scr_x509_der(in, n, &der, &der_len, &decoded)) return NULL;
|
|
2953
|
+
unsigned char digest[32];
|
|
2954
|
+
size_t dn = scr_sha1_digest(der, der_len, digest);
|
|
2955
|
+
free(decoded);
|
|
2956
|
+
char buf[64];
|
|
2957
|
+
static const char hex[] = "0123456789ABCDEF";
|
|
2958
|
+
size_t o = 0;
|
|
2959
|
+
for (size_t i = 0; i < dn; i++) {
|
|
2960
|
+
if (i > 0) buf[o++] = ':';
|
|
2961
|
+
buf[o++] = hex[digest[i] >> 4];
|
|
2962
|
+
buf[o++] = hex[digest[i] & 0x0f];
|
|
2963
|
+
}
|
|
2964
|
+
return scr_str_new(buf, o);
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2967
|
+
/* ── the certificate's Validity window (validFrom / validTo) ─────────────
|
|
2968
|
+
*
|
|
2969
|
+
* A minimal DER walk to TBSCertificate.validity: Certificate ::= SEQUENCE
|
|
2970
|
+
* { tbsCertificate SEQUENCE { version [0] OPTIONAL, serialNumber INTEGER,
|
|
2971
|
+
* signature SEQUENCE, issuer, validity SEQUENCE { notBefore, notAfter },
|
|
2972
|
+
* ... } }. Each Time is UTCTime (YYMMDDHHMMSSZ; RFC 5280's 50-year pivot)
|
|
2973
|
+
* or GeneralizedTime (YYYYMMDDHHMMSSZ), rendered in Node's exact
|
|
2974
|
+
* ASN1_TIME_print shape: "Jul 1 00:00:00 2026 GMT" (%2d space-padded
|
|
2975
|
+
* day). Truncated/non-Zulu encodings (RFC 5280 forbids them in certs)
|
|
2976
|
+
* and walk failures answer OpenSSL's "Bad time value". */
|
|
2977
|
+
|
|
2978
|
+
/* Reads one DER TL header at p (before end): tag to *tag, content length
|
|
2979
|
+
* to *len, and answers the content pointer (NULL on malformed/overlong). */
|
|
2980
|
+
static const uint8_t *scr_der_tl(const uint8_t *p, const uint8_t *end, uint8_t *tag, size_t *len) {
|
|
2981
|
+
if (p == NULL || end - p < 2) return NULL;
|
|
2982
|
+
*tag = p[0];
|
|
2983
|
+
uint8_t l0 = p[1];
|
|
2984
|
+
const uint8_t *content = p + 2;
|
|
2985
|
+
size_t l;
|
|
2986
|
+
if (l0 < 0x80) {
|
|
2987
|
+
l = l0;
|
|
2988
|
+
} else {
|
|
2989
|
+
size_t nb = l0 & 0x7f;
|
|
2990
|
+
if (nb == 0 || nb > sizeof(size_t) || end - content < (ptrdiff_t)nb) return NULL;
|
|
2991
|
+
l = 0;
|
|
2992
|
+
for (size_t i = 0; i < nb; i++) l = (l << 8) | content[i];
|
|
2993
|
+
content += nb;
|
|
2994
|
+
}
|
|
2995
|
+
if ((size_t)(end - content) < l) return NULL;
|
|
2996
|
+
*tag = p[0];
|
|
2997
|
+
*len = l;
|
|
2998
|
+
return content;
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
/* Formats one Time element (UTCTime/GeneralizedTime content bytes) in
|
|
3002
|
+
* ASN1_TIME_print's shape into buf; answers the length (0 = bad value). */
|
|
3003
|
+
static size_t scr_x509_time_print(uint8_t tag, const uint8_t *t, size_t n, char buf[32]) {
|
|
3004
|
+
static const char *mon[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
3005
|
+
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
|
3006
|
+
int year, mo, day, hh, mm, ss;
|
|
3007
|
+
const uint8_t *d = t;
|
|
3008
|
+
size_t need = tag == 0x17 ? 13 : 15; /* ...HHMMSSZ, Zulu only */
|
|
3009
|
+
if (n != need || t[n - 1] != 'Z') return 0;
|
|
3010
|
+
for (size_t i = 0; i + 1 < n; i++) {
|
|
3011
|
+
if (t[i] < '0' || t[i] > '9') return 0;
|
|
3012
|
+
}
|
|
3013
|
+
#define SCR_DER_2D(p) (((p)[0] - '0') * 10 + ((p)[1] - '0'))
|
|
3014
|
+
if (tag == 0x17) {
|
|
3015
|
+
year = SCR_DER_2D(d);
|
|
3016
|
+
year += year < 50 ? 2000 : 1900; /* RFC 5280's UTCTime pivot */
|
|
3017
|
+
d += 2;
|
|
3018
|
+
} else {
|
|
3019
|
+
year = SCR_DER_2D(d) * 100;
|
|
3020
|
+
d += 2;
|
|
3021
|
+
year += SCR_DER_2D(d);
|
|
3022
|
+
d += 2;
|
|
3023
|
+
}
|
|
3024
|
+
mo = SCR_DER_2D(d); d += 2;
|
|
3025
|
+
day = SCR_DER_2D(d); d += 2;
|
|
3026
|
+
hh = SCR_DER_2D(d); d += 2;
|
|
3027
|
+
mm = SCR_DER_2D(d); d += 2;
|
|
3028
|
+
ss = SCR_DER_2D(d);
|
|
3029
|
+
#undef SCR_DER_2D
|
|
3030
|
+
if (mo < 1 || mo > 12) return 0;
|
|
3031
|
+
int r = snprintf(buf, 32, "%s %2d %02d:%02d:%02d %d GMT", mon[mo - 1], day, hh, mm, ss, year);
|
|
3032
|
+
return r > 0 ? (size_t)r : 0;
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
static ScrStr *scr_x509_validity_raw(const uint8_t *in, size_t n, bool want_to) {
|
|
3036
|
+
const uint8_t *der;
|
|
3037
|
+
size_t der_len;
|
|
3038
|
+
uint8_t *decoded;
|
|
3039
|
+
if (!scr_x509_der(in, n, &der, &der_len, &decoded)) return NULL;
|
|
3040
|
+
static const char bad[] = "Bad time value";
|
|
3041
|
+
const uint8_t *end = der + der_len;
|
|
3042
|
+
uint8_t tag;
|
|
3043
|
+
size_t len;
|
|
3044
|
+
char buf[32];
|
|
3045
|
+
size_t blen = 0;
|
|
3046
|
+
/* Certificate SEQUENCE → tbsCertificate SEQUENCE */
|
|
3047
|
+
const uint8_t *p = scr_der_tl(der, end, &tag, &len);
|
|
3048
|
+
if (p && tag == 0x30) {
|
|
3049
|
+
end = p + len;
|
|
3050
|
+
p = scr_der_tl(p, end, &tag, &len);
|
|
3051
|
+
} else {
|
|
3052
|
+
p = NULL;
|
|
3053
|
+
}
|
|
3054
|
+
if (p && tag == 0x30) {
|
|
3055
|
+
const uint8_t *tbs_end = p + len;
|
|
3056
|
+
/* [0] version (optional), serialNumber, signature, issuer */
|
|
3057
|
+
const uint8_t *q = p;
|
|
3058
|
+
for (int skip = 0; skip < 4 && q != NULL; skip++) {
|
|
3059
|
+
const uint8_t *c = scr_der_tl(q, tbs_end, &tag, &len);
|
|
3060
|
+
if (!c) { q = NULL; break; }
|
|
3061
|
+
if (skip == 0 && tag != 0xA0) {
|
|
3062
|
+
/* no version field: this element is already serialNumber */
|
|
3063
|
+
skip++;
|
|
3064
|
+
}
|
|
3065
|
+
q = c + len;
|
|
3066
|
+
}
|
|
3067
|
+
/* validity SEQUENCE { notBefore, notAfter } */
|
|
3068
|
+
const uint8_t *v = scr_der_tl(q, tbs_end, &tag, &len);
|
|
3069
|
+
if (v && tag == 0x30) {
|
|
3070
|
+
const uint8_t *v_end = v + len;
|
|
3071
|
+
const uint8_t *t1 = scr_der_tl(v, v_end, &tag, &len);
|
|
3072
|
+
if (t1 && (tag == 0x17 || tag == 0x18)) {
|
|
3073
|
+
if (!want_to) {
|
|
3074
|
+
blen = scr_x509_time_print(tag, t1, len, buf);
|
|
3075
|
+
} else {
|
|
3076
|
+
const uint8_t *t2 = scr_der_tl(t1 + len, v_end, &tag, &len);
|
|
3077
|
+
if (t2 && (tag == 0x17 || tag == 0x18)) {
|
|
3078
|
+
blen = scr_x509_time_print(tag, t2, len, buf);
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
free(decoded);
|
|
3085
|
+
if (blen == 0) return scr_str_new(bad, sizeof bad - 1);
|
|
3086
|
+
return scr_str_new(buf, blen);
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
ScrStr *scr_crypto_x509_valid_from(ScrBytes *data) {
|
|
3090
|
+
return scr_x509_validity_raw(data->data, data->len * scr_bytes_elem_size(data->elem), false);
|
|
3091
|
+
}
|
|
3092
|
+
|
|
3093
|
+
ScrStr *scr_crypto_x509_valid_from_str(ScrStr *pem) {
|
|
3094
|
+
return scr_x509_validity_raw((const uint8_t *)pem->data, pem->len, false);
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
ScrStr *scr_crypto_x509_valid_to(ScrBytes *data) {
|
|
3098
|
+
return scr_x509_validity_raw(data->data, data->len * scr_bytes_elem_size(data->elem), true);
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
ScrStr *scr_crypto_x509_valid_to_str(ScrStr *pem) {
|
|
3102
|
+
return scr_x509_validity_raw((const uint8_t *)pem->data, pem->len, true);
|
|
3103
|
+
}
|
|
3104
|
+
|
|
3105
|
+
/* ── String surface (fromCharCode / lastIndexOf) ─────────────────────── */
|
|
3106
|
+
|
|
3107
|
+
/* Forward declaration — scr_to_uint32 lives with the bitwise operators
|
|
3108
|
+
* below (ToUint16 is its low 16 bits, per spec). */
|
|
3109
|
+
static uint32_t scr_to_uint32(double d);
|
|
3110
|
+
|
|
3111
|
+
/* String.fromCharCode core over n UTF-16 code units read through
|
|
3112
|
+
* `unit(src, i)` (already ToUint16'd): combine adjacent surrogate pairs,
|
|
3113
|
+
* substitute U+FFFD for lone surrogates (divergence 1's storage policy —
|
|
3114
|
+
* Node writing a lone surrogate to stdout produces the same replacement
|
|
3115
|
+
* bytes), UTF-8 encode. */
|
|
3116
|
+
static ScrStr *scr_str_from_units(size_t n, uint32_t (*unit)(void *, size_t), void *src) {
|
|
3117
|
+
char *out = malloc(n * 3 + 1); /* worst case: 3 bytes per UTF-16 unit */
|
|
3118
|
+
if (!out) {
|
|
3119
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
3120
|
+
abort();
|
|
3121
|
+
}
|
|
3122
|
+
size_t o = 0;
|
|
3123
|
+
for (size_t i = 0; i < n; i++) {
|
|
3124
|
+
uint32_t cp = unit(src, i);
|
|
3125
|
+
if (cp >= 0xD800 && cp <= 0xDBFF && i + 1 < n) {
|
|
3126
|
+
uint32_t lo = unit(src, i + 1);
|
|
3127
|
+
if (lo >= 0xDC00 && lo <= 0xDFFF) {
|
|
3128
|
+
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
|
|
3129
|
+
i++;
|
|
3130
|
+
} else {
|
|
3131
|
+
cp = 0xFFFD;
|
|
3132
|
+
}
|
|
3133
|
+
} else if (cp >= 0xD800 && cp <= 0xDFFF) {
|
|
3134
|
+
cp = 0xFFFD;
|
|
3135
|
+
}
|
|
3136
|
+
if (cp <= 0x7F) {
|
|
3137
|
+
out[o++] = (char)cp;
|
|
3138
|
+
} else if (cp <= 0x7FF) {
|
|
3139
|
+
out[o++] = (char)(0xC0 | (cp >> 6));
|
|
3140
|
+
out[o++] = (char)(0x80 | (cp & 0x3F));
|
|
3141
|
+
} else if (cp <= 0xFFFF) {
|
|
3142
|
+
out[o++] = (char)(0xE0 | (cp >> 12));
|
|
3143
|
+
out[o++] = (char)(0x80 | ((cp >> 6) & 0x3F));
|
|
3144
|
+
out[o++] = (char)(0x80 | (cp & 0x3F));
|
|
3145
|
+
} else {
|
|
3146
|
+
out[o++] = (char)(0xF0 | (cp >> 18));
|
|
3147
|
+
out[o++] = (char)(0x80 | ((cp >> 12) & 0x3F));
|
|
3148
|
+
out[o++] = (char)(0x80 | ((cp >> 6) & 0x3F));
|
|
3149
|
+
out[o++] = (char)(0x80 | (cp & 0x3F));
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
ScrStr *s = scr_str_new(out, o);
|
|
3153
|
+
free(out);
|
|
3154
|
+
return s;
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3157
|
+
static uint32_t scr_fcc_arr_unit(void *src, size_t i) {
|
|
3158
|
+
return scr_to_uint32(scr_arr_get_f64((ScrArr *)src, (double)i)) & 0xFFFFu;
|
|
3159
|
+
}
|
|
3160
|
+
|
|
3161
|
+
/* String.fromCharCode(...codes) over ONE packed f64[]. */
|
|
3162
|
+
ScrStr *scr_str_from_char_code(ScrArr *codes) {
|
|
3163
|
+
return scr_str_from_units(codes->len, scr_fcc_arr_unit, codes);
|
|
3164
|
+
}
|
|
3165
|
+
|
|
3166
|
+
static uint32_t scr_fcc_bytes_unit(void *src, size_t i) {
|
|
3167
|
+
return scr_to_uint32(scr_bytes_get((const ScrBytes *)src, (double)i)) & 0xFFFFu;
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3170
|
+
/* String.fromCharCode(...bytes) — a spread typed array/Buffer source (the
|
|
3171
|
+
* magic-number ASCII probe: String.fromCharCode(...data.slice(4, 8))).
|
|
3172
|
+
* u8 elements are plain code units; u32/f32 elements ride the same
|
|
3173
|
+
* ToUint16 the packed-array form applies. */
|
|
3174
|
+
ScrStr *scr_str_from_char_code_bytes(ScrBytes *codes) {
|
|
3175
|
+
return scr_str_from_units(codes->len, scr_fcc_bytes_unit, codes);
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3178
|
+
/* UTF-16 unit count of the UTF-8 prefix ending at byte offset `end`
|
|
3179
|
+
* (ScrStr storage is well-formed, so lead bytes decide the advance). */
|
|
3180
|
+
static size_t scr_lib_u16_units(const char *s, size_t end) {
|
|
3181
|
+
size_t units = 0;
|
|
3182
|
+
for (size_t i = 0; i < end;) {
|
|
3183
|
+
unsigned char b = (unsigned char)s[i];
|
|
3184
|
+
size_t adv = b < 0x80 ? 1 : b < 0xE0 ? 2 : b < 0xF0 ? 3 : 4;
|
|
3185
|
+
units += adv == 4 ? 2 : 1;
|
|
3186
|
+
i += adv;
|
|
3187
|
+
}
|
|
3188
|
+
return units;
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3191
|
+
/* lastIndexOf(needle), the one-argument form: last occurrence as a UTF-16
|
|
3192
|
+
* index, -1 when absent; the empty needle finds the length (per spec's
|
|
3193
|
+
* clamped +Infinity fromIndex). A byte-wise reverse scan is boundary-safe:
|
|
3194
|
+
* a well-formed needle's first byte is never a continuation byte. */
|
|
3195
|
+
double scr_str_last_index_of(ScrStr *s, ScrStr *needle) {
|
|
3196
|
+
if (needle->len == 0) return (double)scr_lib_u16_units(s->data, s->len);
|
|
3197
|
+
if (needle->len > s->len) return -1.0;
|
|
3198
|
+
for (size_t i = s->len - needle->len + 1; i-- > 0;) {
|
|
3199
|
+
if (memcmp(s->data + i, needle->data, needle->len) == 0) {
|
|
3200
|
+
return (double)scr_lib_u16_units(s->data, i);
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
return -1.0;
|
|
3204
|
+
}
|
|
3205
|
+
|
|
3206
|
+
/* ── Date, the composed slice ──────────────────────────────────────────
|
|
3207
|
+
* Date values have no representation — the compiled surface is exactly
|
|
3208
|
+
* Date.now() and the composed new Date(ms?).toISOString(). */
|
|
3209
|
+
|
|
3210
|
+
double scr_date_now(void) {
|
|
3211
|
+
struct timespec ts;
|
|
3212
|
+
clock_gettime(CLOCK_REALTIME, &ts);
|
|
3213
|
+
/* Node's Date.now() is integer milliseconds. */
|
|
3214
|
+
return floor((double)ts.tv_sec * 1000.0 + (double)ts.tv_nsec / 1e6);
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
/* Node's Date.prototype.toISOString over a millisecond time value:
|
|
3218
|
+
* TimeClip's ToInteger truncation, proleptic Gregorian civil-from-days
|
|
3219
|
+
* (Howard Hinnant's algorithm), YYYY-MM-DDTHH:mm:ss.sssZ with expanded
|
|
3220
|
+
* ±YYYYYY years outside 0–9999, and Node's "Invalid time value"
|
|
3221
|
+
* RangeError on NaN / out-of-range values — verified against Node over
|
|
3222
|
+
* epoch, negative, fractional, boundary (±8.64e15), and expanded-year
|
|
3223
|
+
* inputs. */
|
|
3224
|
+
ScrStr *scr_date_to_iso(double ms) {
|
|
3225
|
+
if (!(fabs(ms) <= 8640000000000000.0)) { /* NaN and out of range */
|
|
3226
|
+
scr_throw_error_msg(SCR_ERR_RANGE, "Invalid time value", 18);
|
|
3227
|
+
return NULL;
|
|
3228
|
+
}
|
|
3229
|
+
double t = trunc(ms);
|
|
3230
|
+
double dayd = floor(t / 86400000.0);
|
|
3231
|
+
long long msday = (long long)(t - dayd * 86400000.0);
|
|
3232
|
+
long long z = (long long)dayd + 719468;
|
|
3233
|
+
long long era = (z >= 0 ? z : z - 146096) / 146097;
|
|
3234
|
+
unsigned long long doe = (unsigned long long)(z - era * 146097);
|
|
3235
|
+
unsigned long long yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
|
|
3236
|
+
long long y = (long long)yoe + era * 400;
|
|
3237
|
+
unsigned long long doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
|
|
3238
|
+
unsigned long long mp = (5 * doy + 2) / 153;
|
|
3239
|
+
unsigned long long d = doy - (153 * mp + 2) / 5 + 1;
|
|
3240
|
+
unsigned long long m = mp < 10 ? mp + 3 : mp - 9;
|
|
3241
|
+
if (m <= 2) y += 1;
|
|
3242
|
+
int hh = (int)(msday / 3600000), mi = (int)(msday / 60000 % 60),
|
|
3243
|
+
ss = (int)(msday / 1000 % 60), sss = (int)(msday % 1000);
|
|
3244
|
+
char buf[40];
|
|
3245
|
+
int len;
|
|
3246
|
+
if (y < 0) {
|
|
3247
|
+
len = snprintf(buf, sizeof buf, "-%06lld-%02llu-%02lluT%02d:%02d:%02d.%03dZ", -y, m, d, hh, mi, ss, sss);
|
|
3248
|
+
} else if (y > 9999) {
|
|
3249
|
+
len = snprintf(buf, sizeof buf, "+%06lld-%02llu-%02lluT%02d:%02d:%02d.%03dZ", y, m, d, hh, mi, ss, sss);
|
|
3250
|
+
} else {
|
|
3251
|
+
len = snprintf(buf, sizeof buf, "%04lld-%02llu-%02lluT%02d:%02d:%02d.%03dZ", y, m, d, hh, mi, ss, sss);
|
|
3252
|
+
}
|
|
3253
|
+
return scr_str_new(buf, (size_t)len);
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3256
|
+
/* ── new Date(dateString).getTime() ──────────────────────────────────────
|
|
3257
|
+
*
|
|
3258
|
+
* The BOUNDED date-string parse (documented divergence — V8's parser
|
|
3259
|
+
* accepts far more): two grammars answer a time value, everything else is
|
|
3260
|
+
* NaN (Node's invalid-date getTime).
|
|
3261
|
+
*
|
|
3262
|
+
* 1. The ASN1_TIME_print shape X509Certificate.validFrom/validTo answer
|
|
3263
|
+
* ("Jul 1 00:00:00 2026 GMT", "Jul 17 17:52:11 2026 GMT") — the
|
|
3264
|
+
* portless cert-expiry read. Month names match case-insensitively;
|
|
3265
|
+
* one or two spaces precede the day (%2d's padding).
|
|
3266
|
+
* 2. ECMA's own date-time string format with an EXPLICIT offset:
|
|
3267
|
+
* YYYY[-MM[-DD]] date-only forms (UTC, per the spec) and full
|
|
3268
|
+
* date-times YYYY-MM-DDTHH:mm[:ss[.sss]] ending in Z or ±HH:MM.
|
|
3269
|
+
* Offset-less date-times are LOCAL time in JS; that arm answers NaN
|
|
3270
|
+
* here rather than guessing a zone (the divergence note).
|
|
3271
|
+
*/
|
|
3272
|
+
|
|
3273
|
+
/* Howard Hinnant's days_from_civil (the to_iso walk inverted). */
|
|
3274
|
+
static double scr_days_from_civil(long long y, int m, int d) {
|
|
3275
|
+
y -= m <= 2;
|
|
3276
|
+
long long era = (y >= 0 ? y : y - 399) / 400;
|
|
3277
|
+
unsigned long long yoe = (unsigned long long)(y - era * 400);
|
|
3278
|
+
unsigned long long doy = (unsigned long long)((153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1);
|
|
3279
|
+
unsigned long long doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
|
|
3280
|
+
return (double)(era * 146097 + (long long)doe - 719468);
|
|
3281
|
+
}
|
|
3282
|
+
|
|
3283
|
+
static double scr_date_ms_of(long long y, int mo, int d, int hh, int mi, int ss, int ms) {
|
|
3284
|
+
/* V8 accepts days 1..31 in every month and ROLLS OVER past the month's
|
|
3285
|
+
* end (Feb 30 → Mar 2) — days_from_civil extrapolates linearly, so the
|
|
3286
|
+
* rollover falls out; day 0 and 32+ are NaN, like V8. */
|
|
3287
|
+
if (mo < 1 || mo > 12 || d < 1 || d > 31) return NAN;
|
|
3288
|
+
if (hh > 24 || mi > 59 || ss > 59 || (hh == 24 && (mi || ss || ms))) return NAN;
|
|
3289
|
+
double t = scr_days_from_civil(y, mo, d) * 86400000.0 +
|
|
3290
|
+
hh * 3600000.0 + mi * 60000.0 + ss * 1000.0 + ms;
|
|
3291
|
+
if (fabs(t) > 8640000000000000.0) return NAN;
|
|
3292
|
+
return t;
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
static bool scr_date_digits(const char **p, const char *end, int n, int *out) {
|
|
3296
|
+
int v = 0;
|
|
3297
|
+
if (end - *p < n) return false;
|
|
3298
|
+
for (int i = 0; i < n; i++) {
|
|
3299
|
+
char c = (*p)[i];
|
|
3300
|
+
if (c < '0' || c > '9') return false;
|
|
3301
|
+
v = v * 10 + (c - '0');
|
|
3302
|
+
}
|
|
3303
|
+
*p += n;
|
|
3304
|
+
*out = v;
|
|
3305
|
+
return true;
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
double scr_date_parse_get_time(ScrStr *s) {
|
|
3309
|
+
const char *p = s->data;
|
|
3310
|
+
const char *end = s->data + s->len;
|
|
3311
|
+
static const char *mon[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
3312
|
+
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
|
3313
|
+
/* Grammar 1: "MMM [D]D HH:MM:SS YYYY GMT" (ASN1_TIME_print). */
|
|
3314
|
+
if (end - p > 3 && ((p[0] >= 'A' && p[0] <= 'Z') || (p[0] >= 'a' && p[0] <= 'z'))) {
|
|
3315
|
+
int mo = 0;
|
|
3316
|
+
for (int i = 0; i < 12; i++) {
|
|
3317
|
+
if (tolower((unsigned char)p[0]) == tolower((unsigned char)mon[i][0]) &&
|
|
3318
|
+
tolower((unsigned char)p[1]) == tolower((unsigned char)mon[i][1]) &&
|
|
3319
|
+
tolower((unsigned char)p[2]) == tolower((unsigned char)mon[i][2])) {
|
|
3320
|
+
mo = i + 1;
|
|
3321
|
+
break;
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
if (mo == 0) return NAN;
|
|
3325
|
+
p += 3;
|
|
3326
|
+
if (p < end && *p == ' ') p++;
|
|
3327
|
+
if (p < end && *p == ' ') p++; /* %2d's padding space */
|
|
3328
|
+
int d = 0, hh, mi, ss, y;
|
|
3329
|
+
if (!scr_date_digits(&p, end, 1, &d)) return NAN;
|
|
3330
|
+
int d2;
|
|
3331
|
+
if (p < end && *p >= '0' && *p <= '9' && scr_date_digits(&p, end, 1, &d2)) d = d * 10 + d2;
|
|
3332
|
+
if (p >= end || *p++ != ' ') return NAN;
|
|
3333
|
+
if (!scr_date_digits(&p, end, 2, &hh) || p >= end || *p++ != ':') return NAN;
|
|
3334
|
+
if (!scr_date_digits(&p, end, 2, &mi) || p >= end || *p++ != ':') return NAN;
|
|
3335
|
+
if (!scr_date_digits(&p, end, 2, &ss) || p >= end || *p++ != ' ') return NAN;
|
|
3336
|
+
if (!scr_date_digits(&p, end, 4, &y)) return NAN;
|
|
3337
|
+
if (end - p != 4 || memcmp(p, " GMT", 4) != 0) return NAN;
|
|
3338
|
+
return scr_date_ms_of(y, mo, d, hh, mi, ss, 0);
|
|
3339
|
+
}
|
|
3340
|
+
/* Grammar 2: ECMA's date-time string format. */
|
|
3341
|
+
{
|
|
3342
|
+
int y, mo = 1, d = 1, hh = 0, mi = 0, ss = 0, ms = 0;
|
|
3343
|
+
bool sign_year = p < end && (*p == '+' || *p == '-');
|
|
3344
|
+
long long yy;
|
|
3345
|
+
if (sign_year) {
|
|
3346
|
+
int y6;
|
|
3347
|
+
bool neg = *p == '-';
|
|
3348
|
+
p++;
|
|
3349
|
+
if (!scr_date_digits(&p, end, 6, &y6)) return NAN;
|
|
3350
|
+
yy = neg ? -(long long)y6 : y6;
|
|
3351
|
+
} else {
|
|
3352
|
+
if (!scr_date_digits(&p, end, 4, &y)) return NAN;
|
|
3353
|
+
yy = y;
|
|
3354
|
+
}
|
|
3355
|
+
if (p < end && *p == '-') {
|
|
3356
|
+
p++;
|
|
3357
|
+
if (!scr_date_digits(&p, end, 2, &mo)) return NAN;
|
|
3358
|
+
if (p < end && *p == '-') {
|
|
3359
|
+
p++;
|
|
3360
|
+
if (!scr_date_digits(&p, end, 2, &d)) return NAN;
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
if (p == end) return scr_date_ms_of(yy, mo, d, 0, 0, 0, 0); /* date-only: UTC */
|
|
3364
|
+
if (*p++ != 'T') return NAN;
|
|
3365
|
+
if (!scr_date_digits(&p, end, 2, &hh) || p >= end || *p++ != ':') return NAN;
|
|
3366
|
+
if (!scr_date_digits(&p, end, 2, &mi)) return NAN;
|
|
3367
|
+
if (p < end && *p == ':') {
|
|
3368
|
+
p++;
|
|
3369
|
+
if (!scr_date_digits(&p, end, 2, &ss)) return NAN;
|
|
3370
|
+
if (p < end && *p == '.') {
|
|
3371
|
+
p++;
|
|
3372
|
+
if (!scr_date_digits(&p, end, 3, &ms)) return NAN;
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
if (p == end) return NAN; /* offset-less date-time: LOCAL in JS — the divergence */
|
|
3376
|
+
double off = 0;
|
|
3377
|
+
if (*p == 'Z') {
|
|
3378
|
+
p++;
|
|
3379
|
+
} else if (*p == '+' || *p == '-') {
|
|
3380
|
+
bool neg = *p == '-';
|
|
3381
|
+
int oh, om;
|
|
3382
|
+
p++;
|
|
3383
|
+
if (!scr_date_digits(&p, end, 2, &oh) || p >= end || *p++ != ':') return NAN;
|
|
3384
|
+
if (!scr_date_digits(&p, end, 2, &om)) return NAN;
|
|
3385
|
+
off = (oh * 60 + om) * 60000.0;
|
|
3386
|
+
if (neg) off = -off;
|
|
3387
|
+
} else {
|
|
3388
|
+
return NAN;
|
|
3389
|
+
}
|
|
3390
|
+
if (p != end) return NAN;
|
|
3391
|
+
double t = scr_date_ms_of(yy, mo, d, hh, mi, ss, ms);
|
|
3392
|
+
return isnan(t) ? t : t - off;
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3396
|
+
/* Date.UTC(year, month, date, hours, minutes, seconds, ms) — the spec's
|
|
3397
|
+
* MakeDay/MakeTime/TimeClip pipeline over ALREADY-NUMBER arguments (tsc
|
|
3398
|
+
* pins them; the frontend completes omitted trailing arguments with the
|
|
3399
|
+
* spec's defaults: month 0, date 1, time parts 0). ToIntegerOrInfinity is
|
|
3400
|
+
* trunc on finite values; any non-finite part is NaN. Years 0–99 map to
|
|
3401
|
+
* 1900+year (the spec's MakeFullYear), out-of-range months ROLL into the
|
|
3402
|
+
* year (Date.UTC(2017, 13) is Feb 2018) and any integer date offsets from
|
|
3403
|
+
* day 1 of that month — days_from_civil extrapolates linearly, so both
|
|
3404
|
+
* rollovers fall out. V8 bounds MakeDay's year to ±1e6 (kMaxYear/kMinYear,
|
|
3405
|
+
* date.h) before TimeClip can see the result, and Node answers NaN past
|
|
3406
|
+
* it — matched here, and it keeps days_from_civil's long long exact.
|
|
3407
|
+
* Never throws. */
|
|
3408
|
+
double scr_date_utc(double y, double mo, double d,
|
|
3409
|
+
double h, double mi, double s, double ms) {
|
|
3410
|
+
if (!isfinite(y) || !isfinite(mo) || !isfinite(d) || !isfinite(h) ||
|
|
3411
|
+
!isfinite(mi) || !isfinite(s) || !isfinite(ms)) {
|
|
3412
|
+
return NAN;
|
|
3413
|
+
}
|
|
3414
|
+
y = trunc(y);
|
|
3415
|
+
mo = trunc(mo);
|
|
3416
|
+
d = trunc(d);
|
|
3417
|
+
h = trunc(h);
|
|
3418
|
+
mi = trunc(mi);
|
|
3419
|
+
s = trunc(s);
|
|
3420
|
+
ms = trunc(ms);
|
|
3421
|
+
if (y >= 0 && y <= 99) y += 1900;
|
|
3422
|
+
double ym = y + floor(mo / 12.0);
|
|
3423
|
+
int mn = (int)(mo - floor(mo / 12.0) * 12.0); /* 0..11 */
|
|
3424
|
+
if (fabs(ym) > 1000000.0) return NAN; /* V8's MakeDay year bound */
|
|
3425
|
+
double days = scr_days_from_civil((long long)ym, mn + 1, 1) + (d - 1.0);
|
|
3426
|
+
double t = days * 86400000.0 + h * 3600000.0 + mi * 60000.0 + s * 1000.0 + ms;
|
|
3427
|
+
if (fabs(t) > 8640000000000000.0) return NAN; /* TimeClip */
|
|
3428
|
+
return t == 0 ? 0 : t; /* normalize -0 (TimeClip's +0) */
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3431
|
+
/* ── Number statics ────────────────────────────────────────────────────
|
|
3432
|
+
* JS-exact: the ES2015 Number statics never coerce (unlike the global
|
|
3433
|
+
* isNaN/isFinite), and the compiler routes only number-typed arguments
|
|
3434
|
+
* here, so plain C predicates are the whole story. */
|
|
3435
|
+
|
|
3436
|
+
bool scr_num_is_finite(double x) { return isfinite(x) != 0; }
|
|
3437
|
+
|
|
3438
|
+
/* Number.prototype.toExponential() with fractionDigits UNDEFINED — the
|
|
3439
|
+
* spec's "as many digits as necessary": the shortest correctly-rounded
|
|
3440
|
+
* mantissa that round-trips, formatted d[.ddd]e±X with no zero-padding of
|
|
3441
|
+
* the exponent ("7e+0", "1.5e-7"). NaN → "NaN", ±Infinity → its text,
|
|
3442
|
+
* ±0 → "0e+0" (the spec's x = +0 arm — the sign of -0 is dropped because
|
|
3443
|
+
* -0 < 0 is false). Verified differentially against Node. */
|
|
3444
|
+
ScrStr *scr_num_to_exponential(double x) {
|
|
3445
|
+
if (isnan(x)) return scr_str_new("NaN", 3);
|
|
3446
|
+
if (isinf(x)) return x < 0 ? scr_str_new("-Infinity", 9) : scr_str_new("Infinity", 8);
|
|
3447
|
+
if (x == 0) return scr_str_new("0e+0", 4);
|
|
3448
|
+
char buf[64];
|
|
3449
|
+
int len = 0;
|
|
3450
|
+
for (int prec = 0; prec <= 17; prec++) {
|
|
3451
|
+
len = snprintf(buf, sizeof buf, "%.*e", prec, x);
|
|
3452
|
+
if (strtod(buf, NULL) == x) break;
|
|
3453
|
+
}
|
|
3454
|
+
/* Normalize C's exponent ("e+05" / "e-123") to JS's ("e+5"/"e-123"):
|
|
3455
|
+
* drop leading zeros after the sign, keeping at least one digit. */
|
|
3456
|
+
char out[64];
|
|
3457
|
+
int o = 0;
|
|
3458
|
+
const char *e = memchr(buf, 'e', (size_t)len);
|
|
3459
|
+
const char *p = buf;
|
|
3460
|
+
while (p < e) out[o++] = *p++;
|
|
3461
|
+
out[o++] = 'e';
|
|
3462
|
+
p++; /* past 'e' */
|
|
3463
|
+
out[o++] = *p++; /* the sign — %e always emits one */
|
|
3464
|
+
while (*p == '0' && p[1] >= '0' && p[1] <= '9') p++;
|
|
3465
|
+
while (p < buf + len) out[o++] = *p++;
|
|
3466
|
+
return scr_str_new(out, (size_t)o);
|
|
3467
|
+
}
|
|
3468
|
+
|
|
3469
|
+
/* Number.prototype.toFixed() with fractionDigits UNDEFINED (= 0 digits) —
|
|
3470
|
+
* the spec's pick of the integer n closest to x with ties toward the
|
|
3471
|
+
* LARGER n on the magnitude ((2.5).toFixed() = "3", (-2.5).toFixed() =
|
|
3472
|
+
* "-3" — printf's half-even would answer "2"), the "-0" result for
|
|
3473
|
+
* negative fractions rounding to zero, and ToString fallback at
|
|
3474
|
+
* |x| ≥ 1e21 (NaN and ±Infinity ride that arm's texts via ToString too,
|
|
3475
|
+
* matching the spec's early answers). */
|
|
3476
|
+
ScrStr *scr_num_to_fixed0(double x) {
|
|
3477
|
+
if (isnan(x)) return scr_str_new("NaN", 3);
|
|
3478
|
+
if (fabs(x) >= 1e21) return scr_f64_to_scrstr(x);
|
|
3479
|
+
double a = fabs(x);
|
|
3480
|
+
double fl = floor(a);
|
|
3481
|
+
double n = (a - fl >= 0.5) ? fl + 1 : fl; /* exact: a < 2^70, and below
|
|
3482
|
+
* 2^52 the fraction is exact;
|
|
3483
|
+
* at/above it a - fl == 0 */
|
|
3484
|
+
ScrStr *digits = scr_f64_to_scrstr(n); /* n < 1e21 → never exponent form */
|
|
3485
|
+
if (!(x < 0)) return digits;
|
|
3486
|
+
size_t len = digits->len;
|
|
3487
|
+
char buf[64];
|
|
3488
|
+
buf[0] = '-';
|
|
3489
|
+
memcpy(buf + 1, digits->data, len);
|
|
3490
|
+
ScrStr *r = scr_str_new(buf, len + 1);
|
|
3491
|
+
scr_str_release(digits);
|
|
3492
|
+
return r;
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
bool scr_num_is_nan(double x) { return isnan(x) != 0; }
|
|
3496
|
+
|
|
3497
|
+
bool scr_num_is_integer(double x) { return isfinite(x) && trunc(x) == x; }
|
|
3498
|
+
|
|
3499
|
+
bool scr_num_is_safe_integer(double x) {
|
|
3500
|
+
return isfinite(x) && trunc(x) == x && fabs(x) <= 9007199254740991.0;
|
|
3501
|
+
}
|
|
3502
|
+
|
|
3503
|
+
/* ── bitwise operators ─────────────────────────────────────────────────
|
|
3504
|
+
* JS-exact (scr_runtime.h has the contract). ToUint32 is the primitive —
|
|
3505
|
+
* ToInt32 and the Int32-typed results are the same 32 bits reinterpreted
|
|
3506
|
+
* as two's complement, spelled portably (no implementation-defined
|
|
3507
|
+
* narrowing casts, no UB shifts of signed values).
|
|
3508
|
+
*/
|
|
3509
|
+
|
|
3510
|
+
static uint32_t scr_to_uint32(double d) {
|
|
3511
|
+
if (!isfinite(d)) return 0; /* NaN, +Infinity, -Infinity */
|
|
3512
|
+
double t = trunc(d);
|
|
3513
|
+
t = fmod(t, 4294967296.0); /* exact for doubles; result in (-2^32, 2^32) */
|
|
3514
|
+
if (t < 0) t += 4294967296.0;
|
|
3515
|
+
return (uint32_t)t;
|
|
3516
|
+
}
|
|
3517
|
+
|
|
3518
|
+
/* The 32 bits as a SIGNED (Int32) JS number. */
|
|
3519
|
+
static double scr_bits_as_int32(uint32_t u) {
|
|
3520
|
+
return u >= UINT32_C(0x80000000)
|
|
3521
|
+
? (double)(int32_t)(u - UINT32_C(0x80000000)) + (double)INT32_MIN
|
|
3522
|
+
: (double)u;
|
|
3523
|
+
}
|
|
3524
|
+
|
|
3525
|
+
double scr_bit_and(double a, double b) {
|
|
3526
|
+
return scr_bits_as_int32(scr_to_uint32(a) & scr_to_uint32(b));
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3529
|
+
double scr_bit_or(double a, double b) {
|
|
3530
|
+
return scr_bits_as_int32(scr_to_uint32(a) | scr_to_uint32(b));
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3533
|
+
double scr_bit_xor(double a, double b) {
|
|
3534
|
+
return scr_bits_as_int32(scr_to_uint32(a) ^ scr_to_uint32(b));
|
|
3535
|
+
}
|
|
3536
|
+
|
|
3537
|
+
double scr_bit_shl(double a, double b) {
|
|
3538
|
+
return scr_bits_as_int32(scr_to_uint32(a) << (scr_to_uint32(b) & 31u));
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3541
|
+
double scr_bit_shr(double a, double b) {
|
|
3542
|
+
uint32_t u = scr_to_uint32(a);
|
|
3543
|
+
uint32_t s = scr_to_uint32(b) & 31u;
|
|
3544
|
+
uint32_t r = u >> s;
|
|
3545
|
+
if ((u & UINT32_C(0x80000000)) != 0 && s != 0) {
|
|
3546
|
+
r |= ~(UINT32_C(0xffffffff) >> s); /* arithmetic shift: sign-fill */
|
|
3547
|
+
}
|
|
3548
|
+
return scr_bits_as_int32(r);
|
|
3549
|
+
}
|
|
3550
|
+
|
|
3551
|
+
double scr_bit_ushr(double a, double b) {
|
|
3552
|
+
/* The one Uint32-typed result: (-1 >>> 0) === 4294967295. */
|
|
3553
|
+
return (double)(scr_to_uint32(a) >> (scr_to_uint32(b) & 31u));
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
double scr_bit_not(double a) {
|
|
3557
|
+
return scr_bits_as_int32(~scr_to_uint32(a));
|
|
3558
|
+
}
|
|
3559
|
+
|
|
3560
|
+
/* ── checked catch-binding cast (`e as C`) ────────────────────────────
|
|
3561
|
+
* The caught analog of the dyn boundary's checked casts: an OBJ payload
|
|
3562
|
+
* inside the class's preorder interval extracts (retained, +1); every
|
|
3563
|
+
* other payload throws a catchable TypeError naming the class. Node's
|
|
3564
|
+
* `as` is erasure — the runtime check is the documented trust-but-verify
|
|
3565
|
+
* stance for dynamic values, extended to exception payloads. */
|
|
3566
|
+
void *scr_caught_check_obj(const ScrCaught *c, size_t pre, size_t post,
|
|
3567
|
+
const char *cls) {
|
|
3568
|
+
if (scr_caught_instanceof(c, pre, post)) return c->retain_fn(c->payload);
|
|
3569
|
+
char msg[160];
|
|
3570
|
+
int len = snprintf(msg, sizeof msg,
|
|
3571
|
+
"caught value is not an instance of %s (checked cast)",
|
|
3572
|
+
cls);
|
|
3573
|
+
scr_throw_error_msg(SCR_ERR_TYPE, msg, (size_t)len);
|
|
3574
|
+
return NULL; /* callers are compiler-emitted pending checks */
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3577
|
+
/* ── Set → array drain ([...set]) ─────────────────────────────────────
|
|
3578
|
+
* The live entries in insertion order (tombstones skipped — the forEach
|
|
3579
|
+
* walk folded into one call; no user code runs mid-drain, so the
|
|
3580
|
+
* live-iteration rules are moot). Borrows the set; the array is +1,
|
|
3581
|
+
* string elements retained into it by the iter_key read. */
|
|
3582
|
+
ScrArr *scr_set_to_arr_f64(const ScrMap *s) {
|
|
3583
|
+
size_t n = (size_t)scr_map_iter_count(s);
|
|
3584
|
+
ScrArr *out = scr_arr_new(SCR_ELEM_F64, (size_t)scr_map_size(s));
|
|
3585
|
+
for (size_t i = 0; i < n; i++) {
|
|
3586
|
+
if (!scr_map_iter_live(s, (double)i)) continue;
|
|
3587
|
+
scr_arr_push_f64(out, scr_map_iter_key_f64(s, (double)i));
|
|
3588
|
+
}
|
|
3589
|
+
return out;
|
|
3590
|
+
}
|
|
3591
|
+
|
|
3592
|
+
ScrArr *scr_set_to_arr_str(const ScrMap *s) {
|
|
3593
|
+
size_t n = (size_t)scr_map_iter_count(s);
|
|
3594
|
+
ScrArr *out = scr_arr_new(SCR_ELEM_STR, (size_t)scr_map_size(s));
|
|
3595
|
+
for (size_t i = 0; i < n; i++) {
|
|
3596
|
+
if (!scr_map_iter_live(s, (double)i)) continue;
|
|
3597
|
+
scr_arr_push_ref(out, scr_map_iter_key_str(s, (double)i));
|
|
3598
|
+
}
|
|
3599
|
+
return out;
|
|
3600
|
+
}
|
|
3601
|
+
|
|
3602
|
+
ScrArr *scr_set_to_arr_ref(const ScrMap *s) {
|
|
3603
|
+
size_t n = (size_t)scr_map_iter_count(s);
|
|
3604
|
+
/* The elements' adapters ride from the set (no trace: handle elements
|
|
3605
|
+
* are acyclic by the set's own construction rule). */
|
|
3606
|
+
ScrArr *out = scr_arr_new_ref(s->key_retain, s->key_release, NULL, (size_t)scr_map_size(s));
|
|
3607
|
+
for (size_t i = 0; i < n; i++) {
|
|
3608
|
+
if (!scr_map_iter_live(s, (double)i)) continue;
|
|
3609
|
+
scr_arr_push_ref(out, scr_map_iter_key_ref(s, (double)i));
|
|
3610
|
+
}
|
|
3611
|
+
return out;
|
|
3612
|
+
}
|