@scriptc/runtime 0.0.23 → 0.0.25
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 +286 -5
- package/src/scr_bytes.c +634 -4
- package/src/scr_bytes_io.c +4 -2
- package/src/scr_file_handle.c +491 -0
- package/src/scr_island.c +10 -0
- package/src/scr_json.c +51 -19
- package/src/scr_lib.c +680 -30
- package/src/scr_net.c +12 -5
- package/src/scr_number.c +9 -0
- package/src/scr_regex.c +12 -1
- package/src/scr_runtime.h +101 -8
- 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);
|
|
@@ -1709,6 +1768,250 @@ static ssize_t scr_fs_pread(int fd, void *data, size_t length, double position)
|
|
|
1709
1768
|
#endif
|
|
1710
1769
|
}
|
|
1711
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
|
+
|
|
1712
2015
|
/* fs.readSync(fd, buffer, offset, length[, position]) — the buffer form.
|
|
1713
2016
|
* Node validates offset/length against the buffer before reading and
|
|
1714
2017
|
* throws ERR_OUT_OF_RANGE; here the checks clamp to the same contract and
|
|
@@ -1884,11 +2187,110 @@ void scr_fs_copyfile(ScrStr *src, ScrStr *dest) {
|
|
|
1884
2187
|
}
|
|
1885
2188
|
|
|
1886
2189
|
/* renameSync(old, new): rename(2), Node's two-path error shape ("ENOENT:
|
|
1887
|
-
* no such file or directory, rename 'a' -> 'b'").
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
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;
|
|
1891
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;
|
|
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);
|
|
1892
2294
|
}
|
|
1893
2295
|
|
|
1894
2296
|
void scr_fs_rm(ScrStr *path) {
|
|
@@ -2176,7 +2578,7 @@ static void scr_fs_throw_nopath(int e, const char *op) {
|
|
|
2176
2578
|
const char *text = scr_errno_text(e);
|
|
2177
2579
|
char msg[256];
|
|
2178
2580
|
int len = snprintf(msg, sizeof msg, "%s: %s, %s", name, text, op);
|
|
2179
|
-
|
|
2581
|
+
scr_throw_error_msg_code(SCR_ERR_ERROR, msg, (size_t)len, name);
|
|
2180
2582
|
}
|
|
2181
2583
|
|
|
2182
2584
|
/* read(2) loop to EOF from the CURRENT position — Node's
|
|
@@ -2367,8 +2769,8 @@ void scr_process_stdin_destroy(void) {
|
|
|
2367
2769
|
}
|
|
2368
2770
|
|
|
2369
2771
|
/* ── Stats values ────────────────────────────────────────────────────
|
|
2370
|
-
* An immutable snapshot of stat(2) results — the
|
|
2371
|
-
*
|
|
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
|
|
2372
2774
|
* other sync fs calls; the promise form rejects (see the fsp section). */
|
|
2373
2775
|
|
|
2374
2776
|
struct ScrStats {
|
|
@@ -2377,6 +2779,9 @@ struct ScrStats {
|
|
|
2377
2779
|
bool is_dir;
|
|
2378
2780
|
bool is_symlink; /* lstat only — a followed stat never sees one */
|
|
2379
2781
|
double size;
|
|
2782
|
+
double blocks; /* allocated size in 512-byte units (Node/libuv) */
|
|
2783
|
+
double nlink;
|
|
2784
|
+
double atime_ms;
|
|
2380
2785
|
double mtime_ms; /* milliseconds with the nanosecond fraction (Node) */
|
|
2381
2786
|
};
|
|
2382
2787
|
|
|
@@ -2397,53 +2802,310 @@ bool scr_stats_is_file(ScrStats *s) { return s->is_file; }
|
|
|
2397
2802
|
bool scr_stats_is_dir(ScrStats *s) { return s->is_dir; }
|
|
2398
2803
|
bool scr_stats_is_symlink(ScrStats *s) { return s->is_symlink; }
|
|
2399
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; }
|
|
2400
2808
|
double scr_stats_mtime_ms(ScrStats *s) { return s->mtime_ms; }
|
|
2401
2809
|
|
|
2402
|
-
static ScrStats *
|
|
2810
|
+
static ScrStats *scr_stats_new(void) {
|
|
2403
2811
|
ScrStats *s = malloc(sizeof(ScrStats));
|
|
2404
2812
|
if (!s) {
|
|
2405
2813
|
scr_trap("scriptc: out of memory\n");
|
|
2406
2814
|
}
|
|
2407
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();
|
|
2408
2825
|
s->is_file = S_ISREG(st->st_mode);
|
|
2409
2826
|
s->is_dir = S_ISDIR(st->st_mode);
|
|
2410
|
-
#if defined(_WIN32)
|
|
2411
|
-
/* CRT stat has no symlink view (lstat above degrades to stat) and no
|
|
2412
|
-
* sub-second mtime — whole seconds where Node reads the FILETIME's
|
|
2413
|
-
* 100ns units (divergence: mtimeMs precision; mechanical fix is
|
|
2414
|
-
* GetFileAttributesEx). */
|
|
2415
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;
|
|
2416
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
|
+
}
|
|
2417
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);
|
|
2418
3066
|
s->is_symlink = S_ISLNK(st->st_mode);
|
|
2419
3067
|
#if defined(__APPLE__)
|
|
3068
|
+
s->atime_ms = (double)st->st_atimespec.tv_sec * 1000.0 +
|
|
3069
|
+
(double)st->st_atimespec.tv_nsec / 1e6;
|
|
2420
3070
|
s->mtime_ms = (double)st->st_mtimespec.tv_sec * 1000.0 +
|
|
2421
3071
|
(double)st->st_mtimespec.tv_nsec / 1e6;
|
|
2422
3072
|
#else
|
|
3073
|
+
s->atime_ms = (double)st->st_atim.tv_sec * 1000.0 +
|
|
3074
|
+
(double)st->st_atim.tv_nsec / 1e6;
|
|
2423
3075
|
s->mtime_ms = (double)st->st_mtim.tv_sec * 1000.0 +
|
|
2424
3076
|
(double)st->st_mtim.tv_nsec / 1e6;
|
|
2425
3077
|
#endif
|
|
2426
|
-
|
|
3078
|
+
s->blocks = (double)st->st_blocks;
|
|
3079
|
+
s->nlink = (double)st->st_nlink;
|
|
2427
3080
|
s->size = (double)st->st_size;
|
|
2428
3081
|
return s;
|
|
2429
3082
|
}
|
|
3083
|
+
#endif
|
|
2430
3084
|
|
|
2431
3085
|
ScrStats *scr_fs_stat(ScrStr *path) {
|
|
3086
|
+
#ifdef _WIN32
|
|
3087
|
+
return scr_stats_of_path(path, "stat", false);
|
|
3088
|
+
#else
|
|
2432
3089
|
struct stat st;
|
|
2433
3090
|
if (stat(path->data, &st) != 0) { /* follows symlinks, like Node's statSync */
|
|
2434
3091
|
scr_fs_throw(errno, "stat", path);
|
|
2435
3092
|
return NULL;
|
|
2436
3093
|
}
|
|
2437
3094
|
return scr_stats_of(&st);
|
|
3095
|
+
#endif
|
|
2438
3096
|
}
|
|
2439
3097
|
|
|
2440
3098
|
ScrStats *scr_fs_lstat(ScrStr *path) {
|
|
3099
|
+
#ifdef _WIN32
|
|
3100
|
+
return scr_stats_of_path(path, "lstat", true);
|
|
3101
|
+
#else
|
|
2441
3102
|
struct stat st;
|
|
2442
3103
|
if (lstat(path->data, &st) != 0) { /* no follow; Node reports lstat */
|
|
2443
3104
|
scr_fs_throw(errno, "lstat", path);
|
|
2444
3105
|
return NULL;
|
|
2445
3106
|
}
|
|
2446
3107
|
return scr_stats_of(&st);
|
|
3108
|
+
#endif
|
|
2447
3109
|
}
|
|
2448
3110
|
|
|
2449
3111
|
ScrArr *scr_fs_readdir(ScrStr *path) {
|
|
@@ -3222,10 +3884,6 @@ ScrStr *scr_crypto_x509_valid_to_str(ScrStr *pem) {
|
|
|
3222
3884
|
|
|
3223
3885
|
/* ── String surface (fromCharCode / lastIndexOf) ─────────────────────── */
|
|
3224
3886
|
|
|
3225
|
-
/* Forward declaration — scr_to_uint32 lives with the bitwise operators
|
|
3226
|
-
* below (ToUint16 is its low 16 bits, per spec). */
|
|
3227
|
-
static uint32_t scr_to_uint32(double d);
|
|
3228
|
-
|
|
3229
3887
|
/* String.fromCharCode core over n UTF-16 code units read through
|
|
3230
3888
|
* `unit(src, i)` (already ToUint16'd): combine adjacent surrogate pairs,
|
|
3231
3889
|
* substitute U+FFFD for lone surrogates (divergence 1's storage policy —
|
|
@@ -4039,14 +4697,6 @@ bool scr_num_is_safe_integer(double x) {
|
|
|
4039
4697
|
* narrowing casts, no UB shifts of signed values).
|
|
4040
4698
|
*/
|
|
4041
4699
|
|
|
4042
|
-
static uint32_t scr_to_uint32(double d) {
|
|
4043
|
-
if (!isfinite(d)) return 0; /* NaN, +Infinity, -Infinity */
|
|
4044
|
-
double t = trunc(d);
|
|
4045
|
-
t = fmod(t, 4294967296.0); /* exact for doubles; result in (-2^32, 2^32) */
|
|
4046
|
-
if (t < 0) t += 4294967296.0;
|
|
4047
|
-
return (uint32_t)t;
|
|
4048
|
-
}
|
|
4049
|
-
|
|
4050
4700
|
/* The 32 bits as a SIGNED (Int32) JS number. */
|
|
4051
4701
|
static double scr_bits_as_int32(uint32_t u) {
|
|
4052
4702
|
return u >= UINT32_C(0x80000000)
|