@scriptc/runtime 0.0.22 → 0.0.24
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/package.json +1 -1
- package/src/scr_array.c +65 -0
- package/src/scr_async.c +291 -7
- package/src/scr_bytes.c +634 -4
- package/src/scr_bytes_io.c +4 -2
- package/src/scr_cycle.c +403 -62
- package/src/scr_fetch.c +652 -77
- package/src/scr_file_handle.c +491 -0
- package/src/scr_island.c +10 -0
- package/src/scr_json.c +137 -21
- package/src/scr_lib.c +957 -48
- package/src/scr_number.c +9 -0
- package/src/scr_regex.c +12 -1
- package/src/scr_runtime.h +189 -19
- package/src/scr_string.c +13 -2
- package/src/scr_text_decoder_data.h +7389 -0
- package/src/scr_util.c +984 -0
- package/src/scr_win_stats.h +23 -0
package/src/scr_lib.c
CHANGED
|
@@ -49,13 +49,14 @@
|
|
|
49
49
|
#include <ws2tcpip.h> /* inet_ntop, sockaddr_in6 */
|
|
50
50
|
#include <iphlpapi.h> /* GetAdaptersAddresses (os.networkInterfaces) */
|
|
51
51
|
#include <windows.h>
|
|
52
|
+
#include <winioctl.h> /* FSCTL_GET_REPARSE_POINT */
|
|
52
53
|
#include <lmcons.h> /* UNLEN for GetUserNameA */
|
|
54
|
+
#include "scr_win_stats.h"
|
|
53
55
|
|
|
54
56
|
|
|
55
|
-
/* CRT
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* FILE_ATTRIBUTE_REPARSE_POINT. */
|
|
57
|
+
/* The CRT has no symlink view, so its internal lstat users degrade to stat.
|
|
58
|
+
* The public Stats path below bypasses this seam and opens the final component
|
|
59
|
+
* as a reparse point for lstatSync, matching Node's no-follow split. */
|
|
59
60
|
#define lstat stat
|
|
60
61
|
|
|
61
62
|
/* Windows has no directory mode bits; the CRT mkdir takes one argument. */
|
|
@@ -76,6 +77,7 @@
|
|
|
76
77
|
#include <ifaddrs.h>
|
|
77
78
|
#include <net/if.h>
|
|
78
79
|
#include <netinet/in.h>
|
|
80
|
+
#include <pthread.h>
|
|
79
81
|
#include <pwd.h>
|
|
80
82
|
#include <sys/ioctl.h>
|
|
81
83
|
#include <sys/socket.h>
|
|
@@ -1111,6 +1113,23 @@ static double scr_filetime_us(FILETIME ft) {
|
|
|
1111
1113
|
v.HighPart = ft.dwHighDateTime;
|
|
1112
1114
|
return (double)(v.QuadPart / 10);
|
|
1113
1115
|
}
|
|
1116
|
+
|
|
1117
|
+
/* A filesystem FILETIME is 100ns ticks since 1601. Match libuv's split into
|
|
1118
|
+
* Unix seconds + nanoseconds before doing Node's millisecond arithmetic; the
|
|
1119
|
+
* split matters for the last-bit rounding of dates far from the epoch. */
|
|
1120
|
+
static double scr_filetime_unix_ms(FILETIME ft) {
|
|
1121
|
+
ULARGE_INTEGER raw;
|
|
1122
|
+
raw.LowPart = ft.dwLowDateTime;
|
|
1123
|
+
raw.HighPart = ft.dwHighDateTime;
|
|
1124
|
+
int64_t ticks = (int64_t)raw.QuadPart - INT64_C(116444736000000000);
|
|
1125
|
+
int64_t sec = ticks / INT64_C(10000000);
|
|
1126
|
+
int64_t rem = ticks % INT64_C(10000000);
|
|
1127
|
+
if (rem < 0) {
|
|
1128
|
+
sec--;
|
|
1129
|
+
rem += INT64_C(10000000);
|
|
1130
|
+
}
|
|
1131
|
+
return (double)sec * 1000.0 + (double)rem / 10000.0;
|
|
1132
|
+
}
|
|
1114
1133
|
double scr_cpu_user(void) {
|
|
1115
1134
|
FILETIME c, e, k, u;
|
|
1116
1135
|
if (!GetProcessTimes(GetCurrentProcess(), &c, &e, &k, &u)) return 0;
|
|
@@ -1380,11 +1399,23 @@ static const char *scr_errno_name(int e, char *fallback, size_t cap) {
|
|
|
1380
1399
|
case ENOENT: return "ENOENT";
|
|
1381
1400
|
case EEXIST: return "EEXIST";
|
|
1382
1401
|
case EACCES: return "EACCES";
|
|
1402
|
+
case EBUSY: return "EBUSY";
|
|
1403
|
+
case EINVAL: return "EINVAL";
|
|
1404
|
+
case EIO: return "EIO";
|
|
1405
|
+
case ENAMETOOLONG: return "ENAMETOOLONG";
|
|
1406
|
+
case ENOMEM: return "ENOMEM";
|
|
1407
|
+
case ENOSPC: return "ENOSPC";
|
|
1383
1408
|
case ENOTDIR: return "ENOTDIR";
|
|
1384
1409
|
case EISDIR: return "EISDIR";
|
|
1385
1410
|
case ENOTEMPTY: return "ENOTEMPTY";
|
|
1386
1411
|
case EPERM: return "EPERM";
|
|
1412
|
+
case EROFS: return "EROFS";
|
|
1413
|
+
case EXDEV: return "EXDEV";
|
|
1387
1414
|
case EBADF: return "EBADF";
|
|
1415
|
+
case EAGAIN: return "EAGAIN";
|
|
1416
|
+
case EFBIG: return "EFBIG";
|
|
1417
|
+
case EPIPE: return "EPIPE";
|
|
1418
|
+
case ESPIPE: return "ESPIPE";
|
|
1388
1419
|
default:
|
|
1389
1420
|
snprintf(fallback, cap, "E%d", e);
|
|
1390
1421
|
return fallback;
|
|
@@ -1396,11 +1427,23 @@ static const char *scr_errno_text(int e) {
|
|
|
1396
1427
|
case ENOENT: return "no such file or directory";
|
|
1397
1428
|
case EEXIST: return "file already exists";
|
|
1398
1429
|
case EACCES: return "permission denied";
|
|
1430
|
+
case EBUSY: return "resource busy or locked";
|
|
1431
|
+
case EINVAL: return "invalid argument";
|
|
1432
|
+
case EIO: return "i/o error";
|
|
1433
|
+
case ENAMETOOLONG: return "name too long";
|
|
1434
|
+
case ENOMEM: return "not enough memory";
|
|
1435
|
+
case ENOSPC: return "no space left on device";
|
|
1399
1436
|
case ENOTDIR: return "not a directory";
|
|
1400
1437
|
case EISDIR: return "illegal operation on a directory";
|
|
1401
1438
|
case ENOTEMPTY: return "directory not empty";
|
|
1402
1439
|
case EPERM: return "operation not permitted";
|
|
1440
|
+
case EROFS: return "read-only file system";
|
|
1441
|
+
case EXDEV: return "cross-device link not permitted";
|
|
1403
1442
|
case EBADF: return "bad file descriptor";
|
|
1443
|
+
case EAGAIN: return "resource temporarily unavailable";
|
|
1444
|
+
case EFBIG: return "file too large";
|
|
1445
|
+
case EPIPE: return "broken pipe";
|
|
1446
|
+
case ESPIPE: return "invalid seek";
|
|
1404
1447
|
default: return strerror(e);
|
|
1405
1448
|
}
|
|
1406
1449
|
}
|
|
@@ -1542,6 +1585,22 @@ void scr_fs_write_file(ScrStr *path, ScrStr *data) {
|
|
|
1542
1585
|
* existing file keeps its permissions, exactly Node (which never chmods
|
|
1543
1586
|
* here). Same error shapes as the plain form. */
|
|
1544
1587
|
void scr_fs_write_file_mode(ScrStr *path, ScrStr *data, double mode) {
|
|
1588
|
+
char recv[48], msg[176];
|
|
1589
|
+
scr_num_received(mode, recv);
|
|
1590
|
+
if (!(isfinite(mode) && trunc(mode) == mode)) {
|
|
1591
|
+
int len = snprintf(msg, sizeof msg,
|
|
1592
|
+
"The value of \"mode\" is out of range. It must be an integer. Received %s",
|
|
1593
|
+
recv);
|
|
1594
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
if (mode < 0 || mode > 4294967295.0) {
|
|
1598
|
+
int len = snprintf(msg, sizeof msg,
|
|
1599
|
+
"The value of \"mode\" is out of range. It must be >= 0 && <= 4294967295. Received %s",
|
|
1600
|
+
recv);
|
|
1601
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1545
1604
|
/* O_BINARY: zero on POSIX; on Windows it keeps the CRT from translating
|
|
1546
1605
|
* \n in these byte-exact writes (fopen's "wb" path already does). */
|
|
1547
1606
|
int fd = open(path->data, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, (mode_t)mode);
|
|
@@ -1646,12 +1705,321 @@ double scr_fs_open(ScrStr *path, ScrStr *flags) {
|
|
|
1646
1705
|
|
|
1647
1706
|
/* fs.closeSync(fd) — close(2); failure throws Node's path-less fs error
|
|
1648
1707
|
* shape ("EBADF: bad file descriptor, close"). */
|
|
1649
|
-
|
|
1708
|
+
|
|
1709
|
+
/* Offset-preserving read for fs.readSync's numeric-position form. POSIX
|
|
1710
|
+
* supplies pread(2). Windows follows libuv's synchronous-handle recipe:
|
|
1711
|
+
* ReadFile with an OVERLAPPED byte offset, then restore the handle position
|
|
1712
|
+
* because synchronous ReadFile updates it even when OVERLAPPED is present. */
|
|
1713
|
+
static ssize_t scr_fs_pread(int fd, void *data, size_t length, double position) {
|
|
1714
|
+
#ifdef _WIN32
|
|
1715
|
+
HANDLE handle = (HANDLE)_get_osfhandle(fd);
|
|
1716
|
+
if (handle == INVALID_HANDLE_VALUE) {
|
|
1717
|
+
errno = EBADF;
|
|
1718
|
+
return -1;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
OVERLAPPED overlapped;
|
|
1722
|
+
memset(&overlapped, 0, sizeof overlapped);
|
|
1723
|
+
LARGE_INTEGER at;
|
|
1724
|
+
at.QuadPart = (LONGLONG)position;
|
|
1725
|
+
overlapped.Offset = at.LowPart;
|
|
1726
|
+
overlapped.OffsetHigh = at.HighPart;
|
|
1727
|
+
|
|
1728
|
+
LARGE_INTEGER zero;
|
|
1729
|
+
LARGE_INTEGER original;
|
|
1730
|
+
zero.QuadPart = 0;
|
|
1731
|
+
BOOL restore = SetFilePointerEx(handle, zero, &original, FILE_CURRENT);
|
|
1732
|
+
DWORD got = 0;
|
|
1733
|
+
DWORD want = length > (size_t)UINT32_MAX ? UINT32_MAX : (DWORD)length;
|
|
1734
|
+
BOOL ok = ReadFile(handle, data, want, &got, &overlapped);
|
|
1735
|
+
DWORD error = ok ? ERROR_SUCCESS : GetLastError();
|
|
1736
|
+
if (restore) (void)SetFilePointerEx(handle, original, NULL, FILE_BEGIN);
|
|
1737
|
+
/* ReadFile may report a terminal status after still filling part of the
|
|
1738
|
+
* caller's buffer (notably ERROR_MORE_DATA for a message-mode pipe).
|
|
1739
|
+
* libuv/Node return those bytes and surface the status on the next read. */
|
|
1740
|
+
if (ok || got > 0 || error == ERROR_HANDLE_EOF || error == ERROR_BROKEN_PIPE) {
|
|
1741
|
+
return (ssize_t)got;
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
/* The read-specific subset of libuv's Win32-to-errno translation. */
|
|
1745
|
+
switch (error) {
|
|
1746
|
+
case ERROR_INVALID_HANDLE:
|
|
1747
|
+
case ERROR_ACCESS_DENIED:
|
|
1748
|
+
errno = EBADF;
|
|
1749
|
+
break;
|
|
1750
|
+
case ERROR_INVALID_FUNCTION:
|
|
1751
|
+
case ERROR_INVALID_PARAMETER:
|
|
1752
|
+
errno = EINVAL;
|
|
1753
|
+
break;
|
|
1754
|
+
case ERROR_NOT_ENOUGH_MEMORY:
|
|
1755
|
+
case ERROR_OUTOFMEMORY:
|
|
1756
|
+
errno = ENOMEM;
|
|
1757
|
+
break;
|
|
1758
|
+
case ERROR_OPERATION_ABORTED:
|
|
1759
|
+
errno = EINTR;
|
|
1760
|
+
break;
|
|
1761
|
+
default:
|
|
1762
|
+
errno = EIO;
|
|
1763
|
+
break;
|
|
1764
|
+
}
|
|
1765
|
+
return -1;
|
|
1766
|
+
#else
|
|
1767
|
+
return pread(fd, data, length, (off_t)position);
|
|
1768
|
+
#endif
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
/* Offset-preserving write for fs.writeSync's numeric-position forms. The
|
|
1772
|
+
* Windows arm mirrors scr_fs_pread: WriteFile receives an OVERLAPPED offset
|
|
1773
|
+
* and the CRT descriptor's current position is restored before returning. */
|
|
1774
|
+
static ssize_t scr_fs_pwrite(int fd, const void *data, size_t length, double position) {
|
|
1775
|
+
#ifdef _WIN32
|
|
1776
|
+
HANDLE handle = (HANDLE)_get_osfhandle(fd);
|
|
1777
|
+
if (handle == INVALID_HANDLE_VALUE) {
|
|
1778
|
+
errno = EBADF;
|
|
1779
|
+
return -1;
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
OVERLAPPED overlapped;
|
|
1783
|
+
memset(&overlapped, 0, sizeof overlapped);
|
|
1784
|
+
LARGE_INTEGER at;
|
|
1785
|
+
at.QuadPart = (LONGLONG)position;
|
|
1786
|
+
overlapped.Offset = at.LowPart;
|
|
1787
|
+
overlapped.OffsetHigh = at.HighPart;
|
|
1788
|
+
|
|
1789
|
+
LARGE_INTEGER zero;
|
|
1790
|
+
LARGE_INTEGER original;
|
|
1791
|
+
zero.QuadPart = 0;
|
|
1792
|
+
BOOL restore = SetFilePointerEx(handle, zero, &original, FILE_CURRENT);
|
|
1793
|
+
DWORD wrote = 0;
|
|
1794
|
+
DWORD want = length > (size_t)UINT32_MAX ? UINT32_MAX : (DWORD)length;
|
|
1795
|
+
BOOL ok = WriteFile(handle, data, want, &wrote, &overlapped);
|
|
1796
|
+
DWORD error = ok ? ERROR_SUCCESS : GetLastError();
|
|
1797
|
+
if (restore) (void)SetFilePointerEx(handle, original, NULL, FILE_BEGIN);
|
|
1798
|
+
if (ok || wrote > 0) return (ssize_t)wrote;
|
|
1799
|
+
|
|
1800
|
+
switch (error) {
|
|
1801
|
+
case ERROR_INVALID_HANDLE:
|
|
1802
|
+
case ERROR_ACCESS_DENIED:
|
|
1803
|
+
errno = EBADF;
|
|
1804
|
+
break;
|
|
1805
|
+
case ERROR_INVALID_FUNCTION:
|
|
1806
|
+
case ERROR_INVALID_PARAMETER:
|
|
1807
|
+
errno = EINVAL;
|
|
1808
|
+
break;
|
|
1809
|
+
case ERROR_DISK_FULL:
|
|
1810
|
+
case ERROR_HANDLE_DISK_FULL:
|
|
1811
|
+
errno = ENOSPC;
|
|
1812
|
+
break;
|
|
1813
|
+
case ERROR_FILE_TOO_LARGE:
|
|
1814
|
+
errno = EFBIG;
|
|
1815
|
+
break;
|
|
1816
|
+
case ERROR_BROKEN_PIPE:
|
|
1817
|
+
case ERROR_NO_DATA:
|
|
1818
|
+
errno = EPIPE;
|
|
1819
|
+
break;
|
|
1820
|
+
case ERROR_NOT_ENOUGH_MEMORY:
|
|
1821
|
+
case ERROR_OUTOFMEMORY:
|
|
1822
|
+
errno = ENOMEM;
|
|
1823
|
+
break;
|
|
1824
|
+
case ERROR_OPERATION_ABORTED:
|
|
1825
|
+
errno = EINTR;
|
|
1826
|
+
break;
|
|
1827
|
+
default:
|
|
1828
|
+
errno = EIO;
|
|
1829
|
+
break;
|
|
1830
|
+
}
|
|
1831
|
+
return -1;
|
|
1832
|
+
#else
|
|
1833
|
+
return pwrite(fd, data, length, (off_t)position);
|
|
1834
|
+
#endif
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
static bool scr_fs_write_fd_valid(double fd) {
|
|
1838
|
+
char msg[160];
|
|
1839
|
+
char recv[48];
|
|
1840
|
+
scr_num_received(fd, recv);
|
|
1841
|
+
if (!(isfinite(fd) && trunc(fd) == fd)) {
|
|
1842
|
+
int len = snprintf(msg, sizeof msg,
|
|
1843
|
+
"The value of \"fd\" is out of range. It must be an integer. Received %s",
|
|
1844
|
+
recv);
|
|
1845
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
|
|
1846
|
+
return false;
|
|
1847
|
+
}
|
|
1848
|
+
if (fd < 0 || fd > 2147483647.0) {
|
|
1849
|
+
int len = snprintf(msg, sizeof msg,
|
|
1850
|
+
"The value of \"fd\" is out of range. It must be >= 0 && <= 2147483647. Received %s",
|
|
1851
|
+
recv);
|
|
1852
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
|
|
1853
|
+
return false;
|
|
1854
|
+
}
|
|
1855
|
+
return true;
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
/* write(2) raises SIGPIPE before returning EPIPE when a pipe/socket peer has
|
|
1859
|
+
* closed; write(2)/pwrite(2) likewise raise SIGXFSZ before returning EFBIG at
|
|
1860
|
+
* RLIMIT_FSIZE. Node turns both into catchable fs errors. Do the same per
|
|
1861
|
+
* calling thread rather than changing process dispositions (which spawned
|
|
1862
|
+
* children would inherit). Consume only a signal freshly generated by this
|
|
1863
|
+
* call, preserving any signal that was already pending. */
|
|
1864
|
+
static ssize_t scr_fs_write_once(int fd, const void *data, size_t length,
|
|
1865
|
+
bool positioned, double position) {
|
|
1866
|
+
#ifdef _WIN32
|
|
1867
|
+
return positioned
|
|
1868
|
+
? scr_fs_pwrite(fd, data, length, position)
|
|
1869
|
+
: write(fd, data, length);
|
|
1870
|
+
#else
|
|
1871
|
+
sigset_t write_set, old_set, pending;
|
|
1872
|
+
sigemptyset(&write_set);
|
|
1873
|
+
#ifdef SIGPIPE
|
|
1874
|
+
sigaddset(&write_set, SIGPIPE);
|
|
1875
|
+
#endif
|
|
1876
|
+
#ifdef SIGXFSZ
|
|
1877
|
+
sigaddset(&write_set, SIGXFSZ);
|
|
1878
|
+
#endif
|
|
1879
|
+
if (pthread_sigmask(SIG_BLOCK, &write_set, &old_set) != 0) {
|
|
1880
|
+
return positioned
|
|
1881
|
+
? scr_fs_pwrite(fd, data, length, position)
|
|
1882
|
+
: write(fd, data, length);
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
bool pending_known = sigpending(&pending) == 0;
|
|
1886
|
+
#ifdef SIGPIPE
|
|
1887
|
+
bool had_pipe = pending_known && sigismember(&pending, SIGPIPE) == 1;
|
|
1888
|
+
#endif
|
|
1889
|
+
#ifdef SIGXFSZ
|
|
1890
|
+
bool had_xfsz = pending_known && sigismember(&pending, SIGXFSZ) == 1;
|
|
1891
|
+
#endif
|
|
1892
|
+
ssize_t n = positioned
|
|
1893
|
+
? scr_fs_pwrite(fd, data, length, position)
|
|
1894
|
+
: write(fd, data, length);
|
|
1895
|
+
int write_errno = errno;
|
|
1896
|
+
|
|
1897
|
+
int generated = 0;
|
|
1898
|
+
if (n < 0 && pending_known && sigpending(&pending) == 0) {
|
|
1899
|
+
#ifdef SIGPIPE
|
|
1900
|
+
if (write_errno == EPIPE && !had_pipe && sigismember(&pending, SIGPIPE) == 1) {
|
|
1901
|
+
generated = SIGPIPE;
|
|
1902
|
+
}
|
|
1903
|
+
#endif
|
|
1904
|
+
#ifdef SIGXFSZ
|
|
1905
|
+
if (write_errno == EFBIG && !had_xfsz && sigismember(&pending, SIGXFSZ) == 1) {
|
|
1906
|
+
generated = SIGXFSZ;
|
|
1907
|
+
}
|
|
1908
|
+
#endif
|
|
1909
|
+
}
|
|
1910
|
+
if (generated != 0) {
|
|
1911
|
+
sigset_t generated_set;
|
|
1912
|
+
sigemptyset(&generated_set);
|
|
1913
|
+
sigaddset(&generated_set, generated);
|
|
1914
|
+
int caught = 0;
|
|
1915
|
+
int wait_err;
|
|
1916
|
+
do {
|
|
1917
|
+
wait_err = sigwait(&generated_set, &caught);
|
|
1918
|
+
} while (wait_err == EINTR);
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
(void)pthread_sigmask(SIG_SETMASK, &old_set, NULL);
|
|
1922
|
+
errno = write_errno;
|
|
1923
|
+
return n;
|
|
1924
|
+
#endif
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
static double scr_fs_write_bytes(double fd, const void *data, size_t length,
|
|
1928
|
+
double position) {
|
|
1929
|
+
if (!scr_fs_write_fd_valid(fd)) return 0;
|
|
1930
|
+
/* Node/libuv treats every position other than a safe nonnegative integer
|
|
1931
|
+
* as the current-offset sentinel for writes (unlike readSync, it does not
|
|
1932
|
+
* throw for negative/fractional positions). */
|
|
1933
|
+
bool positioned = isfinite(position) && trunc(position) == position &&
|
|
1934
|
+
position >= 0 && position <= 9007199254740991.0;
|
|
1935
|
+
ssize_t n;
|
|
1936
|
+
do {
|
|
1937
|
+
n = scr_fs_write_once((int)fd, data, length, positioned, position);
|
|
1938
|
+
} while (n < 0 && errno == EINTR);
|
|
1939
|
+
if (n < 0) {
|
|
1940
|
+
int e = errno;
|
|
1941
|
+
char namebuf[16];
|
|
1942
|
+
const char *name = scr_errno_name(e, namebuf, sizeof namebuf);
|
|
1943
|
+
const char *text = scr_errno_text(e);
|
|
1944
|
+
char msg[160];
|
|
1945
|
+
int len = snprintf(msg, sizeof msg, "%s: %s, write", name, text);
|
|
1946
|
+
scr_throw_error_msg_code(SCR_ERR_ERROR, msg, (size_t)len, name);
|
|
1947
|
+
return 0;
|
|
1948
|
+
}
|
|
1949
|
+
return (double)n;
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
/* fs.writeSync(fd, buffer, offset, length[, position]) — validates the
|
|
1953
|
+
* caller window before touching the descriptor, then submits one write like
|
|
1954
|
+
* Node/libuv. Positioned writes do not advance the descriptor. */
|
|
1955
|
+
double scr_fs_write_sync(double fd, ScrBytes *buf, double offset, double length,
|
|
1956
|
+
double position) {
|
|
1957
|
+
size_t bytelen = buf->len; /* frontend admits u8 buffers only */
|
|
1958
|
+
char msg[160];
|
|
1959
|
+
char recv[48];
|
|
1960
|
+
scr_num_received(offset, recv);
|
|
1961
|
+
if (!(isfinite(offset) && trunc(offset) == offset)) {
|
|
1962
|
+
int len = snprintf(msg, sizeof msg,
|
|
1963
|
+
"The value of \"offset\" is out of range. It must be an integer. Received %s",
|
|
1964
|
+
recv);
|
|
1965
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
|
|
1966
|
+
return 0;
|
|
1967
|
+
}
|
|
1968
|
+
if (offset < 0 || offset > 9007199254740991.0) {
|
|
1969
|
+
int len = snprintf(msg, sizeof msg,
|
|
1970
|
+
"The value of \"offset\" is out of range. It must be >= 0 && <= 9007199254740991. Received %s",
|
|
1971
|
+
recv);
|
|
1972
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
|
|
1973
|
+
return 0;
|
|
1974
|
+
}
|
|
1975
|
+
if (offset > (double)bytelen) {
|
|
1976
|
+
int len = snprintf(msg, sizeof msg,
|
|
1977
|
+
"The value of \"offset\" is out of range. It must be <= %zu. Received %s",
|
|
1978
|
+
bytelen, recv);
|
|
1979
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
|
|
1980
|
+
return 0;
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
scr_num_received(length, recv);
|
|
1984
|
+
if (length < 0) {
|
|
1985
|
+
int len = snprintf(msg, sizeof msg,
|
|
1986
|
+
"The value of \"length\" is out of range. It must be >= 0. Received %s",
|
|
1987
|
+
recv);
|
|
1988
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
|
|
1989
|
+
return 0;
|
|
1990
|
+
}
|
|
1991
|
+
size_t off = (size_t)offset;
|
|
1992
|
+
if (length > (double)(bytelen - off)) {
|
|
1993
|
+
int len = snprintf(msg, sizeof msg,
|
|
1994
|
+
"The value of \"length\" is out of range. It must be <= %zu. Received %s",
|
|
1995
|
+
bytelen - off, recv);
|
|
1996
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
|
|
1997
|
+
return 0;
|
|
1998
|
+
}
|
|
1999
|
+
if (!(isfinite(length) && trunc(length) == length)) {
|
|
2000
|
+
int len = snprintf(msg, sizeof msg,
|
|
2001
|
+
"The value of \"length\" is out of range. It must be an integer. Received %s",
|
|
2002
|
+
recv);
|
|
2003
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
|
|
2004
|
+
return 0;
|
|
2005
|
+
}
|
|
2006
|
+
return scr_fs_write_bytes(fd, buf->data + off, (size_t)length, position);
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
double scr_fs_write_str_sync(double fd, ScrStr *data, double position,
|
|
2010
|
+
ScrStr *encoding) {
|
|
2011
|
+
(void)encoding; /* frontend proves utf8; the argument still evaluates */
|
|
2012
|
+
return scr_fs_write_bytes(fd, data->data, data->len, position);
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
/* fs.readSync(fd, buffer, offset, length[, position]) — the buffer form.
|
|
1650
2016
|
* Node validates offset/length against the buffer before reading and
|
|
1651
2017
|
* throws ERR_OUT_OF_RANGE; here the checks clamp to the same contract and
|
|
1652
|
-
* throw the RangeError shape.
|
|
1653
|
-
*
|
|
1654
|
-
|
|
2018
|
+
* throw the RangeError shape. Position -1 means the fd's current offset;
|
|
2019
|
+
* nonnegative positions do not advance it. Returns the byte count the OS
|
|
2020
|
+
* reports; errors carry the errno name like the other fd operations. */
|
|
2021
|
+
double scr_fs_read_sync(double fd, ScrBytes *buf, double offset, double length,
|
|
2022
|
+
double position) {
|
|
1655
2023
|
size_t bytelen = buf->len; /* u8 buffers: elem count == byte count */
|
|
1656
2024
|
char msg[160];
|
|
1657
2025
|
int mlen;
|
|
@@ -1659,7 +2027,28 @@ double scr_fs_read_sync(double fd, ScrBytes *buf, double offset, double length)
|
|
|
1659
2027
|
* form, length against the remaining window (validateOffset vs the
|
|
1660
2028
|
* buffer-bounds check in fs.readSync). */
|
|
1661
2029
|
char numbuf[40];
|
|
1662
|
-
if (offset
|
|
2030
|
+
if (!(isfinite(offset) && trunc(offset) == offset)) {
|
|
2031
|
+
char recv[48];
|
|
2032
|
+
scr_num_received(offset, recv);
|
|
2033
|
+
mlen = snprintf(msg, sizeof msg,
|
|
2034
|
+
"The value of \"offset\" is out of range. It must be an integer. Received %s",
|
|
2035
|
+
recv);
|
|
2036
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)mlen, "ERR_OUT_OF_RANGE");
|
|
2037
|
+
return 0;
|
|
2038
|
+
}
|
|
2039
|
+
if (offset < 0 || offset > 9007199254740991.0) {
|
|
2040
|
+
numbuf[scr_f64_to_str(offset, numbuf)] = 0;
|
|
2041
|
+
mlen = snprintf(msg, sizeof msg,
|
|
2042
|
+
"The value of \"offset\" is out of range. It must be >= 0 && <= 9007199254740991. Received %s",
|
|
2043
|
+
numbuf);
|
|
2044
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)mlen, "ERR_OUT_OF_RANGE");
|
|
2045
|
+
return 0;
|
|
2046
|
+
}
|
|
2047
|
+
/* Node returns after offset's intrinsic validation when the requested
|
|
2048
|
+
* length normalizes to zero: buffer-window bounds, position, and fd are
|
|
2049
|
+
* not consulted. Preserve the existing size_t coercion's [0, 1) case. */
|
|
2050
|
+
if (length >= 0 && length < 1) return 0;
|
|
2051
|
+
if (offset > (double)bytelen) {
|
|
1663
2052
|
numbuf[scr_f64_to_str(offset, numbuf)] = 0;
|
|
1664
2053
|
mlen = snprintf(msg, sizeof msg,
|
|
1665
2054
|
"The value of \"offset\" is out of range. It must be >= 0 && <= 9007199254740991. Received %s",
|
|
@@ -1677,7 +2066,27 @@ double scr_fs_read_sync(double fd, ScrBytes *buf, double offset, double length)
|
|
|
1677
2066
|
return 0;
|
|
1678
2067
|
}
|
|
1679
2068
|
size_t want = (size_t)length;
|
|
1680
|
-
|
|
2069
|
+
if (!(isfinite(position) && trunc(position) == position)) {
|
|
2070
|
+
char recv[48];
|
|
2071
|
+
scr_num_received(position, recv);
|
|
2072
|
+
mlen = snprintf(msg, sizeof msg,
|
|
2073
|
+
"The value of \"position\" is out of range. It must be an integer. Received %s",
|
|
2074
|
+
recv);
|
|
2075
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)mlen, "ERR_OUT_OF_RANGE");
|
|
2076
|
+
return 0;
|
|
2077
|
+
}
|
|
2078
|
+
if (position < -1 || position > 9007199254740991.0) {
|
|
2079
|
+
char recv[48];
|
|
2080
|
+
scr_num_received(position, recv);
|
|
2081
|
+
mlen = snprintf(msg, sizeof msg,
|
|
2082
|
+
"The value of \"position\" is out of range. It must be >= -1 && <= 9007199254740991. Received %s",
|
|
2083
|
+
recv);
|
|
2084
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)mlen, "ERR_OUT_OF_RANGE");
|
|
2085
|
+
return 0;
|
|
2086
|
+
}
|
|
2087
|
+
ssize_t n = position == -1
|
|
2088
|
+
? read((int)fd, buf->data + off, want)
|
|
2089
|
+
: scr_fs_pread((int)fd, buf->data + off, want, position);
|
|
1681
2090
|
if (n < 0) {
|
|
1682
2091
|
int e = errno;
|
|
1683
2092
|
char namebuf[16];
|
|
@@ -1778,11 +2187,110 @@ void scr_fs_copyfile(ScrStr *src, ScrStr *dest) {
|
|
|
1778
2187
|
}
|
|
1779
2188
|
|
|
1780
2189
|
/* renameSync(old, new): rename(2), Node's two-path error shape ("ENOENT:
|
|
1781
|
-
* no such file or directory, rename 'a' -> 'b'").
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
2190
|
+
* no such file or directory, rename 'a' -> 'b'"). Windows must bypass
|
|
2191
|
+
* the CRT's rename(): it refuses an existing destination and cannot move
|
|
2192
|
+
* directories between parents, while Node/libuv uses MoveFileExW with
|
|
2193
|
+
* MOVEFILE_REPLACE_EXISTING. Runtime strings are UTF-8, so feed the wide
|
|
2194
|
+
* Win32 API rather than the active-code-page `A` form. */
|
|
2195
|
+
#ifdef _WIN32
|
|
2196
|
+
static WCHAR *scr_fs_win_wide(const ScrStr *path) {
|
|
2197
|
+
if (path->len > INT_MAX) {
|
|
2198
|
+
SetLastError(ERROR_FILENAME_EXCED_RANGE);
|
|
2199
|
+
return NULL;
|
|
2200
|
+
}
|
|
2201
|
+
int n = path->len > 0
|
|
2202
|
+
? MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
|
|
2203
|
+
path->data, (int)path->len, NULL, 0)
|
|
2204
|
+
: 0;
|
|
2205
|
+
if (path->len > 0 && n == 0) return NULL;
|
|
2206
|
+
WCHAR *wide = malloc(((size_t)n + 1) * sizeof *wide);
|
|
2207
|
+
if (!wide) {
|
|
2208
|
+
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
|
2209
|
+
return NULL;
|
|
1785
2210
|
}
|
|
2211
|
+
if (n > 0) {
|
|
2212
|
+
(void)MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
|
|
2213
|
+
path->data, (int)path->len, wide, n);
|
|
2214
|
+
}
|
|
2215
|
+
wide[n] = L'\0';
|
|
2216
|
+
return wide;
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
/* The reachable fs subset of libuv's uv_translate_sys_error table. */
|
|
2220
|
+
static int scr_fs_win_errno(DWORD error) {
|
|
2221
|
+
switch (error) {
|
|
2222
|
+
case ERROR_ALREADY_EXISTS:
|
|
2223
|
+
case ERROR_FILE_EXISTS:
|
|
2224
|
+
return EEXIST;
|
|
2225
|
+
case ERROR_LOCK_VIOLATION:
|
|
2226
|
+
case ERROR_PIPE_BUSY:
|
|
2227
|
+
case ERROR_SHARING_VIOLATION:
|
|
2228
|
+
return EBUSY;
|
|
2229
|
+
case ERROR_INVALID_FUNCTION:
|
|
2230
|
+
return EISDIR;
|
|
2231
|
+
case ERROR_INSUFFICIENT_BUFFER:
|
|
2232
|
+
case ERROR_INVALID_DATA:
|
|
2233
|
+
case ERROR_INVALID_PARAMETER:
|
|
2234
|
+
return EINVAL;
|
|
2235
|
+
case ERROR_BUFFER_OVERFLOW:
|
|
2236
|
+
case ERROR_FILENAME_EXCED_RANGE:
|
|
2237
|
+
return ENAMETOOLONG;
|
|
2238
|
+
case ERROR_NOT_ENOUGH_MEMORY:
|
|
2239
|
+
case ERROR_OUTOFMEMORY:
|
|
2240
|
+
return ENOMEM;
|
|
2241
|
+
case ERROR_CANNOT_MAKE:
|
|
2242
|
+
case ERROR_DISK_FULL:
|
|
2243
|
+
case ERROR_HANDLE_DISK_FULL:
|
|
2244
|
+
return ENOSPC;
|
|
2245
|
+
case ERROR_DIR_NOT_EMPTY:
|
|
2246
|
+
return ENOTEMPTY;
|
|
2247
|
+
case ERROR_ACCESS_DENIED:
|
|
2248
|
+
case ERROR_PRIVILEGE_NOT_HELD:
|
|
2249
|
+
return EPERM;
|
|
2250
|
+
case ERROR_WRITE_PROTECT:
|
|
2251
|
+
return EROFS;
|
|
2252
|
+
case ERROR_NOT_SAME_DEVICE:
|
|
2253
|
+
return EXDEV;
|
|
2254
|
+
case ERROR_BAD_PATHNAME:
|
|
2255
|
+
case ERROR_DIRECTORY:
|
|
2256
|
+
case ERROR_FILE_NOT_FOUND:
|
|
2257
|
+
case ERROR_INVALID_DRIVE:
|
|
2258
|
+
case ERROR_INVALID_NAME:
|
|
2259
|
+
case ERROR_PATH_NOT_FOUND:
|
|
2260
|
+
return ENOENT;
|
|
2261
|
+
default:
|
|
2262
|
+
return EIO;
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
#endif
|
|
2266
|
+
|
|
2267
|
+
int scr_fs_rename_raw(const ScrStr *oldpath, const ScrStr *newpath) {
|
|
2268
|
+
#ifdef _WIN32
|
|
2269
|
+
WCHAR *oldwide = scr_fs_win_wide(oldpath);
|
|
2270
|
+
if (!oldwide) return scr_fs_win_errno(GetLastError());
|
|
2271
|
+
WCHAR *newwide = scr_fs_win_wide(newpath);
|
|
2272
|
+
if (!newwide) {
|
|
2273
|
+
DWORD error = GetLastError();
|
|
2274
|
+
free(oldwide);
|
|
2275
|
+
return scr_fs_win_errno(error);
|
|
2276
|
+
}
|
|
2277
|
+
BOOL ok = MoveFileExW(oldwide, newwide, MOVEFILE_REPLACE_EXISTING);
|
|
2278
|
+
DWORD error = ok ? ERROR_SUCCESS : GetLastError();
|
|
2279
|
+
free(oldwide);
|
|
2280
|
+
free(newwide);
|
|
2281
|
+
return ok ? 0 : scr_fs_win_errno(error);
|
|
2282
|
+
#else
|
|
2283
|
+
return rename(oldpath->data, newpath->data) == 0 ? 0 : errno;
|
|
2284
|
+
#endif
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
void scr_fs_rename_error(int error, const ScrStr *oldpath, const ScrStr *newpath) {
|
|
2288
|
+
scr_fs_throw2(error, "rename", oldpath, newpath);
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
void scr_fs_rename(ScrStr *oldpath, ScrStr *newpath) {
|
|
2292
|
+
int error = scr_fs_rename_raw(oldpath, newpath);
|
|
2293
|
+
if (error != 0) scr_fs_rename_error(error, oldpath, newpath);
|
|
1786
2294
|
}
|
|
1787
2295
|
|
|
1788
2296
|
void scr_fs_rm(ScrStr *path) {
|
|
@@ -2070,7 +2578,7 @@ static void scr_fs_throw_nopath(int e, const char *op) {
|
|
|
2070
2578
|
const char *text = scr_errno_text(e);
|
|
2071
2579
|
char msg[256];
|
|
2072
2580
|
int len = snprintf(msg, sizeof msg, "%s: %s, %s", name, text, op);
|
|
2073
|
-
|
|
2581
|
+
scr_throw_error_msg_code(SCR_ERR_ERROR, msg, (size_t)len, name);
|
|
2074
2582
|
}
|
|
2075
2583
|
|
|
2076
2584
|
/* read(2) loop to EOF from the CURRENT position — Node's
|
|
@@ -2261,8 +2769,8 @@ void scr_process_stdin_destroy(void) {
|
|
|
2261
2769
|
}
|
|
2262
2770
|
|
|
2263
2771
|
/* ── Stats values ────────────────────────────────────────────────────
|
|
2264
|
-
* An immutable snapshot of stat(2) results — the
|
|
2265
|
-
*
|
|
2772
|
+
* An immutable snapshot of stat(2) results — the lowered type/mode, size,
|
|
2773
|
+
* allocation/link, and access/write-time slice. statSync THROWS like the
|
|
2266
2774
|
* other sync fs calls; the promise form rejects (see the fsp section). */
|
|
2267
2775
|
|
|
2268
2776
|
struct ScrStats {
|
|
@@ -2271,6 +2779,9 @@ struct ScrStats {
|
|
|
2271
2779
|
bool is_dir;
|
|
2272
2780
|
bool is_symlink; /* lstat only — a followed stat never sees one */
|
|
2273
2781
|
double size;
|
|
2782
|
+
double blocks; /* allocated size in 512-byte units (Node/libuv) */
|
|
2783
|
+
double nlink;
|
|
2784
|
+
double atime_ms;
|
|
2274
2785
|
double mtime_ms; /* milliseconds with the nanosecond fraction (Node) */
|
|
2275
2786
|
};
|
|
2276
2787
|
|
|
@@ -2291,53 +2802,310 @@ bool scr_stats_is_file(ScrStats *s) { return s->is_file; }
|
|
|
2291
2802
|
bool scr_stats_is_dir(ScrStats *s) { return s->is_dir; }
|
|
2292
2803
|
bool scr_stats_is_symlink(ScrStats *s) { return s->is_symlink; }
|
|
2293
2804
|
double scr_stats_size(ScrStats *s) { return s->size; }
|
|
2805
|
+
double scr_stats_blocks(ScrStats *s) { return s->blocks; }
|
|
2806
|
+
double scr_stats_nlink(ScrStats *s) { return s->nlink; }
|
|
2807
|
+
double scr_stats_atime_ms(ScrStats *s) { return s->atime_ms; }
|
|
2294
2808
|
double scr_stats_mtime_ms(ScrStats *s) { return s->mtime_ms; }
|
|
2295
2809
|
|
|
2296
|
-
static ScrStats *
|
|
2810
|
+
static ScrStats *scr_stats_new(void) {
|
|
2297
2811
|
ScrStats *s = malloc(sizeof(ScrStats));
|
|
2298
2812
|
if (!s) {
|
|
2299
2813
|
scr_trap("scriptc: out of memory\n");
|
|
2300
2814
|
}
|
|
2301
2815
|
s->rc = 1;
|
|
2816
|
+
return s;
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
#ifdef _WIN32
|
|
2820
|
+
/* The CRT fallback is deliberately complete: callers either get every field
|
|
2821
|
+
* from this one stat() result or every field from one Win32 handle below,
|
|
2822
|
+
* never a snapshot spliced across two path resolutions. */
|
|
2823
|
+
static ScrStats *scr_stats_of_crt(const struct stat *st) {
|
|
2824
|
+
ScrStats *s = scr_stats_new();
|
|
2302
2825
|
s->is_file = S_ISREG(st->st_mode);
|
|
2303
2826
|
s->is_dir = S_ISDIR(st->st_mode);
|
|
2304
|
-
#if defined(_WIN32)
|
|
2305
|
-
/* CRT stat has no symlink view (lstat above degrades to stat) and no
|
|
2306
|
-
* sub-second mtime — whole seconds where Node reads the FILETIME's
|
|
2307
|
-
* 100ns units (divergence: mtimeMs precision; mechanical fix is
|
|
2308
|
-
* GetFileAttributesEx). */
|
|
2309
2827
|
s->is_symlink = false;
|
|
2828
|
+
s->size = (double)st->st_size;
|
|
2829
|
+
s->blocks = st->st_size <= 0 ? 0.0 : (double)(((uint64_t)st->st_size + 511) >> 9);
|
|
2830
|
+
s->nlink = (double)st->st_nlink;
|
|
2831
|
+
s->atime_ms = (double)st->st_atime * 1000.0;
|
|
2310
2832
|
s->mtime_ms = (double)st->st_mtime * 1000.0;
|
|
2833
|
+
return s;
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
static ScrStats *scr_stats_crt_fallback(const ScrStr *path, const char *op) {
|
|
2837
|
+
struct stat st;
|
|
2838
|
+
if (stat(path->data, &st) != 0) {
|
|
2839
|
+
scr_fs_throw(errno, op, path);
|
|
2840
|
+
return NULL;
|
|
2841
|
+
}
|
|
2842
|
+
return scr_stats_of_crt(&st);
|
|
2843
|
+
}
|
|
2844
|
+
|
|
2845
|
+
/* Resolve ordinary disk paths once and populate every public field from the
|
|
2846
|
+
* resulting handle. Splitting size/type across CRT stat() and a later
|
|
2847
|
+
* CreateFile call can mix two entries when another process replaces path. */
|
|
2848
|
+
static bool scr_stats_is_link_tag(DWORD tag) {
|
|
2849
|
+
return tag == IO_REPARSE_TAG_SYMLINK ||
|
|
2850
|
+
tag == IO_REPARSE_TAG_MOUNT_POINT ||
|
|
2851
|
+
tag == IO_REPARSE_TAG_APPEXECLINK ||
|
|
2852
|
+
tag == UINT32_C(0xA000001D); /* IO_REPARSE_TAG_LX_SYMLINK */
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
static double scr_stats_utf16_len(const WCHAR *text, size_t len) {
|
|
2856
|
+
if (len == 0) return 0;
|
|
2857
|
+
if (len > INT_MAX) return -1;
|
|
2858
|
+
int bytes = WideCharToMultiByte(
|
|
2859
|
+
CP_UTF8, 0, text, (int)len, NULL, 0, NULL, NULL);
|
|
2860
|
+
return bytes > 0 ? (double)bytes : -1;
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2863
|
+
typedef struct {
|
|
2864
|
+
ULONG tag;
|
|
2865
|
+
USHORT data_len;
|
|
2866
|
+
USHORT reserved;
|
|
2867
|
+
union {
|
|
2868
|
+
struct {
|
|
2869
|
+
USHORT substitute_offset;
|
|
2870
|
+
USHORT substitute_len;
|
|
2871
|
+
USHORT print_offset;
|
|
2872
|
+
USHORT print_len;
|
|
2873
|
+
ULONG flags;
|
|
2874
|
+
WCHAR path[1];
|
|
2875
|
+
} symlink;
|
|
2876
|
+
struct {
|
|
2877
|
+
USHORT substitute_offset;
|
|
2878
|
+
USHORT substitute_len;
|
|
2879
|
+
USHORT print_offset;
|
|
2880
|
+
USHORT print_len;
|
|
2881
|
+
WCHAR path[1];
|
|
2882
|
+
} mount;
|
|
2883
|
+
struct {
|
|
2884
|
+
unsigned char bytes[1];
|
|
2885
|
+
} generic;
|
|
2886
|
+
} body;
|
|
2887
|
+
} ScrStatsReparseData;
|
|
2888
|
+
|
|
2889
|
+
/* Node/libuv reports a Windows link's target-text length as st_size. The
|
|
2890
|
+
* ordinary handle information does not carry that value, so read the reparse
|
|
2891
|
+
* payload while the no-follow handle is live. */
|
|
2892
|
+
static bool scr_stats_link_size(HANDLE h, DWORD tag, double *size_out) {
|
|
2893
|
+
union {
|
|
2894
|
+
ScrStatsReparseData align;
|
|
2895
|
+
unsigned char bytes[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
|
|
2896
|
+
} storage;
|
|
2897
|
+
DWORD used;
|
|
2898
|
+
if (!DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, NULL, 0,
|
|
2899
|
+
storage.bytes, sizeof storage.bytes, &used, NULL)) return false;
|
|
2900
|
+
ScrStatsReparseData *data = (ScrStatsReparseData *)storage.bytes;
|
|
2901
|
+
const size_t body_offset = offsetof(ScrStatsReparseData, body);
|
|
2902
|
+
if (used < body_offset || data->data_len > used - body_offset ||
|
|
2903
|
+
data->tag != tag) return false;
|
|
2904
|
+
|
|
2905
|
+
const WCHAR *target;
|
|
2906
|
+
size_t len;
|
|
2907
|
+
if (tag == IO_REPARSE_TAG_SYMLINK) {
|
|
2908
|
+
const size_t fixed = offsetof(ScrStatsReparseData, body.symlink.path) -
|
|
2909
|
+
body_offset;
|
|
2910
|
+
size_t offset = data->body.symlink.substitute_offset;
|
|
2911
|
+
size_t bytes = data->body.symlink.substitute_len;
|
|
2912
|
+
if (data->data_len < fixed || ((offset | bytes) & 1) != 0 ||
|
|
2913
|
+
offset > data->data_len - fixed ||
|
|
2914
|
+
bytes > data->data_len - fixed - offset) return false;
|
|
2915
|
+
target = (const WCHAR *)((const unsigned char *)data->body.symlink.path +
|
|
2916
|
+
offset);
|
|
2917
|
+
len = bytes / sizeof(WCHAR);
|
|
2918
|
+
} else if (tag == IO_REPARSE_TAG_MOUNT_POINT) {
|
|
2919
|
+
const size_t fixed = offsetof(ScrStatsReparseData, body.mount.path) -
|
|
2920
|
+
body_offset;
|
|
2921
|
+
size_t offset = data->body.mount.substitute_offset;
|
|
2922
|
+
size_t bytes = data->body.mount.substitute_len;
|
|
2923
|
+
if (data->data_len < fixed || ((offset | bytes) & 1) != 0 ||
|
|
2924
|
+
offset > data->data_len - fixed ||
|
|
2925
|
+
bytes > data->data_len - fixed - offset) return false;
|
|
2926
|
+
target = (const WCHAR *)((const unsigned char *)data->body.mount.path +
|
|
2927
|
+
offset);
|
|
2928
|
+
len = bytes / sizeof(WCHAR);
|
|
2929
|
+
_Static_assert(sizeof(WCHAR) == sizeof(uint16_t),
|
|
2930
|
+
"Windows reparse payloads use UTF-16 code units");
|
|
2931
|
+
if (!scr_win_stats_mount_target_is_junction(
|
|
2932
|
+
(const uint16_t *)target, len)) return false;
|
|
2933
|
+
} else if (tag == UINT32_C(0xA000001D)) {
|
|
2934
|
+
/* WSL's LX symlink payload is a version word followed by UTF-8 bytes. */
|
|
2935
|
+
if (data->data_len < sizeof(ULONG)) return false;
|
|
2936
|
+
*size_out = (double)(data->data_len - sizeof(ULONG));
|
|
2937
|
+
return true;
|
|
2938
|
+
} else {
|
|
2939
|
+
/* App execution links carry a counted UTF-16 string list; the third
|
|
2940
|
+
* string is the target path. */
|
|
2941
|
+
if (tag != IO_REPARSE_TAG_APPEXECLINK ||
|
|
2942
|
+
data->data_len < sizeof(ULONG)) return false;
|
|
2943
|
+
const unsigned char *raw = data->body.generic.bytes;
|
|
2944
|
+
ULONG count;
|
|
2945
|
+
memcpy(&count, raw, sizeof count);
|
|
2946
|
+
if (count < 3 || ((data->data_len - sizeof count) & 1) != 0) return false;
|
|
2947
|
+
target = (const WCHAR *)(raw + sizeof count);
|
|
2948
|
+
size_t chars = (data->data_len - sizeof count) / sizeof(WCHAR);
|
|
2949
|
+
for (size_t item = 0; item < 2; item++) {
|
|
2950
|
+
size_t part = 0;
|
|
2951
|
+
while (part < chars && target[part] != L'\0') part++;
|
|
2952
|
+
if (part == 0 || part == chars) return false;
|
|
2953
|
+
target += part + 1;
|
|
2954
|
+
chars -= part + 1;
|
|
2955
|
+
}
|
|
2956
|
+
len = 0;
|
|
2957
|
+
while (len < chars && target[len] != L'\0') len++;
|
|
2958
|
+
if (len == 0 || len == chars || len < 3 ||
|
|
2959
|
+
!((target[0] >= L'A' && target[0] <= L'Z') ||
|
|
2960
|
+
(target[0] >= L'a' && target[0] <= L'z')) ||
|
|
2961
|
+
target[1] != L':' || target[2] != L'\\') return false;
|
|
2962
|
+
double size = scr_stats_utf16_len(target, len);
|
|
2963
|
+
if (size < 0) return false;
|
|
2964
|
+
*size_out = size;
|
|
2965
|
+
return true;
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
/* Undo the NT namespace prefix CreateSymbolicLinkW stores for absolute
|
|
2969
|
+
* DOS/UNC targets, matching libuv's readlink normalization. */
|
|
2970
|
+
if (len >= 4 && target[0] == L'\\' && target[1] == L'?' &&
|
|
2971
|
+
target[2] == L'?' && target[3] == L'\\') {
|
|
2972
|
+
if (len >= 6 && target[5] == L':' &&
|
|
2973
|
+
((target[4] >= L'A' && target[4] <= L'Z') ||
|
|
2974
|
+
(target[4] >= L'a' && target[4] <= L'z')) &&
|
|
2975
|
+
(len == 6 || target[6] == L'\\')) {
|
|
2976
|
+
target += 4;
|
|
2977
|
+
len -= 4;
|
|
2978
|
+
} else if (len >= 8 &&
|
|
2979
|
+
(target[4] == L'U' || target[4] == L'u') &&
|
|
2980
|
+
(target[5] == L'N' || target[5] == L'n') &&
|
|
2981
|
+
(target[6] == L'C' || target[6] == L'c') &&
|
|
2982
|
+
target[7] == L'\\') {
|
|
2983
|
+
target += 6;
|
|
2984
|
+
len -= 6;
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
double size = scr_stats_utf16_len(target, len);
|
|
2988
|
+
if (size < 0) return false;
|
|
2989
|
+
*size_out = size;
|
|
2990
|
+
return true;
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
static ScrStats *scr_stats_of_path(const ScrStr *path, const char *op,
|
|
2994
|
+
bool no_follow) {
|
|
2995
|
+
WCHAR *wide = scr_fs_win_wide(path);
|
|
2996
|
+
if (!wide) return scr_stats_crt_fallback(path, op);
|
|
2997
|
+
|
|
2998
|
+
DWORD flags = FILE_FLAG_BACKUP_SEMANTICS;
|
|
2999
|
+
if (no_follow) flags |= FILE_FLAG_OPEN_REPARSE_POINT;
|
|
3000
|
+
HANDLE h = CreateFileW(wide, FILE_READ_ATTRIBUTES,
|
|
3001
|
+
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
3002
|
+
NULL, OPEN_EXISTING, flags, NULL);
|
|
3003
|
+
free(wide);
|
|
3004
|
+
if (h == INVALID_HANDLE_VALUE) return scr_stats_crt_fallback(path, op);
|
|
3005
|
+
|
|
3006
|
+
DWORD file_type = GetFileType(h);
|
|
3007
|
+
if (file_type != FILE_TYPE_DISK) {
|
|
3008
|
+
CloseHandle(h);
|
|
3009
|
+
return scr_stats_crt_fallback(path, op);
|
|
3010
|
+
}
|
|
3011
|
+
BY_HANDLE_FILE_INFORMATION basic;
|
|
3012
|
+
if (!GetFileInformationByHandle(h, &basic)) {
|
|
3013
|
+
CloseHandle(h);
|
|
3014
|
+
return scr_stats_crt_fallback(path, op);
|
|
3015
|
+
}
|
|
3016
|
+
bool is_link = false;
|
|
3017
|
+
double link_size = -1;
|
|
3018
|
+
if (no_follow && (basic.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
|
|
3019
|
+
FILE_ATTRIBUTE_TAG_INFO tagged;
|
|
3020
|
+
if (!GetFileInformationByHandleEx(
|
|
3021
|
+
h, FileAttributeTagInfo, &tagged, sizeof tagged) ||
|
|
3022
|
+
!scr_stats_is_link_tag(tagged.ReparseTag)) {
|
|
3023
|
+
/* Reparse points are a general interception mechanism. Node/libuv
|
|
3024
|
+
* follows non-link tags even for lstat, so reopen the resolved target. */
|
|
3025
|
+
CloseHandle(h);
|
|
3026
|
+
return scr_stats_of_path(path, op, false);
|
|
3027
|
+
}
|
|
3028
|
+
if (!scr_stats_link_size(h, tagged.ReparseTag, &link_size)) {
|
|
3029
|
+
/* A mount-point tag may name a mounted volume rather than a junction.
|
|
3030
|
+
* libuv treats those (and malformed/unsupported link payloads) as
|
|
3031
|
+
* ordinary reparse points and retries with the final component
|
|
3032
|
+
* followed. Do not classify from the tag alone. */
|
|
3033
|
+
CloseHandle(h);
|
|
3034
|
+
return scr_stats_of_path(path, op, false);
|
|
3035
|
+
}
|
|
3036
|
+
is_link = true;
|
|
3037
|
+
}
|
|
3038
|
+
FILE_STANDARD_INFO standard;
|
|
3039
|
+
bool have_standard = GetFileInformationByHandleEx(
|
|
3040
|
+
h, FileStandardInfo, &standard, sizeof standard);
|
|
3041
|
+
CloseHandle(h);
|
|
3042
|
+
|
|
3043
|
+
bool is_dir = (basic.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
|
3044
|
+
uint64_t size = ((uint64_t)basic.nFileSizeHigh << 32) | basic.nFileSizeLow;
|
|
3045
|
+
ScrStats *s = scr_stats_new();
|
|
3046
|
+
s->is_file = !is_link && (have_standard ? !standard.Directory : !is_dir);
|
|
3047
|
+
s->is_dir = !is_link && (have_standard ? standard.Directory : is_dir);
|
|
3048
|
+
s->is_symlink = is_link;
|
|
3049
|
+
s->size = is_link && link_size >= 0 ? link_size : s->is_dir ? 0.0
|
|
3050
|
+
: have_standard ? (double)standard.EndOfFile.QuadPart : (double)size;
|
|
3051
|
+
s->blocks = have_standard
|
|
3052
|
+
? (double)((uint64_t)standard.AllocationSize.QuadPart >> 9)
|
|
3053
|
+
: size == 0 ? 0.0 : (double)((size + 511) >> 9);
|
|
3054
|
+
s->nlink = have_standard
|
|
3055
|
+
? (double)standard.NumberOfLinks
|
|
3056
|
+
: (double)basic.nNumberOfLinks;
|
|
3057
|
+
s->atime_ms = scr_filetime_unix_ms(basic.ftLastAccessTime);
|
|
3058
|
+
s->mtime_ms = scr_filetime_unix_ms(basic.ftLastWriteTime);
|
|
3059
|
+
return s;
|
|
3060
|
+
}
|
|
2311
3061
|
#else
|
|
3062
|
+
static ScrStats *scr_stats_of(const struct stat *st) {
|
|
3063
|
+
ScrStats *s = scr_stats_new();
|
|
3064
|
+
s->is_file = S_ISREG(st->st_mode);
|
|
3065
|
+
s->is_dir = S_ISDIR(st->st_mode);
|
|
2312
3066
|
s->is_symlink = S_ISLNK(st->st_mode);
|
|
2313
3067
|
#if defined(__APPLE__)
|
|
3068
|
+
s->atime_ms = (double)st->st_atimespec.tv_sec * 1000.0 +
|
|
3069
|
+
(double)st->st_atimespec.tv_nsec / 1e6;
|
|
2314
3070
|
s->mtime_ms = (double)st->st_mtimespec.tv_sec * 1000.0 +
|
|
2315
3071
|
(double)st->st_mtimespec.tv_nsec / 1e6;
|
|
2316
3072
|
#else
|
|
3073
|
+
s->atime_ms = (double)st->st_atim.tv_sec * 1000.0 +
|
|
3074
|
+
(double)st->st_atim.tv_nsec / 1e6;
|
|
2317
3075
|
s->mtime_ms = (double)st->st_mtim.tv_sec * 1000.0 +
|
|
2318
3076
|
(double)st->st_mtim.tv_nsec / 1e6;
|
|
2319
3077
|
#endif
|
|
2320
|
-
|
|
3078
|
+
s->blocks = (double)st->st_blocks;
|
|
3079
|
+
s->nlink = (double)st->st_nlink;
|
|
2321
3080
|
s->size = (double)st->st_size;
|
|
2322
3081
|
return s;
|
|
2323
3082
|
}
|
|
3083
|
+
#endif
|
|
2324
3084
|
|
|
2325
3085
|
ScrStats *scr_fs_stat(ScrStr *path) {
|
|
3086
|
+
#ifdef _WIN32
|
|
3087
|
+
return scr_stats_of_path(path, "stat", false);
|
|
3088
|
+
#else
|
|
2326
3089
|
struct stat st;
|
|
2327
3090
|
if (stat(path->data, &st) != 0) { /* follows symlinks, like Node's statSync */
|
|
2328
3091
|
scr_fs_throw(errno, "stat", path);
|
|
2329
3092
|
return NULL;
|
|
2330
3093
|
}
|
|
2331
3094
|
return scr_stats_of(&st);
|
|
3095
|
+
#endif
|
|
2332
3096
|
}
|
|
2333
3097
|
|
|
2334
3098
|
ScrStats *scr_fs_lstat(ScrStr *path) {
|
|
3099
|
+
#ifdef _WIN32
|
|
3100
|
+
return scr_stats_of_path(path, "lstat", true);
|
|
3101
|
+
#else
|
|
2335
3102
|
struct stat st;
|
|
2336
3103
|
if (lstat(path->data, &st) != 0) { /* no follow; Node reports lstat */
|
|
2337
3104
|
scr_fs_throw(errno, "lstat", path);
|
|
2338
3105
|
return NULL;
|
|
2339
3106
|
}
|
|
2340
3107
|
return scr_stats_of(&st);
|
|
3108
|
+
#endif
|
|
2341
3109
|
}
|
|
2342
3110
|
|
|
2343
3111
|
ScrArr *scr_fs_readdir(ScrStr *path) {
|
|
@@ -3116,10 +3884,6 @@ ScrStr *scr_crypto_x509_valid_to_str(ScrStr *pem) {
|
|
|
3116
3884
|
|
|
3117
3885
|
/* ── String surface (fromCharCode / lastIndexOf) ─────────────────────── */
|
|
3118
3886
|
|
|
3119
|
-
/* Forward declaration — scr_to_uint32 lives with the bitwise operators
|
|
3120
|
-
* below (ToUint16 is its low 16 bits, per spec). */
|
|
3121
|
-
static uint32_t scr_to_uint32(double d);
|
|
3122
|
-
|
|
3123
3887
|
/* String.fromCharCode core over n UTF-16 code units read through
|
|
3124
3888
|
* `unit(src, i)` (already ToUint16'd): combine adjacent surrogate pairs,
|
|
3125
3889
|
* substitute U+FFFD for lone surrogates (divergence 1's storage policy —
|
|
@@ -3214,9 +3978,10 @@ double scr_str_last_index_of(ScrStr *s, ScrStr *needle) {
|
|
|
3214
3978
|
return -1.0;
|
|
3215
3979
|
}
|
|
3216
3980
|
|
|
3217
|
-
/* ── Date, the
|
|
3218
|
-
*
|
|
3219
|
-
*
|
|
3981
|
+
/* ── Date, the read-only value slice ───────────────────────────────────
|
|
3982
|
+
* Values are TimeClip'd epoch-millisecond scalars. Identity/mutation are
|
|
3983
|
+
* frontend-fenced; construction, storage, getters, and ISO formatting
|
|
3984
|
+
* observe exactly this payload. */
|
|
3220
3985
|
|
|
3221
3986
|
double scr_date_now(void) {
|
|
3222
3987
|
struct timespec ts;
|
|
@@ -3225,6 +3990,16 @@ double scr_date_now(void) {
|
|
|
3225
3990
|
return floor((double)ts.tv_sec * 1000.0 + (double)ts.tv_nsec / 1e6);
|
|
3226
3991
|
}
|
|
3227
3992
|
|
|
3993
|
+
/* Date's TimeClip: non-finite/out-of-range values become Invalid Date,
|
|
3994
|
+
* finite values truncate toward zero, and -0 normalizes to +0. */
|
|
3995
|
+
double scr_date_new_ms(double ms) {
|
|
3996
|
+
if (!isfinite(ms) || fabs(ms) > 8640000000000000.0) return NAN;
|
|
3997
|
+
double clipped = trunc(ms);
|
|
3998
|
+
return clipped == 0 ? 0 : clipped;
|
|
3999
|
+
}
|
|
4000
|
+
|
|
4001
|
+
double scr_date_get_time(double ms) { return ms; }
|
|
4002
|
+
|
|
3228
4003
|
/* Node's Date.prototype.toISOString over a millisecond time value:
|
|
3229
4004
|
* TimeClip's ToInteger truncation, proleptic Gregorian civil-from-days
|
|
3230
4005
|
* (Howard Hinnant's algorithm), YYYY-MM-DDTHH:mm:ss.sssZ with expanded
|
|
@@ -3291,7 +4066,7 @@ static double scr_days_from_civil(long long y, int m, int d) {
|
|
|
3291
4066
|
return (double)(era * 146097 + (long long)doe - 719468);
|
|
3292
4067
|
}
|
|
3293
4068
|
|
|
3294
|
-
static double
|
|
4069
|
+
static double scr_date_make_ms(long long y, int mo, int d, int hh, int mi, int ss, int ms) {
|
|
3295
4070
|
/* V8 accepts days 1..31 in every month and ROLLS OVER past the month's
|
|
3296
4071
|
* end (Feb 30 → Mar 2) — days_from_civil extrapolates linearly, so the
|
|
3297
4072
|
* rollover falls out; day 0 and 32+ are NaN, like V8. */
|
|
@@ -3299,10 +4074,13 @@ static double scr_date_ms_of(long long y, int mo, int d, int hh, int mi, int ss,
|
|
|
3299
4074
|
if (hh > 24 || mi > 59 || ss > 59 || (hh == 24 && (mi || ss || ms))) return NAN;
|
|
3300
4075
|
double t = scr_days_from_civil(y, mo, d) * 86400000.0 +
|
|
3301
4076
|
hh * 3600000.0 + mi * 60000.0 + ss * 1000.0 + ms;
|
|
3302
|
-
if (fabs(t) > 8640000000000000.0) return NAN;
|
|
3303
4077
|
return t;
|
|
3304
4078
|
}
|
|
3305
4079
|
|
|
4080
|
+
static double scr_date_ms_of(long long y, int mo, int d, int hh, int mi, int ss, int ms) {
|
|
4081
|
+
return scr_date_new_ms(scr_date_make_ms(y, mo, d, hh, mi, ss, ms));
|
|
4082
|
+
}
|
|
4083
|
+
|
|
3306
4084
|
static bool scr_date_digits(const char **p, const char *end, int n, int *out) {
|
|
3307
4085
|
int v = 0;
|
|
3308
4086
|
if (end - *p < n) return false;
|
|
@@ -3358,6 +4136,7 @@ double scr_date_parse_get_time(ScrStr *s) {
|
|
|
3358
4136
|
bool neg = *p == '-';
|
|
3359
4137
|
p++;
|
|
3360
4138
|
if (!scr_date_digits(&p, end, 6, &y6)) return NAN;
|
|
4139
|
+
if (neg && y6 == 0) return NAN; /* ECMA forbids expanded -000000 */
|
|
3361
4140
|
yy = neg ? -(long long)y6 : y6;
|
|
3362
4141
|
} else {
|
|
3363
4142
|
if (!scr_date_digits(&p, end, 4, &y)) return NAN;
|
|
@@ -3393,14 +4172,18 @@ double scr_date_parse_get_time(ScrStr *s) {
|
|
|
3393
4172
|
p++;
|
|
3394
4173
|
if (!scr_date_digits(&p, end, 2, &oh) || p >= end || *p++ != ':') return NAN;
|
|
3395
4174
|
if (!scr_date_digits(&p, end, 2, &om)) return NAN;
|
|
4175
|
+
if (oh > 23 || om > 59) return NAN;
|
|
3396
4176
|
off = (oh * 60 + om) * 60000.0;
|
|
3397
4177
|
if (neg) off = -off;
|
|
3398
4178
|
} else {
|
|
3399
4179
|
return NAN;
|
|
3400
4180
|
}
|
|
3401
4181
|
if (p != end) return NAN;
|
|
3402
|
-
|
|
3403
|
-
|
|
4182
|
+
/* MakeDate can lie just beyond the TimeClip boundary while the
|
|
4183
|
+
* explicit offset brings the final UTC instant back into range. The
|
|
4184
|
+
* spec clips only after that offset has been applied. */
|
|
4185
|
+
double t = scr_date_make_ms(yy, mo, d, hh, mi, ss, ms);
|
|
4186
|
+
return scr_date_new_ms(t - off);
|
|
3404
4187
|
}
|
|
3405
4188
|
}
|
|
3406
4189
|
|
|
@@ -3412,10 +4195,10 @@ double scr_date_parse_get_time(ScrStr *s) {
|
|
|
3412
4195
|
* 1900+year (the spec's MakeFullYear), out-of-range months ROLL into the
|
|
3413
4196
|
* year (Date.UTC(2017, 13) is Feb 2018) and any integer date offsets from
|
|
3414
4197
|
* day 1 of that month — days_from_civil extrapolates linearly, so both
|
|
3415
|
-
* rollovers fall out. V8 bounds MakeDay's year to ±1e6
|
|
3416
|
-
*
|
|
3417
|
-
*
|
|
3418
|
-
* Never throws. */
|
|
4198
|
+
* rollovers fall out. V8 bounds MakeDay's input year to ±1e6 and input
|
|
4199
|
+
* month to ±1e7 before normalizing the month (kMaxYear/kMinYear and
|
|
4200
|
+
* kMaxMonth/kMinMonth, date.h); Node answers NaN past either bound even
|
|
4201
|
+
* when the two inputs would normalize back into range. Never throws. */
|
|
3419
4202
|
double scr_date_utc(double y, double mo, double d,
|
|
3420
4203
|
double h, double mi, double s, double ms) {
|
|
3421
4204
|
if (!isfinite(y) || !isfinite(mo) || !isfinite(d) || !isfinite(h) ||
|
|
@@ -3429,16 +4212,150 @@ double scr_date_utc(double y, double mo, double d,
|
|
|
3429
4212
|
mi = trunc(mi);
|
|
3430
4213
|
s = trunc(s);
|
|
3431
4214
|
ms = trunc(ms);
|
|
4215
|
+
if (fabs(y) > 1000000.0 || fabs(mo) > 10000000.0) return NAN;
|
|
3432
4216
|
if (y >= 0 && y <= 99) y += 1900;
|
|
3433
4217
|
double ym = y + floor(mo / 12.0);
|
|
3434
4218
|
int mn = (int)(mo - floor(mo / 12.0) * 12.0); /* 0..11 */
|
|
3435
|
-
if (fabs(ym) > 1000000.0) return NAN; /* V8's MakeDay year bound */
|
|
3436
4219
|
double days = scr_days_from_civil((long long)ym, mn + 1, 1) + (d - 1.0);
|
|
3437
4220
|
double t = days * 86400000.0 + h * 3600000.0 + mi * 60000.0 + s * 1000.0 + ms;
|
|
3438
4221
|
if (fabs(t) > 8640000000000000.0) return NAN; /* TimeClip */
|
|
3439
4222
|
return t == 0 ? 0 : t; /* normalize -0 (TimeClip's +0) */
|
|
3440
4223
|
}
|
|
3441
4224
|
|
|
4225
|
+
/* ── Date calendar getters ────────────────────────────────────────────
|
|
4226
|
+
* UTC fields use the same proleptic-Gregorian walk as toISOString, so the
|
|
4227
|
+
* whole Date range is portable. Local fields use the host timezone via
|
|
4228
|
+
* localtime, exactly the environment the sibling Node process observes;
|
|
4229
|
+
* a libc that cannot represent an extreme instant answers NaN rather than
|
|
4230
|
+
* inventing a zone. */
|
|
4231
|
+
|
|
4232
|
+
typedef struct ScrDateParts {
|
|
4233
|
+
long long year;
|
|
4234
|
+
int month, date, day, hours, minutes, seconds, milliseconds;
|
|
4235
|
+
double timezone_offset;
|
|
4236
|
+
} ScrDateParts;
|
|
4237
|
+
|
|
4238
|
+
/* Calendar decomposition itself also serves LocalTime(t), which can lie
|
|
4239
|
+
* just outside TimeClip's UTC interval after applying a zone offset at an
|
|
4240
|
+
* endpoint. The checked wrapper is the public UTC-getter gate; the inner
|
|
4241
|
+
* walk accepts those bounded local-time values too. */
|
|
4242
|
+
static void scr_date_utc_parts_unchecked(double t, ScrDateParts *out) {
|
|
4243
|
+
double dayd = floor(t / 86400000.0);
|
|
4244
|
+
long long msday = (long long)(t - dayd * 86400000.0);
|
|
4245
|
+
long long z = (long long)dayd + 719468;
|
|
4246
|
+
long long era = (z >= 0 ? z : z - 146096) / 146097;
|
|
4247
|
+
unsigned long long doe = (unsigned long long)(z - era * 146097);
|
|
4248
|
+
unsigned long long yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
|
|
4249
|
+
long long y = (long long)yoe + era * 400;
|
|
4250
|
+
unsigned long long doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
|
|
4251
|
+
unsigned long long mp = (5 * doy + 2) / 153;
|
|
4252
|
+
unsigned long long d = doy - (153 * mp + 2) / 5 + 1;
|
|
4253
|
+
unsigned long long m = mp < 10 ? mp + 3 : mp - 9;
|
|
4254
|
+
if (m <= 2) y++;
|
|
4255
|
+
long long wday = ((long long)dayd + 4) % 7;
|
|
4256
|
+
if (wday < 0) wday += 7;
|
|
4257
|
+
out->year = y;
|
|
4258
|
+
out->month = (int)m - 1;
|
|
4259
|
+
out->date = (int)d;
|
|
4260
|
+
out->day = (int)wday;
|
|
4261
|
+
out->hours = (int)(msday / 3600000);
|
|
4262
|
+
out->minutes = (int)(msday / 60000 % 60);
|
|
4263
|
+
out->seconds = (int)(msday / 1000 % 60);
|
|
4264
|
+
out->milliseconds = (int)(msday % 1000);
|
|
4265
|
+
out->timezone_offset = 0;
|
|
4266
|
+
}
|
|
4267
|
+
|
|
4268
|
+
static bool scr_date_utc_parts(double ms, ScrDateParts *out) {
|
|
4269
|
+
if (!isfinite(ms) || fabs(ms) > 8640000000000000.0) return false;
|
|
4270
|
+
scr_date_utc_parts_unchecked(trunc(ms), out);
|
|
4271
|
+
return true;
|
|
4272
|
+
}
|
|
4273
|
+
|
|
4274
|
+
static bool scr_date_localtime(double secd, struct tm *out) {
|
|
4275
|
+
time_t sec = (time_t)secd;
|
|
4276
|
+
if ((double)sec != secd) return false;
|
|
4277
|
+
#ifdef _WIN32
|
|
4278
|
+
return localtime_s(out, &sec) == 0;
|
|
4279
|
+
#else
|
|
4280
|
+
return localtime_r(&sec, out) != NULL;
|
|
4281
|
+
#endif
|
|
4282
|
+
}
|
|
4283
|
+
|
|
4284
|
+
static bool scr_date_local_parts(double ms, ScrDateParts *out) {
|
|
4285
|
+
if (!isfinite(ms) || fabs(ms) > 8640000000000000.0) return false;
|
|
4286
|
+
double clipped = trunc(ms);
|
|
4287
|
+
double secd = floor(clipped / 1000.0);
|
|
4288
|
+
struct tm tmv;
|
|
4289
|
+
double basis_secd = secd;
|
|
4290
|
+
if (!scr_date_localtime(basis_secd, &tmv)) {
|
|
4291
|
+
/* Windows' _localtime64_s rejects pre-epoch instants and years after
|
|
4292
|
+
* 3001 even though both are valid ECMAScript Dates. Query the host's
|
|
4293
|
+
* zone rule at a calendar-equivalent surrogate year in 2000..2399:
|
|
4294
|
+
* Gregorian weekdays/leap years repeat every 400 years. This keeps
|
|
4295
|
+
* every valid Date finite; the OS-vs-Node historical-rule difference
|
|
4296
|
+
* remains the documented timezone-data divergence. */
|
|
4297
|
+
ScrDateParts utc;
|
|
4298
|
+
scr_date_utc_parts_unchecked(clipped, &utc);
|
|
4299
|
+
long long cycle_year = (utc.year - 2000) % 400;
|
|
4300
|
+
if (cycle_year < 0) cycle_year += 400;
|
|
4301
|
+
long long surrogate_year = 2000 + cycle_year;
|
|
4302
|
+
basis_secd =
|
|
4303
|
+
scr_days_from_civil(surrogate_year, utc.month + 1, utc.date) * 86400.0 +
|
|
4304
|
+
utc.hours * 3600.0 + utc.minutes * 60.0 + utc.seconds;
|
|
4305
|
+
if (!scr_date_localtime(basis_secd, &tmv)) return false;
|
|
4306
|
+
}
|
|
4307
|
+
/* Treat the local broken-down fields as UTC. Its distance from the real
|
|
4308
|
+
* (or surrogate) epoch second is the zone offset. Apply that offset to
|
|
4309
|
+
* the original instant and use the portable Gregorian walk for its
|
|
4310
|
+
* fields; Date#getTimezoneOffset uses the
|
|
4311
|
+
* opposite sign (UTC - local), in whole minutes. Historical local-mean
|
|
4312
|
+
* offsets can contain seconds, which JavaScript truncates toward zero. */
|
|
4313
|
+
double local_as_utc =
|
|
4314
|
+
scr_days_from_civil((long long)tmv.tm_year + 1900, tmv.tm_mon + 1, tmv.tm_mday) * 86400.0 +
|
|
4315
|
+
tmv.tm_hour * 3600.0 + tmv.tm_min * 60.0 + tmv.tm_sec;
|
|
4316
|
+
double local_offset = local_as_utc - basis_secd;
|
|
4317
|
+
scr_date_utc_parts_unchecked(clipped + local_offset * 1000.0, out);
|
|
4318
|
+
double timezone_offset = trunc(-local_offset / 60.0);
|
|
4319
|
+
out->timezone_offset = timezone_offset == 0 ? 0 : timezone_offset;
|
|
4320
|
+
return true;
|
|
4321
|
+
}
|
|
4322
|
+
|
|
4323
|
+
static bool scr_date_parts(double ms, bool utc, ScrDateParts *out) {
|
|
4324
|
+
return utc ? scr_date_utc_parts(ms, out) : scr_date_local_parts(ms, out);
|
|
4325
|
+
}
|
|
4326
|
+
|
|
4327
|
+
#define SCR_DATE_PART_GETTER(name, field) \
|
|
4328
|
+
double scr_date_get_##name(double ms, bool utc) { \
|
|
4329
|
+
ScrDateParts p; \
|
|
4330
|
+
return scr_date_parts(ms, utc, &p) ? (double)p.field : NAN; \
|
|
4331
|
+
} \
|
|
4332
|
+
double scr_date_get_##name##_local(double ms) { \
|
|
4333
|
+
return scr_date_get_##name(ms, false); \
|
|
4334
|
+
} \
|
|
4335
|
+
double scr_date_get_##name##_utc(double ms) { \
|
|
4336
|
+
return scr_date_get_##name(ms, true); \
|
|
4337
|
+
}
|
|
4338
|
+
|
|
4339
|
+
SCR_DATE_PART_GETTER(full_year, year)
|
|
4340
|
+
SCR_DATE_PART_GETTER(month, month)
|
|
4341
|
+
SCR_DATE_PART_GETTER(date, date)
|
|
4342
|
+
SCR_DATE_PART_GETTER(day, day)
|
|
4343
|
+
SCR_DATE_PART_GETTER(hours, hours)
|
|
4344
|
+
SCR_DATE_PART_GETTER(minutes, minutes)
|
|
4345
|
+
SCR_DATE_PART_GETTER(seconds, seconds)
|
|
4346
|
+
|
|
4347
|
+
#undef SCR_DATE_PART_GETTER
|
|
4348
|
+
|
|
4349
|
+
double scr_date_get_milliseconds(double ms) {
|
|
4350
|
+
ScrDateParts p;
|
|
4351
|
+
return scr_date_utc_parts(ms, &p) ? (double)p.milliseconds : NAN;
|
|
4352
|
+
}
|
|
4353
|
+
|
|
4354
|
+
double scr_date_get_timezone_offset(double ms) {
|
|
4355
|
+
ScrDateParts p;
|
|
4356
|
+
return scr_date_local_parts(ms, &p) ? p.timezone_offset : NAN;
|
|
4357
|
+
}
|
|
4358
|
+
|
|
3442
4359
|
/* ── Number statics ────────────────────────────────────────────────────
|
|
3443
4360
|
* JS-exact: the ES2015 Number statics never coerce (unlike the global
|
|
3444
4361
|
* isNaN/isFinite), and the compiler routes only number-typed arguments
|
|
@@ -3780,14 +4697,6 @@ bool scr_num_is_safe_integer(double x) {
|
|
|
3780
4697
|
* narrowing casts, no UB shifts of signed values).
|
|
3781
4698
|
*/
|
|
3782
4699
|
|
|
3783
|
-
static uint32_t scr_to_uint32(double d) {
|
|
3784
|
-
if (!isfinite(d)) return 0; /* NaN, +Infinity, -Infinity */
|
|
3785
|
-
double t = trunc(d);
|
|
3786
|
-
t = fmod(t, 4294967296.0); /* exact for doubles; result in (-2^32, 2^32) */
|
|
3787
|
-
if (t < 0) t += 4294967296.0;
|
|
3788
|
-
return (uint32_t)t;
|
|
3789
|
-
}
|
|
3790
|
-
|
|
3791
4700
|
/* The 32 bits as a SIGNED (Int32) JS number. */
|
|
3792
4701
|
static double scr_bits_as_int32(uint32_t u) {
|
|
3793
4702
|
return u >= UINT32_C(0x80000000)
|