* feat: serve asar file descriptors, streams and copies from the archive
fs.open/openSync/promises.open, fs.createReadStream, fs.copyFile/cp (and
their sync/promise variants) previously extracted a packed asar entry into a
temporary file first (Archive::CopyFileOut): a synchronous, main-thread copy of
the whole file on first access, a temp copy left behind for the life of the
process, and a cached copy that later opens with write flags could corrupt.
The wrapper now hands out real file descriptors duplicated from the archive's
retained handle and hooks the fs binding so every fd-based API (read/readv/
fstat/close/readFile(fd), ReadStream, FileHandle) reads straight out of the
archive. With ASAR integrity enabled, bytes are only ever served from a
block whose hash was just verified, and a violation terminates the process
synchronously instead of after the tampered data was already handed back.
Copies stream from the archive to the destination; opening a packed file for
writing fails with EACCES; fchmod/fchown/futimes on such descriptors are
refused. Only process.dlopen and child_process.execFile still need a real
path and keep using the temporary copy (now made read-only).
Also adds fs.opendir/readlink support for archives, makes fs.stat follow
archive symlinks (fs.cp dereference now works), returns BigIntStats when
asked, and gives original-fs its own pristine copy of the fs binding so it is
unaffected by these overrides.
* docs: note the asar file descriptor behavior change in breaking-changes
* fix: address review of the asar fd reader
- readv on a packed entry: a zero-length buffer no longer ends the clamped
read early; later buffers are still filled, like preadv.
- copyFile with COPYFILE_FICLONE_FORCE on an unpacked entry is passed to
native fs (which may reflink); only packed entries reject it with ENOTSUP.
- FileHandle#close() forgets the descriptor entry immediately instead of
waiting for the C++ handle's fd to be reset after the threadpool close, so
a real file that reuses the number in that window is never routed through
the archive reader.
- open() of a symbolic link to a directory reports EISDIR, not ENOENT.
- readlink and opendir honour name encodings other than 'buffer'.
- recursive readdir withFileTypes skips an unreadable archive entry instead
of dropping the rest of the listing.
* fix: keep the asar fs binding hooks working when Array's iterator is deleted
- Forward pass-through calls with Function#apply and index access instead of
spread / array destructuring, which need Array.prototype[Symbol.iterator]
(user code may delete it; parallel/test-require-delete-array-iterator).
- Use literal permission bits for the fake asar stats: the S_IRGRP/S_IROTH/
S_IX* constants do not exist on Windows, so the modes came out as 0600.
- Allow blocking around the chmod of a copied-out temp file; CopyFileOut is
also reached from the UI thread via WebContents.downloadURL().
* fix: report the executable bit from asar stat and drop stale fd entries
- Archive::Stat now exposes `executable`, so fs.stat/lstat of a packed
executable report 0755 like fs.fstat does; fs.cp no longer strips the bit
that copyFile preserved when it chmods the destination to the source mode.
- A descriptor for a packed entry can be closed without going through
fs.close (a native FileHandle built on it, e.g. http2's respondWithFile,
a FileHandle moved to a worker, or one closed on GC). Whenever fs hands out
a descriptor number (native pass-through open or a new archive-backed one),
any stale entry for that number is dropped first, so it can never capture
the new owner's reads, and its retained block is returned to the budget.
- Document http2stream.respondWithFile()/respondWithFD() among the APIs
that read the raw descriptor.
* fix: hand out a sentinel descriptor for packed asar entries, not the archive
The descriptor returned by open() for a packed entry used to be a dup of the
archive's own handle. Code that read it directly instead of through fs (a
native addon, a child's stdio, net.Socket({ fd }), http2's respondWithFile)
silently got bytes of the archive at the wrong offset, and a raw handle to
the archive escaped to user code.
Hand out a per-open dup of a write-only null device instead. It identifies
the entry to fs, which serves every read from the archive's retained handle
positionally as before, and it is useless to anything else: direct reads
fail with EBADF, the archive itself is never exposed, and a descriptor
closed behind our back can at worst misroute through fs, never leak archive
bytes. Writes, truncation and metadata changes through such a descriptor are
refused with EACCES rather than vanishing into the null device. copyFile and
the writeFile fast path read the entry without allocating a descriptor at
all.
* chore: clang-format electron_api_asar.cc
* fix: use libuv error numbers for asar fs errors
libuv's error numbers differ from POSIX errno on Windows (UV_EACCES is
-4092 there, not -13) and Node decodes `errno` through libuv's table, so the
hand-written errno values decoded as UNKNOWN on Windows. Take them from the
uv binding instead.
Also refuse writeFileSync's utf8 fast path when it is given a packed entry's
descriptor rather than a path.
fix: pass root_gen_dir from GN to generate_node_headers.py
PR #50828 replaced a local get_out_dir() (defaulting to 'Testing') with
the shared one from script/lib/util.py (defaulting to 'Default').
Neither default is correct because the actual output directory depends
on the active build config. Pass $root_gen_dir from the GN action so
the script always uses the correct path.
The local get_out_dir() defaulted to 'Testing' instead of 'Default',
causing e test to fail when using a non-Testing build config. Replace
it with the canonical version from script/lib/util.py.