From 98c962843842c7e1df8fe0abf353c9e53e34b037 Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Fri, 25 Sep 2026 13:17:13 +0000 Subject: [PATCH] deps: update libuv to 1.53.0 --- deps/uv/.mailmap | 5 + deps/uv/AUTHORS | 33 + deps/uv/CMakeLists.txt | 16 +- deps/uv/ChangeLog | 246 ++++- deps/uv/Makefile.am | 12 +- deps/uv/SECURITY.md | 6 +- deps/uv/configure.ac | 4 +- deps/uv/docs/requirements.txt | 1 + deps/uv/docs/src/async.rst | 33 +- deps/uv/docs/src/conf.py | 6 +- deps/uv/docs/src/design.rst | 34 +- deps/uv/docs/src/fs.rst | 26 +- deps/uv/docs/src/fs_event.rst | 4 +- deps/uv/docs/src/misc.rst | 13 +- deps/uv/docs/src/pipe.rst | 4 + deps/uv/docs/src/process.rst | 19 +- deps/uv/docs/src/request.rst | 21 +- deps/uv/docs/src/static/architecture.txt | 23 + deps/uv/docs/src/static/loop_iteration.txt | 35 + deps/uv/docs/src/stream.rst | 18 +- deps/uv/docs/src/tcp.rst | 1 - deps/uv/docs/src/threading.rst | 10 + deps/uv/docs/src/timer.rst | 2 +- deps/uv/docs/src/udp.rst | 20 +- deps/uv/include/uv.h | 20 +- deps/uv/include/uv/unix.h | 20 +- deps/uv/include/uv/version.h | 4 +- deps/uv/include/uv/win.h | 26 +- deps/uv/src/idna.c | 2 +- deps/uv/src/inet.c | 12 +- deps/uv/src/random.c | 2 +- deps/uv/src/threadpool.c | 8 + deps/uv/src/unix/aix.c | 43 +- deps/uv/src/unix/async.c | 137 +-- deps/uv/src/unix/core.c | 16 +- deps/uv/src/unix/darwin.c | 13 +- deps/uv/src/unix/freebsd.c | 10 +- deps/uv/src/unix/fs.c | 126 ++- deps/uv/src/unix/ibmi.c | 3 +- deps/uv/src/unix/internal.h | 3 +- deps/uv/src/unix/kqueue.c | 28 +- deps/uv/src/unix/linux.c | 103 +- deps/uv/src/unix/netbsd.c | 4 +- deps/uv/src/unix/openbsd.c | 32 +- deps/uv/src/unix/pipe.c | 53 +- deps/uv/src/unix/poll.c | 13 + deps/uv/src/unix/process.c | 271 ++++-- deps/uv/src/unix/proctitle.c | 3 +- deps/uv/src/unix/stream.c | 126 ++- deps/uv/src/unix/sunos.c | 12 +- deps/uv/src/unix/tty.c | 20 +- deps/uv/src/unix/udp.c | 76 +- deps/uv/src/uv-common.c | 173 +++- deps/uv/src/uv-common.h | 38 +- deps/uv/src/win/async.c | 70 +- deps/uv/src/win/core.c | 22 +- deps/uv/src/win/fs-event.c | 26 + deps/uv/src/win/fs.c | 22 +- deps/uv/src/win/internal.h | 2 + deps/uv/src/win/pipe.c | 630 ++++++++---- deps/uv/src/win/process-stdio.c | 11 +- deps/uv/src/win/process.c | 317 +++--- deps/uv/src/win/req-inl.h | 3 + deps/uv/src/win/stream.c | 62 +- deps/uv/src/win/tcp.c | 46 +- deps/uv/src/win/thread.c | 7 +- deps/uv/src/win/tty.c | 15 +- deps/uv/src/win/udp.c | 14 +- deps/uv/src/win/util.c | 12 +- deps/uv/src/win/winapi.h | 3 + deps/uv/src/win/winsock.c | 6 +- deps/uv/test/appcontainer.c | 610 ++++++++++++ deps/uv/test/run-tests.c | 19 +- deps/uv/test/runner-win.c | 66 +- deps/uv/test/runner.c | 6 +- deps/uv/test/task.h | 51 +- deps/uv/test/test-fork.c | 18 + deps/uv/test/test-fs-event.c | 59 +- deps/uv/test/test-fs-readdir.c | 2 + deps/uv/test/test-fs.c | 58 +- deps/uv/test/test-idna.c | 47 + deps/uv/test/test-io-64-safe.c | 182 ++++ deps/uv/test/test-list.h | 66 +- deps/uv/test/test-metrics.c | 4 + deps/uv/test/test-osx-resident-set-memory.c | 64 ++ deps/uv/test/test-pipe-bind-error.c | 28 + .../test/test-pipe-close-stdout-read-stdin.c | 2 + deps/uv/test/test-pipe-connect-multiple.c | 2 + deps/uv/test/test-pipe-getsockname.c | 52 + deps/uv/test/test-pipe-sendmsg.c | 6 +- deps/uv/test/test-pipe-write.c | 81 ++ deps/uv/test/test-poll-close.c | 13 + deps/uv/test/test-poll-multiple-handles.c | 7 + deps/uv/test/test-poll-oob.c | 5 +- deps/uv/test/test-poll.c | 2 +- deps/uv/test/test-sizeof.c | 912 ++++++++++++++++++ deps/uv/test/test-spawn.c | 175 +++- deps/uv/test/test-tcp-rst.c | 5 - deps/uv/test/test-tcp-write-fail.c | 2 + ...est-tcp-write-in-read-cb-backend-timeout.c | 138 +++ deps/uv/test/test-thread-priority.c | 6 - deps/uv/test/test-threadpool-cancel.c | 99 +- deps/uv/test/test-tty.c | 13 +- deps/uv/test/test-udp-mmsg.c | 246 +++++ .../test/test-udp-recvmsg-unreachable-error.c | 7 + deps/uv/test/test-udp-send-fail.c | 70 ++ deps/uv/test/test-udp-try-send.c | 53 +- deps/uv/test/test-write-cancel.c | 539 +++++++++++ 108 files changed, 5892 insertions(+), 1080 deletions(-) create mode 100644 deps/uv/docs/src/static/architecture.txt create mode 100644 deps/uv/docs/src/static/loop_iteration.txt create mode 100644 deps/uv/test/appcontainer.c create mode 100644 deps/uv/test/test-io-64-safe.c create mode 100644 deps/uv/test/test-osx-resident-set-memory.c create mode 100644 deps/uv/test/test-pipe-write.c create mode 100644 deps/uv/test/test-sizeof.c create mode 100644 deps/uv/test/test-tcp-write-in-read-cb-backend-timeout.c create mode 100644 deps/uv/test/test-udp-send-fail.c create mode 100644 deps/uv/test/test-write-cancel.c diff --git a/deps/uv/.mailmap b/deps/uv/.mailmap index b8adf7b6a5d..71840260cd6 100644 --- a/deps/uv/.mailmap +++ b/deps/uv/.mailmap @@ -2,6 +2,7 @@ A. Hauptmann AJ Heller Aaron Bieber Alan Gutierrez +Alb3e3 <74142887+Alb3e3@users.noreply.github.com> Andrius Bentkus Andy Fiddaman Andy Pan @@ -17,11 +18,13 @@ Darshan Sen Darshan Sen David Carlier Devchandra Meetei Leishangthem +Edigleysson Silva (Edy) Fedor Indutny Frank Denis Hüseyin Açacak <110401522+huseyinacacak-janea@users.noreply.github.com> Imran Iqbal Isaac Z. Schlueter +James Ross Jason Williams Jesse Gorzinski Jesse Gorzinski @@ -56,6 +59,7 @@ Santiago Gimeno Saúl Ibarra Corretgé Saúl Ibarra Corretgé Saúl Ibarra Corretgé +Saúl Ibarra Corretgé Shigeki Ohtsu Shuowang (Wayne) Zhang TK-one @@ -64,6 +68,7 @@ Yasuhiro Matsumoto Yazhong Liu Yuki Okumura cjihrig +cui fliter gengjiawen jBarz jBarz diff --git a/deps/uv/AUTHORS b/deps/uv/AUTHORS index c8def153553..1ce14879f8a 100644 --- a/deps/uv/AUTHORS +++ b/deps/uv/AUTHORS @@ -634,3 +634,36 @@ Przemysław Sobala Quaylyn Rimer <31830590+killerdevildog@users.noreply.github.com> Yasser Nascimento Rudi Heitbaum +apocelipes +locus-x64 +Oren +cui +Gabriel Baraldi +HArsil +PRANAV KUMAR +Benjamin Gilbert +BarryLhm +ivinx +skooch +SRAVANI GUNDEPALLI <88590399+sravani1510@users.noreply.github.com> +Isaac Elbaz +sturcotte06 +orbisai0security +Alb3e3 <74142887+Alb3e3@users.noreply.github.com> +Nandan Acharya +Samuel Williams +Archkon <180910180+Archkon@users.noreply.github.com> +Petr Sumbera +Yuki Ibe +Jérôme Duval +sravani1510 +Russ Cox +Tyler Gibbs <104282235+tylergibbs1@users.noreply.github.com> +Yury Semikhatsky +Jarred Sumner +Milad Fa <46688537+miladfarca@users.noreply.github.com> +neil +Yiğit Tanrıverdi +hunterinvariants +Piotr Kubaj +Brad Smith diff --git a/deps/uv/CMakeLists.txt b/deps/uv/CMakeLists.txt index 9b38b55530e..ab0413b64fc 100644 --- a/deps/uv/CMakeLists.txt +++ b/deps/uv/CMakeLists.txt @@ -197,7 +197,8 @@ if(WIN32) ws2_32 dbghelp ole32 - shell32) + shell32 + synchronization) list(APPEND uv_sources src/win/async.c src/win/core.c @@ -407,12 +408,13 @@ if(CMAKE_SYSTEM_NAME STREQUAL "SunOS") list(APPEND uv_libraries kstat nsl sendfile socket) list(APPEND uv_sources src/unix/no-proctitle.c + src/unix/random-getrandom.c src/unix/sunos.c) endif() if(CMAKE_SYSTEM_NAME STREQUAL "Haiku") list(APPEND uv_defines _BSD_SOURCE) - list(APPEND uv_libraries bsd network) + list(APPEND uv_libraries network) list(APPEND uv_sources src/unix/haiku.c src/unix/bsd-ifaddrs.c @@ -420,6 +422,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Haiku") src/unix/no-proctitle.c src/unix/posix-hrtime.c src/unix/posix-poll.c) + list(APPEND uv_test_libraries bsd) endif() if(CMAKE_SYSTEM_NAME STREQUAL "QNX") @@ -593,6 +596,7 @@ if(LIBUV_BUILD_TESTS) test/test-mutexes.c test/test-not-readable-nor-writable-on-read-error.c test/test-not-writable-after-shutdown.c + test/test-osx-resident-set-memory.c test/test-osx-select.c test/test-pass-always.c test/test-ping-pong.c @@ -607,6 +611,7 @@ if(LIBUV_BUILD_TESTS) test/test-pipe-server-close.c test/test-pipe-set-fchmod.c test/test-pipe-set-non-blocking.c + test/test-pipe-write.c test/test-platform-output.c test/test-poll-close-doesnt-corrupt-stack.c test/test-poll-close.c @@ -631,9 +636,11 @@ if(LIBUV_BUILD_TESTS) test/test-signal-multiple-loops.c test/test-signal-pending-on-close.c test/test-signal.c + test/test-sizeof.c test/test-socket-buffer-size.c test/test-spawn.c test/test-stdio-over-pipes.c + test/test-io-64-safe.c test/test-strscpy.c test/test-strtok.c test/test-tcp-alloc-cb-fail.c @@ -663,6 +670,8 @@ if(LIBUV_BUILD_TESTS) test/test-tcp-unexpected-read.c test/test-tcp-write-after-connect.c test/test-tcp-write-fail.c + test/test-write-cancel.c + test/test-tcp-write-in-read-cb-backend-timeout.c test/test-tcp-write-queue-order.c test/test-tcp-write-to-half-open-connection.c test/test-tcp-writealot.c @@ -697,6 +706,7 @@ if(LIBUV_BUILD_TESTS) test/test-udp-open.c test/test-udp-options.c test/test-udp-send-and-recv.c + test/test-udp-send-fail.c test/test-udp-send-hang-loop.c test/test-udp-send-immediate.c test/test-udp-sendmmsg-error.c @@ -749,6 +759,8 @@ if(LIBUV_BUILD_TESTS) COMMAND "${CMAKE_COMMAND}" -E copy "$" "$/uv_run_tests_a_no_ext") + add_executable(uv_run_appcontainer test/appcontainer.c) + target_link_libraries(uv_run_appcontainer userenv advapi32 ntdll) endif() if(QNX) diff --git a/deps/uv/ChangeLog b/deps/uv/ChangeLog index 0a4fd4b51b0..f7785690a08 100644 --- a/deps/uv/ChangeLog +++ b/deps/uv/ChangeLog @@ -1,4 +1,248 @@ -2026.03.06, Version 1.52.1 (Stable) +2026.09.24, Version 1.53.0 (Stable) + +Changes since version 1.52.1: + +* unix: fix pedantic compiler warnings (Ben Noordhuis) + +* win: fix const correctness compiler error (apocelipes) + +* doc: clarify handle close behavior of uv_spawn (Ben Noordhuis) + +* doc: name function crosslinks consistently (Ben Noordhuis) + +* win: fix const correctness warning (Ben Noordhuis) + +* win: fix off-by-one in utf-16 to wtf-8 conversion (locus-x64) + +* udp: validate nbufs in send functions (Oren) + +* unix: improve handling of uv_async_send mistakes (Jameson Nash) + +* unix,stream: verify uv_try_write2 handle (cui) + +* test,win: fix race in test runner (Jameson Nash) + +* unix,tty: don't drain when setting the TTY mode (Gabriel Baraldi) + +* inet: prefer sizeof with parenthesis (Juan José Arboleda) + +* test: remove unused headers and use newer ASSERT macros (Juan José Arboleda) + +* doc: Add textual images into libuv man page (Stacey Marshall) + +* darwin: replace EV_OOBAND handling with EVFILT_EXCEPT + NOTE_OOB (Juan José + Arboleda) + +* docs: add copybutton sphinx extension (HArsil) + +* doc: fix threading bugs in progress example (PRANAV KUMAR) + +* unix: support long path names in pipe.c (Edigleysson Silva (Edy)) + +* win: fix some compiler warnings (Benjamin Gilbert) + +* docs: fix broken and redirected links (Jameson Nash) + +* doc: remove random reference to libev (Ben Noordhuis) + +* process: better validation for process->pid usage (Jameson Nash) + +* unix: check return value of fcntl call (Ben Noordhuis) + +* test: fix -Wunused-function warning (Ben Noordhuis) + +* linux: close streams without an extra read (Jameson Nash) + +* unix: enforce recvmmsg buffer size requirements (Ben Noordhuis) + +* android: fix termux build (BarryLhm) + +* io: make libuv 64-bit safe (Jameson Nash) + +* win,pipe: skip IOCP for pipe handles (Jameson Nash) + +* win: use WSA_FLAG_NO_HANDLE_INHERIT in all WSASocketW (Jameson Nash) + +* unix: use posix_spawn instead of fork (Jameson Nash) + +* test: use correct type for uv_fileno arguments (Saúl Ibarra Corretgé) + +* misc: revise security vulnerability reporting instructions (Jameson Nash) + +* win: fix watch loop logic (Stefan Stojanovic) + +* win: properly initialize OSVERSIONINFOW (Santiago Gimeno) + +* misc: fix error code returned from uv_wtf8_length_as_utf16 (Ryan Liptak) + +* stream: fix server stop accepting after uv_accept() error (ivinx) + +* unix,ibmi: abort on process_title_mutex init failure (Juan José Arboleda) + +* unix,stream: fix getsockopt error handling in connect (Juan José Arboleda) + +* unix: treat futimens() as best-effort in copyfile (skooch) + +* unix: free statxbuf on iouring statx fallback (Santiago Gimeno) + +* aix,ibmi: fix undeclared identifiers (SRAVANI GUNDEPALLI) + +* unix: fix uv__udp_recvmsg crash (theanarkh) + +* win: use PROC_THREAD_ATTRIBUTE_HANDLE_LIST in uv_spawn (Jameson Nash) + +* unix: avoid duplicate recvmmsg terminal callback (Isaac Elbaz) + +* win: fix spawn process with no argument (sturcotte06) + +* unix: handle cloexec failure in kqueue init (Juan José Arboleda) + +* unix: remove UV_HANDLE_READING flag (Edigleysson Silva (Edy)) + +* src: use INET6_ADDRSTRLEN for IPv6 address buffer (orbisai0security) + +* test: include pty.h for GNU Hurd (Jérémy Lal) + +* test: skip recvmsg unreachable test when no ipv6 (Santiago Gimeno) + +* win: fix race condition in uv_async_send (Jameson Nash) + +* unix,fs: fix busyloop in big copyfile (Jameson Nash) + +* process,unix: avoid off-by-one in uv__spawn_resolve_and_spawn (Jameson Nash) + +* test: check that sizeof is constant (Jameson Nash) + +* async: make seq_cst the default (Jameson Nash) + +* Reland "linux: eliminate a read on eventfd per wakeup" (Jameson Nash) + +* win,process: skip dump silently if no valid folder can be determined (Jameson + Nash) + +* win: handle short path mismatch with a fallback in fs events (Joyee Cheung) + +* unix,udp: handle msg_hdr.msg_namelen=0 on recvmmsg (Santiago Gimeno) + +* ci: lock down permissions and pin action SHAs (Alb3e3) + +* docs: ignore unstable ncurses linkcheck URL (Alb3e3) + +* unix: handle EINTR in uv_resident_set_memory (Nandan Acharya) + +* build(deps): bump reactivecircus/android-emulator-runner (dependabot[bot]) + +* build(deps): bump actions/setup-python from 6.2.0 to 6.3.0 (dependabot[bot]) + +* unix: return EINVAL for invalid tty modes (Samuel Williams) + +* ci: use VS 18 / 2026 in Windows Server 2025 (Saúl Ibarra Corretgé) + +* win: fix use of wrong constant in tcp code (Ben Noordhuis) + +* unix: fix UV_FS_O_DIRECT on more linux architectures (Archkon) + +* doc: add UB warning to threading documentation (Ben Noordhuis) + +* build(deps): bump reactivecircus/android-emulator-runner (dependabot[bot]) + +* sunos: use getrandom(2) for uv_random (Petr Sumbera) + +* unix,poll: fix callback event bits on error/hangup (Yuki Ibe) + +* haiku: build tests with libbsd (Jérôme Duval) + +* unix: fix static declaration in nested block for AIX and IBM i PASE + (sravani1510) + +* win: fix unique named pipes to work inside Windows AppContainer (Russ Cox) + +* build(deps): bump actions/setup-python from 6.3.0 to 7.0.0 (dependabot[bot]) + +* build(deps): bump actions/checkout from 6.0.3 to 7.0.0 (dependabot[bot]) + +* stream: Implement cancellation support for uv_write_t (Keno Fischer) + +* unix: handle trailing empty write buffers (Alb3e3) + +* stream: handle cancel write after close (Tyler Gibbs) + +* build(deps): bump actions/checkout from 7.0.0 to 7.0.1 (dependabot[bot]) + +* test: don't generate io_uring variants of loopless fs tests (Jameson Nash) + +* test: close the loop in udp_recvmsg_unreachable_error{,6} (Jameson Nash) + +* linux: use scaling_max_freq in uv_cpu_info() (James Ross) + +* win: fix leak if uv_loop_init fails (Jameson Nash) + +* solaris: fix build and test failures, add CI (Petr Sumbera) + +* test: improve fs event filename assertion (Petr Sumbera) + +* build(deps): bump vmactions/solaris-vm from 1.3.7 to 1.3.8 (dependabot[bot]) + +* build(deps): bump actions/checkout from 6.0.3 to 7.0.1 (dependabot[bot]) + +* win: fix crash when closing a pipe after a failed bind (Yury Semikhatsky) + +* darwin: report physical footprint as rss (Jarred Sumner) + +* test: close file descriptors leaked by tests (Jameson Nash) + +* unix: short-circuit no-op event mask changes in uv__io_stop (Shelley Vohr) + +* unix,process: never shrink the stdio socket pair buffers (Shelley Vohr) + +* build(deps): bump vmactions/solaris-vm from 1.3.8 to 1.3.9 (dependabot[bot]) + +* linux: fix tcc build (Ben Noordhuis) + +* aix: canonicalize fs event path with realpath() (Milad Fa) + +* openbsd: use fork instead of posix_spawn (Ben Noordhuis) + +* freebsd,netbsd,openbsd: add build CI (neil) + +* win,fs: suppress CRT assertion on close (Yiğit Tanrıverdi) + +* unix: drain tty reads on POLLHUP without POLLIN (Santiago Gimeno) + +* doc: fix on guide filesystem.rst (Viacheslav Muravyev) + +* linux: support cancelling io_uring file requests (hunterinvariants) + +* build: distribute Windows long path manifest (Yiğit Tanrıverdi) + +* netbsd: free cpu times on sysctl failure (cui fliter) + +* build(deps): bump vmactions/netbsd-vm from 1.4.8 to 1.4.9 (dependabot[bot]) + +* freebsd: don't fail uv_cpu_info() when hw.clockrate is missing (Piotr Kubaj) + +* build(deps): bump vmactions/netbsd-vm from 1.4.9 to 1.5.0 (dependabot[bot]) + +* build(deps): bump vmactions/solaris-vm from 1.3.9 to 1.4.0 (dependabot[bot]) + +* build(deps): bump vmactions/freebsd-vm from 1.5.5 to 1.5.6 (dependabot[bot]) + +* unix: fix warning in OpenBSD code (Brad Smith) + +* test: lower number of iterations (Ben Noordhuis) + +* openbsd: update EVFILT_USER comment (Brad Smith) + +* freebsd: don't use long AF_UNIX socket paths (Juan José Arboleda) + +* openbsd: use getexecpath in uv_exepath if present (Brad Smith) + +* build(deps): bump vmactions/freebsd-vm from 1.5.6 to 1.5.7 (dependabot[bot]) + +* win: reserve explicit thread stack size (Zuohui Yang) + + +2026.03.06, Version 1.52.1 (Stable), 1cfa32ff59c076ffb6ed735bbc8c18361558661f Changes since version 1.52.0: diff --git a/deps/uv/Makefile.am b/deps/uv/Makefile.am index cc70ffe4bd0..21544bd8e4e 100644 --- a/deps/uv/Makefile.am +++ b/deps/uv/Makefile.am @@ -128,7 +128,8 @@ EXTRA_DIST = test/fixtures/empty_file \ CONTRIBUTING.md \ LICENSE \ LICENSE-extra \ - README.md + README.md \ + uv_win_longpath.manifest @@ -218,6 +219,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-mutexes.c \ test/test-not-readable-nor-writable-on-read-error.c \ test/test-not-writable-after-shutdown.c \ + test/test-osx-resident-set-memory.c \ test/test-osx-select.c \ test/test-pass-always.c \ test/test-ping-pong.c \ @@ -232,6 +234,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-pipe-close-stdout-read-stdin.c \ test/test-pipe-set-non-blocking.c \ test/test-pipe-set-fchmod.c \ + test/test-pipe-write.c \ test/test-platform-output.c \ test/test-poll.c \ test/test-poll-close.c \ @@ -256,9 +259,11 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-signal-multiple-loops.c \ test/test-signal-pending-on-close.c \ test/test-signal.c \ + test/test-sizeof.c \ test/test-socket-buffer-size.c \ test/test-spawn.c \ test/test-stdio-over-pipes.c \ + test/test-io-64-safe.c \ test/test-strscpy.c \ test/test-strtok.c \ test/test-tcp-alloc-cb-fail.c \ @@ -290,6 +295,8 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-tcp-try-write.c \ test/test-tcp-write-in-a-row.c \ test/test-tcp-try-write-error.c \ + test/test-write-cancel.c \ + test/test-tcp-write-in-read-cb-backend-timeout.c \ test/test-tcp-write-queue-order.c \ test/test-test-macros.c \ test/test-thread-equal.c \ @@ -322,6 +329,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-udp-open.c \ test/test-udp-options.c \ test/test-udp-send-and-recv.c \ + test/test-udp-send-fail.c \ test/test-udp-send-hang-loop.c \ test/test-udp-send-immediate.c \ test/test-udp-sendmmsg-error.c \ @@ -473,6 +481,7 @@ libuv_la_SOURCES += src/unix/bsd-ifaddrs.c \ src/unix/no-proctitle.c \ src/unix/posix-hrtime.c \ src/unix/posix-poll.c +test_run_tests_LDFLAGS += -lbsd endif if HURD @@ -536,6 +545,7 @@ libuv_la_CFLAGS += -D__EXTENSIONS__ \ -D_XOPEN_SOURCE=500 \ -D_REENTRANT libuv_la_SOURCES += src/unix/no-proctitle.c \ + src/unix/random-getrandom.c \ src/unix/sunos.c endif diff --git a/deps/uv/SECURITY.md b/deps/uv/SECURITY.md index 32abba81544..e8587b075e7 100644 --- a/deps/uv/SECURITY.md +++ b/deps/uv/SECURITY.md @@ -10,9 +10,9 @@ Currently, we are providing security updates for the latest release in the v1.x ## Reporting a Vulnerability -If you believe you have found a security vulnerability in `libuv`, please use the [GitHub's private vulnerability reporting feature](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) in the [libuv repository](https://github.com/libuv/libuv) to report it to us. +If you believe you have found an active security vulnerability in `libuv`, please report it to libuv-security@googlegroups.com. Please report all other issues on the github issue tracker. We have been forced to terminate the ability to use Github's private vulnerability reporting due to a flood of AI-generated report spam, and a lack of sufficient moderation tools to manage the false reports. -This will allow us to assess the risk, and make a fix available before we add a bug report to the GitHub repository. +This will allow us to assess the risk and make a fix available before we add a bug report to the GitHub repository and issue a Github security advisory and assign a CVE. Please do: @@ -24,4 +24,4 @@ Please do not: * Post any information about the vulnerability in public places. * Attempt to exploit the vulnerability yourself. -We take all security bugs seriously. Thank you for improving the security of `libuv`. We appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions. \ No newline at end of file +We take all security bugs seriously. Thank you for improving the security of `libuv`. We appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions. diff --git a/deps/uv/configure.ac b/deps/uv/configure.ac index c5246110a2d..9d39228a8b2 100644 --- a/deps/uv/configure.ac +++ b/deps/uv/configure.ac @@ -13,7 +13,7 @@ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. AC_PREREQ(2.57) -AC_INIT([libuv], [1.52.1], [https://github.com/libuv/libuv/issues]) +AC_INIT([libuv], [1.53.0], [https://github.com/libuv/libuv/issues]) AC_CONFIG_MACRO_DIR([m4]) m4_include([m4/libuv-extra-automake-flags.m4]) m4_include([m4/as_case.m4]) @@ -74,7 +74,7 @@ AM_CONDITIONAL([OS400], [AS_CASE([$host_os],[os400], [true], [false]) AM_CONDITIONAL([SUNOS], [AS_CASE([$host_os],[solaris*], [true], [false])]) AM_CONDITIONAL([WINNT], [AS_CASE([$host_os],[mingw*], [true], [false])]) AS_CASE([$host_os],[mingw*], [ - LIBS="$LIBS -lws2_32 -lpsapi -liphlpapi -luserenv -luser32 -ldbghelp -lole32 -lshell32" + LIBS="$LIBS -lws2_32 -lpsapi -liphlpapi -luserenv -luser32 -ldbghelp -lole32 -lshell32 -lsynchronization" ]) AS_CASE([$host_os], [solaris2.10], [ CFLAGS="$CFLAGS -DSUNOS_NO_IFADDRS" diff --git a/deps/uv/docs/requirements.txt b/deps/uv/docs/requirements.txt index 0319b30871d..dce5387bc4e 100644 --- a/deps/uv/docs/requirements.txt +++ b/deps/uv/docs/requirements.txt @@ -1,6 +1,7 @@ # primary furo==2024.8.6 Sphinx==7.0.1 +sphinx-copybutton==0.5.2 # dependencies alabaster==0.7.16 diff --git a/deps/uv/docs/src/async.rst b/deps/uv/docs/src/async.rst index 029c051cfcd..97ef7510142 100644 --- a/deps/uv/docs/src/async.rst +++ b/deps/uv/docs/src/async.rst @@ -7,6 +7,28 @@ Async handles allow the user to "wakeup" the event loop and get a callback called from another thread. +.. note:: + + :c:func:`uv_async_send` and the :c:type:`uv_async_cb` invocation are + sequentially consistent (seq_cst) operations for a given async handle: all + memory accesses (reads and writes) made before :c:func:`uv_async_send` are + visible to that callback. + +.. warning:: + libuv will coalesce calls to :c:func:`uv_async_send`, that is, not every + call to it will yield an execution of the callback. For example: if + :c:func:`uv_async_send` is called 5 times in a row before the callback is + called, the callback will only be called once. If :c:func:`uv_async_send` + is called again after the callback was called, it will be called again. + However, since it is sequentially consistent, the values read or written in + that callback will always be the same (or newer) as those read or written + by the other thread. + +.. versionchanged:: 1.53.0 + :c:func:`uv_async_send` and :c:type:`uv_async_cb` are sequentially + consistent. Prior to this version, any case where libuv might coalesce + calls probably requires a full `seq_cst` fence before it for correctness. + Data types ---------- @@ -54,12 +76,11 @@ API :c:func:`uv_async_send` is `async-signal-safe `_. It's safe to call this function from a signal handler. - .. warning:: - libuv will coalesce calls to :c:func:`uv_async_send`, that is, not every call to it will - yield an execution of the callback. For example: if :c:func:`uv_async_send` is called 5 - times in a row before the callback is called, the callback will only be called once. If - :c:func:`uv_async_send` is called again after the callback was called, it will be called - again. + .. note:: + This is a full memory fence with respect to other calls and callbacks + using this same async handle, and so it will order all operations + around this call and the corresponding callback on the sending thread + and receiving thread. .. seealso:: The :c:type:`uv_handle_t` API functions also apply. diff --git a/deps/uv/docs/src/conf.py b/deps/uv/docs/src/conf.py index 354759a23c5..a2aba14ecc9 100644 --- a/deps/uv/docs/src/conf.py +++ b/deps/uv/docs/src/conf.py @@ -48,7 +48,7 @@ def get_libuv_version(): # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['manpage'] +extensions = ['manpage', 'sphinx_copybutton'] # Add any paths that contain templates here, relative to this directory. templates_path = ['templates'] @@ -89,6 +89,10 @@ def get_libuv_version(): # directories to ignore when looking for source files. exclude_patterns = [] +linkcheck_ignore = [ + r'https://invisible-island\.net/ncurses/announce\.html', +] + # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None diff --git a/deps/uv/docs/src/design.rst b/deps/uv/docs/src/design.rst index 7ad62076f33..80a1fd1303e 100644 --- a/deps/uv/docs/src/design.rst +++ b/deps/uv/docs/src/design.rst @@ -16,10 +16,16 @@ cross-platform file I/O and threading functionality is also provided, amongst ot Here is a diagram illustrating the different parts that compose libuv and what subsystem they relate to: -.. image:: static/architecture.png - :scale: 75% - :align: center +.. only:: Graphical + .. image:: static/architecture.png + :scale: 75% + :align: center + :alt: Picture of libuv architecture + +.. only:: Textual + + .. literalinclude:: static/architecture.txt Handles and requests ^^^^^^^^^^^^^^^^^^^^ @@ -39,7 +45,7 @@ don't need a handle they run directly on the loop. Guidelines for dealing with handles and requests: 1. If `uv_foo_init()` succeeds in initializing the handle, you must call - :c:func:`uv_close()`. If the handle's init function errors, you don't + :c:func:`uv_close`. If the handle's init function errors, you don't need to do anything. 2. Only handles are closed, not requests. For example, :c:type:`uv_tcp_t` @@ -49,16 +55,16 @@ Guidelines for dealing with handles and requests: :c:type:`uv_close_cb` or afterwards, not before. 4. Most handles have init + start/stop functions; some handles don't. - Example: :c:type:`uv_tcp_t` vs. :c:type:`uv_process_t`; :c:func:`uv_spawn()` + Example: :c:type:`uv_tcp_t` vs. :c:type:`uv_process_t`; :c:func:`uv_spawn` combines handle initialization and process start into one. 5. Requests are closed automatically when they complete, or when they are - cancelled with :c:func:`uv_cancel()`. + cancelled with :c:func:`uv_cancel`. 6. No additional cleanup is needed except for :c:type:`uv_fs_t` and :c:type:`uv_getaddrinfo_t` requests. For :c:type:`uv_fs_t`, call - :c:func:`uv_fs_req_cleanup()` once you are done with it; for - :c:type:`uv_getaddrinfo_t`, that's :c:func:`uv_freeaddrinfo()`. + :c:func:`uv_fs_req_cleanup` once you are done with it; for + :c:type:`uv_getaddrinfo_t`, that's :c:func:`uv_freeaddrinfo`. 7. The request's memory can only be reclaimed or reused from that point onward. @@ -80,10 +86,16 @@ which have been added to the poller and callbacks will be fired indicating socke In order to better understand how the event loop operates, the following diagram illustrates all stages of a loop iteration: -.. image:: static/loop_iteration.png - :scale: 75% - :align: center +.. only:: Graphical + + .. image:: static/loop_iteration.png + :scale: 75% + :align: center + :alt: Picture of libuv loop iteration + +.. only:: Textual + .. literalinclude:: static/loop_iteration.txt #. The loop concept of 'now' is initially set. diff --git a/deps/uv/docs/src/fs.rst b/deps/uv/docs/src/fs.rst index 99524e70289..dce38f36ce2 100644 --- a/deps/uv/docs/src/fs.rst +++ b/deps/uv/docs/src/fs.rst @@ -166,8 +166,8 @@ Data types .. c:type:: uv_dir_t Data type used for streaming directory iteration. - Used by :c:func:`uv_fs_opendir()`, :c:func:`uv_fs_readdir()`, and - :c:func:`uv_fs_closedir()`. `dirents` represents a user provided array of + Used by :c:func:`uv_fs_opendir`, :c:func:`uv_fs_readdir`, and + :c:func:`uv_fs_closedir`. `dirents` represents a user provided array of `uv_dirent_t`s used to hold results. `nentries` is the user provided maximum array size of `dirents`. @@ -391,7 +391,7 @@ API create a copy-on-write reflink. If the underlying platform does not support copy-on-write, or an error occurs while attempting to use copy-on-write, a fallback copy mechanism based on - :c:func:`uv_fs_sendfile()` is used. + :c:func:`uv_fs_sendfile` is used. - `UV_FS_COPYFILE_FICLONE_FORCE`: If present, `uv_fs_copyfile()` will attempt to create a copy-on-write reflink. If the underlying platform does not support copy-on-write, or an error occurs while attempting to use @@ -471,7 +471,7 @@ API .. c:function:: int uv_fs_realpath(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) - Equivalent to :man:`realpath(3)` on Unix. Windows uses `GetFinalPathNameByHandleW `_. + Equivalent to :man:`realpath(3)` on Unix. Windows uses `GetFinalPathNameByHandleW `_. The resulting string is stored in `req->ptr`. .. warning:: @@ -551,7 +551,7 @@ Helper functions .. c:function:: uv_os_fd_t uv_get_osfhandle(int fd) For a file descriptor in the C runtime, get the OS-dependent handle. - On UNIX, returns the ``fd`` intact. On Windows, this calls `_get_osfhandle `_. + On UNIX, returns the ``fd`` intact. On Windows, this calls `_get_osfhandle `_. Note that the return value is still owned by the C runtime, any attempts to close it or to use it after closing the fd may lead to malfunction. @@ -560,7 +560,7 @@ Helper functions .. c:function:: int uv_open_osfhandle(uv_os_fd_t os_fd) For a OS-dependent handle, get the file descriptor in the C runtime. - On UNIX, returns the ``os_fd`` intact. On Windows, this calls `_open_osfhandle `_. + On UNIX, returns the ``os_fd`` intact. On Windows, this calls `_open_osfhandle `_. Note that this consumes the argument, any attempts to close it or to use it after closing the return value may lead to malfunction. @@ -586,7 +586,7 @@ File open constants .. note:: `UV_FS_O_DIRECT` is supported on Linux, and on Windows via - `FILE_FLAG_NO_BUFFERING `_. + `FILE_FLAG_NO_BUFFERING `_. `UV_FS_O_DIRECT` is not supported on macOS. .. c:macro:: UV_FS_O_DIRECTORY @@ -603,7 +603,7 @@ File open constants .. note:: `UV_FS_O_DSYNC` is supported on Windows via - `FILE_FLAG_WRITE_THROUGH `_. + `FILE_FLAG_WRITE_THROUGH `_. .. c:macro:: UV_FS_O_EXCL @@ -670,7 +670,7 @@ File open constants .. note:: `UV_FS_O_RANDOM` is only supported on Windows via - `FILE_FLAG_RANDOM_ACCESS `_. + `FILE_FLAG_RANDOM_ACCESS `_. .. c:macro:: UV_FS_O_RDONLY @@ -687,7 +687,7 @@ File open constants .. note:: `UV_FS_O_SEQUENTIAL` is only supported on Windows via - `FILE_FLAG_SEQUENTIAL_SCAN `_. + `FILE_FLAG_SEQUENTIAL_SCAN `_. .. c:macro:: UV_FS_O_SHORT_LIVED @@ -695,7 +695,7 @@ File open constants .. note:: `UV_FS_O_SHORT_LIVED` is only supported on Windows via - `FILE_ATTRIBUTE_TEMPORARY `_. + `FILE_ATTRIBUTE_TEMPORARY `_. .. c:macro:: UV_FS_O_SYMLINK @@ -708,7 +708,7 @@ File open constants .. note:: `UV_FS_O_SYNC` is supported on Windows via - `FILE_FLAG_WRITE_THROUGH `_. + `FILE_FLAG_WRITE_THROUGH `_. .. c:macro:: UV_FS_O_TEMPORARY @@ -716,7 +716,7 @@ File open constants .. note:: `UV_FS_O_TEMPORARY` is only supported on Windows via - `FILE_ATTRIBUTE_TEMPORARY `_. + `FILE_ATTRIBUTE_TEMPORARY `_. .. c:macro:: UV_FS_O_TRUNC diff --git a/deps/uv/docs/src/fs_event.rst b/deps/uv/docs/src/fs_event.rst index bfdecdd7329..baa4b33d9c9 100644 --- a/deps/uv/docs/src/fs_event.rst +++ b/deps/uv/docs/src/fs_event.rst @@ -23,8 +23,8 @@ the best backend for the job on each platform. creation/deletion within a directory that is being monitored. See the `IBM Knowledge centre`_ for more details. - .. _documentation: https://developer.ibm.com/articles/au-aix_event_infrastructure/ - .. _`IBM Knowledge centre`: https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.2.0/com.ibm.zos.v2r1.bpxb100/ioc.htm + .. _documentation: https://www.ibm.com/docs/en/aix/7.2?topic=management-aix-event-infrastructure-aix-aix-clusters-ahafs + .. _`IBM Knowledge centre`: https://www.ibm.com/docs/en/zos/2.2.0?topic=SSLTBW_2.2.0/com.ibm.zos.v2r1.bpxb100/ioc.html diff --git a/deps/uv/docs/src/misc.rst b/deps/uv/docs/src/misc.rst index 09db6244a0e..78bfa062bc9 100644 --- a/deps/uv/docs/src/misc.rst +++ b/deps/uv/docs/src/misc.rst @@ -281,13 +281,13 @@ API do so automatically when it is unloaded but it can be instructed to perform cleanup manually. - .. warning:: Only call :c:func:`uv_library_shutdown()` once. + .. warning:: Only call :c:func:`uv_library_shutdown` once. - .. warning:: Don't call :c:func:`uv_library_shutdown()` when there are + .. warning:: Don't call :c:func:`uv_library_shutdown` when there are still event loops or I/O requests active. .. warning:: Don't call libuv functions after calling - :c:func:`uv_library_shutdown()`. + :c:func:`uv_library_shutdown`. .. c:function:: uv_buf_t uv_buf_init(char* base, unsigned int len) @@ -297,6 +297,13 @@ API `base` and `len` members of the uv_buf_t struct. The user is responsible for freeing `base` after the uv_buf_t is done. Return struct passed by value. + .. warning:: It is discouraged to set `len` to a large value as that may + result in spurious failures. Specifically, Windows may fail on + writes larger than about 511 MB, and various Unicies may fail + on I/O larger than about 2 GB (0x7ffff000 bytes). Instead it is + generally better to split the data into multiple `uv_write` + calls (attach the `write_cb` to the last one). + .. c:function:: char** uv_setup_args(int argc, char** argv) Store the program arguments. Required for getting / setting the process title diff --git a/deps/uv/docs/src/pipe.rst b/deps/uv/docs/src/pipe.rst index bb77f90e3ce..c71242635ab 100644 --- a/deps/uv/docs/src/pipe.rst +++ b/deps/uv/docs/src/pipe.rst @@ -120,6 +120,10 @@ API output. If the buffer is not big enough ``UV_ENOBUFS`` will be returned and len will contain the required size. + UNIX domain socket paths on FreeBSD cannot exceed the size of + ``sockaddr_un.sun_path`` although the platform defines a + ``SOCK_MAXADDRLEN`` that is larger. + .. versionchanged:: 1.3.0 the returned length no longer includes the terminating null byte, and the buffer is not null terminated. diff --git a/deps/uv/docs/src/process.rst b/deps/uv/docs/src/process.rst index f15dcf610f5..c6bcd9fa81e 100644 --- a/deps/uv/docs/src/process.rst +++ b/deps/uv/docs/src/process.rst @@ -172,7 +172,10 @@ Public members .. c:member:: int uv_process_t.pid - The PID of the spawned process. It's set after calling :c:func:`uv_spawn`. + The PID of the spawned process. It is set after calling :c:func:`uv_spawn` + and retains the value even after the process exits. The value is only + unique while the process is alive; after exit, another process may be + reassigned the same PID. .. note:: The :c:type:`uv_handle_t` members also apply. @@ -259,13 +262,20 @@ API Initializes the process handle and starts the process. If the process is successfully spawned, this function will return 0. Otherwise, the negative error code corresponding to the reason it couldn't spawn is - returned. + returned. Note that either way-success or failure--you must eventually call + :c:func:`uv_close` to close the handle again before freeing the memory of + the handle, unlike other the other init functions in libuv. Possible reasons for failing to spawn would include (but not be limited to) the file to execute not existing, not having permissions to use the setuid or setgid specified, or not having enough memory to allocate for the new process. + .. warning:: + On unix, if the process has not yet exited when you call `uv_close`, + you will create a zombie that libuv cannot reap. You are responsible + for calling `waitpid` later. This is not relevant on Windows. + .. versionchanged:: 1.24.0 Added `UV_PROCESS_WINDOWS_HIDE_CONSOLE` and `UV_PROCESS_WINDOWS_HIDE_GUI` flags. @@ -277,6 +287,11 @@ API Sends the specified signal to the given process handle. Check the documentation on :c:ref:`signal` for signal support, specially on Windows. + If the specified process is already dead, this will not kill a different + process which happened to reuse the same pid. By contrast, `uv_kill` may + kill an arbitrary other process if you use a cached value of + :c:func:`uv_process_get_pid`. + .. c:function:: int uv_kill(int pid, int signum) Sends the specified signal to the given PID. Check the documentation diff --git a/deps/uv/docs/src/request.rst b/deps/uv/docs/src/request.rst index aacabe026a7..eda3829bc55 100644 --- a/deps/uv/docs/src/request.rst +++ b/deps/uv/docs/src/request.rst @@ -68,13 +68,17 @@ API .. c:function:: int uv_cancel(uv_req_t* req) Cancel a pending request. Fails if the request is executing or has finished - executing. + executing. Write requests are an exception: cancellation of a write that is + already in progress will attempt to interrupt it (the callback may report + a partial write), and cancellation of an already-completed write is a + successful no-op. Returns 0 on success, or an error code < 0 on failure. - Only cancellation of :c:type:`uv_fs_t`, :c:type:`uv_getaddrinfo_t`, - :c:type:`uv_getnameinfo_t`, :c:type:`uv_random_t` and :c:type:`uv_work_t` - requests is currently supported. + Only cancellation of :c:type:`uv_write_t`, :c:type:`uv_fs_t`, + :c:type:`uv_getaddrinfo_t`, :c:type:`uv_getnameinfo_t`, + :c:type:`uv_random_t` and :c:type:`uv_work_t` requests is currently + supported. Cancelled requests have their callbacks invoked some time in the future. It's **not** safe to free the memory associated with the request until the @@ -88,6 +92,15 @@ API :c:type:`uv_getnameinfo_t` or :c:type:`uv_random_t` request has its callback invoked with status == `UV_ECANCELED`. + * A :c:type:`uv_write_t` request has its callback invoked with + status == `UV_ECANCELED`. Use :c:func:`uv_write_nwritten` from the + callback to determine how many bytes were written before cancellation. + Fully cancelled writes (where no bytes were written) may have their + callbacks called out of order with respect to other writes on the same + stream. Note that cancelled writes may still succeed or fail with + other errors if the kernel finished processing the write before the + cancellation took effect. + .. c:function:: size_t uv_req_size(uv_req_type type) Returns the size of the given request type. Useful for FFI binding writers diff --git a/deps/uv/docs/src/static/architecture.txt b/deps/uv/docs/src/static/architecture.txt new file mode 100644 index 00000000000..77fc0136ada --- /dev/null +++ b/deps/uv/docs/src/static/architecture.txt @@ -0,0 +1,23 @@ +LIBUV + ++----------------------------------------------+ +---+ +---+ +---+ +| | | | | | | | +| Network I/O | | F | | D | | U | +| | | i | | N | | s | +| | | l | | S | | e | +| | | e | | | | r | +| | | | | O | | | +| | | I | | p | | C | +| +------+ +------+ +------+ +------+ +------+ | | / | | s | | o | +| | TCP | | UDP | | TTY | | Pipe | | ... | | | O | | | | d | +| +------+ +------+ +------+ +------+ +------+ | | | | | | e | +| | | | | | | | ++----------------------------------------------+ +---+ +---+ +---+ + ++----------------------------------+ +--------+ +---------------+ +| uv__io_t | | | | | ++----------------------------------+ | | | | + | IOCP | | Thread Pool | ++-------+ +--------+ +-------------+ | | | | +| epoll | | kqueue | | event ports | | | | | ++-------+ +--------+ +-------------+ +--------+ +---------------+ diff --git a/deps/uv/docs/src/static/loop_iteration.txt b/deps/uv/docs/src/static/loop_iteration.txt new file mode 100644 index 00000000000..b67006ee4c4 --- /dev/null +++ b/deps/uv/docs/src/static/loop_iteration.txt @@ -0,0 +1,35 @@ + +------------------------+ + | Initialze loop time | + +------------------------+ + | Run due timers | + +------------------------+ + | + v + __ + _____ / \ + / \ / \ + / \ No / Loop \ + | end |<-------| alive? |<----------+ + \ / \ / | + \ / \ / | + ----- \__/ | + | | + | Yes | + v | + +------------------------+ | + | Call pending callbacks | | + +------------------------+ | + | Run idle handles | | + +------------------------+ | + | Run prepare handles | | + +------------------------+ | + | Poll for I/O | | + +------------------------+ | + | Run check handles | | + +------------------------+ | + | Call close callbacks | | + +------------------------+ | + | Update loop time | | + +------------------------+ | + | Run due timers |---+ + +------------------------+ diff --git a/deps/uv/docs/src/stream.rst b/deps/uv/docs/src/stream.rst index 0b42c4b3f8c..955055b17ab 100644 --- a/deps/uv/docs/src/stream.rst +++ b/deps/uv/docs/src/stream.rst @@ -139,7 +139,7 @@ API be made several times until there is no more data to read or :c:func:`uv_read_stop` is called. - .. versionchanged:: 1.38.0 :c:func:`uv_read_start()` now consistently + .. versionchanged:: 1.38.0 :c:func:`uv_read_start` now consistently returns `UV_EALREADY` when called twice, and `UV_EINVAL` when the stream is closing. With older libuv versions, it returns `UV_EALREADY` on Windows but not UNIX, and `UV_EINVAL` on UNIX but not Windows. @@ -217,7 +217,21 @@ API where it returns ``UV_EAGAIN``. .. versionadded:: 1.42.0 - + +.. c:function:: size_t uv_write_nwritten(const uv_write_t* req) + + Returns the number of bytes written by a write request. Only valid when + called from within the write callback (:c:type:`uv_write_cb`). + + This is primarily useful when a write has been cancelled via + :c:func:`uv_cancel` and the callback receives ``UV_ECANCELED`` + status, to determine how many bytes were actually written before + cancellation. Note that cancelled writes may still succeed or fail + with other errors if the kernel finished processing the write before + the cancellation took effect. + + .. versionadded:: 1.53.0 + .. c:function:: int uv_is_readable(const uv_stream_t* handle) Returns 1 if the stream is readable, 0 otherwise. diff --git a/deps/uv/docs/src/tcp.rst b/deps/uv/docs/src/tcp.rst index a17ccba5bc7..94a3b2741e4 100644 --- a/deps/uv/docs/src/tcp.rst +++ b/deps/uv/docs/src/tcp.rst @@ -112,7 +112,6 @@ API - AIX - DragonFlyBSD - FreeBSD - - HP-UX - illumos - Linux - macOS diff --git a/deps/uv/docs/src/threading.rst b/deps/uv/docs/src/threading.rst index 27c1d6ee28a..a4c7ae50d4b 100644 --- a/deps/uv/docs/src/threading.rst +++ b/deps/uv/docs/src/threading.rst @@ -157,6 +157,8 @@ Threads The buffer should be large enough to hold the name of the thread plus the trailing NUL, or it will be truncated to fit with the trailing NUL. + It's undefined behavior to call this after the thread has terminated. + Not supported on Windows Server 2016, returns `UV_ENOSYS`. .. versionadded:: 1.50.0 @@ -171,6 +173,10 @@ Threads UV_THREAD_PRIORITY_ABOVE_NORMAL, UV_THREAD_PRIORITY_NORMAL, UV_THREAD_PRIORITY_BELOW_NORMAL, UV_THREAD_PRIORITY_LOWEST. + It's undefined behavior to call this after the thread has terminated. + + .. versionadded:: 1.48.0 + .. c:function:: int uv_thread_getpriority(uv_thread_t tid, int* priority) If the function succeeds, the return value is 0. @@ -179,6 +185,10 @@ Threads output parameter priority is platform dependent. For Linux, when schedule policy is SCHED_OTHER (default), priority is 0. + It's undefined behavior to call this after the thread has terminated. + + .. versionadded:: 1.48.0 + Thread-local storage ^^^^^^^^^^^^^^^^^^^^ diff --git a/deps/uv/docs/src/timer.rst b/deps/uv/docs/src/timer.rst index 474c6b8c4cd..dea162f1eea 100644 --- a/deps/uv/docs/src/timer.rst +++ b/deps/uv/docs/src/timer.rst @@ -85,7 +85,7 @@ API .. c:function:: uint64_t uv_timer_get_due_in(const uv_timer_t* handle) Get the timer due value or 0 if it has expired. The time is relative to - :c:func:`uv_now()`. + :c:func:`uv_now`. .. versionadded:: 1.40.0 diff --git a/deps/uv/docs/src/udp.rst b/deps/uv/docs/src/udp.rst index 39fe77f5e1b..e4126fc4c0b 100644 --- a/deps/uv/docs/src/udp.rst +++ b/deps/uv/docs/src/udp.rst @@ -76,7 +76,8 @@ Data types */ UV_UDP_REUSEPORT = 64, /* - * Indicates that recvmmsg should be used, if available. + * Indicates that recvmmsg should be used, if available. The uv_alloc_cb + * for this handle should create buffers that are multiples of 64 KiB. */ UV_UDP_RECVMMSG = 256 }; @@ -168,7 +169,8 @@ API The remaining bits can be used to set one of these flags: * `UV_UDP_RECVMMSG`: if set, and the platform supports it, :man:`recvmmsg(2)` will - be used. + be used. The :c:type:`uv_alloc_cb` for this handle should create + buffers that are multiples of 64 KiB. .. versionadded:: 1.7.0 .. versionchanged:: 1.37.0 added the `UV_UDP_RECVMMSG` flag. @@ -334,7 +336,7 @@ API :param handle: UDP handle. Should have been initialized with :c:func:`uv_udp_init_ex` as either ``AF_INET`` or ``AF_INET6``, or have been bound to an address explicitly with :c:func:`uv_udp_bind`, or - implicitly with :c:func:`uv_udp_send()` or :c:func:`uv_udp_recv_start`. + implicitly with :c:func:`uv_udp_send` or :c:func:`uv_udp_recv_start`. :param on: 1 for on, 0 for off. @@ -347,7 +349,7 @@ API :param handle: UDP handle. Should have been initialized with :c:func:`uv_udp_init_ex` as either ``AF_INET`` or ``AF_INET6``, or have been bound to an address explicitly with :c:func:`uv_udp_bind`, or - implicitly with :c:func:`uv_udp_send()` or :c:func:`uv_udp_recv_start`. + implicitly with :c:func:`uv_udp_send` or :c:func:`uv_udp_recv_start`. :param ttl: 1 through 255. @@ -360,7 +362,7 @@ API :param handle: UDP handle. Should have been initialized with :c:func:`uv_udp_init_ex` as either ``AF_INET`` or ``AF_INET6``, or have been bound to an address explicitly with :c:func:`uv_udp_bind`, or - implicitly with :c:func:`uv_udp_send()` or :c:func:`uv_udp_recv_start`. + implicitly with :c:func:`uv_udp_send` or :c:func:`uv_udp_recv_start`. :param interface_addr: interface address. @@ -373,7 +375,7 @@ API :param handle: UDP handle. Should have been initialized with :c:func:`uv_udp_init_ex` as either ``AF_INET`` or ``AF_INET6``, or have been bound to an address explicitly with :c:func:`uv_udp_bind`, or - implicitly with :c:func:`uv_udp_send()` or :c:func:`uv_udp_recv_start`. + implicitly with :c:func:`uv_udp_send` or :c:func:`uv_udp_recv_start`. :param on: 1 for on, 0 for off. @@ -386,7 +388,7 @@ API :param handle: UDP handle. Should have been initialized with :c:func:`uv_udp_init_ex` as either ``AF_INET`` or ``AF_INET6``, or have been bound to an address explicitly with :c:func:`uv_udp_bind`, or - implicitly with :c:func:`uv_udp_send()` or :c:func:`uv_udp_recv_start`. + implicitly with :c:func:`uv_udp_send` or :c:func:`uv_udp_recv_start`. :param ttl: 1 through 255. @@ -481,8 +483,8 @@ API `suggested_size` in `alloc_cb` for udp_recv is always set to the size of 1 max size dgram. .. versionchanged:: 1.35.0 added support for :man:`recvmmsg(2)` on supported platforms). - The use of this feature requires a buffer larger than - 2 * 64KB to be passed to `alloc_cb`. + The :c:type:`uv_alloc_cb` for this handle should create + buffers that are multiples of 64 KiB. .. versionchanged:: 1.37.0 :man:`recvmmsg(2)` support is no longer enabled implicitly, it must be explicitly requested by passing the `UV_UDP_RECVMMSG` flag to :c:func:`uv_udp_init_ex`. diff --git a/deps/uv/include/uv.h b/deps/uv/include/uv.h index da06345557b..bf0ccb652c9 100644 --- a/deps/uv/include/uv.h +++ b/deps/uv/include/uv.h @@ -435,7 +435,7 @@ UV_EXTERN char* uv_err_name_r(int err, char* buf, size_t buflen); /* read-only */ \ uv_req_type type; \ /* private */ \ - void* reserved[6]; \ + void* reserved[5]; \ UV_REQ_PRIVATE_FIELDS \ /* Abstract base class of all requests. */ @@ -566,6 +566,21 @@ UV_EXTERN int uv_try_write2(uv_stream_t* handle, unsigned int nbufs, uv_stream_t* send_handle); +/* + * Returns the number of bytes written by a write request. + * + * NOTE: The result is only well-defined when called from within the + * write callback (uv_write_cb). The value is undefined if called at + * other times. + * + * This is primarily useful when a write has been cancelled via uv_cancel() + * and the callback receives UV_ECANCELED status, to determine how many + * bytes were actually written before cancellation. Note that cancelled + * writes may still succeed or fail with other errors if the kernel finished + * processing the write before the cancellation took effect. + */ +UV_EXTERN size_t uv_write_nwritten(const uv_write_t* req); + /* uv_write_t is a subclass of uv_req_t. */ struct uv_write_s { UV_REQ_FIELDS @@ -705,7 +720,8 @@ enum uv_udp_flags { */ UV_UDP_REUSEPORT = 64, /* - * Indicates that recvmmsg should be used, if available. + * Indicates that recvmmsg should be used, if available. The uv_alloc_cb + * for this handle should create buffers that are multiples of 64 KiB. */ UV_UDP_RECVMMSG = 256 }; diff --git a/deps/uv/include/uv/unix.h b/deps/uv/include/uv/unix.h index c6ba419da98..4d5a9582d37 100644 --- a/deps/uv/include/uv/unix.h +++ b/deps/uv/include/uv/unix.h @@ -249,7 +249,15 @@ typedef struct { #define UV_REQ_TYPE_PRIVATE /* empty */ -#define UV_REQ_PRIVATE_FIELDS /* empty */ +struct uv__req_write_extra_s { + size_t nwritten; +}; + +#define UV_REQ_PRIVATE_FIELDS \ + union { \ + void* reserved2[1]; \ + struct uv__req_write_extra_s write_extra; \ + }; #define UV_PRIVATE_REQ_TYPES /* empty */ @@ -408,7 +416,7 @@ typedef struct { # define UV_FS_O_CREAT 0 #endif -#if defined(__linux__) && defined(__arm__) +#if defined(__linux__) && (defined(__arm__) || defined(__aarch64__)) # define UV_FS_O_DIRECT 0x10000 #elif defined(__linux__) && defined(__m68k__) # define UV_FS_O_DIRECT 0x10000 @@ -416,11 +424,9 @@ typedef struct { # define UV_FS_O_DIRECT 0x08000 #elif defined(__linux__) && defined(__powerpc__) # define UV_FS_O_DIRECT 0x20000 -#elif defined(__linux__) && defined(__s390x__) -# define UV_FS_O_DIRECT 0x04000 -#elif defined(__linux__) && defined(__x86_64__) -# define UV_FS_O_DIRECT 0x04000 -#elif defined(__linux__) && defined(__loongarch__) +#elif defined(__linux__) && defined(__sparc__) +# define UV_FS_O_DIRECT 0x100000 +#elif defined(__linux__) # define UV_FS_O_DIRECT 0x04000 #elif defined(O_DIRECT) # define UV_FS_O_DIRECT O_DIRECT diff --git a/deps/uv/include/uv/version.h b/deps/uv/include/uv/version.h index 70f071a478f..6d5934b6b2f 100644 --- a/deps/uv/include/uv/version.h +++ b/deps/uv/include/uv/version.h @@ -31,8 +31,8 @@ */ #define UV_VERSION_MAJOR 1 -#define UV_VERSION_MINOR 52 -#define UV_VERSION_PATCH 1 +#define UV_VERSION_MINOR 53 +#define UV_VERSION_PATCH 0 #define UV_VERSION_IS_RELEASE 1 #define UV_VERSION_SUFFIX "" diff --git a/deps/uv/include/uv/win.h b/deps/uv/include/uv/win.h index 7b4ebd4b7b2..5741b467fbb 100644 --- a/deps/uv/include/uv/win.h +++ b/deps/uv/include/uv/win.h @@ -279,7 +279,7 @@ typedef struct { DWORD tls_index; } uv_key_t; -#define UV_ONCE_INIT { 0, { NULL } } +#define UV_ONCE_INIT { 0, INIT_ONCE_STATIC_INIT } typedef struct uv_once_s { unsigned char unused; @@ -364,6 +364,10 @@ typedef struct { UV_WAKEUP, \ UV_SIGNAL_REQ, +struct uv__req_write_extra_s { + size_t nwritten; +}; + #define UV_REQ_PRIVATE_FIELDS \ union { \ /* Used by I/O operations */ \ @@ -376,10 +380,16 @@ typedef struct { ULONG_PTR result; /* overlapped.Internal is reused to hold the result */\ HANDLE pipeHandle; \ DWORD duplex_flags; \ - WCHAR* name; \ + WCHAR* name; \ } connect; \ } u; \ - struct uv_req_s* next_req; + /* Singly linked list of pending reqs. For non-overlapped pipes, also used \ + * to keep track of reqs no yet submitted to the thread pool */ \ + struct uv_req_s* next_req; \ + union { \ + void* reserved2[1]; \ + struct uv__req_write_extra_s write_extra; \ + }; #define UV_WRITE_PRIVATE_FIELDS \ int coalesced; \ @@ -472,7 +482,10 @@ typedef struct { #define uv_pipe_connection_fields \ uv_timer_t* eof_timer; \ - uv_write_t dummy; /* TODO: retained for ABI compat; remove this in v2.x. */ \ + /* TODO: This is here for ABI compat - remove in 2.x. */ \ + uintptr_t dummy[sizeof(uv_write_t) / sizeof(uintptr_t) - 2]; \ + uv_write_t* non_overlapped_write_active; \ + volatile HANDLE writefile_thread_handle; \ DWORD ipc_remote_pid; \ union { \ uint32_t payload_remaining; \ @@ -481,7 +494,7 @@ typedef struct { struct uv__queue ipc_xfer_queue; \ int ipc_xfer_queue_length; \ uv_write_t* non_overlapped_writes_tail; \ - CRITICAL_SECTION readfile_thread_lock; \ + CRITICAL_SECTION thread_lock; \ volatile HANDLE readfile_thread_handle; #define UV_PIPE_PRIVATE_FIELDS \ @@ -558,8 +571,7 @@ typedef struct { #define UV_ASYNC_PRIVATE_FIELDS \ struct uv_req_s async_req; \ uv_async_cb async_cb; \ - /* char to avoid alignment issues */ \ - char volatile async_sent; + int pending; #define UV_PREPARE_PRIVATE_FIELDS \ uv_prepare_t* prepare_prev; \ diff --git a/deps/uv/src/idna.c b/deps/uv/src/idna.c index 4c876df9fd1..db1edafbec3 100644 --- a/deps/uv/src/idna.c +++ b/deps/uv/src/idna.c @@ -374,7 +374,7 @@ ssize_t uv_wtf8_length_as_utf16(const char* source_ptr) { do { code_point = uv__wtf8_decode1(&source_ptr); if (code_point < 0) - return -1; + return UV_EINVAL; if (code_point > 0xFFFF) w_target_len++; w_target_len++; diff --git a/deps/uv/src/inet.c b/deps/uv/src/inet.c index 620be5a352e..9e812dfb1d9 100644 --- a/deps/uv/src/inet.c +++ b/deps/uv/src/inet.c @@ -77,7 +77,7 @@ static int inet_ntop6(const unsigned char *src, char *dst, size_t size) { * Copy the input (bytewise) array into a wordwise array. * Find the longest run of 0x00's in src[] for :: shorthanding. */ - memset(words, '\0', sizeof words); + memset(words, '\0', sizeof(words)); for (i = 0; i < (int) sizeof(struct in6_addr); i++) words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3)); best.base = -1; @@ -124,13 +124,13 @@ static int inet_ntop6(const unsigned char *src, char *dst, size_t size) { if (i == 6 && best.base == 0 && (best.len == 6 || (best.len == 7 && words[7] != 0x0001) || (best.len == 5 && words[5] == 0xffff))) { - int err = inet_ntop4(src+12, tp, sizeof tmp - (tp - tmp)); + int err = inet_ntop4(src+12, tp, sizeof(tmp) - (tp - tmp)); if (err) return err; tp += strlen(tp); break; } - tp += snprintf(tp, sizeof tmp - (tp - tmp), "%x", words[i]); + tp += snprintf(tp, sizeof(tmp) - (tp - tmp), "%x", words[i]); } /* Was it a trailing run of 0x00's? */ if (best.base != -1 && (best.base + best.len) == ARRAY_SIZE(words)) @@ -219,8 +219,8 @@ static int inet_pton6(const char *src, unsigned char *dst) { int ch, seen_xdigits; unsigned int val; - memset((tp = tmp), '\0', sizeof tmp); - endp = tp + sizeof tmp; + memset((tp = tmp), '\0', sizeof(tmp)); + endp = tp + sizeof(tmp); colonp = NULL; /* Leading :: requires some special handling. */ if (*src == ':') @@ -293,6 +293,6 @@ static int inet_pton6(const char *src, unsigned char *dst) { } if (tp != endp) return UV_EINVAL; - memcpy(dst, tmp, sizeof tmp); + memcpy(dst, tmp, sizeof(tmp)); return 0; } diff --git a/deps/uv/src/random.c b/deps/uv/src/random.c index 402678cb69b..ba7286fc0fc 100644 --- a/deps/uv/src/random.c +++ b/deps/uv/src/random.c @@ -42,7 +42,7 @@ static int uv__random(void* buf, size_t buflen) { rc = uv__random_devurandom(buf, buflen); #elif defined(__NetBSD__) rc = uv__random_sysctl(buf, buflen); -#elif defined(__FreeBSD__) || defined(__linux__) +#elif defined(__FreeBSD__) || defined(__linux__) || defined(__sun) rc = uv__random_getrandom(buf, buflen); if (rc == UV_ENOSYS) rc = uv__random_devurandom(buf, buflen); diff --git a/deps/uv/src/threadpool.c b/deps/uv/src/threadpool.c index 2a129a5d4a5..e89761daf43 100644 --- a/deps/uv/src/threadpool.c +++ b/deps/uv/src/threadpool.c @@ -401,9 +401,17 @@ int uv_cancel(uv_req_t* req) { uv_loop_t* loop; switch (req->type) { + case UV_WRITE: + if (uv__is_closing(((uv_write_t*) req)->handle)) + return UV_EBUSY; + return uv__write_cancel((uv_write_t*) req); case UV_FS: loop = ((uv_fs_t*) req)->loop; wreq = &((uv_fs_t*) req)->work_req; +#ifdef __linux__ + if (wreq->done == NULL) + return uv__iou_cancel(loop, (uv_fs_t*) req); +#endif break; case UV_GETADDRINFO: loop = ((uv_getaddrinfo_t*) req)->loop; diff --git a/deps/uv/src/unix/aix.c b/deps/uv/src/unix/aix.c index 4c0f4adb4c8..133d9c1c09d 100644 --- a/deps/uv/src/unix/aix.c +++ b/deps/uv/src/unix/aix.c @@ -773,38 +773,17 @@ int uv_fs_event_start(uv_fs_event_t* handle, const char* filename, unsigned int flags) { #ifdef HAVE_SYS_AHAFS_EVPRODS_H - int fd, rc, str_offset = 0; - char cwd[PATH_MAX]; + int fd, rc; char absolute_path[PATH_MAX]; - char readlink_cwd[PATH_MAX]; struct timeval zt; fd_set pollfd; - - /* Figure out whether filename is absolute or not */ - if (filename[0] == '\0') { - /* Missing a pathname */ + if (filename[0] == '\0') return UV_ENOENT; - } - else if (filename[0] == '/') { - /* We have absolute pathname */ - /* TODO(bnoordhuis) Check uv__strscpy() return value. */ - uv__strscpy(absolute_path, filename, sizeof(absolute_path)); - } else { - /* We have a relative pathname, compose the absolute pathname */ - snprintf(cwd, sizeof(cwd), "/proc/%lu/cwd", (unsigned long) getpid()); - rc = readlink(cwd, readlink_cwd, sizeof(readlink_cwd) - 1); - if (rc < 0) - return rc; - /* readlink does not null terminate our string */ - readlink_cwd[rc] = '\0'; - - if (filename[0] == '.' && filename[1] == '/') - str_offset = 2; - - snprintf(absolute_path, sizeof(absolute_path), "%s%s", readlink_cwd, - filename + str_offset); - } + + /* Resolve filename to a canonical, absolute path using realpath() */ + if (realpath(filename, absolute_path) == NULL) + return UV__ERR(errno); if (uv__is_ahafs_mounted() < 0) /* /aha checks failed */ return UV_ENOSYS; @@ -997,6 +976,7 @@ void uv__process_title_cleanup(void) { int uv_resident_set_memory(size_t* rss) { char pp[64]; psinfo_t psinfo; + ssize_t nread; int err; int fd; @@ -1006,11 +986,16 @@ int uv_resident_set_memory(size_t* rss) { if (fd == -1) return UV__ERR(errno); - /* FIXME(bnoordhuis) Handle EINTR. */ + do + nread = read(fd, &psinfo, sizeof(psinfo)); + while (nread == -1 && errno == EINTR); + err = UV_EINVAL; - if (read(fd, &psinfo, sizeof(psinfo)) == sizeof(psinfo)) { + if (nread == (ssize_t) sizeof(psinfo)) { *rss = (size_t)psinfo.pr_rssize * 1024; err = 0; + } else if (nread < 0) { + err = UV__ERR(errno); } uv__close(fd); diff --git a/deps/uv/src/unix/async.c b/deps/uv/src/unix/async.c index f7995fc3519..dab168da904 100644 --- a/deps/uv/src/unix/async.c +++ b/deps/uv/src/unix/async.c @@ -32,7 +32,6 @@ #include #include #include -#include /* sched_yield() */ #ifdef __linux__ #include @@ -68,7 +67,6 @@ static void uv__kqueue_runtime_detection(void) { static void uv__async_send(uv_loop_t* loop); static int uv__async_start(uv_loop_t* loop); -static void uv__cpu_relax(void); int uv_async_init(uv_loop_t* loop, uv_async_t* handle, uv_async_cb async_cb) { @@ -81,7 +79,6 @@ int uv_async_init(uv_loop_t* loop, uv_async_t* handle, uv_async_cb async_cb) { uv__handle_init(loop, (uv_handle_t*)handle, UV_ASYNC); handle->async_cb = async_cb; handle->pending = 0; - handle->u.fd = 0; /* This will be used as a busy flag. */ uv__queue_insert_tail(&loop->async_handles, &handle->queue); uv__handle_start(handle); @@ -90,63 +87,8 @@ int uv_async_init(uv_loop_t* loop, uv_async_t* handle, uv_async_cb async_cb) { } -int uv_async_send(uv_async_t* handle) { - _Atomic int* pending; - _Atomic int* busy; - - pending = (_Atomic int*) &handle->pending; - busy = (_Atomic int*) &handle->u.fd; - - /* Do a cheap read first. */ - if (atomic_load_explicit(pending, memory_order_relaxed) != 0) - return 0; - - /* Set the loop to busy. */ - atomic_fetch_add(busy, 1); - - /* Wake up the other thread's event loop. */ - if (atomic_exchange(pending, 1) == 0) - uv__async_send(handle->loop); - - /* Set the loop to not-busy. */ - atomic_fetch_add(busy, -1); - - return 0; -} - - -/* Wait for the busy flag to clear before closing. - * Only call this from the event loop thread. */ -static void uv__async_spin(uv_async_t* handle) { - _Atomic int* pending; - _Atomic int* busy; - int i; - - pending = (_Atomic int*) &handle->pending; - busy = (_Atomic int*) &handle->u.fd; - - /* Set the pending flag first, so no new events will be added by other - * threads after this function returns. */ - atomic_store(pending, 1); - - for (;;) { - /* 997 is not completely chosen at random. It's a prime number, acyclic by - * nature, and should therefore hopefully dampen sympathetic resonance. - */ - for (i = 0; i < 997; i++) { - if (atomic_load(busy) == 0) - return; - - /* Other thread is busy with this handle, spin until it's done. */ - uv__cpu_relax(); - } - - /* Yield the CPU. We may have preempted the other thread while it's - * inside the critical section and if it's running on the same CPU - * as us, we'll just burn CPU cycles until the end of our time slice. - */ - sched_yield(); - } +void uv__async_notify(uv_async_t* handle) { + uv__async_send(handle->loop); } @@ -158,8 +100,10 @@ void uv__async_close(uv_async_t* handle) { void uv__async_io(uv_loop_t* loop, uv__io_t* w, unsigned int events) { +#ifndef __linux__ char buf[1024]; ssize_t r; +#endif struct uv__queue queue; struct uv__queue* q; uv_async_t* h; @@ -167,6 +111,7 @@ void uv__async_io(uv_loop_t* loop, uv__io_t* w, unsigned int events) { assert(w == &loop->async_io_watcher); +#ifndef __linux__ #if UV__KQUEUE_EVFILT_USER for (;!kqueue_evfilt_user_support;) { #else @@ -188,6 +133,7 @@ void uv__async_io(uv_loop_t* loop, uv__io_t* w, unsigned int events) { abort(); } +#endif /* !__linux__ */ uv__queue_move(&loop->async_handles, &queue); while (!uv__queue_empty(&queue)) { @@ -197,9 +143,13 @@ void uv__async_io(uv_loop_t* loop, uv__io_t* w, unsigned int events) { uv__queue_remove(q); uv__queue_insert_tail(&loop->async_handles, q); - /* Atomically fetch and clear pending flag */ + /* Atomically clear the pending flag (bit 0) and check if it was set. + * The seq_cst (default order) synchronizes with the seq_cst CAS in + * uv_async_send, making all accesses before that call visible here and + * ensuring no access here can get reordered before this as visible to + * another thread. */ pending = (_Atomic int*) &h->pending; - if (atomic_exchange(pending, 0) == 0) + if (!(atomic_fetch_and(pending, ~1) & 1)) continue; if (h->async_cb == NULL) @@ -211,22 +161,36 @@ void uv__async_io(uv_loop_t* loop, uv__io_t* w, unsigned int events) { static void uv__async_send(uv_loop_t* loop) { - const void* buf; - ssize_t len; int fd; int r; - - buf = ""; - len = 1; - fd = loop->async_wfd; +#if !(defined(__linux__) || UV__KQUEUE_EVFILT_USER) + static char buf = '\0'; +#endif #if defined(__linux__) - if (fd == -1) { - static const uint64_t val = 1; - buf = &val; - len = sizeof(val); - fd = loop->async_io_watcher.fd; /* eventfd */ + uint64_t val; + + fd = loop->async_io_watcher.fd; /* eventfd */ + for (val = 1; /* empty */; val = 1) { + r = write(fd, &val, sizeof(uint64_t)); + if (r < 0) { + /* When EAGAIN occurs, the eventfd counter hits the maximum value of the unsigned 64-bit. + * We need to first drain the eventfd and then write again. + * + * Check out https://man7.org/linux/man-pages/man2/eventfd.2.html for details. + */ + if (errno == EAGAIN) { + /* It's ready to retry. */ + if (read(fd, &val, sizeof(uint64_t)) > 0 || errno == EAGAIN) { + continue; + } + } + /* Unknown error occurs. */ + break; + } + return; } + #elif UV__KQUEUE_EVFILT_USER struct kevent ev; @@ -238,18 +202,20 @@ static void uv__async_send(uv_loop_t* loop) { return; abort(); } -#endif +#else + fd = loop->async_wfd; do - r = write(fd, buf, len); + r = write(fd, &buf, 1); while (r == -1 && errno == EINTR); - if (r == len) + if (r == 1) return; if (r == -1) if (errno == EAGAIN || errno == EWOULDBLOCK) return; +#endif abort(); } @@ -385,9 +351,7 @@ int uv__async_fork(uv_loop_t* loop) { * behavior anyways, unless async-signal-safe, for multithreaded programs * like libuv, and nothing interesting in pthreads is async-signal-safe. */ - h->pending = 0; - /* This is the busy flag, and we just abruptly lost all other threads. */ - h->u.fd = 0; + h->pending = 0; /* Clears both the pending flag and busy counter. */ } /* Recreate these, since they still exist, but belong to the wrong pid now. */ @@ -403,18 +367,3 @@ int uv__async_fork(uv_loop_t* loop) { return uv__async_start(loop); } - - -static void uv__cpu_relax(void) { -#if defined(__i386__) || defined(__x86_64__) - __asm__ __volatile__ ("rep; nop" ::: "memory"); /* a.k.a. PAUSE */ -#elif (defined(__arm__) && __ARM_ARCH >= 7) || defined(__aarch64__) - __asm__ __volatile__ ("isb" ::: "memory"); -#elif (defined(__ppc__) || defined(__ppc64__)) && defined(__APPLE__) - __asm volatile ("" : : : "memory"); -#elif !defined(__APPLE__) && (defined(__powerpc64__) || defined(__ppc64__) || defined(__PPC64__)) - __asm__ __volatile__ ("or 1,1,1; or 2,2,2" ::: "memory"); -#elif defined(__riscv) && __riscv_xlen == 64 - __asm__ volatile(".insn 0x0100000f" ::: "memory"); /* FENCE */ -#endif -} diff --git a/deps/uv/src/unix/core.c b/deps/uv/src/unix/core.c index 73027cbc52a..03e0b4bdcfb 100644 --- a/deps/uv/src/unix/core.c +++ b/deps/uv/src/unix/core.c @@ -1027,8 +1027,22 @@ void uv__io_stop(uv_loop_t* loop, uv__io_t* w, unsigned int events) { loop->watchers[w->fd] = NULL; loop->nfds--; } + return; } - else if (uv__queue_empty(&w->watcher_queue)) + +#if !defined(__sun) + /* Short-circuit if the event mask is unchanged, like uv__io_start() does. + * Without this, stopping an event that was never started (e.g. the + * uv__io_stop(POLLOUT) in uv__drain() after a write that completed + * synchronously) needlessly re-registers the file descriptor with the + * kernel on the next loop iteration and makes uv_backend_timeout() report + * zero in the meantime. + */ + if (w->events == w->pevents) + return; +#endif + + if (uv__queue_empty(&w->watcher_queue)) uv__queue_insert_tail(&loop->watcher_queue, &w->watcher_queue); } diff --git a/deps/uv/src/unix/darwin.c b/deps/uv/src/unix/darwin.c index c26a1faccbc..7fccac5e30a 100644 --- a/deps/uv/src/unix/darwin.c +++ b/deps/uv/src/unix/darwin.c @@ -153,20 +153,23 @@ void uv_loadavg(double avg[3]) { int uv_resident_set_memory(size_t* rss) { mach_msg_type_number_t count; - task_basic_info_data_t info; + task_vm_info_data_t info; kern_return_t err; - count = TASK_BASIC_INFO_COUNT; + /* phys_footprint, not resident_size: the latter keeps counting pages + * freed with madvise(MADV_FREE_REUSABLE), i.e. it never comes down. + */ + count = TASK_VM_INFO_REV1_COUNT; err = task_info(mach_task_self(), - TASK_BASIC_INFO, + TASK_VM_INFO, (task_info_t) &info, &count); (void) &err; - /* task_info(TASK_BASIC_INFO) cannot really fail. Anything other than + /* task_info(TASK_VM_INFO) cannot really fail. Anything other than * KERN_SUCCESS implies a libuv bug. */ assert(err == KERN_SUCCESS); - *rss = info.resident_size; + *rss = info.phys_footprint; return 0; } diff --git a/deps/uv/src/unix/freebsd.c b/deps/uv/src/unix/freebsd.c index d7ac94b0c23..d2e7b57cca5 100644 --- a/deps/uv/src/unix/freebsd.c +++ b/deps/uv/src/unix/freebsd.c @@ -213,8 +213,14 @@ int uv_cpu_info(uv_cpu_info_t** cpu_infos, int* count) { model_key = "hw.model"; size = sizeof(cpuspeed); - if (sysctlbyname("hw.clockrate", &cpuspeed, &size, NULL, 0)) - return -errno; + if (sysctlbyname("hw.clockrate", &cpuspeed, &size, NULL, 0)) { + /* hw.clockrate is not available on all platforms (e.g. it is missing + * on PowerPC). Try the cpufreq(4) driver's current frequency and fall + * back to an unknown speed like on ARM instead of failing the call. */ + size = sizeof(cpuspeed); + if (sysctlbyname("dev.cpu.0.freq", &cpuspeed, &size, NULL, 0)) + cpuspeed = 0; + } #endif size = sizeof(model); diff --git a/deps/uv/src/unix/fs.c b/deps/uv/src/unix/fs.c index 90cb40d98d4..06a6986e7fb 100644 --- a/deps/uv/src/unix/fs.c +++ b/deps/uv/src/unix/fs.c @@ -167,7 +167,7 @@ static int uv__fs_close(int fd) { } -static ssize_t uv__fs_fsync(uv_fs_t* req) { +static int uv__fs_fsync(uv_fs_t* req) { #if defined(__APPLE__) /* Apple's fdatasync and fsync explicitly do NOT flush the drive write cache * to the drive platters. This is in contrast to Linux's fdatasync and fsync @@ -191,7 +191,7 @@ static ssize_t uv__fs_fsync(uv_fs_t* req) { } -static ssize_t uv__fs_fdatasync(uv_fs_t* req) { +static int uv__fs_fdatasync(uv_fs_t* req) { #if defined(__linux__) || defined(__sun) || defined(__NetBSD__) return fdatasync(req->file); #elif defined(__APPLE__) @@ -233,7 +233,7 @@ static struct timespec uv__fs_to_timespec(double time) { #endif -static ssize_t uv__fs_futime(uv_fs_t* req) { +static int uv__fs_futime(uv_fs_t* req) { #if defined(__APPLE__) \ || defined(_AIX71) \ || defined(__DragonFly__) \ @@ -263,7 +263,7 @@ static ssize_t uv__fs_futime(uv_fs_t* req) { } -static ssize_t uv__fs_mkdtemp(uv_fs_t* req) { +static int uv__fs_mkdtemp(uv_fs_t* req) { return mkdtemp((char*) req->path) ? 0 : -1; } @@ -359,7 +359,7 @@ static int uv__fs_mkstemp(uv_fs_t* req) { } -static ssize_t uv__fs_open(uv_fs_t* req) { +static int uv__fs_open(uv_fs_t* req) { #ifdef O_CLOEXEC return open(req->path, req->flags | O_CLOEXEC, req->mode); #else /* O_CLOEXEC */ @@ -388,11 +388,11 @@ static ssize_t uv__fs_open(uv_fs_t* req) { } -static ssize_t uv__preadv_or_pwritev_emul(int fd, - const struct iovec* bufs, - size_t nbufs, - off_t off, - int is_pread) { +static int uv__preadv_or_pwritev_emul(int fd, + const struct iovec* bufs, + size_t nbufs, + off_t off, + int is_pread) { ssize_t total; ssize_t r; size_t i; @@ -435,18 +435,18 @@ typedef size_t uv__iovcnt; #endif -static ssize_t uv__preadv_emul(int fd, - const struct iovec* bufs, - uv__iovcnt nbufs, - off_t off) { +static int uv__preadv_emul(int fd, + const struct iovec* bufs, + uv__iovcnt nbufs, + off_t off) { return uv__preadv_or_pwritev_emul(fd, bufs, nbufs, off, /*is_pread*/1); } -static ssize_t uv__pwritev_emul(int fd, - const struct iovec* bufs, - uv__iovcnt nbufs, - off_t off) { +static int uv__pwritev_emul(int fd, + const struct iovec* bufs, + uv__iovcnt nbufs, + off_t off) { return uv__preadv_or_pwritev_emul(fd, bufs, nbufs, off, /*is_pread*/0); } @@ -454,14 +454,14 @@ static ssize_t uv__pwritev_emul(int fd, /* The function pointer cache is an uintptr_t because _Atomic void* * doesn't work on macos/ios/etc... */ -static ssize_t uv__preadv_or_pwritev(int fd, - const struct iovec* bufs, - size_t nbufs, - off_t off, - _Atomic uintptr_t* cache, - int is_pread) { +static int uv__preadv_or_pwritev(int fd, + const struct iovec* bufs, + size_t nbufs, + off_t off, + _Atomic uintptr_t* cache, + int is_pread) { union { - ssize_t (*f)(int, const struct iovec*, uv__iovcnt, off_t); + int (*f)(int, const struct iovec*, uv__iovcnt, off_t); void* p; } u; @@ -485,7 +485,7 @@ static ssize_t uv__preadv_or_pwritev(int fd, } -static ssize_t uv__preadv(int fd, +static int uv__preadv(int fd, const struct iovec* bufs, size_t nbufs, off_t off) { @@ -494,16 +494,16 @@ static ssize_t uv__preadv(int fd, } -static ssize_t uv__pwritev(int fd, - const struct iovec* bufs, - size_t nbufs, - off_t off) { +static int uv__pwritev(int fd, + const struct iovec* bufs, + size_t nbufs, + off_t off) { static _Atomic uintptr_t cache; return uv__preadv_or_pwritev(fd, bufs, nbufs, off, &cache, /*is_pread*/0); } -static ssize_t uv__fs_read(uv_fs_t* req) { +static int uv__fs_read(uv_fs_t* req) { const struct iovec* bufs; unsigned int iovmax; size_t nbufs; @@ -520,17 +520,35 @@ static ssize_t uv__fs_read(uv_fs_t* req) { if (nbufs > iovmax) nbufs = iovmax; + /* Truncate multi-buf reads to UV__IO_MAX_BYTES total, dropping trailing bufs. */ + if (nbufs > 1) { + size_t total; + size_t n; + for (total = 0, n = 0; n < nbufs; n++) { + if (bufs[n].iov_len > UV__IO_MAX_BYTES - total) + break; + total += bufs[n].iov_len; + } + nbufs = n > 0 ? n : 1; + } + r = 0; if (off < 0) { - if (nbufs == 1) - r = read(fd, bufs->iov_base, bufs->iov_len); - else if (nbufs > 1) + if (nbufs == 1) { + r = read(fd, bufs->iov_base, + bufs->iov_len > UV__IO_MAX_BYTES ? UV__IO_MAX_BYTES : bufs->iov_len); + } else if (nbufs > 1) { r = readv(fd, bufs, nbufs); + } } else { - if (nbufs == 1) - r = pread(fd, bufs->iov_base, bufs->iov_len, off); - else if (nbufs > 1) + if (nbufs == 1) { + r = pread(fd, bufs->iov_base, + bufs->iov_len > UV__IO_MAX_BYTES ? UV__IO_MAX_BYTES : bufs->iov_len, + off); + } + else if (nbufs > 1) { r = uv__preadv(fd, bufs, nbufs, off); + } } #ifdef __PASE__ @@ -567,7 +585,7 @@ static int uv__fs_scandir_sort(const uv__dirent_t** a, const uv__dirent_t** b) { } -static ssize_t uv__fs_scandir(uv_fs_t* req) { +static int uv__fs_scandir(uv_fs_t* req) { uv__dirent_t** dents; int n; @@ -732,7 +750,7 @@ static ssize_t uv__fs_pathmax_size(const char* path) { return pathmax; } -static ssize_t uv__fs_readlink(uv_fs_t* req) { +static int uv__fs_readlink(uv_fs_t* req) { ssize_t maxlen; ssize_t len; char* buf; @@ -791,7 +809,7 @@ static ssize_t uv__fs_readlink(uv_fs_t* req) { return 0; } -static ssize_t uv__fs_realpath(uv_fs_t* req) { +static int uv__fs_realpath(uv_fs_t* req) { char* buf; char* tmp; @@ -829,7 +847,7 @@ static ssize_t uv__fs_realpath(uv_fs_t* req) { return 0; } -static ssize_t uv__fs_sendfile_emul(uv_fs_t* req) { +static int uv__fs_sendfile_emul(uv_fs_t* req) { struct pollfd pfd; int use_pread; off_t offset; @@ -1027,7 +1045,7 @@ static ssize_t uv__fs_try_copy_file_range(int in_fd, off_t* off, #endif /* __linux__ */ -static ssize_t uv__fs_sendfile(uv_fs_t* req) { +static int uv__fs_sendfile(uv_fs_t* req) { int in_fd; int out_fd; @@ -1116,7 +1134,7 @@ static ssize_t uv__fs_sendfile(uv_fs_t* req) { */ if (r == 0 || ((errno == EAGAIN || errno == EINTR) && len != 0)) { req->off += len; - return (ssize_t) len; + return len; } if (errno == EINVAL || @@ -1139,7 +1157,7 @@ static ssize_t uv__fs_sendfile(uv_fs_t* req) { } -static ssize_t uv__fs_utime(uv_fs_t* req) { +static int uv__fs_utime(uv_fs_t* req) { #if defined(__APPLE__) \ || defined(_AIX71) \ || defined(__DragonFly__) \ @@ -1174,7 +1192,7 @@ static ssize_t uv__fs_utime(uv_fs_t* req) { } -static ssize_t uv__fs_lutime(uv_fs_t* req) { +static int uv__fs_lutime(uv_fs_t* req) { #if defined(__APPLE__) \ || defined(_AIX71) \ || defined(__DragonFly__) \ @@ -1196,7 +1214,7 @@ static ssize_t uv__fs_lutime(uv_fs_t* req) { } -static ssize_t uv__fs_write(uv_fs_t* req) { +static int uv__fs_write(uv_fs_t* req) { const struct iovec* bufs; size_t nbufs; ssize_t r; @@ -1225,7 +1243,7 @@ static ssize_t uv__fs_write(uv_fs_t* req) { } -static ssize_t uv__fs_copyfile(uv_fs_t* req) { +static int uv__fs_copyfile(uv_fs_t* req) { uv_fs_t fs_req; uv_file srcfd; uv_file dstfd; @@ -1378,11 +1396,10 @@ static ssize_t uv__fs_copyfile(uv_fs_t* req) { bytes_to_send = src_statsbuf.st_size; in_offset = 0; while (bytes_to_send != 0) { - bytes_chunk = SSIZE_MAX; + bytes_chunk = UV__IO_MAX_BYTES; if (bytes_to_send < (off_t) bytes_chunk) bytes_chunk = bytes_to_send; - uv_fs_sendfile(NULL, &fs_req, dstfd, srcfd, in_offset, bytes_chunk, NULL); - bytes_written = fs_req.result; + bytes_written = uv_fs_sendfile(NULL, &fs_req, dstfd, srcfd, in_offset, bytes_chunk, NULL); uv_fs_req_cleanup(&fs_req); if (bytes_written < 0) { @@ -1630,7 +1647,7 @@ static size_t uv__fs_buf_offset(uv_buf_t* bufs, size_t size) { return offset; } -static ssize_t uv__fs_write_all(uv_fs_t* req) { +static int uv__fs_write_all(uv_fs_t* req) { unsigned int iovmax; unsigned int nbufs; uv_buf_t* bufs; @@ -1679,7 +1696,7 @@ static ssize_t uv__fs_write_all(uv_fs_t* req) { static void uv__fs_work(struct uv__work* w) { int retry_on_eintr; uv_fs_t* req; - ssize_t r; + int r; req = container_of(w, uv_fs_t, work_req); retry_on_eintr = !(req->fs_type == UV_FS_CLOSE || @@ -2147,6 +2164,8 @@ int uv_fs_sendfile(uv_loop_t* loop, req->flags = in_fd; /* hack */ req->file = out_fd; req->off = off; + if (len > UV__IO_MAX_BYTES) + return UV_EINVAL; req->bufsml[0].len = len; POST; } @@ -2214,6 +2233,9 @@ int uv_fs_write(uv_loop_t* loop, if (bufs == NULL || nbufs == 0) return UV_EINVAL; + if (uv__count_bufs(bufs, nbufs) > UV__IO_MAX_BYTES) + return UV_EINVAL; + req->file = file; req->nbufs = nbufs; diff --git a/deps/uv/src/unix/ibmi.c b/deps/uv/src/unix/ibmi.c index a534dfc5636..be12fda870a 100644 --- a/deps/uv/src/unix/ibmi.c +++ b/deps/uv/src/unix/ibmi.c @@ -174,7 +174,8 @@ static void iconv_a2e(const char* src, unsigned char dst[], size_t length) { } void init_process_title_mutex_once(void) { - uv_mutex_init(&process_title_mutex); + if (uv_mutex_init(&process_title_mutex)) + abort(); } static int get_ibmi_system_status(SSTS0200* rcvr) { diff --git a/deps/uv/src/unix/internal.h b/deps/uv/src/unix/internal.h index 80fe41f8ef6..f557f94d42d 100644 --- a/deps/uv/src/unix/internal.h +++ b/deps/uv/src/unix/internal.h @@ -525,6 +525,7 @@ int uv__statx(int dirfd, unsigned int mask, struct uv__statx* statxbuf); void uv__statx_to_stat(const struct uv__statx* statxbuf, uv_stat_t* buf); +int uv__iou_cancel(uv_loop_t* loop, uv_fs_t* req); ssize_t uv__getrandom(void* buf, size_t buflen, unsigned flags); unsigned uv__kernel_version(void); #endif @@ -576,7 +577,7 @@ int uv__get_constrained_cpu(long long* quota); #if defined(EVFILT_USER) && defined(NOTE_TRIGGER) /* EVFILT_USER is available since OS X 10.6, DragonFlyBSD 4.0, - * FreeBSD 8.1, and NetBSD 10.0. + * FreeBSD 8.1, NetBSD 10.0, and OpenBSD 7.8. * * Note that even though EVFILT_USER is defined on the current system, * it may still fail to work at runtime somehow. In that case, we fall diff --git a/deps/uv/src/unix/kqueue.c b/deps/uv/src/unix/kqueue.c index 27d28814363..9971f2abd59 100644 --- a/deps/uv/src/unix/kqueue.c +++ b/deps/uv/src/unix/kqueue.c @@ -50,12 +50,20 @@ int uv__kqueue_init(uv_loop_t* loop) { - loop->backend_fd = kqueue(); - if (loop->backend_fd == -1) + int fd; + int err; + + fd = kqueue(); + if (fd == -1) return UV__ERR(errno); - uv__cloexec(loop->backend_fd, 1); + err = uv__cloexec(fd, 1); + if (err) { + uv__close(fd); + return err; + } + loop->backend_fd = fd; return 0; } @@ -229,7 +237,16 @@ void uv__io_poll(uv_loop_t* loop, int timeout) { } if ((w->events & UV__POLLPRI) == 0 && (w->pevents & UV__POLLPRI) != 0) { +#ifdef __APPLE__ + /* + * Use EVFILT_EXCEPT+ NOTE_OOB for macOS since it defines this flag. + * FreeBSD does not. + * Refs: https://github.com/libuv/libuv/issues/3947 + */ + EV_SET(events + nevents, w->fd, EVFILT_EXCEPT, EV_ADD, NOTE_OOB, 0, 0); +#else EV_SET(events + nevents, w->fd, EV_OOBAND, EV_ADD, 0, 0, 0); +#endif if (++nevents == ARRAY_SIZE(events)) { if (kevent(loop->backend_fd, events, nevents, NULL, 0, NULL)) @@ -369,7 +386,12 @@ void uv__io_poll(uv_loop_t* loop, int timeout) { revents |= UV__POLLRDHUP; } +#ifdef __APPLE__ + /* Match EVFILT_EXCEPT used above for macOS. */ + if (ev->filter == EVFILT_EXCEPT) { +#else if (ev->filter == EV_OOBAND) { +#endif if (w->pevents & UV__POLLPRI) revents |= UV__POLLPRI; else diff --git a/deps/uv/src/unix/linux.c b/deps/uv/src/unix/linux.c index 110964884dd..19a63af02f7 100644 --- a/deps/uv/src/unix/linux.c +++ b/deps/uv/src/unix/linux.c @@ -27,6 +27,7 @@ #include "internal.h" #include +#include #include #include /* offsetof */ #include @@ -56,6 +57,14 @@ #include #include +/* android ndk workaround */ +#ifndef LLONG_MAX +#define LLONG_MAX 9223372036854775807LL +#endif +#ifndef LLONG_MIN +#define LLONG_MIN (-9223372036854775807LL - 1) +#endif + #ifndef __NR_io_uring_setup # define __NR_io_uring_setup 425 #endif @@ -129,6 +138,10 @@ enum { UV__IORING_SETUP_NO_SQARRAY = 0x10000u, }; +enum { + UV__IORING_REGISTER_SYNC_CANCEL = 24, +}; + enum { UV__IORING_FEAT_SINGLE_MMAP = 1u, UV__IORING_FEAT_NODROP = 2u, @@ -250,6 +263,19 @@ STATIC_ASSERT(40 + 40 + 40 == sizeof(struct uv__io_uring_params)); STATIC_ASSERT(40 == offsetof(struct uv__io_uring_params, sq_off)); STATIC_ASSERT(80 == offsetof(struct uv__io_uring_params, cq_off)); +struct uv__io_uring_sync_cancel_reg { + uint64_t addr; + int32_t fd; + uint32_t flags; + struct { + int64_t tv_sec; + int64_t tv_nsec; + } timeout; + uint64_t reserved[4]; +}; + +STATIC_ASSERT(64 == sizeof(struct uv__io_uring_sync_cancel_reg)); + STATIC_ASSERT(EPOLL_CTL_ADD < 4); STATIC_ASSERT(EPOLL_CTL_DEL < 4); STATIC_ASSERT(EPOLL_CTL_MOD < 4); @@ -451,6 +477,35 @@ int uv__io_uring_register(int fd, unsigned opcode, void* arg, unsigned nargs) { return syscall(__NR_io_uring_register, fd, opcode, arg, nargs); } +int uv__iou_cancel(uv_loop_t* loop, uv_fs_t* req) { + struct uv__io_uring_sync_cancel_reg reg; + struct uv__iou* iou; + int rc; + + if (uv__kernel_version() < /* 6.0 */0x060000) + return UV_EBUSY; + + iou = &uv__get_internal_fields(loop)->iou; + if (iou->ringfd < 0) + return UV_EBUSY; + + memset(®, 0, sizeof(reg)); + reg.addr = (uintptr_t) req; + reg.fd = -1; + reg.timeout.tv_sec = -1; + reg.timeout.tv_nsec = -1; + + rc = uv__io_uring_register(iou->ringfd, + UV__IORING_REGISTER_SYNC_CANCEL, + ®, + 1); + + if (rc == 0) + return 0; + + return UV_EBUSY; +} + static int uv__use_io_uring(uint32_t flags) { #if defined(__ANDROID_API__) @@ -1159,6 +1214,21 @@ static void uv__iou_fs_statx_post(uv_fs_t* req) { } +static void uv__iou_fs_cleanup_fallback(uv_fs_t* req) { + switch (req->fs_type) { + /* Free statxbuf */ + case UV_FS_FSTAT: + case UV_FS_LSTAT: + case UV_FS_STAT: + uv__free(req->ptr); + req->ptr = NULL; + break; + default: + break; + } +} + + static void uv__poll_io_uring(uv_loop_t* loop, struct uv__iou* iou) { struct uv__io_uring_cqe* cqe; struct uv__io_uring_cqe* e; @@ -1189,6 +1259,7 @@ static void uv__poll_io_uring(uv_loop_t* loop, struct uv__iou* iou) { /* If the op is not supported by the kernel retry using the thread pool */ if (e->res == -EOPNOTSUPP) { + uv__iou_fs_cleanup_fallback(req); uv__fs_post(loop, req); continue; } @@ -1412,6 +1483,12 @@ void uv__io_poll(uv_loop_t* loop, int timeout) { w->events = w->pevents; e.events = w->pevents; + if (w == &loop->async_io_watcher) + /* Enable edge-triggered mode on async_io_watcher(eventfd), + * so that we're able to eliminate the overhead of reading + * the eventfd via system call on each event loop wakeup. + */ + e.events |= EPOLLET; e.data.fd = w->fd; fd = w->fd; @@ -1516,25 +1593,6 @@ void uv__io_poll(uv_loop_t* loop, int timeout) { */ pe->events &= w->pevents | POLLERR | POLLHUP; - /* Work around an epoll quirk where it sometimes reports just the - * EPOLLERR or EPOLLHUP event. In order to force the event loop to - * move forward, we merge in the read/write events that the watcher - * is interested in; uv__read() and uv__write() will then deal with - * the error or hangup in the usual fashion. - * - * Note to self: happens when epoll reports EPOLLIN|EPOLLHUP, the user - * reads the available data, calls uv_read_stop(), then sometime later - * calls uv_read_start() again. By then, libuv has forgotten about the - * hangup and the kernel won't report EPOLLIN again because there's - * nothing left to read. If anything, libuv is to blame here. The - * current hack is just a quick bandaid; to properly fix it, libuv - * needs to remember the error/hangup event. We should get that for - * free when we switch over to edge-triggered I/O. - */ - if (pe->events == POLLERR || pe->events == POLLHUP) - pe->events |= - w->pevents & (POLLIN | POLLOUT | UV__POLLRDHUP | UV__POLLPRI); - if (pe->events != 0) { /* Run signal watchers last. This also affects child process watchers * because those are implemented in terms of signal watchers. @@ -1867,8 +1925,13 @@ int uv_cpu_info(uv_cpu_info_t** ci, int* count) { continue; n++; + /* Use scaling_max_freq, not scaling_cur_freq: reading the latter takes + * ~20ms per core on some AMD CPUs because the ACPI cpufreq driver does + * a slow SMM round-trip on every read; scaling_max_freq is a plain + * policy value and, unlike cpuinfo_cur_freq, is world-readable. + */ snprintf(buf, sizeof(buf), - "/sys/devices/system/cpu/cpu%u/cpufreq/scaling_cur_freq", cpu); + "/sys/devices/system/cpu/cpu%u/cpufreq/scaling_max_freq", cpu); fp = uv__open_file(buf); if (fp == NULL) diff --git a/deps/uv/src/unix/netbsd.c b/deps/uv/src/unix/netbsd.c index 37d962c4b10..7123d70af67 100644 --- a/deps/uv/src/unix/netbsd.c +++ b/deps/uv/src/unix/netbsd.c @@ -215,8 +215,10 @@ int uv_cpu_info(uv_cpu_info_t** cpu_infos, int* count) { if (cp_times == NULL) return UV_ENOMEM; - if (sysctlbyname("kern.cp_time", cp_times, &size, NULL, 0)) + if (sysctlbyname("kern.cp_time", cp_times, &size, NULL, 0)) { + uv__free(cp_times); return UV__ERR(errno); + } *cpu_infos = uv__malloc(numcpus * sizeof(**cpu_infos)); if (!(*cpu_infos)) { diff --git a/deps/uv/src/unix/openbsd.c b/deps/uv/src/unix/openbsd.c index bc66316955b..1b226db0485 100644 --- a/deps/uv/src/unix/openbsd.c +++ b/deps/uv/src/unix/openbsd.c @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -35,6 +36,24 @@ #include #include +typedef int (*uv__getexecpath_cb)(char *, size_t); + +static uv__getexecpath_cb uv__getexecpath; +static uv_once_t once = UV_ONCE_INIT; + +static void uv__getexecpath_init_once(void) { + uv__getexecpath = + (uv__getexecpath_cb) dlsym(RTLD_DEFAULT, "getexecpath"); +} + +static int uv__getexecpath_init(void) { + uv_once(&once, uv__getexecpath_init_once); + + if (uv__getexecpath == NULL) + return UV_ENOSYS; + + return 0; +} int uv__platform_loop_init(uv_loop_t* loop) { return uv__kqueue_init(loop); @@ -59,9 +78,20 @@ void uv_loadavg(double avg[3]) { int uv_exepath(char* buffer, size_t* size) { + char path[PATH_MAX]; + if (buffer == NULL || size == NULL || *size == 0) return UV_EINVAL; + if (uv__getexecpath_init() == 0) { + if (uv__getexecpath(path, sizeof path)) + return UV__ERR(errno); + + strlcpy(buffer, path, *size); + *size = strlen(buffer); + return 0; + } + if (uv_saved_argv0 == NULL) return UV_EINVAL; @@ -204,7 +234,7 @@ int uv_cpu_info(uv_cpu_info_t** cpu_infos, int* count) { error: *count = 0; for (j = 0; j < i; j++) - uv__free((*cpu_infos)[j].model); + uv__free((void*)(*cpu_infos)[j].model); uv__free(*cpu_infos); *cpu_infos = NULL; diff --git a/deps/uv/src/unix/pipe.c b/deps/uv/src/unix/pipe.c index eb03a47cccb..1754ddf38b4 100644 --- a/deps/uv/src/unix/pipe.c +++ b/deps/uv/src/unix/pipe.c @@ -29,6 +29,21 @@ #include #include +union uv__sockaddr_un { + struct sockaddr sa; + struct uv__sockaddr_un_path { + char pad[offsetof(struct sockaddr_un, sun_path)]; + /* FreeBSD advertises SOCK_MAXADDRLEN but nevertheless clamps the + * path length to sizeof(sun_path). + */ +#if defined(SOCK_MAXADDRLEN) && !defined(__FreeBSD__) + char path[SOCK_MAXADDRLEN - offsetof(struct sockaddr_un, sun_path)]; +#else + char path[sizeof(((struct sockaddr_un*)0)->sun_path)]; +#endif + } up; +}; + /* Does the file path contain embedded nul bytes? */ static int includes_invalid_nul(const char *s, size_t n) { @@ -64,7 +79,7 @@ int uv_pipe_bind2(uv_pipe_t* handle, const char* name, size_t namelen, unsigned int flags) { - struct sockaddr_un saddr; + union uv__sockaddr_un saddr; char* pipe_fname; int sockfd; int err; @@ -90,12 +105,12 @@ int uv_pipe_bind2(uv_pipe_t* handle, return UV_EINVAL; if (flags & UV_PIPE_NO_TRUNCATE) - if (namelen > sizeof(saddr.sun_path)) + if (namelen > sizeof(saddr.up.path)) return UV_EINVAL; /* Truncate long paths. Documented behavior. */ - if (namelen > sizeof(saddr.sun_path)) - namelen = sizeof(saddr.sun_path); + if (namelen > sizeof(saddr.up.path)) + namelen = sizeof(saddr.up.path); /* Already bound? */ if (uv__stream_fd(handle) >= 0) @@ -125,10 +140,10 @@ int uv_pipe_bind2(uv_pipe_t* handle, sockfd = err; memset(&saddr, 0, sizeof saddr); - memcpy(&saddr.sun_path, name, namelen); - saddr.sun_family = AF_UNIX; + memcpy(saddr.up.path, name, namelen); + saddr.sa.sa_family = AF_UNIX; - if (bind(sockfd, (struct sockaddr*)&saddr, addrlen)) { + if (bind(sockfd, &saddr.sa, addrlen)) { err = UV__ERR(errno); /* Convert ENOENT to EACCES for compatibility with Windows. */ if (err == UV_ENOENT) @@ -257,7 +272,7 @@ int uv_pipe_connect2(uv_connect_t* req, size_t namelen, unsigned int flags, uv_connect_cb cb) { - struct sockaddr_un saddr; + union uv__sockaddr_un saddr; int new_sock; int err; int r; @@ -276,12 +291,12 @@ int uv_pipe_connect2(uv_connect_t* req, return UV_EINVAL; if (flags & UV_PIPE_NO_TRUNCATE) - if (namelen > sizeof(saddr.sun_path)) + if (namelen > sizeof(saddr.up.path)) return UV_EINVAL; /* Truncate long paths. Documented behavior. */ - if (namelen > sizeof(saddr.sun_path)) - namelen = sizeof(saddr.sun_path); + if (namelen > sizeof(saddr.up.path)) + namelen = sizeof(saddr.up.path); new_sock = (uv__stream_fd(handle) == -1); @@ -293,8 +308,8 @@ int uv_pipe_connect2(uv_connect_t* req, } memset(&saddr, 0, sizeof saddr); - memcpy(&saddr.sun_path, name, namelen); - saddr.sun_family = AF_UNIX; + memcpy(saddr.up.path, name, namelen); + saddr.sa.sa_family = AF_UNIX; if (*name == '\0') addrlen = offsetof(struct sockaddr_un, sun_path) + namelen; @@ -355,7 +370,7 @@ static int uv__pipe_getsockpeername(const uv_pipe_t* handle, #else static const int is_linux = 0; #endif - struct sockaddr_un sa; + union uv__sockaddr_un sa; socklen_t addrlen; size_t slop; char* p; @@ -376,15 +391,15 @@ static int uv__pipe_getsockpeername(const uv_pipe_t* handle, } slop = 1; - if (is_linux && sa.sun_path[0] == '\0') { + if (is_linux && sa.up.path[0] == '\0') { /* Linux abstract namespace. Not zero-terminated. */ slop = 0; addrlen -= offsetof(struct sockaddr_un, sun_path); } else { - p = memchr(sa.sun_path, '\0', sizeof(sa.sun_path)); + p = memchr(sa.up.path, '\0', sizeof(sa.up.path)); if (p == NULL) - p = ARRAY_END(sa.sun_path); - addrlen = p - sa.sun_path; + p = ARRAY_END(sa.up.path); + addrlen = p - sa.up.path; } if ((size_t)addrlen + slop > *size) { @@ -392,7 +407,7 @@ static int uv__pipe_getsockpeername(const uv_pipe_t* handle, return UV_ENOBUFS; } - memcpy(buffer, sa.sun_path, addrlen); + memcpy(buffer, sa.up.path, addrlen); *size = addrlen; /* only null-terminate if it's not an abstract socket */ diff --git a/deps/uv/src/unix/poll.c b/deps/uv/src/unix/poll.c index 60c3c20096f..21735020654 100644 --- a/deps/uv/src/unix/poll.c +++ b/deps/uv/src/unix/poll.c @@ -60,6 +60,19 @@ void uv__poll_io(uv_loop_t* loop, uv__io_t* w, unsigned int events) { if (events & UV__POLLRDHUP) pevents |= UV_DISCONNECT; + /* On error or hangup, mix in events the user is interested in so the + * appropriate read/write callbacks are invoked. */ + if (events & (POLLERR | POLLHUP)) { + if (w->pevents & POLLIN) + pevents |= UV_READABLE; + if (w->pevents & UV__POLLPRI) + pevents |= UV_PRIORITIZED; + if (w->pevents & POLLOUT) + pevents |= UV_WRITABLE; + if (w->pevents & UV__POLLRDHUP) + pevents |= UV_DISCONNECT; + } + handle->poll_cb(handle, 0, pevents); } diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c index 539e7d9417d..5d8d6fa2b66 100644 --- a/deps/uv/src/unix/process.c +++ b/deps/uv/src/unix/process.c @@ -33,15 +33,26 @@ #include #include #include -#include +#include +#include +#include + +#if defined(__PASE__) +#define _PATH_DEFPATH "/QOpenSys/pkgs/bin:/QOpenSys/usr/bin:/usr/bin" +#elif defined(_AIX) +#define _PATH_DEFPATH "/opt/freeware/bin:/usr/bin:/bin" +#endif +/* Some platforms (e.g. Solaris) do not define this in . */ +#ifndef _PATH_DEFPATH +#define _PATH_DEFPATH "/usr/bin:/bin" +#endif +#ifndef NAME_MAX +#define NAME_MAX 255 +#endif #if defined(__APPLE__) -# include -# include # include -# include # include -# include # include # include # define environ (*_NSGetEnviron()) @@ -53,6 +64,9 @@ #else extern char **environ; +#ifndef POSIX_SPAWN_SETSID +# define POSIX_SPAWN_SETSID 0 +#endif #endif #if defined(__linux__) || \ @@ -70,6 +84,15 @@ extern char **environ; #define UV_USE_SIGCHLD #endif +static uv_once_t posix_spawn_init_once = UV_ONCE_INIT; +static int posix_spawn_can_use_setsid; +static volatile int posix_spawn_works; + +static struct uv__posix_spawn_fncs_s { + struct { + int (*addchdir)(posix_spawn_file_actions_t *, const char *); + } file_actions; +} posix_spawn_fncs; #ifdef UV_USE_SIGCHLD static void uv__chld(uv_signal_t* handle, int signum) { @@ -145,6 +168,7 @@ void uv__wait_children(uv_loop_t* loop) { } assert(pid == process->pid); + process->flags |= UV_HANDLE_ESRCH; /* pid is no longer valid (or unique) */ process->status = status; uv__queue_remove(&process->queue); uv__queue_insert_tail(&pending, &process->queue); @@ -185,6 +209,23 @@ void uv__wait_children(uv_loop_t* loop) { * avoided. Since this isn't called on those targets, the function * doesn't even need to be defined for them. */ +/* Raise a socket buffer to at least `size` bytes but never shrink it: the + * defaults differ wildly between platforms (8 KiB on macOS, ~208 KiB on Linux) + * and a smaller buffer means more wake-ups per bulk transfer over stdio. + */ +static void uv__process_stdio_bufsize(int fd, int opt, int size) { + socklen_t len; + int cur; + + len = sizeof(cur); + if (getsockopt(fd, SOL_SOCKET, opt, &cur, &len)) + cur = 0; + + if (cur < size) + setsockopt(fd, SOL_SOCKET, opt, &size, sizeof(size)); +} + + static int uv__process_init_stdio(uv_stdio_container_t* container, int fds[2]) { int mask; int fd; @@ -208,8 +249,8 @@ static int uv__process_init_stdio(uv_stdio_container_t* container, int fds[2]) { if (ret == 0) for (i = 0; i < 2; i++) { - setsockopt(fds[i], SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)); - setsockopt(fds[i], SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)); + uv__process_stdio_bufsize(fds[i], SO_RCVBUF, size); + uv__process_stdio_bufsize(fds[i], SO_SNDBUF, size); } } @@ -291,6 +332,7 @@ static void uv__process_child_init(const uv_process_options_t* options, sigset_t signewset; int close_fd; int use_fd; + int err; int fd; int n; @@ -333,9 +375,9 @@ static void uv__process_child_init(const uv_process_options_t* options, if (pipes[fd][1] == -1) uv__write_errno(error_fd); #ifndef F_DUPFD_CLOEXEC /* POSIX 2008 */ - n = uv__cloexec(pipes[fd][1], 1); - if (n) - uv__write_int(error_fd, n); + err = uv__cloexec(pipes[fd][1], 1); + if (err) + uv__write_int(error_fd, err); #endif } @@ -360,9 +402,9 @@ static void uv__process_child_init(const uv_process_options_t* options, if (fd == use_fd) { if (close_fd == -1) { - n = uv__cloexec(use_fd, 0); - if (n) - uv__write_int(error_fd, n); + err = uv__cloexec(use_fd, 0); + if (err) + uv__write_int(error_fd, err); } } else { @@ -372,8 +414,11 @@ static void uv__process_child_init(const uv_process_options_t* options, if (fd == -1) uv__write_errno(error_fd); - if (fd <= 2 && close_fd == -1) - uv__nonblock_fcntl(fd, 0); + if (fd <= 2 && close_fd == -1) { + err = uv__nonblock_fcntl(fd, 0); + if (err) + uv__write_int(error_fd, err); + } if (close_fd >= stdio_count) uv__close(close_fd); @@ -418,25 +463,6 @@ static void uv__process_child_init(const uv_process_options_t* options, #if defined(__APPLE__) -typedef struct uv__posix_spawn_fncs_tag { - struct { - int (*addchdir_np)(const posix_spawn_file_actions_t *, const char *); - } file_actions; -} uv__posix_spawn_fncs_t; - - -static uv_once_t posix_spawn_init_once = UV_ONCE_INIT; -static uv__posix_spawn_fncs_t posix_spawn_fncs; -static int posix_spawn_can_use_setsid; - - -static void uv__spawn_init_posix_spawn_fncs(void) { - /* Try to locate all non-portable functions at runtime */ - posix_spawn_fncs.file_actions.addchdir_np = - dlsym(RTLD_DEFAULT, "posix_spawn_file_actions_addchdir_np"); -} - - static void uv__spawn_init_can_use_setsid(void) { int which[] = {CTL_KERN, KERN_OSRELEASE}; unsigned major; @@ -455,20 +481,59 @@ static void uv__spawn_init_can_use_setsid(void) { posix_spawn_can_use_setsid = (major >= 19); /* macOS Catalina */ } +#endif static void uv__spawn_init_posix_spawn(void) { - /* Init handles to all potentially non-defined functions */ - uv__spawn_init_posix_spawn_fncs(); +#if defined(__OpenBSD__) + /* Always use fork(). Its posix_spawn() works different from other + * Unices in that it returns 0 instead of EACCES or ENOENT for paths + * that don't exist. See https://github.com/libuv/libuv/issues/5240. + */ +#else +#if !defined(__linux__) && !defined(_AIX) && !defined(__PASE__) + posix_spawn_works = 1; +#elif !defined(__ANDROID__) + pid_t pid; + int status; + + /* Probe whether vfork()/clone(CLONE_VM) correctly shares the address space, + * i.e. a write by the child before _exit() is visible to the parent once it + * resumes. On Linux vfork() is equivalent to + * clone(CLONE_VM|CLONE_VFORK|SIGCHLD). On QEMU and WSL1, CLONE_VM is broken, + * resulting in glibc errors if we try to use posix_spawn(). */ + posix_spawn_works = 0; + pid = vfork(); + if (pid == 0) { + posix_spawn_works = 1; + _exit(0); + } + if (pid > 0) + waitpid(pid, &status, 0); +#endif - /* Init feature detection for POSIX_SPAWN_SETSID flag */ + /* Try to locate all new functions at runtime. + * Expected on macOS, glibc, and musl. */ + posix_spawn_fncs.file_actions.addchdir = + dlsym(RTLD_DEFAULT, "posix_spawn_file_actions_addchdir"); + if (posix_spawn_fncs.file_actions.addchdir == NULL) + posix_spawn_fncs.file_actions.addchdir = + dlsym(RTLD_DEFAULT, "posix_spawn_file_actions_addchdir_np"); + +#ifdef __APPLE__ + /* Init feature detection for POSIX_SPAWN_SETSID flag. */ uv__spawn_init_can_use_setsid(); +#elif POSIX_SPAWN_SETSID != 0 + /* Otherwise, if SETSID is defined, we can use it + * (added in glibc 2.26 circa 2017). */ + posix_spawn_can_use_setsid = 1; +#endif +#endif } static int uv__spawn_set_posix_spawn_attrs( posix_spawnattr_t* attrs, - const uv__posix_spawn_fncs_t* posix_spawn_fncs, const uv_process_options_t* options) { int err; unsigned int flags; @@ -490,18 +555,17 @@ static int uv__spawn_set_posix_spawn_attrs( } /* Set flags for spawn behavior - * 1) POSIX_SPAWN_CLOEXEC_DEFAULT: (Apple Extension) All descriptors in the - * parent will be treated as if they had been created with O_CLOEXEC. The - * only fds that will be passed on to the child are those manipulated by - * the file actions - * 2) POSIX_SPAWN_SETSIGDEF: Signals mentioned in spawn-sigdefault in the + * 1) POSIX_SPAWN_SETSIGDEF: Signals mentioned in spawn-sigdefault in the * spawn attributes will be reset to behave as their default - * 3) POSIX_SPAWN_SETSIGMASK: Signal mask will be set to the value of + * 2) POSIX_SPAWN_SETSIGMASK: Signal mask will be set to the value of * spawn-sigmask in attributes - * 4) POSIX_SPAWN_SETSID: Make the process a new session leader if a detached - * session was requested. */ - flags = POSIX_SPAWN_CLOEXEC_DEFAULT | - POSIX_SPAWN_SETSIGDEF | + * 3) POSIX_SPAWN_SETSID: Make the process a new session leader if a detached + * session was requested. + * 4) POSIX_SPAWN_CLOEXEC_DEFAULT: (Apple Extension) All descriptors in the + * parent will be treated as if they had been created with O_CLOEXEC. The + * only fds that will be passed on to the child are those manipulated by + * the file actions */ + flags = POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK; if (options->flags & UV_PROCESS_DETACHED) { /* If running on a version of macOS where this flag is not supported, @@ -514,6 +578,9 @@ static int uv__spawn_set_posix_spawn_attrs( flags |= POSIX_SPAWN_SETSID; } +#ifdef __APPLE__ + flags |= POSIX_SPAWN_CLOEXEC_DEFAULT; +#endif err = posix_spawnattr_setflags(attrs, flags); if (err != 0) goto error; @@ -540,7 +607,6 @@ static int uv__spawn_set_posix_spawn_attrs( static int uv__spawn_set_posix_spawn_file_actions( posix_spawn_file_actions_t* actions, - const uv__posix_spawn_fncs_t* posix_spawn_fncs, const uv_process_options_t* options, int stdio_count, int (*pipes)[2]) { @@ -557,12 +623,12 @@ static int uv__spawn_set_posix_spawn_file_actions( /* Set the current working directory if requested */ if (options->cwd != NULL) { - if (posix_spawn_fncs->file_actions.addchdir_np == NULL) { + if (posix_spawn_fncs.file_actions.addchdir == NULL) { err = ENOSYS; goto error; } - err = posix_spawn_fncs->file_actions.addchdir_np(actions, options->cwd); + err = posix_spawn_fncs.file_actions.addchdir(actions, options->cwd); if (err != 0) goto error; } @@ -575,8 +641,16 @@ static int uv__spawn_set_posix_spawn_file_actions( * stdout and stderr go to the same fd, which was not the intention. */ for (fd = 0; fd < stdio_count; fd++) { use_fd = pipes[fd][1]; +#if defined(__APPLE__) || defined(__linux__) if (use_fd < 0 || use_fd >= fd) continue; +#else + /* The behavior of posix_spawn_file_actions_adddup2 may be undefined if + * use_fd==fd, so we do this extra little dance to copy it up and back, on + * platforms where we aren't sure if it works. */ + if (use_fd < 0 || use_fd > fd) + continue; +#endif use_fd = stdio_count; for (fd2 = 0; fd2 < stdio_count; fd2++) { /* If we were not setting POSIX_SPAWN_CLOEXEC_DEFAULT, we would need to @@ -618,9 +692,11 @@ static int uv__spawn_set_posix_spawn_file_actions( } } +#ifdef __APPLE__ if (fd == use_fd) err = posix_spawn_file_actions_addinherit_np(actions, fd); else +#endif err = posix_spawn_file_actions_adddup2(actions, use_fd, fd); assert(err != ENOSYS); if (err != 0) @@ -682,7 +758,6 @@ static int uv__spawn_resolve_and_spawn(const uv_process_options_t* options, const char *p; const char *z; const char *path; - size_t l; size_t k; int err; int seen_eacces; @@ -728,19 +803,16 @@ static int uv__spawn_resolve_and_spawn(const uv_process_options_t* options, if (k > NAME_MAX) return ENAMETOOLONG; - l = strnlen(path, PATH_MAX - 1) + 1; - - for (p = path;; p = z) { + p = path; + do { /* Compose the new process file from the entry in the PATH * environment variable and the actual file name */ - char b[PATH_MAX + NAME_MAX]; + char b[PATH_MAX + NAME_MAX + 1]; z = strchr(p, ':'); if (!z) z = p + strlen(p); - if ((size_t)(z - p) >= l) { - if (!*z++) - break; - + if ((size_t)(z - p) >= PATH_MAX) { + p = z + 1; continue; } memcpy(b, p, z - p); @@ -764,10 +836,8 @@ static int uv__spawn_resolve_and_spawn(const uv_process_options_t* options, default: return err; } - - if (!*z++) - break; - } + p = z + 1; + } while (*z == ':'); if (seen_eacces) return EACCES; @@ -776,22 +846,24 @@ static int uv__spawn_resolve_and_spawn(const uv_process_options_t* options, static int uv__spawn_and_init_child_posix_spawn( + uv_loop_t* loop, const uv_process_options_t* options, int stdio_count, int (*pipes)[2], - pid_t* pid, - const uv__posix_spawn_fncs_t* posix_spawn_fncs) { + pid_t* pid) { int err; posix_spawnattr_t attrs; posix_spawn_file_actions_t actions; - err = uv__spawn_set_posix_spawn_attrs(&attrs, posix_spawn_fncs, options); + if (!posix_spawn_works) + return UV_ENOSYS; + + err = uv__spawn_set_posix_spawn_attrs(&attrs, options); if (err != 0) goto error; /* This may mutate pipes. */ err = uv__spawn_set_posix_spawn_file_actions(&actions, - posix_spawn_fncs, options, stdio_count, pipes); @@ -800,12 +872,23 @@ static int uv__spawn_and_init_child_posix_spawn( goto error; } +#ifndef __APPLE__ + /* Acquire write lock to prevent opening new fds in worker threads. + * Unnecessary on Apple, since we set POSIX_SPAWN_CLOEXEC_DEFAULT. */ + uv_rwlock_wrlock(&loop->cloexec_lock); +#endif + /* Try to spawn options->file resolving in the provided environment - * if any */ + * if any. */ err = uv__spawn_resolve_and_spawn(options, &attrs, &actions, pid); assert(err != ENOSYS); - /* Destroy the actions/attributes */ +#ifndef __APPLE__ + /* Release lock in parent process. */ + uv_rwlock_wrunlock(&loop->cloexec_lock); +#endif + + /* Destroy the actions/attributes. */ (void) posix_spawn_file_actions_destroy(&actions); (void) posix_spawnattr_destroy(&attrs); @@ -814,7 +897,7 @@ static int uv__spawn_and_init_child_posix_spawn( * already destroyed, only the happy path requires cleanup */ return UV__ERR(err); } -#endif + static int uv__spawn_and_init_child_fork(const uv_process_options_t* options, int stdio_count, @@ -869,36 +952,21 @@ static int uv__spawn_and_init_child( int exec_errorno; ssize_t r; -#if defined(__APPLE__) uv_once(&posix_spawn_init_once, uv__spawn_init_posix_spawn); - /* Special child process spawn case for macOS Big Sur (11.0) onwards - * - * Big Sur introduced a significant performance degradation on a call to - * fork/exec when the process has many pages mmaped in with MAP_JIT, like, say - * a javascript interpreter. Electron-based applications, for example, - * are impacted; though the magnitude of the impact depends on how much the - * app relies on subprocesses. - * - * On macOS, though, posix_spawn is implemented in a way that does not - * exhibit the problem. This block implements the forking and preparation - * logic with posix_spawn and its related primitives. It also takes advantage of - * the macOS extension POSIX_SPAWN_CLOEXEC_DEFAULT that makes impossible to - * leak descriptors to the child process. */ - err = uv__spawn_and_init_child_posix_spawn(options, + /* Calling posix_spawn is considerably faster, if it supports the given + * options. The posix_spawn flow will return UV_ENOSYS if any of the + * posix_spawn_x_np non-standard functions is both _needed_ and _undefined_. + * In those cases, default back to the fork/execve strategy. For all other + * errors, just fail. */ + err = uv__spawn_and_init_child_posix_spawn(loop, + options, stdio_count, pipes, - pid, - &posix_spawn_fncs); - - /* The posix_spawn flow will return UV_ENOSYS if any of the posix_spawn_x_np - * non-standard functions is both _needed_ and _undefined_. In those cases, - * default back to the fork/execve strategy. For all other errors, just fail. */ + pid); if (err != UV_ENOSYS) return err; -#endif - /* This pipe is used by the parent to wait until * the child has called `execve()`. We need this * to avoid the following race condition: @@ -923,12 +991,12 @@ static int uv__spawn_and_init_child( if (err) return err; - /* Acquire write lock to prevent opening new fds in worker threads */ + /* Acquire write lock to prevent opening new fds in worker threads. */ uv_rwlock_wrlock(&loop->cloexec_lock); err = uv__spawn_and_init_child_fork(options, stdio_count, pipes, signal_pipe[1], pid); - /* Release lock in parent process */ + /* Release lock in parent process. */ uv_rwlock_wrunlock(&loop->cloexec_lock); uv__close(signal_pipe[1]); @@ -963,11 +1031,16 @@ static int uv__spawn_and_init_child( } #endif /* ISN'T TARGET_OS_TV || TARGET_OS_WATCH */ + int uv_spawn(uv_loop_t* loop, uv_process_t* process, const uv_process_options_t* options) { #if defined(__APPLE__) && (TARGET_OS_TV || TARGET_OS_WATCH) /* fork is marked __WATCHOS_PROHIBITED __TVOS_PROHIBITED. */ + uv__handle_init(loop, (uv_handle_t*)process, UV_PROCESS); + QUEUE_INIT(&process->queue); + process->status = 0; + process->pid = 0; return UV_ENOSYS; #else int pipes_storage[8][2]; @@ -991,6 +1064,7 @@ int uv_spawn(uv_loop_t* loop, uv__handle_init(loop, (uv_handle_t*)process, UV_PROCESS); uv__queue_init(&process->queue); process->status = 0; + process->pid = 0; stdio_count = options->stdio_count; if (stdio_count < 3) @@ -1095,6 +1169,8 @@ int uv_spawn(uv_loop_t* loop, int uv_process_kill(uv_process_t* process, int signum) { + if (process->flags & UV_HANDLE_ESRCH) + return UV_ESRCH; return uv_kill(process->pid, signum); } @@ -1115,6 +1191,9 @@ int uv_kill(int pid, int signum) { void uv__process_close(uv_process_t* handle) { + /* Warning: if UV_HANDLE_ESRCH is not set, the caller is creating a zombie + * that we cannot reap. We assume here that it is intentional, and that the + * user will be wise and cleanup later. */ uv__queue_remove(&handle->queue); uv__handle_stop(handle); #ifdef UV_USE_SIGCHLD diff --git a/deps/uv/src/unix/proctitle.c b/deps/uv/src/unix/proctitle.c index 9d1f00ddf66..b78c250a27a 100644 --- a/deps/uv/src/unix/proctitle.c +++ b/deps/uv/src/unix/proctitle.c @@ -39,7 +39,8 @@ static void* args_mem; static void init_process_title_mutex_once(void) { - uv_mutex_init(&process_title_mutex); + if (uv_mutex_init(&process_title_mutex)) + abort(); } diff --git a/deps/uv/src/unix/stream.c b/deps/uv/src/unix/stream.c index 08a18314b1e..af7c44ed2b0 100644 --- a/deps/uv/src/unix/stream.c +++ b/deps/uv/src/unix/stream.c @@ -547,7 +547,6 @@ int uv_accept(uv_stream_t* server, uv_stream_t* client) { server->accepted_fd, UV_HANDLE_READABLE | UV_HANDLE_WRITABLE); if (err) { - /* TODO handle error */ uv__close(server->accepted_fd); goto done; } @@ -590,8 +589,7 @@ int uv_accept(uv_stream_t* server, uv_stream_t* client) { } } else { server->accepted_fd = -1; - if (err == 0) - uv__io_start(server->loop, &server->io_watcher, POLLIN); + uv__io_start(server->loop, &server->io_watcher, POLLIN); } return err; } @@ -692,6 +690,7 @@ static int uv__write_req_update(uv_stream_t* stream, assert(n <= stream->write_queue_size); stream->write_queue_size -= n; + req->write_extra.nwritten += n; buf = req->bufs + req->write_index; @@ -704,6 +703,9 @@ static int uv__write_req_update(uv_stream_t* stream, n -= len; } while (n > 0); + while (buf < req->bufs + req->nbufs && buf->len == 0) + buf++; + req->write_index = buf - req->bufs; return req->write_index == req->nbufs; @@ -915,6 +917,9 @@ static void uv__write_callbacks(uv_stream_t* stream) { uv__req_unregister(stream->loop); if (req->bufs != NULL) { + /* bufs are non-NULL on errors (including cancel and stream close). + * Success path sets bufs to NULL after adjusting size in uv__write. */ + assert(req->error != 0); stream->write_queue_size -= uv__write_req_size(req); if (req->bufs != req->bufsml) uv__free(req->bufs); @@ -929,12 +934,16 @@ static void uv__write_callbacks(uv_stream_t* stream) { static void uv__stream_eof(uv_stream_t* stream, const uv_buf_t* buf) { + uv_read_cb read_cb; + stream->flags |= UV_HANDLE_READ_EOF; - stream->flags &= ~UV_HANDLE_READING; uv__io_stop(stream->loop, &stream->io_watcher, POLLIN); uv__handle_stop(stream); uv__stream_osx_interrupt_select(stream); - stream->read_cb(stream, UV_EOF, buf); + read_cb = stream->read_cb; + stream->read_cb = NULL; + stream->alloc_cb = NULL; + read_cb(stream, UV_EOF, buf); } @@ -1037,11 +1046,7 @@ static void uv__read(uv_stream_t* stream) { is_ipc = stream->type == UV_NAMED_PIPE && ((uv_pipe_t*) stream)->ipc; - /* XXX: Maybe instead of having UV_HANDLE_READING we just test if - * tcp->read_cb is NULL or not? - */ while (stream->read_cb - && (stream->flags & UV_HANDLE_READING) && (count-- > 0)) { assert(stream->alloc_cb != NULL); @@ -1058,11 +1063,14 @@ static void uv__read(uv_stream_t* stream) { if (!is_ipc) { do { - nread = read(uv__stream_fd(stream), buf.base, buf.len); - } - while (nread < 0 && errno == EINTR); + nread = read(uv__stream_fd(stream), + buf.base, + buf.len > UV__IO_MAX_BYTES ? UV__IO_MAX_BYTES : buf.len); + } while (nread < 0 && errno == EINTR); } else { /* ipc uses recvmsg */ + if (buf.len > UV__IO_MAX_BYTES) + buf.len = UV__IO_MAX_BYTES; msg.msg_flags = 0; msg.msg_iov = (struct iovec*) &buf; msg.msg_iovlen = 1; @@ -1074,15 +1082,14 @@ static void uv__read(uv_stream_t* stream) { do { nread = uv__recvmsg(uv__stream_fd(stream), &msg, 0); - } - while (nread < 0 && errno == EINTR); + } while (nread < 0 && errno == EINTR); } if (nread < 0) { /* Error */ if (errno == EAGAIN || errno == EWOULDBLOCK) { /* Wait for the next one. */ - if (stream->flags & UV_HANDLE_READING) { + if (stream->read_cb != NULL) { uv__io_start(stream->loop, &stream->io_watcher, POLLIN); uv__stream_osx_interrupt_select(stream); } @@ -1096,8 +1103,9 @@ static void uv__read(uv_stream_t* stream) { /* Error. User should call uv_close(). */ stream->flags &= ~(UV_HANDLE_READABLE | UV_HANDLE_WRITABLE); stream->read_cb(stream, UV__ERR(errno), &buf); - if (stream->flags & UV_HANDLE_READING) { - stream->flags &= ~UV_HANDLE_READING; + if (stream->read_cb != NULL) { + stream->read_cb = NULL; + stream->alloc_cb = NULL; uv__io_stop(stream->loop, &stream->io_watcher, POLLIN); uv__handle_stop(stream); uv__stream_osx_interrupt_select(stream); @@ -1218,16 +1226,23 @@ void uv__stream_io(uv_loop_t* loop, uv__io_t* w, unsigned int events) { * operating systems, devices like PTYs sometimes produce partial reads even * when more data is available. */ - if ((events & POLLHUP) && + if ((events & (POLLHUP | UV__POLLRDHUP)) && !(events & POLLIN) && - (stream->flags & UV_HANDLE_READING) && + (stream->read_cb != NULL) && !(stream->flags & UV_HANDLE_READ_EOF)) { - uv_buf_t buf = { NULL, 0 }; - uv__stream_eof(stream, &buf); - } + /* When a PTY reports POLLHUP without POLLIN, there might be still data + * buffered. Do one more read instead of signalling EOF immediately. + */ + if (stream->type == UV_TTY) { + uv__read(stream); + } else { + uv_buf_t buf = { NULL, 0 }; + uv__stream_eof(stream, &buf); + } - if (uv__stream_fd(stream) == -1) - return; /* read_cb closed stream. */ + if (uv__stream_fd(stream) == -1) + return; /* read_cb closed stream. */ + } if (events & (POLLOUT | POLLERR | POLLHUP)) { uv__write(stream); @@ -1263,12 +1278,15 @@ static void uv__stream_connect(uv_stream_t* stream) { } else { /* Normal situation: we need to get the socket error from the kernel. */ assert(uv__stream_fd(stream) >= 0); - getsockopt(uv__stream_fd(stream), - SOL_SOCKET, - SO_ERROR, - &error, - &errorsize); - error = UV__ERR(error); + if (getsockopt(uv__stream_fd(stream), + SOL_SOCKET, + SO_ERROR, + &error, + &errorsize)) { + error = UV__ERR(errno); + } else { + error = UV__ERR(error); + } } if (error == UV__ERR(EINPROGRESS)) @@ -1295,6 +1313,7 @@ static void uv__stream_connect(uv_stream_t* stream) { static int uv__check_before_write(uv_stream_t* stream, + const uv_buf_t bufs[], unsigned int nbufs, uv_stream_t* send_handle) { assert((stream->type == UV_TCP || @@ -1309,6 +1328,13 @@ static int uv__check_before_write(uv_stream_t* stream, if (nbufs < 1 || nbufs > 1024*1024) return UV_EINVAL; + /* Reject writes above UV__IO_MAX_BYTES to be consistent with EINVAL on platforms + * such as macOS that fail when the total size of the iov exceeds 2GB, + * and catch/prevent sign-extension bugs. + */ + if (uv__count_bufs(bufs, nbufs) > UV__IO_MAX_BYTES) + return UV_EINVAL; + if (uv__stream_fd(stream) < 0) return UV_EBADF; @@ -1347,7 +1373,7 @@ int uv_write2(uv_write_t* req, int empty_queue; int err; - err = uv__check_before_write(stream, nbufs, send_handle); + err = uv__check_before_write(stream, bufs, nbufs, send_handle); if (err < 0) return err; @@ -1365,6 +1391,7 @@ int uv_write2(uv_write_t* req, req->handle = stream; req->error = 0; req->send_handle = send_handle; + req->write_extra.nwritten = 0; uv__queue_init(&req->queue); req->bufs = req->bufsml; @@ -1407,6 +1434,35 @@ int uv_write2(uv_write_t* req, } +size_t uv_write_nwritten(const uv_write_t* req) { + return req->write_extra.nwritten; +} + + +int uv__write_cancel(uv_write_t* req) { + struct uv__queue* q; + uv_stream_t* stream; + + stream = req->handle; + + /* N.B.: If the request already completed, we still return 0, but the callback + will not return ECANCELED - nothing to do here. */ + uv__queue_foreach(q, &stream->write_queue) { + if (q == &req->queue) { + uv__queue_remove(&req->queue); + req->error = UV_ECANCELED; + + /* uv__write_callbacks will handle write_queue_size and freeing bufs. */ + uv__queue_insert_tail(&stream->write_completed_queue, &req->queue); + uv__io_feed(stream->loop, &stream->io_watcher); + break; + } + } + + return 0; +} + + /* The buffers to be written must remain valid until the callback is called. * This is not required for the uv_buf_t array. */ @@ -1436,7 +1492,7 @@ int uv_try_write2(uv_stream_t* stream, if (stream->connect_req != NULL || stream->write_queue_size != 0) return UV_EAGAIN; - err = uv__check_before_write(stream, nbufs, NULL); + err = uv__check_before_write(stream, bufs, nbufs, send_handle); if (err < 0) return err; @@ -1450,9 +1506,6 @@ int uv__read_start(uv_stream_t* stream, assert(stream->type == UV_TCP || stream->type == UV_NAMED_PIPE || stream->type == UV_TTY); - /* The UV_HANDLE_READING flag is irrelevant of the state of the stream - it - * just expresses the desired state of the user. */ - stream->flags |= UV_HANDLE_READING; stream->flags &= ~UV_HANDLE_READ_EOF; /* TODO: try to do the read inline? */ @@ -1471,10 +1524,9 @@ int uv__read_start(uv_stream_t* stream, int uv_read_stop(uv_stream_t* stream) { - if (!(stream->flags & UV_HANDLE_READING)) + if (stream->read_cb == NULL) return 0; - stream->flags &= ~UV_HANDLE_READING; uv__io_stop(stream->loop, &stream->io_watcher, POLLIN); uv__handle_stop(stream); uv__stream_osx_interrupt_select(stream); diff --git a/deps/uv/src/unix/sunos.c b/deps/uv/src/unix/sunos.c index c241e3506ce..cb8d32dda25 100644 --- a/deps/uv/src/unix/sunos.c +++ b/deps/uv/src/unix/sunos.c @@ -162,7 +162,7 @@ void uv__io_poll(uv_loop_t* loop, int timeout) { int count; int err; int fd; - int user_timeout; + int user_timeout = 0; int reset_timeout; if (loop->nfds == 0) { @@ -612,6 +612,7 @@ void uv__fs_event_close(uv_fs_event_t* handle) { int uv_resident_set_memory(size_t* rss) { psinfo_t psinfo; + ssize_t nread; int err; int fd; @@ -619,11 +620,16 @@ int uv_resident_set_memory(size_t* rss) { if (fd == -1) return UV__ERR(errno); - /* FIXME(bnoordhuis) Handle EINTR. */ + do + nread = read(fd, &psinfo, sizeof(psinfo)); + while (nread == -1 && errno == EINTR); + err = UV_EINVAL; - if (read(fd, &psinfo, sizeof(psinfo)) == sizeof(psinfo)) { + if (nread == (ssize_t) sizeof(psinfo)) { *rss = (size_t)psinfo.pr_rssize * 1024; err = 0; + } else if (nread < 0) { + err = UV__ERR(errno); } uv__close(fd); diff --git a/deps/uv/src/unix/tty.c b/deps/uv/src/unix/tty.c index b8610720267..21d37882630 100644 --- a/deps/uv/src/unix/tty.c +++ b/deps/uv/src/unix/tty.c @@ -52,7 +52,7 @@ */ static int isreallyatty(int file) { int rc; - + rc = !ioctl(file, TXISATTY + 0x81, NULL); if (!rc && errno != EBADF) errno = ENOTTY; @@ -284,9 +284,17 @@ int uv_tty_set_mode(uv_tty_t* tty, uv_tty_mode_t mode) { int fd; int rc; - if (uv__is_raw_tty_mode(mode)) { - /* There is only a single raw TTY mode on UNIX. */ - mode = UV_TTY_MODE_RAW; + switch (mode) { + case UV_TTY_MODE_RAW_VT: + /* There is only a single raw TTY mode on UNIX. */ + mode = UV_TTY_MODE_RAW; + break; + case UV_TTY_MODE_NORMAL: + case UV_TTY_MODE_RAW: + case UV_TTY_MODE_IO: + break; + default: + return UV_EINVAL; } if (tty->mode == (int) mode) @@ -333,8 +341,8 @@ int uv_tty_set_mode(uv_tty_t* tty, uv_tty_mode_t mode) { UNREACHABLE(); } - /* Apply changes after draining */ - rc = uv__tcsetattr(fd, TCSADRAIN, &tmp); + /* Apply changes now, to avoid blocking. */ + rc = uv__tcsetattr(fd, TCSANOW, &tmp); if (rc == 0) tty->mode = mode; diff --git a/deps/uv/src/unix/udp.c b/deps/uv/src/unix/udp.c index 0a41391ffa6..14fd8365853 100644 --- a/deps/uv/src/unix/udp.c +++ b/deps/uv/src/unix/udp.c @@ -178,7 +178,11 @@ void uv__udp_io(uv_loop_t* loop, uv__io_t* w, unsigned int revents) { handle = container_of(w, uv_udp_t, io_watcher); assert(handle->type == UV_UDP); - if (revents & POLLIN) + /* Trigger a recv and send to find out what POLLERR occurred. */ + if (revents & POLLERR) + revents |= POLLIN | POLLOUT; + + if (revents & (POLLIN | POLLERR)) uv__udp_recvmsg(handle, 0); /* Just Linux support for now. */ @@ -187,7 +191,7 @@ void uv__udp_io(uv_loop_t* loop, uv__io_t* w, unsigned int revents) { * uv_udp_recv_stop + uv_close) already cleared recv_cb in the same * revents iteration. uv__udp_recvmsg asserts recv_cb != NULL, so * calling it with a NULL recv_cb would be wrong regardless of POLLERR. */ - if ((revents & POLLERR) && uv__is_active(handle)) + if ((revents & POLLERR) && uv__is_active(handle) && handle->recv_cb != NULL) uv__udp_recvmsg(handle, MSG_ERRQUEUE); #endif @@ -213,6 +217,8 @@ static int uv__udp_recvmmsg(uv_udp_t* handle, uv_buf_t* buf, int flag) { /* prepare structures for recvmmsg */ chunks = buf->len / UV__UDP_DGRAM_MAXSIZE; + if (chunks == 0) + return UV_EINVAL; if (chunks > ARRAY_SIZE(iov)) chunks = ARRAY_SIZE(iov); for (k = 0; k < chunks; ++k) { @@ -246,36 +252,40 @@ static int uv__udp_recvmmsg(uv_udp_t* handle, uv_buf_t* buf, int flag) { #endif if (nread < 1) { - if (nread == 0 || errno == EAGAIN || errno == EWOULDBLOCK) - handle->recv_cb(handle, 0, buf, NULL, 0); - else - handle->recv_cb(handle, UV__ERR(errno), buf, NULL, 0); - } else { - /* pass each chunk to the application */ - for (k = 0; k < (size_t) nread && handle->recv_cb != NULL; k++) { - flags = UV_UDP_MMSG_CHUNK; - if (msgs[k].msg_hdr.msg_flags & MSG_TRUNC) - flags |= UV_UDP_PARTIAL; + /* uv__udp_recvmsg() emits the terminal callback for nread <= 0. */ + if (errno == EAGAIN || errno == EWOULDBLOCK) + return 0; - chunk_buf = uv_buf_init(iov[k].iov_base, iov[k].iov_len); + return UV__ERR(errno); + } + + /* pass each chunk to the application */ + for (k = 0; k < (size_t) nread && handle->recv_cb != NULL; k++) { + flags = UV_UDP_MMSG_CHUNK; + if (msgs[k].msg_hdr.msg_flags & MSG_TRUNC) + flags |= UV_UDP_PARTIAL; + + chunk_buf = uv_buf_init(iov[k].iov_base, iov[k].iov_len); #if defined(__linux__) - if ((flag & MSG_ERRQUEUE) && - uv__udp_recvmsg_errqueue(handle, &msgs[k].msg_hdr, &chunk_buf, - (const struct sockaddr*) &peers[k], flags)) { - continue; - } -#endif - handle->recv_cb(handle, - msgs[k].msg_len, - &chunk_buf, - msgs[k].msg_hdr.msg_name, - flags); + if ((flag & MSG_ERRQUEUE) && + uv__udp_recvmsg_errqueue(handle, &msgs[k].msg_hdr, &chunk_buf, + (const struct sockaddr*) &peers[k], flags)) { + continue; } - - /* one last callback so the original buffer is freed */ - if (handle->recv_cb != NULL) - handle->recv_cb(handle, 0, buf, NULL, UV_UDP_MMSG_FREE); +#endif + handle->recv_cb(handle, + msgs[k].msg_len, + &chunk_buf, + msgs[k].msg_hdr.msg_namelen > 0 ? + msgs[k].msg_hdr.msg_name : + NULL, + flags); } + + /* one last callback so the original buffer is freed */ + if (handle->recv_cb != NULL) + handle->recv_cb(handle, 0, buf, NULL, UV_UDP_MMSG_FREE); + return nread; #else /* __linux__ || ____FreeBSD__ || __APPLE__ */ return UV_ENOSYS; @@ -312,8 +322,11 @@ static void uv__udp_recvmsg(uv_udp_t* handle, int flag) { if (uv_udp_using_recvmmsg(handle)) { nread = uv__udp_recvmmsg(handle, &buf, flag); - if (nread > 0) - count -= nread; + if (nread <= 0) { + handle->recv_cb(handle, nread, &buf, NULL, 0); + return; + } + count -= nread; continue; } @@ -721,9 +734,6 @@ int uv__udp_try_send(uv_udp_t* handle, unsigned int addrlen) { int err; - if (nbufs < 1) - return UV_EINVAL; - /* already sending a message */ if (handle->send_queue_count != 0) return UV_EAGAIN; diff --git a/deps/uv/src/uv-common.c b/deps/uv/src/uv-common.c index f1e8928d37e..4538c15222d 100644 --- a/deps/uv/src/uv-common.c +++ b/deps/uv/src/uv-common.c @@ -34,6 +34,7 @@ # include /* malloc */ #else # include /* if_nametoindex */ +# include /* sched_yield() */ # include /* AF_UNIX, sockaddr_un */ #endif @@ -263,7 +264,11 @@ int uv_ip4_addr(const char* ip, int port, struct sockaddr_in* addr) { int uv_ip6_addr(const char* ip, int port, struct sockaddr_in6* addr) { - char address_part[40]; + /* INET6_ADDRSTRLEN is needed for a full IPv4-mapped IPv6 address + * for the given platform plus a NUL byte. In posix this is defined to be 46. + * On Windows this buffer needs 65 bytes for additional optional + * formatting marks that may be present. */ + char address_part[INET6_ADDRSTRLEN]; size_t address_part_size; const char* zone_index; @@ -453,7 +458,10 @@ int uv__udp_is_connected(uv_udp_t* handle) { } -int uv__udp_check_before_send(uv_udp_t* handle, const struct sockaddr* addr) { +int uv__udp_check_before_send(uv_udp_t* handle, + const uv_buf_t bufs[], + unsigned int nbufs, + const struct sockaddr* addr) { unsigned int addrlen; if (handle->type != UV_UDP) @@ -480,6 +488,12 @@ int uv__udp_check_before_send(uv_udp_t* handle, const struct sockaddr* addr) { addrlen = 0; } + if (nbufs < 1 || nbufs > 1024 * 1024) + return UV_EINVAL; + + if (uv__count_bufs(bufs, nbufs) > UV__IO_MAX_BYTES) + return UV_EINVAL; + return addrlen; } @@ -492,7 +506,7 @@ int uv_udp_send(uv_udp_send_t* req, uv_udp_send_cb send_cb) { int addrlen; - addrlen = uv__udp_check_before_send(handle, addr); + addrlen = uv__udp_check_before_send(handle, bufs, nbufs, addr); if (addrlen < 0) return addrlen; @@ -506,7 +520,7 @@ int uv_udp_try_send(uv_udp_t* handle, const struct sockaddr* addr) { int addrlen; - addrlen = uv__udp_check_before_send(handle, addr); + addrlen = uv__udp_check_before_send(handle, bufs, nbufs, addr); if (addrlen < 0) return addrlen; @@ -520,12 +534,21 @@ int uv_udp_try_send2(uv_udp_t* handle, unsigned int nbufs[/*count*/], struct sockaddr* addrs[/*count*/], unsigned int flags) { + unsigned int i; + int addrlen; + if (count < 1) return UV_EINVAL; if (flags != 0) return UV_EINVAL; + for (i = 0; i < count; i++) { + addrlen = uv__udp_check_before_send(handle, bufs[i], nbufs[i], addrs[i]); + if (addrlen < 0) + return addrlen; + } + if (handle->send_queue_count > 0) return UV_EAGAIN; @@ -651,8 +674,11 @@ size_t uv__count_bufs(const uv_buf_t bufs[], unsigned int nbufs) { size_t bytes; bytes = 0; - for (i = 0; i < nbufs; i++) - bytes += (size_t) bufs[i].len; + for (i = 0; i < nbufs; i++) { + if (bufs[i].len > (size_t) INT32_MAX - bytes) + return INT32_MAX; + bytes += bufs[i].len; + } return bytes; } @@ -875,6 +901,126 @@ uv_loop_t* uv_loop_new(void) { } +/* Pause the CPU briefly to avoid burning power in spin-wait loops. */ +static void uv__cpu_relax(void) { +#if defined(_WIN32) + YieldProcessor(); +#elif defined(__i386__) || defined(__x86_64__) + __asm__ __volatile__ ("rep; nop" ::: "memory"); /* a.k.a. PAUSE */ +#elif (defined(__arm__) && __ARM_ARCH >= 7) || defined(__aarch64__) + __asm__ __volatile__ ("isb" ::: "memory"); +#elif !defined(__APPLE__) && (defined(__powerpc64__) || defined(__ppc64__) || defined(__PPC64__)) + __asm__ __volatile__ ("or 1,1,1; or 2,2,2" ::: "memory"); +#elif (defined(__ppc__) || defined(__ppc64__)) && defined(__APPLE__) + __asm volatile ("" : : : "memory"); +#elif defined(__riscv) && __riscv_xlen == 64 + __asm__ volatile(".insn 0x0100000f" ::: "memory"); /* FENCE */ +#endif +} + + +/* Atomic helpers for the async pending field. + * Bit 0: pending flag (notification sent or handle closing). + * Bits 1+: busy counter (2 per in-flight uv_async_send call). + * + * uv__pending_cas and uv__pending_fetch_or are seq_cst (InterlockedXxx on + * MSVC, default memory_order_seq_cst on stdatomic). Their seq_cst pairing + * with the fetch_and/InterlockedAnd on the callback side makes uv_async_send + * and the uv_async_cb invocation sequentially consistent — all accesses + * (reads and writes) before uv_async_send are visible to the callback. + * + * uv__pending_load and uv__pending_fetch_add are relaxed; they touch only the + * busy counter and need not participate in that ordering. */ +#ifdef _MSC_VER + +static int uv__pending_cas(int* p, int* expected, int desired) { + LONG old; + old = InterlockedCompareExchange((LONG volatile*) p, (LONG) desired, (LONG) *expected); + if (old == (LONG) *expected) return 1; + *expected = (int) old; + return 0; +} + +#define uv__pending_load(p) ((int) *(volatile int*)(p)) +#define uv__pending_fetch_add(p, v) \ + ((void) InterlockedExchangeAdd((LONG volatile*)(p), (LONG)(v))) +#define uv__pending_fetch_or(p, v) \ + ((int) InterlockedOr((LONG volatile*)(p), (LONG)(v))) + +#else /* GCC / Clang / MinGW — use C11 stdatomic */ + +static int uv__pending_cas(int* p, int* expected, int desired) { + return atomic_compare_exchange_weak((_Atomic int*) p, expected, desired); +} + +#define uv__pending_load(p) \ + atomic_load_explicit((_Atomic int*)(p), memory_order_relaxed) +#define uv__pending_fetch_add(p, v) \ + ((void) atomic_fetch_add_explicit((_Atomic int*)(p), (v), memory_order_relaxed)) +#define uv__pending_fetch_or(p, v) \ + ((int) atomic_fetch_or((_Atomic int*)(p), (v))) + +#endif /* _MSC_VER */ + + +int uv_async_send(uv_async_t* handle) { + int current = 0; + + /* Atomically set the pending flag (bit 0) and increment the busy counter + * (bits 1+). Adding 3 sets bit 0 and adds 2 to the busy counter at once. + * The seq_cst CAS synchronizes with the seq_cst fetch_and in the callback, + * making all accesses before this call visible to the callback (and vice + * versa from the callback). */ + while (!uv__pending_cas(&handle->pending, ¤t, current + 3)) + if (current & 1) + return 0; + + /* Wake up the event loop. The notification write establishes a + * happens-before relationship with the reader via the kernel. */ + uv__async_notify(handle); + + /* Decrement the busy counter (bits 1+). */ + uv__pending_fetch_add(&handle->pending, -2); + + return 0; +} + + +int uv__async_spin(uv_async_t* handle) { + int old; + int i; + + /* Atomically set the pending flag (bit 0) so no new notifications will be + * sent after this function returns. Save whether the flag was already set + * so callers can determine whether a notification is in flight. */ + old = uv__pending_fetch_or(&handle->pending, 1); + + for (;;) { + /* 997 is not completely chosen at random. It's a prime number, acyclic by + * nature, and should therefore hopefully dampen sympathetic resonance. + */ + for (i = 0; i < 997; i++) { + /* Wait until the busy counter (bits 1+) is zero. */ + if ((uv__pending_load(&handle->pending) & ~1) == 0) + return old & 1; + + /* Another thread is busy with this handle; spin until it's done. */ + uv__cpu_relax(); + } + + /* Yield the CPU. We may have preempted the other thread while it's + * inside the critical section and if it's running on the same CPU + * as us, we'll just burn CPU cycles until the end of our time slice. + */ +#ifdef _WIN32 + SwitchToThread(); +#else + sched_yield(); +#endif + } +} + + int uv_loop_close(uv_loop_t* loop) { struct uv__queue* q; uv_handle_t* h; @@ -928,8 +1074,13 @@ int uv_read_start(uv_stream_t* stream, if (stream->flags & UV_HANDLE_CLOSING) return UV_EINVAL; - if (stream->flags & UV_HANDLE_READING) +#ifdef _WIN32 + if (stream->flags & UV_HANDLE_READING) + return UV_EALREADY; +#else + if (stream->read_cb != NULL) return UV_EALREADY; +#endif if (!(stream->flags & UV_HANDLE_READABLE)) return UV_ENOTCONN; @@ -963,6 +1114,14 @@ void uv_free_cpu_info(uv_cpu_info_t* cpu_infos, int count) { #endif /* __linux__ */ } +#ifdef _MSC_VER +#define uv__exchange_int_relaxed(p, v) \ + InterlockedExchangeNoFence((LONG volatile*)(p), v) +#else +#define uv__exchange_int_relaxed(p, v) \ + atomic_exchange_explicit((_Atomic int*)(p), v, memory_order_relaxed) +#endif + /* Also covers __clang__ and __INTEL_COMPILER. Disabled on Windows because * threads have already been forcibly terminated by the operating system diff --git a/deps/uv/src/uv-common.h b/deps/uv/src/uv-common.h index 5452a549388..7e811371ba3 100644 --- a/deps/uv/src/uv-common.h +++ b/deps/uv/src/uv-common.h @@ -58,20 +58,18 @@ extern int snprintf(char*, size_t, const char*, ...); #define container_of(ptr, type, member) \ ((type *) ((char *) (ptr) - offsetof(type, member))) -/* C11 defines static_assert to be a macro which calls _Static_assert. */ -#if defined(static_assert) +/* C11 defines static_assert to be a macro which calls _Static_assert. + * tcc gets confused by glibc's definition of _Static_assert in sys/cdefs.h + * so fall back to our pre-C11 workaround. + */ +#if defined(static_assert) && !defined(__TINYC__) #define STATIC_ASSERT(expr) static_assert(expr, #expr) #else +#define UV_CONCAT_MORE(x, y) x##y +#define UV_CONCAT(x, y) UV_CONCAT_MORE(x, y) #define STATIC_ASSERT(expr) \ - void uv__static_assert(int static_assert_failed[1 - 2 * !(expr)]) -#endif - -#ifdef _MSC_VER -#define uv__exchange_int_relaxed(p, v) \ - InterlockedExchangeNoFence((LONG volatile*)(p), v) -#else -#define uv__exchange_int_relaxed(p, v) \ - atomic_exchange_explicit((_Atomic int*)(p), v, memory_order_relaxed) + void UV_CONCAT(uv__static_assert, __LINE__)( \ + int static_assert_failed[1 - 2 * !(expr)]) #endif #define UV__UDP_DGRAM_MAXSIZE (64 * 1024) @@ -102,7 +100,7 @@ enum { UV_HANDLE_ZERO_READ = 0x00040000, UV_HANDLE_EMULATE_IOCP = 0x00080000, UV_HANDLE_BLOCKING_WRITES = 0x00100000, - UV_HANDLE_CANCELLATION_PENDING = 0x00200000, + UV_HANDLE_READ_CANCELLATION_PENDING = 0x00200000, /* Used by uv_tcp_t and uv_udp_t handles */ UV_HANDLE_IPV6 = 0x00400000, @@ -137,6 +135,7 @@ enum { UV_HANDLE_POLL_SLOW = 0x01000000, /* Only used by uv_process_t handles. */ + UV_HANDLE_ESRCH = 0x01000000, UV_HANDLE_REAP = 0x10000000 }; @@ -148,6 +147,15 @@ int uv__loop_configure(uv_loop_t* loop, uv_loop_option option, va_list ap); void uv__loop_close(uv_loop_t* loop); +/* Sets the pending flag (bit 0) and waits for the busy counter (bits 1+) to + * drain. Returns the previous value of bit 0. */ +int uv__async_spin(uv_async_t* handle); + +/* Platform hook: post a wakeup notification for the given async handle. */ +void uv__async_notify(uv_async_t* handle); + +int uv__write_cancel(uv_write_t* req); + int uv__read_start(uv_stream_t* stream, uv_alloc_cb alloc_cb, uv_read_cb read_cb); @@ -226,6 +234,12 @@ void uv__work_done(uv_async_t* handle); size_t uv__count_bufs(const uv_buf_t bufs[], unsigned int nbufs); +/* On some platforms, notably macOS, attempting a read or write > 2GB returns + * an EINVAL. On Linux, IO syscalls will transfer at most this many bytes. + * Use this limit everywhere to avoid platform-specific failures. + */ +#define UV__IO_MAX_BYTES 0x7ffff000 + int uv__socket_sockopt(uv_handle_t* handle, int optname, int* value); void uv__fs_scandir_cleanup(uv_fs_t* req); diff --git a/deps/uv/src/win/async.c b/deps/uv/src/win/async.c index 4c2cd265e62..7cef813d0cf 100644 --- a/deps/uv/src/win/async.c +++ b/deps/uv/src/win/async.c @@ -26,29 +26,12 @@ #include "handle-inl.h" #include "req-inl.h" -#ifdef _MSC_VER /* MSVC */ - -/* _InterlockedOr8 is supported by MSVC on x32 and x64. It is slightly less - * efficient than InterlockedExchange, but InterlockedExchange8 does not exist, - * and interlocked operations on larger targets might require the target to be - * aligned. */ -#pragma intrinsic(_InterlockedOr8) - -static char uv__atomic_exchange_set(char volatile* target) { - return _InterlockedOr8(target, 1); -} - -#else /* GCC, Clang in mingw mode */ - -static char uv__atomic_exchange_set(char volatile* target) { - return __sync_fetch_and_or(target, 1); -} - -#endif /* _MSC_VER */ void uv__async_endgame(uv_loop_t* loop, uv_async_t* handle) { - if (handle->flags & UV_HANDLE_CLOSING && - !handle->async_sent) { + /* uv__async_close guarantees uv__want_endgame is called exactly once: the + * spin drains all in-flight senders and the return value selects which path + * schedules the endgame, so no double-close guard is needed here. */ + if (handle->flags & UV_HANDLE_CLOSING) { assert(!(handle->flags & UV_HANDLE_CLOSED)); uv__handle_close(handle); } @@ -59,7 +42,7 @@ int uv_async_init(uv_loop_t* loop, uv_async_t* handle, uv_async_cb async_cb) { uv_req_t* req; uv__handle_init(loop, (uv_handle_t*) handle, UV_ASYNC); - handle->async_sent = 0; + handle->pending = 0; handle->async_cb = async_cb; req = &handle->async_req; @@ -73,31 +56,40 @@ int uv_async_init(uv_loop_t* loop, uv_async_t* handle, uv_async_cb async_cb) { void uv__async_close(uv_loop_t* loop, uv_async_t* handle) { - if (!((uv_async_t*)handle)->async_sent) { + /* Block new senders and wait for any in-flight send to finish. + * uv__async_spin returns the previous value of the pending flag (bit 0): + * if it was already set, an IOCP notification is in flight and will trigger + * the endgame via uv__process_async_wakeup_req; otherwise we schedule the + * endgame immediately because no further IOCP completion will arrive. */ + if (!uv__async_spin(handle)) uv__want_endgame(loop, (uv_handle_t*) handle); - } uv__handle_closing(handle); } -int uv_async_send(uv_async_t* handle) { +void uv__async_notify(uv_async_t* handle) { uv_loop_t* loop = handle->loop; + POST_COMPLETION_FOR_REQ(loop, &handle->async_req); +} - if (handle->type != UV_ASYNC) { - /* Can't set errno because that's not thread-safe. */ - return -1; - } - /* The user should make sure never to call uv_async_send to a closing or - * closed handle. */ - assert(!(handle->flags & UV_HANDLE_CLOSING)); +void uv__async_stop(uv_loop_t* loop) { + struct uv__queue* q; + uv_handle_t* h; - if (!uv__atomic_exchange_set(&handle->async_sent)) { - POST_COMPLETION_FOR_REQ(loop, &handle->async_req); + /* Spin all UV_ASYNC handles that are still open. */ + uv__queue_foreach(q, &loop->handle_queue) { + h = uv__queue_data(q, uv_handle_t, handle_queue); + if (h->type == UV_ASYNC) + uv__async_spin((uv_async_t*) h); } - return 0; + /* Close the internal wq_async handle directly, bypassing the normal endgame: + * any pending IOCP message will be discarded with loop->iocp. */ + loop->wq_async.close_cb = NULL; + uv__handle_closing(&loop->wq_async); + uv__handle_close(&loop->wq_async); } @@ -106,10 +98,14 @@ void uv__process_async_wakeup_req(uv_loop_t* loop, uv_async_t* handle, assert(handle->type == UV_ASYNC); assert(req->type == UV_WAKEUP); - handle->async_sent = 0; + /* Clear pending flag, retain busy counter. The InterlockedAnd is seq_cst (a + * full barrier), and synchronizing with the seq_cst + * InterlockedCompareExchange in uv_async_send. This makes all accesses + * before that call visible here (and vice versa). */ + InterlockedAnd((LONG volatile*) &handle->pending, ~1); if (handle->flags & UV_HANDLE_CLOSING) { - uv__want_endgame(loop, (uv_handle_t*)handle); + uv__want_endgame(loop, (uv_handle_t*) handle); } else if (handle->async_cb != NULL) { handle->async_cb(handle); } diff --git a/deps/uv/src/win/core.c b/deps/uv/src/win/core.c index 317238fd229..96a79610623 100644 --- a/deps/uv/src/win/core.c +++ b/deps/uv/src/win/core.c @@ -238,8 +238,10 @@ int uv_loop_init(uv_loop_t* loop) { return uv_translate_sys_error(GetLastError()); lfields = (uv__loop_internal_fields_t*) uv__calloc(1, sizeof(*lfields)); - if (lfields == NULL) - return UV_ENOMEM; + if (lfields == NULL) { + err = UV_ENOMEM; + goto fail_lfields_alloc; + } loop->internal_fields = lfields; err = uv_mutex_init(&lfields->loop_metrics.lock); @@ -315,6 +317,8 @@ int uv_loop_init(uv_loop_t* loop) { fail_metrics_mutex_init: uv__free(lfields); loop->internal_fields = NULL; + +fail_lfields_alloc: CloseHandle(loop->iocp); loop->iocp = INVALID_HANDLE_VALUE; @@ -340,16 +344,10 @@ void uv__loop_close(uv_loop_t* loop) { uv__loops_remove(loop); - /* Close the async handle without needing an extra loop iteration. - * We might have a pending message, but we're just going to destroy the IOCP - * soon, so we can just discard it now without the usual risk of a getting - * another notification from GetQueuedCompletionStatusEx after calling the - * close_cb (which we also skip defining). We'll assert later that queue was - * actually empty and all reqs handled. */ - loop->wq_async.async_sent = 0; - loop->wq_async.close_cb = NULL; - uv__handle_closing(&loop->wq_async); - uv__handle_close(&loop->wq_async); + /* Any pending IOCP message for wq_async is discarded when loop->iocp is + * closed below; uv__async_stop spins to ensure no thread is still inside + * PostQueuedCompletionStatus before that happens. */ + uv__async_stop(loop); for (i = 0; i < ARRAY_SIZE(loop->poll_peer_sockets); i++) { SOCKET sock = loop->poll_peer_sockets[i]; diff --git a/deps/uv/src/win/fs-event.c b/deps/uv/src/win/fs-event.c index 5cb7caea9ac..d61d419b4ad 100644 --- a/deps/uv/src/win/fs-event.c +++ b/deps/uv/src/win/fs-event.c @@ -438,6 +438,7 @@ void uv__process_fs_event_req(uv_loop_t* loop, uv_req_t* req, WCHAR* filenamew = NULL; WCHAR* long_filenamew = NULL; DWORD offset = 0; + int dir_event_detected = 0; assert(req->type == UV_FS_EVENT_REQ); assert(handle->req_pending); @@ -464,6 +465,10 @@ void uv__process_fs_event_req(uv_loop_t* loop, uv_req_t* req, assert(!filenamew); assert(!long_filenamew); + if (file_info->FileNameLength == 0) { + dir_event_detected = 1; + } + /* * Fire the event only if we were asked to watch a directory, * or if the filename filter matches. @@ -601,6 +606,7 @@ void uv__process_fs_event_req(uv_loop_t* loop, uv_req_t* req, sizeof(info)) && info.Directory && info.DeletePending) { + dir_event_detected = 1; uv__convert_utf16_to_utf8(handle->dirw, -1, &filename); handle->cb(handle, filename, UV_RENAME, 0); uv__free(filename); @@ -613,6 +619,26 @@ void uv__process_fs_event_req(uv_loop_t* loop, uv_req_t* req, if (handle->flags & UV_HANDLE_CLOSING) { uv__want_endgame(loop, (uv_handle_t*)handle); } else if (uv__is_active(handle)) { + /* + * Check if the handle has become a zombie pointing to \$Extend\$Deleted\. + * Only perform the check if we detected an event on the directory, which + * may indicate deletion. + */ + if (dir_event_detected) { + WCHAR path_buf[MAX_PATH]; + DWORD path_len = GetFinalPathNameByHandleW(handle->dir_handle, + path_buf, + ARRAY_SIZE(path_buf), + FILE_NAME_NORMALIZED | VOLUME_NAME_NONE); + + if (path_len > 0 && path_len < ARRAY_SIZE(path_buf)) { + if (wcsstr(path_buf, L"\\$Extend\\$Deleted\\") != NULL) { + handle->cb(handle, NULL, 0, UV_ENOENT); + return; + } + } + } + uv__fs_event_queue_readdirchanges(loop, handle); } } diff --git a/deps/uv/src/win/fs.c b/deps/uv/src/win/fs.c index 4092de0ab31..c5678436f38 100644 --- a/deps/uv/src/win/fs.c +++ b/deps/uv/src/win/fs.c @@ -709,10 +709,13 @@ void fs__close(uv_fs_t* req) { } } - if (fd > 2) + if (fd > 2) { + UV_BEGIN_DISABLE_CRT_ASSERT(); result = _close(fd); - else + UV_END_DISABLE_CRT_ASSERT(); + } else { result = 0; + } /* _close doesn't set _doserrno on failure, but it does always set errno * to EBADF on failure. @@ -889,6 +892,7 @@ void fs__read(uv_fs_t* req) { bytes = 0; do { DWORD incremental_bytes; + DWORD to_read; if (offset != -1) { offset_.QuadPart = offset + bytes; @@ -896,9 +900,12 @@ void fs__read(uv_fs_t* req) { overlapped.OffsetHigh = offset_.HighPart; } + to_read = req->fs.info.bufs[index].len; + if (to_read > UV__IO_MAX_BYTES) + to_read = UV__IO_MAX_BYTES; result = ReadFile(handle, req->fs.info.bufs[index].base, - req->fs.info.bufs[index].len, + to_read, &incremental_bytes, overlapped_ptr); bytes += incremental_bytes; @@ -1103,7 +1110,7 @@ void fs__write(uv_fs_t* req) { result = WriteFile(handle, req->fs.info.bufs[index].base, - req->fs.info.bufs[index].len, + (DWORD) req->fs.info.bufs[index].len, &incremental_bytes, overlapped_ptr); bytes += incremental_bytes; @@ -3329,6 +3336,11 @@ int uv_fs_write(uv_loop_t* loop, return UV_EINVAL; } + if (uv__count_bufs(bufs, nbufs) > UV__IO_MAX_BYTES) { + SET_REQ_UV_ERROR(req, UV_EINVAL, ERROR_INVALID_PARAMETER); + return UV_EINVAL; + } + req->file.fd = fd; req->fs.info.nbufs = nbufs; @@ -3698,6 +3710,8 @@ int uv_fs_sendfile(uv_loop_t* loop, uv_fs_t* req, uv_file fd_out, req->file.fd = fd_in; req->fs.info.fd_out = fd_out; req->fs.info.offset = in_offset; + if (length > UV__IO_MAX_BYTES) + return UV_EINVAL; req->fs.info.bufsml[0].len = length; POST; } diff --git a/deps/uv/src/win/internal.h b/deps/uv/src/win/internal.h index db488be77d9..c68aa6617a0 100644 --- a/deps/uv/src/win/internal.h +++ b/deps/uv/src/win/internal.h @@ -134,6 +134,7 @@ int uv__pipe_write(uv_loop_t* loop, uv_stream_t* send_handle, uv_write_cb cb); void uv__pipe_shutdown(uv_loop_t* loop, uv_pipe_t* handle, uv_shutdown_t* req); +int uv__pipe_write_cancel_non_overlapped(uv_pipe_t* handle, uv_write_t* req); void uv__process_pipe_read_req(uv_loop_t* loop, uv_pipe_t* handle, uv_req_t* req); @@ -201,6 +202,7 @@ void uv__once_init(void); /* * Async watcher */ +void uv__async_stop(uv_loop_t* loop); void uv__async_close(uv_loop_t* loop, uv_async_t* handle); void uv__async_endgame(uv_loop_t* loop, uv_async_t* handle); diff --git a/deps/uv/src/win/pipe.c b/deps/uv/src/win/pipe.c index 8f86a1fee2e..5b45fee0ee0 100644 --- a/deps/uv/src/win/pipe.c +++ b/deps/uv/src/win/pipe.c @@ -83,13 +83,6 @@ typedef struct { STATIC_ASSERT(sizeof(uv__ipc_frame_header_t) == 16); STATIC_ASSERT(sizeof(uv__ipc_socket_xfer_info_t) == 632); -/* Coalesced write request. */ -typedef struct { - uv_write_t req; /* Internal heap-allocated write request. */ - uv_write_t* user_req; /* Pointer to user-specified uv_write_t. */ -} uv__coalesced_write_t; - - static void eof_timer_init(uv_pipe_t* pipe); static void eof_timer_start(uv_pipe_t* pipe); static void eof_timer_stop(uv_pipe_t* pipe); @@ -106,8 +99,42 @@ static int includes_nul(const char *s, size_t n) { } +static int uv_is_app_container_; +static uv_once_t uv_is_app_container_guard_ = UV_ONCE_INIT; + + +/* Is this process running under Windows AppContainer? */ +/* Detection algorithm from https://learn.microsoft.com/en-us/windows/win32/secauthz/appcontainer-for-legacy-applications- */ +static void uv__init__is_app_container(void) { + HANDLE token; + DWORD ac; + DWORD len; + int ok; + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) { + uv_is_app_container_ = 0; + return; + } + ac = 0; + len = sizeof(ac); + ok = GetTokenInformation(token, TokenIsAppContainer, &ac, len, &len); + CloseHandle(token); + uv_is_app_container_ = ok && ac != 0; +} + + +/* Is this process running under Windows AppContainer? */ +/* Cached form for repeated use in the process. */ +static int uv__is_appcontainer(void) { + uv_once(&uv_is_app_container_guard_, uv__init__is_app_container); + return uv_is_app_container_; +} + + static void uv__unique_pipe_name(unsigned long long ptr, char* name, size_t size) { - snprintf(name, size, "\\\\?\\pipe\\uv\\%llu-%lu", ptr, GetCurrentProcessId()); + snprintf(name, size, "\\\\?\\pipe\\%suv\\%llu-%lu", + uv__is_appcontainer() ? "LOCAL\\" : "", + ptr, GetCurrentProcessId()); } @@ -391,6 +418,32 @@ int uv_pipe(uv_file fds[2], int read_flags, int write_flags) { } +static DWORD uv__pipe_attach_iocp(HANDLE pipeHandle, + HANDLE iocp, + uv_pipe_t* handle) { + UCHAR sfcnm_flags; + DWORD err = 0; + + if (CreateIoCompletionPort(pipeHandle, iocp, (ULONG_PTR) handle, 0) == NULL) { + err = GetLastError(); + handle->flags |= UV_HANDLE_EMULATE_IOCP; + } + + sfcnm_flags = FILE_SKIP_SET_EVENT_ON_HANDLE; + if (!err) + sfcnm_flags |= FILE_SKIP_COMPLETION_PORT_ON_SUCCESS; + + if (SetFileCompletionNotificationModes(pipeHandle, sfcnm_flags)) { + if (sfcnm_flags & FILE_SKIP_COMPLETION_PORT_ON_SUCCESS) + handle->flags |= UV_HANDLE_SYNC_BYPASS_IOCP; + } else { + err = GetLastError(); + } + + return err; +} + + int uv__create_stdio_pipe_pair(uv_loop_t* loop, uv_pipe_t* parent_pipe, HANDLE* child_pipe_ptr, unsigned int flags) { /* The parent_pipe is always the server_pipe and kept by libuv. @@ -430,13 +483,9 @@ int uv__create_stdio_pipe_pair(uv_loop_t* loop, if (err) goto error; - if (CreateIoCompletionPort(server_pipe, - loop->iocp, - (ULONG_PTR) parent_pipe, - 0) == NULL) { - err = GetLastError(); - goto error; - } + err = uv__pipe_attach_iocp(server_pipe, loop->iocp, parent_pipe); + if (err) + uv_fatal_error(err, "uv__pipe_attach_iocp"); parent_pipe->handle = server_pipe; *child_pipe_ptr = client_pipe; @@ -518,16 +567,15 @@ static int uv__set_pipe_handle(uv_loop_t* loop, mode_info.Mode & FILE_SYNCHRONOUS_IO_NONALERT) { /* Non-overlapped pipe. */ handle->flags |= UV_HANDLE_NON_OVERLAPPED_PIPE; - handle->pipe.conn.readfile_thread_handle = NULL; - InitializeCriticalSection(&handle->pipe.conn.readfile_thread_lock); + handle->pipe.conn.non_overlapped_write_active = NULL; + handle->pipe.conn.readfile_thread_handle = INVALID_HANDLE_VALUE; + handle->pipe.conn.writefile_thread_handle = INVALID_HANDLE_VALUE; + InitializeCriticalSection(&handle->pipe.conn.thread_lock); } else { - /* Overlapped pipe. Try to associate with IOCP. */ - if (CreateIoCompletionPort(pipeHandle, - loop->iocp, - (ULONG_PTR) handle, - 0) == NULL) { - handle->flags |= UV_HANDLE_EMULATE_IOCP; - } + /* Overlapped pipe. Try to associate with IOCP. + * Will set compatibility flags internally if this fails + * (because some other process already has activated IOCP). */ + uv__pipe_attach_iocp(pipeHandle, loop->iocp, handle); } handle->handle = pipeHandle; @@ -540,6 +588,8 @@ static int uv__set_pipe_handle(uv_loop_t* loop, static int pipe_alloc_accept(uv_loop_t* loop, uv_pipe_t* handle, uv_pipe_accept_t* req, BOOL firstInstance) { + DWORD err; + assert(req->pipeHandle == INVALID_HANDLE_VALUE); req->pipeHandle = @@ -554,12 +604,9 @@ static int pipe_alloc_accept(uv_loop_t* loop, uv_pipe_t* handle, } /* Associate it with IOCP so we can get events. */ - if (CreateIoCompletionPort(req->pipeHandle, - loop->iocp, - (ULONG_PTR) handle, - 0) == NULL) { - uv_fatal_error(GetLastError(), "CreateIoCompletionPort"); - } + err = uv__pipe_attach_iocp(req->pipeHandle, loop->iocp, handle); + if (err) + uv_fatal_error(err, "uv__pipe_attach_iocp"); /* Stash a handle in the server object for use from places such as * getsockname and chmod. As we transfer ownership of these to client @@ -664,7 +711,7 @@ void uv__pipe_endgame(uv_loop_t* loop, uv_pipe_t* handle) { FROM_PROTOCOL_INFO, &xfer_queue_item->xfer_info.socket_info, 0, - WSA_FLAG_OVERLAPPED); + WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT); uv__free(xfer_queue_item); if (socket != INVALID_SOCKET) @@ -679,11 +726,12 @@ void uv__pipe_endgame(uv_loop_t* loop, uv_pipe_t* handle) { } if (handle->flags & UV_HANDLE_NON_OVERLAPPED_PIPE) - DeleteCriticalSection(&handle->pipe.conn.readfile_thread_lock); + DeleteCriticalSection(&handle->pipe.conn.thread_lock); } if (handle->flags & UV_HANDLE_PIPESERVER) { - assert(handle->pipe.serv.accept_reqs); + /* accept_reqs is NULL when uv_pipe_pending_instances() was called but + * the pipe was never successfully bound. */ uv__free(handle->pipe.serv.accept_reqs); handle->pipe.serv.accept_reqs = NULL; } @@ -996,51 +1044,69 @@ int uv_pipe_connect2(uv_connect_t* req, return 0; } +/* Cancel a synchronous I/O operation running in a thread pool thread. + * thread_ptr is the volatile handle set by the worker thread, and lock + * is held briefly to synchronize the handshake. */ +static void uv__pipe_cancel_synchronous_io(volatile HANDLE* thread_ptr, + CRITICAL_SECTION* lock) { + HANDLE thread; + HANDLE expected_invalid = INVALID_HANDLE_VALUE; -void uv__pipe_interrupt_read(uv_pipe_t* handle) { - BOOL r; + EnterCriticalSection(lock); + thread = *thread_ptr; + if (thread == NULL) { + /* The thread pool thread has not yet reached the point of blocking, we + * can pre-empt it here. However, we still need to wait for the thread + * to acknowledge the interrupt. Otherwise it could race with the next + * request. It does this by setting *thread_ptr back to NULL. */ + *thread_ptr = INVALID_HANDLE_VALUE; + do { + LeaveCriticalSection(lock); + WaitOnAddress(thread_ptr, &expected_invalid, sizeof(*thread_ptr), INFINITE); + EnterCriticalSection(lock); + } while (*thread_ptr != NULL); + /* Finally set this back to INVALID_HANDLE_VALUE to retain the + * invariant that it'll be INVALID_HANDLE_VALUE on exit from this function. */ + *thread_ptr = INVALID_HANDLE_VALUE; + } else { + /* Spin until the thread has acknowledged (by changing *thread_ptr) + * that it is past the point of blocking. */ + while (thread != INVALID_HANDLE_VALUE) { + BOOL r = CancelSynchronousIo(thread); + assert(r || GetLastError() == ERROR_NOT_FOUND); + LeaveCriticalSection(lock); + SwitchToThread(); + EnterCriticalSection(lock); + thread = *thread_ptr; + } + } + + LeaveCriticalSection(lock); +} + + +void uv__pipe_interrupt_read(uv_pipe_t* handle) { if (!(handle->flags & UV_HANDLE_READ_PENDING)) return; /* No pending reads. */ - if (handle->flags & UV_HANDLE_CANCELLATION_PENDING) + if (handle->flags & UV_HANDLE_READ_CANCELLATION_PENDING) return; /* Already cancelled. */ if (handle->handle == INVALID_HANDLE_VALUE) return; /* Pipe handle closed. */ if (!(handle->flags & UV_HANDLE_NON_OVERLAPPED_PIPE)) { /* Cancel asynchronous read. */ - r = CancelIoEx(handle->handle, &handle->read_req.u.io.overlapped); + BOOL r = CancelIoEx(handle->handle, &handle->read_req.u.io.overlapped); assert(r || GetLastError() == ERROR_NOT_FOUND); (void) r; } else { - /* Cancel synchronous read (which is happening in the thread pool). */ - HANDLE thread; - volatile HANDLE* thread_ptr = &handle->pipe.conn.readfile_thread_handle; - - EnterCriticalSection(&handle->pipe.conn.readfile_thread_lock); - - thread = *thread_ptr; - if (thread == NULL) { - /* The thread pool thread has not yet reached the point of blocking, we - * can pre-empt it by setting thread_handle to INVALID_HANDLE_VALUE. */ - *thread_ptr = INVALID_HANDLE_VALUE; - - } else { - /* Spin until the thread has acknowledged (by setting the thread to - * INVALID_HANDLE_VALUE) that it is past the point of blocking. */ - while (thread != INVALID_HANDLE_VALUE) { - r = CancelSynchronousIo(thread); - assert(r || GetLastError() == ERROR_NOT_FOUND); - SwitchToThread(); /* Yield thread. */ - thread = *thread_ptr; - } - } - - LeaveCriticalSection(&handle->pipe.conn.readfile_thread_lock); + uv__pipe_cancel_synchronous_io( + &handle->pipe.conn.readfile_thread_handle, + &handle->pipe.conn.thread_lock); } /* Set flag to indicate that read has been cancelled. */ - handle->flags |= UV_HANDLE_CANCELLATION_PENDING; + handle->flags |= UV_HANDLE_READ_CANCELLATION_PENDING; } @@ -1051,6 +1117,110 @@ void uv__pipe_read_stop(uv_pipe_t* handle) { } +/* Remove the element after prev from the non-overlapped write queue. + * prev must be a node in the queue. Returns the removed element. */ +static uv_write_t* uv__remove_non_overlapped_write_req_after( + uv_pipe_t* handle, uv_write_t* prev) { + uv_write_t* req; + + req = (uv_write_t*) prev->next_req; + if (req == prev) { + /* Only element. */ + handle->pipe.conn.non_overlapped_writes_tail = NULL; + } else { + prev->next_req = req->next_req; + if (req == handle->pipe.conn.non_overlapped_writes_tail) + handle->pipe.conn.non_overlapped_writes_tail = prev; + } + return req; +} + + +static uv_write_t* uv__remove_non_overlapped_write_req(uv_pipe_t* handle) { + if (handle->pipe.conn.non_overlapped_writes_tail == NULL) + return NULL; + + return uv__remove_non_overlapped_write_req_after( + handle, handle->pipe.conn.non_overlapped_writes_tail); +} + + +/* Find and remove a specific request from the non-overlapped write queue. + * Returns the queued req if found and removed, NULL otherwise. */ +static uv_write_t* uv__remove_specific_non_overlapped_write_req( + uv_pipe_t* handle, uv_write_t* target) { + uv_write_t* tail; + uv_write_t* prev; + uv_write_t* curr; + + tail = handle->pipe.conn.non_overlapped_writes_tail; + if (tail == NULL) + return NULL; + + prev = tail; + curr = (uv_write_t*) tail->next_req; + do { + if (curr == target) + return uv__remove_non_overlapped_write_req_after(handle, prev); + + prev = curr; + curr = (uv_write_t*) curr->next_req; + } while (prev != tail); + + return NULL; +} + + +int uv__pipe_write_cancel_non_overlapped(uv_pipe_t* handle, uv_write_t* req) { + uv_write_t* queued_req; + + assert(handle->flags & UV_HANDLE_NON_OVERLAPPED_PIPE); + assert(!(handle->flags & UV_HANDLE_BLOCKING_WRITES)); + + /* On the thread pool - send the cancellation signal. */ + if (handle->pipe.conn.non_overlapped_write_active == req) { + /* N.B.: It's possible to end up here multiple times if `req` is cancelled + * again after an initial cancellation, but before the completion is processed. + * This is harmless - uv__pipe_cancel_synchronous_io will see that the thread + * has concluded and do nothing. */ + uv__pipe_cancel_synchronous_io( + &handle->pipe.conn.writefile_thread_handle, + &handle->pipe.conn.thread_lock); + return 0; + } + + /* If it's in the queue, remove and complete it as cancelled. */ + queued_req = uv__remove_specific_non_overlapped_write_req(handle, req); + if (queued_req != NULL) { + SET_REQ_ERROR(queued_req, ERROR_OPERATION_ABORTED); + SET_REQ_NWRITTEN(queued_req, 0); + uv__insert_pending_req(handle->loop, (uv_req_t*) queued_req); + return 0; + } + + /* Already completed (including previously cancelled). */ + return 0; +} + +/* Cancel all active and pending non-overlapped writes. */ +static void uv__pipe_flush_non_overlapped_writes(uv_pipe_t* handle) { + uv_write_t* req; + + /* Cancel the active write if there is one. */ + if (handle->pipe.conn.non_overlapped_write_active != NULL) + uv__pipe_cancel_synchronous_io( + &handle->pipe.conn.writefile_thread_handle, + &handle->pipe.conn.thread_lock); + + /* Drain the entire queue. */ + while ((req = uv__remove_non_overlapped_write_req(handle)) != NULL) { + SET_REQ_ERROR(req, ERROR_OPERATION_ABORTED); + SET_REQ_NWRITTEN(req, 0); + uv__insert_pending_req(handle->loop, (uv_req_t*) req); + } +} + + /* Cleans up uv_pipe_t (server or connection) and all resources associated with * it. */ void uv__pipe_close(uv_loop_t* loop, uv_pipe_t* handle) { @@ -1079,11 +1249,15 @@ void uv__pipe_close(uv_loop_t* loop, uv_pipe_t* handle) { } if (handle->flags & UV_HANDLE_PIPESERVER) { - for (i = 0; i < handle->pipe.serv.pending_instances; i++) { - pipeHandle = handle->pipe.serv.accept_reqs[i].pipeHandle; - if (pipeHandle != INVALID_HANDLE_VALUE) { - CloseHandle(pipeHandle); - handle->pipe.serv.accept_reqs[i].pipeHandle = INVALID_HANDLE_VALUE; + /* accept_reqs is NULL when uv_pipe_pending_instances() was called but + * the pipe was never successfully bound. */ + if (handle->pipe.serv.accept_reqs != NULL) { + for (i = 0; i < handle->pipe.serv.pending_instances; i++) { + pipeHandle = handle->pipe.serv.accept_reqs[i].pipeHandle; + if (pipeHandle != INVALID_HANDLE_VALUE) { + CloseHandle(pipeHandle); + handle->pipe.serv.accept_reqs[i].pipeHandle = INVALID_HANDLE_VALUE; + } } } handle->handle = INVALID_HANDLE_VALUE; @@ -1095,6 +1269,15 @@ void uv__pipe_close(uv_loop_t* loop, uv_pipe_t* handle) { if ((handle->flags & UV_HANDLE_CONNECTION) && handle->handle != INVALID_HANDLE_VALUE) { + if (handle->flags & UV_HANDLE_NON_OVERLAPPED_PIPE) { + /* The non-overlapped writer thread reads handle->handle. To avoid + * the possibility of a stale value, we must shoot it down now before we + * close the handle. While we're at it, cancel the whole queue, to + * avoid putting things on the thread pool only for them to come straight + * back with an invalid handle error. */ + uv__pipe_flush_non_overlapped_writes(handle); + } + /* This will eventually destroy the write queue for us too. */ close_pipe(handle); } @@ -1106,6 +1289,8 @@ void uv__pipe_close(uv_loop_t* loop, uv_pipe_t* handle) { static void uv__pipe_queue_accept(uv_loop_t* loop, uv_pipe_t* handle, uv_pipe_accept_t* req, BOOL firstInstance) { + BOOL success; + assert(handle->flags & UV_HANDLE_LISTENING); if (!firstInstance && !pipe_alloc_accept(loop, handle, req, FALSE)) { @@ -1120,22 +1305,22 @@ static void uv__pipe_queue_accept(uv_loop_t* loop, uv_pipe_t* handle, /* Prepare the overlapped structure. */ memset(&(req->u.io.overlapped), 0, sizeof(req->u.io.overlapped)); - if (!ConnectNamedPipe(req->pipeHandle, &req->u.io.overlapped) && - GetLastError() != ERROR_IO_PENDING) { + success = ConnectNamedPipe(req->pipeHandle, &req->u.io.overlapped); + + if (UV_SUCCEEDED_WITHOUT_IOCP(success)) { + /* Process the req without IOCP. */ + SET_REQ_SUCCESS(req); + uv__insert_pending_req(loop, (uv_req_t*) req); + } else if (!UV_SUCCEEDED_WITH_IOCP(success)) { if (GetLastError() == ERROR_PIPE_CONNECTED) { SET_REQ_SUCCESS(req); } else { CloseHandle(req->pipeHandle); req->pipeHandle = INVALID_HANDLE_VALUE; - /* Make this req pending reporting an error. */ SET_REQ_ERROR(req, GetLastError()); } uv__insert_pending_req(loop, (uv_req_t*) req); - handle->reqs_pending++; - return; } - - /* Wait for completion via IOCP */ handle->reqs_pending++; } @@ -1183,6 +1368,7 @@ int uv__pipe_accept(uv_pipe_t* server, uv_stream_t* client) { /* Initialize the client handle and copy the pipeHandle to the client */ pipe_client->handle = req->pipeHandle; pipe_client->flags |= UV_HANDLE_READABLE | UV_HANDLE_WRITABLE; + pipe_client->flags |= UV_HANDLE_SYNC_BYPASS_IOCP; /* Prepare the req to pick up a new connection */ server->pipe.serv.pending_accepts = req->next_pending; @@ -1239,22 +1425,16 @@ int uv__pipe_listen(uv_pipe_t* handle, int backlog, uv_connection_cb cb) { } -static DWORD WINAPI uv_pipe_zero_readfile_thread_proc(void* arg) { - uv_read_t* req = (uv_read_t*) arg; - uv_pipe_t* handle = (uv_pipe_t*) req->data; - uv_loop_t* loop = handle->loop; - volatile HANDLE* thread_ptr = &handle->pipe.conn.readfile_thread_handle; - CRITICAL_SECTION* lock = &handle->pipe.conn.readfile_thread_lock; +/* Register the current thread for cancellation via CancelSynchronousIo. + * Returns 0 on success, or a Windows error code on failure (including + * ERROR_OPERATION_ABORTED if pre-empted by uv__pipe_cancel_synchronous_io). + * On success, the caller must call uv__pipe_end_synchronous_io after the + * blocking operation. On failure, the caller must not. */ +static DWORD uv__pipe_begin_synchronous_io(volatile HANDLE* thread_ptr, + CRITICAL_SECTION* lock, + HANDLE* thread_out) { HANDLE thread; - DWORD bytes; - DWORD err; - - assert(req->type == UV_READ); - assert(handle->type == UV_NAMED_PIPE); - - err = 0; - /* Create a handle to the current thread. */ if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), @@ -1262,46 +1442,74 @@ static DWORD WINAPI uv_pipe_zero_readfile_thread_proc(void* arg) { 0, FALSE, DUPLICATE_SAME_ACCESS)) { - err = GetLastError(); - goto out1; + /* Even if we failed here, we still need to synchronize */ + DWORD err = GetLastError(); + EnterCriticalSection(lock); + *thread_ptr = *thread_ptr == INVALID_HANDLE_VALUE ? NULL : INVALID_HANDLE_VALUE; + LeaveCriticalSection(lock); + WakeByAddressSingle((HANDLE*) thread_ptr); + return err; } - /* The lock needs to be held when thread handle is modified. */ EnterCriticalSection(lock); if (*thread_ptr == INVALID_HANDLE_VALUE) { - /* uv__pipe_interrupt_read() cancelled reading before we got here. */ - err = ERROR_OPERATION_ABORTED; - } else { - /* Let main thread know which worker thread is doing the blocking read. */ - assert(*thread_ptr == NULL); - *thread_ptr = thread; - } + /* Cancelled before we got here. */ + /* Acknowledge the cancellation and wake the waiting canceller. */ + *thread_ptr = NULL; + LeaveCriticalSection(lock); + WakeByAddressSingle((HANDLE*) thread_ptr); + CloseHandle(thread); + return ERROR_OPERATION_ABORTED; + } + assert(*thread_ptr == NULL); + *thread_ptr = thread; LeaveCriticalSection(lock); - if (err) - goto out2; + *thread_out = thread; + return 0; +} - /* Block the thread until data is available on the pipe, or the read is - * cancelled. */ - if (!ReadFile(handle->handle, &uv_zero_, 0, &bytes, NULL)) - err = GetLastError(); - /* Let the main thread know the worker is past the point of blocking. */ +/* Deregister the current thread after a blocking operation and synchronize + * with any in-progress cancellation. */ +static void uv__pipe_end_synchronous_io(volatile HANDLE* thread_ptr, + CRITICAL_SECTION* lock, + HANDLE thread) { + /* Acquire the lock, set the thread handle to INVALID_HANDLE_VALUE to signal + * completion, then release. This synchronizes with the cancellation spin + * loop which releases the lock around SwitchToThread(). */ + EnterCriticalSection(lock); assert(thread == *thread_ptr); *thread_ptr = INVALID_HANDLE_VALUE; - - /* Briefly acquire the mutex. Since the main thread holds the lock while it - * is spinning trying to cancel this thread's I/O, we will block here until - * it stops doing that. */ - EnterCriticalSection(lock); LeaveCriticalSection(lock); -out2: - /* Close the handle to the current thread. */ CloseHandle(thread); +} -out1: - /* Set request status and post a completion record to the IOCP. */ + +static DWORD WINAPI uv_pipe_zero_readfile_thread_proc(void* arg) { + uv_read_t* req = (uv_read_t*) arg; + uv_pipe_t* handle = (uv_pipe_t*) req->data; + uv_loop_t* loop = handle->loop; + volatile HANDLE* thread_ptr = &handle->pipe.conn.readfile_thread_handle; + CRITICAL_SECTION* lock = &handle->pipe.conn.thread_lock; + HANDLE thread; + DWORD bytes; + DWORD err; + + assert(req->type == UV_READ); + assert(handle->type == UV_NAMED_PIPE); + + err = uv__pipe_begin_synchronous_io(thread_ptr, lock, &thread); + if (err) + goto done; + + if (!ReadFile(handle->handle, &uv_zero_, 0, &bytes, NULL)) + err = GetLastError(); + + uv__pipe_end_synchronous_io(thread_ptr, lock, thread); + +done: if (err) SET_REQ_ERROR(req, err); else @@ -1314,24 +1522,44 @@ static DWORD WINAPI uv_pipe_zero_readfile_thread_proc(void* arg) { static DWORD WINAPI uv_pipe_writefile_thread_proc(void* parameter) { int result; + HANDLE thread; DWORD bytes; + DWORD err; uv_write_t* req = (uv_write_t*) parameter; uv_pipe_t* handle = (uv_pipe_t*) req->handle; uv_loop_t* loop = handle->loop; + volatile HANDLE* thread_ptr = &handle->pipe.conn.writefile_thread_handle; + CRITICAL_SECTION* lock = &handle->pipe.conn.thread_lock; assert(req != NULL); assert(req->type == UV_WRITE); assert(handle->type == UV_NAMED_PIPE); + bytes = 0; + + err = uv__pipe_begin_synchronous_io(thread_ptr, lock, &thread); + if (err) + goto done; + result = WriteFile(handle->handle, req->write_buffer.base, req->write_buffer.len, &bytes, NULL); - if (!result) { - SET_REQ_ERROR(req, GetLastError()); - } + if (!result) + err = GetLastError(); + + uv__pipe_end_synchronous_io(thread_ptr, lock, thread); + +done: + /* If CancelSynchronousIo fired after WriteFile already finished writing + * all the data, treat it as a successful write rather than a cancellation. */ + if (err == ERROR_OPERATION_ABORTED && bytes == req->write_buffer.len) + err = 0; + if (err) + SET_REQ_ERROR(req, err); + SET_REQ_NWRITTEN(req, bytes); POST_COMPLETION_FOR_REQ(loop, req); return 0; @@ -1388,12 +1616,11 @@ static void uv__pipe_queue_read(uv_loop_t* loop, uv_pipe_t* handle) { req = &handle->read_req; if (handle->flags & UV_HANDLE_NON_OVERLAPPED_PIPE) { + assert(handle->pipe.conn.readfile_thread_handle == INVALID_HANDLE_VALUE); handle->pipe.conn.readfile_thread_handle = NULL; /* Reset cancellation. */ if (!QueueUserWorkItem(&uv_pipe_zero_readfile_thread_proc, req, WT_EXECUTELONGFUNCTION)) { - /* Make this req pending reporting an error. */ - SET_REQ_ERROR(req, GetLastError()); goto error; } } else { @@ -1410,18 +1637,15 @@ static void uv__pipe_queue_read(uv_loop_t* loop, uv_pipe_t* handle) { NULL, &req->u.io.overlapped); - if (!result && GetLastError() != ERROR_IO_PENDING) { - /* Make this req pending reporting an error. */ - SET_REQ_ERROR(req, GetLastError()); + if (UV_SUCCEEDED_WITHOUT_IOCP(result)) { + uv__insert_pending_req(loop, (uv_req_t*) req); + } else if (!UV_SUCCEEDED_WITH_IOCP(result)) { goto error; - } - - if (handle->flags & UV_HANDLE_EMULATE_IOCP) { + } else if (handle->flags & UV_HANDLE_EMULATE_IOCP) { assert(req->wait_handle == INVALID_HANDLE_VALUE); if (!RegisterWaitForSingleObject(&req->wait_handle, req->event_handle, post_completion_read_wait, (void*) req, INFINITE, WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE)) { - SET_REQ_ERROR(req, GetLastError()); goto error; } } @@ -1434,6 +1658,8 @@ static void uv__pipe_queue_read(uv_loop_t* loop, uv_pipe_t* handle) { return; error: + /* Make this req pending reporting an error. */ + SET_REQ_ERROR(req, GetLastError()); uv__insert_pending_req(loop, (uv_req_t*)req); handle->flags |= UV_HANDLE_READ_PENDING; handle->reqs_pending++; @@ -1482,30 +1708,13 @@ static void uv__insert_non_overlapped_write_req(uv_pipe_t* handle, } -static uv_write_t* uv_remove_non_overlapped_write_req(uv_pipe_t* handle) { - uv_write_t* req; - - if (handle->pipe.conn.non_overlapped_writes_tail) { - req = (uv_write_t*)handle->pipe.conn.non_overlapped_writes_tail->next_req; - - if (req == handle->pipe.conn.non_overlapped_writes_tail) { - handle->pipe.conn.non_overlapped_writes_tail = NULL; - } else { - handle->pipe.conn.non_overlapped_writes_tail->next_req = - req->next_req; - } - - return req; - } else { - /* queue empty */ - return NULL; - } -} - - static void uv__queue_non_overlapped_write(uv_pipe_t* handle) { - uv_write_t* req = uv_remove_non_overlapped_write_req(handle); + uv_write_t* req = uv__remove_non_overlapped_write_req(handle); if (req) { + assert(handle->pipe.conn.non_overlapped_write_active == NULL); + assert(handle->pipe.conn.writefile_thread_handle == INVALID_HANDLE_VALUE); + handle->pipe.conn.non_overlapped_write_active = req; + handle->pipe.conn.writefile_thread_handle = NULL; /* Reset cancellation. */ if (!QueueUserWorkItem(&uv_pipe_writefile_thread_proc, req, WT_EXECUTELONGFUNCTION)) { @@ -1515,20 +1724,13 @@ static void uv__queue_non_overlapped_write(uv_pipe_t* handle) { } -static int uv__build_coalesced_write_req(uv_write_t* user_req, - const uv_buf_t bufs[], - size_t nbufs, - uv_write_t** req_out, - uv_buf_t* write_buf_out) { - /* Pack into a single heap-allocated buffer: - * (a) a uv_write_t structure where libuv stores the actual state. - * (b) a pointer to the original uv_write_t. - * (c) data from all `bufs` entries. - */ +static int uv__coalesce_write_bufs(const uv_buf_t bufs[], + size_t nbufs, + uv_buf_t* write_buf_out) { + /* Merge the data from all `bufs` entries into a single heap-allocated + * buffer, freed when the write completion is processed. */ char* heap_buffer; - size_t heap_buffer_length, heap_buffer_offset; - uv__coalesced_write_t* coalesced_write_req; /* (a) + (b) */ - char* data_start; /* (c) */ + size_t heap_buffer_offset; size_t data_length; unsigned int i; @@ -1542,35 +1744,26 @@ static int uv__build_coalesced_write_req(uv_write_t* user_req, if (data_length > UINT32_MAX) return WSAENOBUFS; /* Maps to UV_ENOBUFS. */ - /* Compute heap buffer size. */ - heap_buffer_length = sizeof *coalesced_write_req + /* (a) + (b) */ - data_length; /* (c) */ + /* All buffers empty: write nothing. */ + if (data_length == 0) { + *write_buf_out = uv_null_buf_; + return 0; + } /* Allocate buffer. */ - heap_buffer = uv__malloc(heap_buffer_length); + heap_buffer = uv__malloc(data_length); if (heap_buffer == NULL) return ERROR_NOT_ENOUGH_MEMORY; /* Maps to UV_ENOMEM. */ - /* Copy uv_write_t information to the buffer. */ - coalesced_write_req = (uv__coalesced_write_t*) heap_buffer; - coalesced_write_req->req = *user_req; /* copy (a) */ - coalesced_write_req->req.coalesced = 1; - coalesced_write_req->user_req = user_req; /* copy (b) */ - heap_buffer_offset = sizeof *coalesced_write_req; /* offset (a) + (b) */ - /* Copy data buffers to the heap buffer. */ - data_start = &heap_buffer[heap_buffer_offset]; + heap_buffer_offset = 0; for (i = 0; i < nbufs; i++) { - memcpy(&heap_buffer[heap_buffer_offset], - bufs[i].base, - bufs[i].len); /* copy (c) */ - heap_buffer_offset += bufs[i].len; /* offset (c) */ + memcpy(&heap_buffer[heap_buffer_offset], bufs[i].base, bufs[i].len); + heap_buffer_offset += bufs[i].len; } - assert(heap_buffer_offset == heap_buffer_length); + assert(heap_buffer_offset == data_length); - /* Set out arguments and return. */ - *req_out = &coalesced_write_req->req; - *write_buf_out = uv_buf_init(data_start, (unsigned int) data_length); + *write_buf_out = uv_buf_init(heap_buffer, (unsigned int) data_length); return 0; } @@ -1592,6 +1785,7 @@ static int uv__pipe_write_data(uv_loop_t* loop, req->handle = (uv_stream_t*) handle; req->send_handle = NULL; req->cb = cb; + req->write_extra.nwritten = 0; /* Private fields. */ req->coalesced = 0; req->event_handle = NULL; @@ -1615,13 +1809,18 @@ static int uv__pipe_write_data(uv_loop_t* loop, /* Write directly from bufs[0]. */ write_buf = bufs[0]; } else { - /* Coalesce all `bufs` into one big buffer. This also creates a new - * write-request structure that replaces the old one. */ - err = uv__build_coalesced_write_req(req, bufs, nbufs, &req, &write_buf); + /* Coalesce all `bufs` into one big heap-allocated buffer, owned by the + * request and freed when its completion is processed. */ + err = uv__coalesce_write_bufs(bufs, nbufs, &write_buf); if (err != 0) return err; + req->coalesced = 1; + req->write_buffer = write_buf; } + if (write_buf.len > UV__IO_MAX_BYTES) + return ERROR_INVALID_PARAMETER; /* Maps to UV_EINVAL. */ + if ((handle->flags & (UV_HANDLE_BLOCKING_WRITES | UV_HANDLE_NON_OVERLAPPED_PIPE)) == (UV_HANDLE_BLOCKING_WRITES | UV_HANDLE_NON_OVERLAPPED_PIPE)) { @@ -1637,16 +1836,21 @@ static int uv__pipe_write_data(uv_loop_t* loop, req->u.io.queued_bytes = 0; } + SET_REQ_NWRITTEN(req, bytes); REGISTER_HANDLE_REQ(loop, handle); handle->reqs_pending++; handle->stream.conn.write_reqs_pending++; - POST_COMPLETION_FOR_REQ(loop, req); + uv__insert_pending_req(loop, (uv_req_t*)req); return 0; } else if (handle->flags & UV_HANDLE_NON_OVERLAPPED_PIPE) { req->write_buffer = write_buf; uv__insert_non_overlapped_write_req(handle, req); if (handle->stream.conn.write_reqs_pending == 0) { uv__queue_non_overlapped_write(handle); + /* There shouldn't have been any queued writes before, so we should have + * dispatched the request we just added. Sanity check the state + * synchronization here. */ + assert(handle->pipe.conn.non_overlapped_writes_tail == NULL); } /* Request queued by the kernel. */ @@ -1695,21 +1899,18 @@ static int uv__pipe_write_data(uv_loop_t* loop, write_buf.len, NULL, &req->u.io.overlapped); - - if (!result && GetLastError() != ERROR_IO_PENDING) { - return GetLastError(); - } - if (result) { - /* Request completed immediately. */ req->u.io.queued_bytes = 0; } else { /* Request queued by the kernel. */ req->u.io.queued_bytes = write_buf.len; handle->write_queue_size += req->u.io.queued_bytes; } - - if (handle->flags & UV_HANDLE_EMULATE_IOCP) { + if (UV_SUCCEEDED_WITHOUT_IOCP(result)) { + uv__insert_pending_req(loop, (uv_req_t*) req); + } else if (!UV_SUCCEEDED_WITH_IOCP(result)) { + return GetLastError(); + } else if (handle->flags & UV_HANDLE_EMULATE_IOCP) { if (!RegisterWaitForSingleObject(&req->wait_handle, req->event_handle, post_completion_write_wait, (void*) req, INFINITE, WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE)) { @@ -1968,8 +2169,11 @@ static int uv__pipe_read_data(uv_loop_t* loop, /* Ensure we read at most the smaller of: * (a) the length of the user-allocated buffer. * (b) the maximum data length as specified by the `max_bytes` argument. - * (c) the amount of data that can be read non-blocking + * (c) the amount of data that can be read non-blocking. + * (d) UV__IO_MAX_BYTES. */ + if (buf.len > UV__IO_MAX_BYTES) + buf.len = UV__IO_MAX_BYTES; if (max_bytes > buf.len) max_bytes = buf.len; @@ -2121,7 +2325,7 @@ void uv__process_pipe_read_req(uv_loop_t* loop, DWORD bytes_requested; assert(handle->type == UV_NAMED_PIPE); - handle->flags &= ~(UV_HANDLE_READ_PENDING | UV_HANDLE_CANCELLATION_PENDING); + handle->flags &= ~(UV_HANDLE_READ_PENDING | UV_HANDLE_READ_CANCELLATION_PENDING); DECREASE_PENDING_REQ_COUNT(handle); eof_timer_stop(handle); @@ -2176,11 +2380,15 @@ void uv__process_pipe_read_req(uv_loop_t* loop, void uv__process_pipe_write_req(uv_loop_t* loop, uv_pipe_t* handle, uv_write_t* req) { int err; + size_t bytes_written; assert(handle->type == UV_NAMED_PIPE); assert(handle->write_queue_size >= req->u.io.queued_bytes); handle->write_queue_size -= req->u.io.queued_bytes; + /* Ask the kernel how many bytes were actually written. + * N.B.: If the write was partially cancelled, this could differ from queued_bytes. */ + bytes_written = req->u.io.overlapped.InternalHigh; UNREGISTER_HANDLE_REQ(loop, handle); @@ -2195,27 +2403,33 @@ void uv__process_pipe_write_req(uv_loop_t* loop, uv_pipe_t* handle, err = GET_REQ_ERROR(req); - /* If this was a coalesced write, extract pointer to the user_provided - * uv_write_t structure so we can pass the expected pointer to the callback, - * then free the heap-allocated write req. */ + /* For non-overlapped pipes, if this request was the active write + * (dispatched to the thread pool), clear the active slot and dispatch the + * next queued write. Queue-cancelled writes (removed by + * uv__pipe_write_cancel or flush) were never dispatched, so they must not + * trigger another dispatch. */ + if (handle->flags & UV_HANDLE_NON_OVERLAPPED_PIPE) { + if (req == handle->pipe.conn.non_overlapped_write_active) { + handle->pipe.conn.non_overlapped_write_active = NULL; + uv__queue_non_overlapped_write(handle); + } + } + + /* If this was a coalesced write, free the heap-allocated merged data + * buffer. */ if (req->coalesced) { - uv__coalesced_write_t* coalesced_write = - container_of(req, uv__coalesced_write_t, req); - req = coalesced_write->user_req; - uv__free(coalesced_write); + uv__free(req->write_buffer.base); + req->write_buffer = uv_null_buf_; } + + req->write_extra.nwritten += bytes_written; + if (req->cb) { req->cb(req, uv_translate_sys_error(err)); } handle->stream.conn.write_reqs_pending--; - if (handle->flags & UV_HANDLE_NON_OVERLAPPED_PIPE && - handle->pipe.conn.non_overlapped_writes_tail) { - assert(handle->stream.conn.write_reqs_pending > 0); - uv__queue_non_overlapped_write(handle); - } - if (handle->stream.conn.write_reqs_pending == 0 && uv__is_stream_shutting(handle)) uv__pipe_shutdown(loop, handle, handle->stream.conn.shutdown_req); diff --git a/deps/uv/src/win/process-stdio.c b/deps/uv/src/win/process-stdio.c index 181db92ea30..bd78f3fab98 100644 --- a/deps/uv/src/win/process-stdio.c +++ b/deps/uv/src/win/process-stdio.c @@ -215,7 +215,8 @@ int uv__stdio_create(uv_loop_t* loop, * handles in the stdio buffer are initialized with. * INVALID_HANDLE_VALUE, which should be okay. */ if (i <= 2) { - HANDLE nul; + /* Redundantly initialize to make the compiler happy. */ + HANDLE nul = INVALID_HANDLE_VALUE; DWORD access = (i == 0) ? FILE_GENERIC_READ : FILE_GENERIC_WRITE | FILE_READ_ATTRIBUTES; @@ -223,7 +224,7 @@ int uv__stdio_create(uv_loop_t* loop, if (err) goto error; - memcpy(CHILD_STDIO_HANDLE(buffer, i), &nul, sizeof(HANDLE)); + memcpy(CHILD_STDIO_HANDLE(buffer, i), &nul, sizeof(HANDLE)); CHILD_STDIO_CRT_FLAGS(buffer, i) = FOPEN | FDEV; } break; @@ -248,7 +249,7 @@ int uv__stdio_create(uv_loop_t* loop, if (err) goto error; - memcpy(CHILD_STDIO_HANDLE(buffer, i), &child_pipe, sizeof(HANDLE)); + memcpy(CHILD_STDIO_HANDLE(buffer, i), &child_pipe, sizeof(HANDLE)); CHILD_STDIO_CRT_FLAGS(buffer, i) = FOPEN | FPIPE; break; } @@ -299,7 +300,7 @@ int uv__stdio_create(uv_loop_t* loop, return -1; } - memcpy(CHILD_STDIO_HANDLE(buffer, i), &child_handle, sizeof(HANDLE)); + memcpy(CHILD_STDIO_HANDLE(buffer, i), &child_handle, sizeof(HANDLE)); break; } @@ -335,7 +336,7 @@ int uv__stdio_create(uv_loop_t* loop, if (err) goto error; - memcpy(CHILD_STDIO_HANDLE(buffer, i), &child_handle, sizeof(HANDLE)); + memcpy(CHILD_STDIO_HANDLE(buffer, i), &child_handle, sizeof(HANDLE)); CHILD_STDIO_CRT_FLAGS(buffer, i) = crt_flags; break; } diff --git a/deps/uv/src/win/process.c b/deps/uv/src/win/process.c index 45e85ed9613..86234b0be11 100644 --- a/deps/uv/src/win/process.c +++ b/deps/uv/src/win/process.c @@ -546,7 +546,7 @@ int make_program_args(char** args, int verbatim_arguments, WCHAR** dst_ptr) { /* Adjust for potential quotes. Also assume the worst-case scenario that * every character needs escaping, so we need twice as much space. */ - dst_len = dst_len * 2 + arg_count * 2; + dst_len = (dst_len * 2 + arg_count * 2) + 1; /* Allocate buffer for the final command line. */ dst = uv__malloc(dst_len * sizeof(WCHAR)); @@ -554,39 +554,42 @@ int make_program_args(char** args, int verbatim_arguments, WCHAR** dst_ptr) { err = UV_ENOMEM; goto error; } + + dst[0] = '\0'; + if (arg_count > 0) { + /* Allocate temporary working buffer. */ + temp_buffer = uv__malloc(temp_buffer_len * sizeof(WCHAR)); + if (temp_buffer == NULL) { + err = UV_ENOMEM; + goto error; + } - /* Allocate temporary working buffer. */ - temp_buffer = uv__malloc(temp_buffer_len * sizeof(WCHAR)); - if (temp_buffer == NULL) { - err = UV_ENOMEM; - goto error; - } - - pos = dst; - for (arg = args; *arg; arg++) { - ssize_t arg_len; + pos = dst; + for (arg = args; *arg; arg++) { + ssize_t arg_len; + + /* Convert argument to wide char. */ + arg_len = uv_wtf8_length_as_utf16(*arg); + assert(arg_len > 0); + assert(temp_buffer_len >= (size_t) arg_len); + uv_wtf8_to_utf16(*arg, temp_buffer, arg_len); + + if (verbatim_arguments) { + /* Copy verbatim. */ + wcscpy(pos, temp_buffer); + pos += arg_len - 1; + } else { + /* Quote/escape, if needed. */ + pos = quote_cmd_arg(temp_buffer, pos); + } - /* Convert argument to wide char. */ - arg_len = uv_wtf8_length_as_utf16(*arg); - assert(arg_len > 0); - assert(temp_buffer_len >= (size_t) arg_len); - uv_wtf8_to_utf16(*arg, temp_buffer, arg_len); - - if (verbatim_arguments) { - /* Copy verbatim. */ - wcscpy(pos, temp_buffer); - pos += arg_len - 1; - } else { - /* Quote/escape, if needed. */ - pos = quote_cmd_arg(temp_buffer, pos); + *pos++ = *(arg + 1) ? L' ' : L'\0'; + assert(pos <= dst + dst_len); } - *pos++ = *(arg + 1) ? L' ' : L'\0'; - assert(pos <= dst + dst_len); + uv__free(temp_buffer); } - uv__free(temp_buffer); - *dst_ptr = dst; return 0; @@ -836,10 +839,6 @@ void uv__process_proc_exit(uv_loop_t* loop, uv_process_t* handle) { handle->wait_handle = INVALID_HANDLE_VALUE; } - /* Set the handle to inactive: no callbacks will be made after the exit - * callback. */ - uv__handle_stop(handle); - if (GetExitCodeProcess(handle->process_handle, &status)) { exit_code = status; } else { @@ -847,6 +846,15 @@ void uv__process_proc_exit(uv_loop_t* loop, uv_process_t* handle) { exit_code = uv_translate_sys_error(GetLastError()); } + /* Clean-up the process handle eagerly. */ + CloseHandle(handle->process_handle); + handle->process_handle = INVALID_HANDLE_VALUE; + handle->flags |= UV_HANDLE_ESRCH; + + /* Set the handle to inactive: no callbacks will be made after the exit + * callback. */ + uv__handle_stop(handle); + /* Fire the exit callback. */ if (handle->exit_cb) { handle->exit_cb(handle, exit_code, handle->exit_signal); @@ -881,7 +889,8 @@ void uv__process_endgame(uv_loop_t* loop, uv_process_t* handle) { assert(!(handle->flags & UV_HANDLE_CLOSED)); /* Clean-up the process handle. */ - CloseHandle(handle->process_handle); + if (handle->process_handle != INVALID_HANDLE_VALUE) + CloseHandle(handle->process_handle); uv__handle_close(handle); } @@ -896,14 +905,20 @@ int uv_spawn(uv_loop_t* loop, BOOL result; WCHAR* application_path = NULL, *application = NULL, *arguments = NULL, *env = NULL, *cwd = NULL; - STARTUPINFOW startup; + STARTUPINFOEXW startup; PROCESS_INFORMATION info; DWORD process_flags, cwd_len; BYTE* child_stdio_buffer; + HANDLE* handle_list; + LPPROC_THREAD_ATTRIBUTE_LIST attr_list; + int attr_list_initialized; uv__process_init(loop, process); process->exit_cb = options->exit_cb; child_stdio_buffer = NULL; + handle_list = NULL; + attr_list = NULL; + attr_list_initialized = 0; if (options->flags & (UV_PROCESS_SETGID | UV_PROCESS_SETUID)) { return UV_ENOTSUP; @@ -1016,20 +1031,70 @@ int uv_spawn(uv_loop_t* loop, goto done; } - startup.cb = sizeof(startup); - startup.lpReserved = NULL; - startup.lpDesktop = NULL; - startup.lpTitle = NULL; - startup.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + memset(&startup, 0, sizeof startup); + startup.StartupInfo.cb = sizeof(startup); + startup.StartupInfo.lpReserved = NULL; + startup.StartupInfo.lpDesktop = NULL; + startup.StartupInfo.lpTitle = NULL; + startup.StartupInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + + startup.StartupInfo.cbReserved2 = uv__stdio_size(child_stdio_buffer); + startup.StartupInfo.lpReserved2 = (BYTE*) child_stdio_buffer; + + startup.StartupInfo.hStdInput = uv__stdio_handle(child_stdio_buffer, 0); + startup.StartupInfo.hStdOutput = uv__stdio_handle(child_stdio_buffer, 1); + startup.StartupInfo.hStdError = uv__stdio_handle(child_stdio_buffer, 2); + + /* Build the list of handles to inherit. Using + * PROC_THREAD_ATTRIBUTE_HANDLE_LIST ensures only these specific handles are + * inherited, closing the race condition where concurrent uv_spawn calls + * could cause handles intended for one child to leak into another. */ + { +#define CHILD_STDIO_COUNT(buffer) \ + *((unsigned int*) (buffer)) + int count = CHILD_STDIO_COUNT(child_stdio_buffer); +#undef CHILD_STDIO_COUNT + int n = 0; + SIZE_T attr_size = 0; + + handle_list = (HANDLE*) uv__malloc(count * sizeof(HANDLE)); + if (handle_list == NULL) { + err = ERROR_OUTOFMEMORY; + goto done; + } + + for (i = 0; i < count; i++) { + HANDLE h = uv__stdio_handle(child_stdio_buffer, i); + if (h != INVALID_HANDLE_VALUE) + handle_list[n++] = h; + } - startup.cbReserved2 = uv__stdio_size(child_stdio_buffer); - startup.lpReserved2 = (BYTE*) child_stdio_buffer; + InitializeProcThreadAttributeList(NULL, 1, 0, &attr_size); + attr_list = (LPPROC_THREAD_ATTRIBUTE_LIST) uv__malloc(attr_size); + if (attr_list == NULL) { + err = ERROR_OUTOFMEMORY; + goto done; + } + if (!InitializeProcThreadAttributeList(attr_list, 1, 0, &attr_size)) { + err = GetLastError(); + goto done; + } + attr_list_initialized = 1; + if (!UpdateProcThreadAttribute(attr_list, + 0, + PROC_THREAD_ATTRIBUTE_HANDLE_LIST, + handle_list, + n * sizeof(HANDLE), + NULL, + NULL)) { + err = GetLastError(); + goto done; + } + } - startup.hStdInput = uv__stdio_handle(child_stdio_buffer, 0); - startup.hStdOutput = uv__stdio_handle(child_stdio_buffer, 1); - startup.hStdError = uv__stdio_handle(child_stdio_buffer, 2); + startup.lpAttributeList = attr_list; - process_flags = CREATE_UNICODE_ENVIRONMENT; + process_flags = CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT; if ((options->flags & UV_PROCESS_WINDOWS_HIDE_CONSOLE) || (options->flags & UV_PROCESS_WINDOWS_HIDE)) { @@ -1044,9 +1109,9 @@ int uv_spawn(uv_loop_t* loop, if ((options->flags & UV_PROCESS_WINDOWS_HIDE_GUI) || (options->flags & UV_PROCESS_WINDOWS_HIDE)) { /* Use SW_HIDE to avoid any potential process window. */ - startup.wShowWindow = SW_HIDE; + startup.StartupInfo.wShowWindow = SW_HIDE; } else { - startup.wShowWindow = SW_SHOWDEFAULT; + startup.StartupInfo.wShowWindow = SW_SHOWDEFAULT; } if (options->flags & UV_PROCESS_DETACHED) { @@ -1072,7 +1137,7 @@ int uv_spawn(uv_loop_t* loop, process_flags, env, cwd, - &startup, + &startup.StartupInfo, &info)) { /* CreateProcessW failed. */ err = GetLastError(); @@ -1155,6 +1220,14 @@ int uv_spawn(uv_loop_t* loop, uv__free(env); uv__free(alloc_path); + if (attr_list != NULL) { + if (attr_list_initialized) + DeleteProcThreadAttributeList(attr_list); + uv__free(attr_list); + attr_list = NULL; + } + uv__free(handle_list); + if (child_stdio_buffer != NULL) { /* Clean up child stdio handles. */ uv__stdio_destroy(child_stdio_buffer); @@ -1179,11 +1252,12 @@ static int uv__kill(HANDLE process_handle, int signum) { * [0]: https://learn.microsoft.com/en-us/windows/win32/wer/collecting-user-mode-dumps */ if (signum == SIGQUIT) { HKEY registry_key; - DWORD pid, ret; - WCHAR basename[MAX_PATH]; + DWORD pid; + DWORD ret; + WCHAR basename[MAX_PATH] = L""; /* Get target process name. */ - GetModuleBaseNameW(process_handle, NULL, &basename[0], sizeof(basename)); + GetModuleBaseNameW(process_handle, NULL, &basename[0], ARRAY_SIZE(basename)); /* Get PID of target process. */ pid = GetProcessId(process_handle); @@ -1197,8 +1271,10 @@ static int uv__kill(HANDLE process_handle, int signum) { ®istry_key); if (ret == ERROR_SUCCESS) { HANDLE hDumpFile = NULL; - WCHAR dump_folder[MAX_PATH], dump_name[MAX_PATH]; - DWORD dump_folder_len = sizeof(dump_folder), key_type = 0; + WCHAR dump_folder[MAX_PATH] = L""; + WCHAR dump_name[MAX_PATH]; + DWORD dump_folder_len = sizeof(dump_folder); + DWORD key_type = 0; ret = RegGetValueW(registry_key, NULL, L"DumpFolder", @@ -1207,6 +1283,8 @@ static int uv__kill(HANDLE process_handle, int signum) { (PVOID) dump_folder, &dump_folder_len); if (ret != ERROR_SUCCESS) { + /* Discard any partial write from ERROR_MORE_DATA. */ + dump_folder[0] = L'\0'; /* Workaround for missing uuid.dll on MinGW. */ static const GUID FOLDERID_LocalAppData_libuv = { 0xf1b32785, 0x6fba, 0x4fcf, @@ -1214,79 +1292,82 @@ static int uv__kill(HANDLE process_handle, int signum) { }; /* Default value for `dump_folder` is `%LOCALAPPDATA%\CrashDumps`. */ - WCHAR* localappdata; - SHGetKnownFolderPath(&FOLDERID_LocalAppData_libuv, - 0, - NULL, - &localappdata); - _snwprintf_s(dump_folder, - ARRAY_SIZE(dump_folder), - _TRUNCATE, - L"%ls\\CrashDumps", - localappdata); + WCHAR* localappdata = NULL; + if (SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_LocalAppData_libuv, + 0, + NULL, + &localappdata))) { + _snwprintf_s(dump_folder, + ARRAY_SIZE(dump_folder), + _TRUNCATE, + L"%ls\\CrashDumps", + localappdata); + } CoTaskMemFree(localappdata); } RegCloseKey(registry_key); - /* Create dump folder if it doesn't already exist. */ - CreateDirectoryW(dump_folder, NULL); - - /* Construct dump filename from process name and PID. */ - _snwprintf_s(dump_name, - ARRAY_SIZE(dump_name), - _TRUNCATE, - L"%ls\\%ls.%d.dmp", - dump_folder, - basename, - pid); - - hDumpFile = CreateFileW(dump_name, - GENERIC_WRITE, - 0, - NULL, - CREATE_NEW, - FILE_ATTRIBUTE_NORMAL, - NULL); - if (hDumpFile != INVALID_HANDLE_VALUE) { - DWORD dump_options, sym_options; - FILE_DISPOSITION_INFO DeleteOnClose = { TRUE }; - - /* If something goes wrong while writing it out, delete the file. */ - SetFileInformationByHandle(hDumpFile, - FileDispositionInfo, - &DeleteOnClose, - sizeof(DeleteOnClose)); - - /* Tell wine to dump ELF modules as well. */ - sym_options = SymGetOptions(); - SymSetOptions(sym_options | 0x40000000); + if (dump_folder[0] != L'\0') { + /* Create dump folder if it doesn't already exist. */ + CreateDirectoryW(dump_folder, NULL); + + /* Construct dump filename from process name and PID. */ + _snwprintf_s(dump_name, + ARRAY_SIZE(dump_name), + _TRUNCATE, + L"%ls\\%ls.%d.dmp", + dump_folder, + basename, + pid); + + hDumpFile = CreateFileW(dump_name, + GENERIC_WRITE, + 0, + NULL, + CREATE_NEW, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (hDumpFile != INVALID_HANDLE_VALUE) { + DWORD dump_options, sym_options; + FILE_DISPOSITION_INFO DeleteOnClose = { TRUE }; + + /* If something goes wrong while writing it out, delete the file. */ + SetFileInformationByHandle(hDumpFile, + FileDispositionInfo, + &DeleteOnClose, + sizeof(DeleteOnClose)); + + /* Tell wine to dump ELF modules as well. */ + sym_options = SymGetOptions(); + SymSetOptions(sym_options | 0x40000000); /* MiniDumpWithAvxXStateContext might be undef in server2012r2 or mingw < 12 */ #ifndef MiniDumpWithAvxXStateContext #define MiniDumpWithAvxXStateContext 0x00200000 #endif - /* We default to a fairly complete dump. In the future, we may want to - * allow clients to customize what kind of dump to create. */ - dump_options = MiniDumpWithFullMemory | - MiniDumpIgnoreInaccessibleMemory | - MiniDumpWithAvxXStateContext; - - if (MiniDumpWriteDump(process_handle, - pid, - hDumpFile, - dump_options, - NULL, - NULL, - NULL)) { - /* Don't delete the file on close if we successfully wrote it out. */ - FILE_DISPOSITION_INFO DontDeleteOnClose = { FALSE }; - SetFileInformationByHandle(hDumpFile, - FileDispositionInfo, - &DontDeleteOnClose, - sizeof(DontDeleteOnClose)); + /* We default to a fairly complete dump. In the future, we may want to + * allow clients to customize what kind of dump to create. */ + dump_options = MiniDumpWithFullMemory | + MiniDumpIgnoreInaccessibleMemory | + MiniDumpWithAvxXStateContext; + + if (MiniDumpWriteDump(process_handle, + pid, + hDumpFile, + dump_options, + NULL, + NULL, + NULL)) { + /* Don't delete the file on close if we successfully wrote it out. */ + FILE_DISPOSITION_INFO DontDeleteOnClose = { FALSE }; + SetFileInformationByHandle(hDumpFile, + FileDispositionInfo, + &DontDeleteOnClose, + sizeof(DontDeleteOnClose)); + } + SymSetOptions(sym_options); + CloseHandle(hDumpFile); } - SymSetOptions(sym_options); - CloseHandle(hDumpFile); } } } @@ -1364,8 +1445,8 @@ static int uv__kill(HANDLE process_handle, int signum) { int uv_process_kill(uv_process_t* process, int signum) { int err; - if (process->process_handle == INVALID_HANDLE_VALUE) { - return UV_EINVAL; + if (process->flags & UV_HANDLE_ESRCH) { + return UV_ESRCH; } err = uv__kill(process->process_handle, signum); diff --git a/deps/uv/src/win/req-inl.h b/deps/uv/src/win/req-inl.h index af6fb752267..d9308eab0ac 100644 --- a/deps/uv/src/win/req-inl.h +++ b/deps/uv/src/win/req-inl.h @@ -34,6 +34,9 @@ #define SET_REQ_ERROR(req, error) \ SET_REQ_STATUS((req), NTSTATUS_FROM_WIN32((error))) +#define SET_REQ_NWRITTEN(req, nwritten) \ + (req)->u.io.overlapped.InternalHigh = (nwritten); + /* Note: used open-coded in UV_REQ_INIT() because of a circular dependency * between src/uv-common.h and src/win/internal.h. */ diff --git a/deps/uv/src/win/stream.c b/deps/uv/src/win/stream.c index ac19d3bdf38..e88a49347b7 100644 --- a/deps/uv/src/win/stream.c +++ b/deps/uv/src/win/stream.c @@ -111,7 +111,9 @@ int uv_read_stop(uv_stream_t* handle) { } -static int uv__check_before_write(uv_stream_t* handle, unsigned int nbufs) { +static int uv__check_before_write(uv_stream_t* handle, + const uv_buf_t bufs[], + unsigned int nbufs) { /* We're not beholden to IOV_MAX but limit the buffer count to catch sign * conversion bugs where a caller passes in a signed negative number that * then gets converted to a really large unsigned number. @@ -120,6 +122,10 @@ static int uv__check_before_write(uv_stream_t* handle, unsigned int nbufs) { return UV_EINVAL; } + if (uv__count_bufs(bufs, nbufs) > UV__IO_MAX_BYTES) { + return UV_EINVAL; + } + if (!(handle->flags & UV_HANDLE_WRITABLE)) { return UV_EPIPE; } @@ -136,7 +142,7 @@ int uv_write(uv_write_t* req, uv_loop_t* loop = handle->loop; int err; - err = uv__check_before_write(handle, nbufs); + err = uv__check_before_write(handle, bufs, nbufs); if (err != 0) { return err; } @@ -174,7 +180,7 @@ int uv_write2(uv_write_t* req, return uv_write(req, handle, bufs, nbufs, cb); } - err = uv__check_before_write(handle, nbufs); + err = uv__check_before_write(handle, bufs, nbufs); if (err != 0) { return err; } @@ -194,7 +200,7 @@ int uv_try_write(uv_stream_t* stream, unsigned int nbufs) { int err; - err = uv__check_before_write(stream, nbufs); + err = uv__check_before_write(stream, bufs, nbufs); if (err != 0) { return err; } @@ -276,3 +282,51 @@ int uv_stream_set_blocking(uv_stream_t* handle, int blocking) { return 0; } + + +size_t uv_write_nwritten(const uv_write_t* req) { + return req->write_extra.nwritten; +} + + +int uv__write_cancel(uv_write_t* req) { + uv_stream_t* stream; + HANDLE handle; + BOOL result; + + stream = req->handle; + + switch (stream->type) { + case UV_TCP: + handle = (HANDLE) ((uv_tcp_t*) stream)->socket; + break; + case UV_NAMED_PIPE: + handle = ((uv_pipe_t*) stream)->handle; + + if ((stream->flags & (UV_HANDLE_BLOCKING_WRITES | UV_HANDLE_NON_OVERLAPPED_PIPE)) == + UV_HANDLE_NON_OVERLAPPED_PIPE) { + return uv__pipe_write_cancel_non_overlapped((uv_pipe_t*) stream, req); + } + + break; + case UV_TTY: + /* TTY writes complete synchronously on Windows, so cancellation + * is not applicable - the callback has already been queued. */ + return 0; + default: + return UV_EINVAL; + } + + result = CancelIoEx(handle, &req->u.io.overlapped); + + if (!result) { + DWORD err = GetLastError(); + if (err == ERROR_NOT_FOUND) { + /* The operation has already completed. */ + return 0; + } + return uv_translate_sys_error(err); + } + + return 0; +} diff --git a/deps/uv/src/win/tcp.c b/deps/uv/src/win/tcp.c index 8d6d5bef17f..bc8c0a81722 100644 --- a/deps/uv/src/win/tcp.c +++ b/deps/uv/src/win/tcp.c @@ -52,7 +52,7 @@ static int uv__tcp_nodelay(uv_tcp_t* handle, SOCKET socket, int enable) { /* * Check if Windows version is 10.0.16299 (Windows 10, version 1709) or later. */ -static int minimal_windows10_version1709(void) { +static int uv__windows10_version1709(void) { OSVERSIONINFOW os_info; if (!pRtlGetVersion) return 0; @@ -91,7 +91,7 @@ static int uv__tcp_keepalive(uv_tcp_t* handle, /* Windows 10, version 1709 (build 10.0.16299) and later require second units * for TCP keepalive options. */ - if (minimal_windows10_version1709()) { + if (uv__windows10_version1709()) { if (setsockopt(socket, IPPROTO_TCP, TCP_KEEPIDLE, @@ -165,10 +165,6 @@ static int uv__tcp_set_socket(uv_loop_t* loop, return WSAGetLastError(); } - /* Make the socket non-inheritable */ - if (!SetHandleInformation((HANDLE) socket, HANDLE_FLAG_INHERIT, 0)) - return GetLastError(); - /* Associate it with the I/O completion port. Use uv_handle_t pointer as * completion key. */ if (CreateIoCompletionPort((HANDLE)socket, @@ -250,7 +246,8 @@ int uv_tcp_init_ex(uv_loop_t* loop, uv_tcp_t* handle, unsigned int flags) { SOCKET sock; DWORD err; - sock = socket(domain, SOCK_STREAM, 0); + sock = WSASocketW(domain, SOCK_STREAM, 0, NULL, 0, + WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT); if (sock == INVALID_SOCKET) { err = WSAGetLastError(); uv__queue_remove(&handle->handle_queue); @@ -377,7 +374,8 @@ static int uv__tcp_try_bind(uv_tcp_t* handle, if ((flags & UV_TCP_IPV6ONLY) && addr->sa_family != AF_INET6) return ERROR_INVALID_PARAMETER; - sock = socket(addr->sa_family, SOCK_STREAM, 0); + sock = WSASocketW(addr->sa_family, SOCK_STREAM, 0, NULL, 0, + WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT); if (sock == INVALID_SOCKET) { return WSAGetLastError(); } @@ -480,7 +478,8 @@ static void uv__tcp_queue_accept(uv_tcp_t* handle, uv_tcp_accept_t* req) { } /* Open a socket for the accepted connection. */ - accept_socket = socket(family, SOCK_STREAM, 0); + accept_socket = WSASocketW(family, SOCK_STREAM, 0, NULL, 0, + WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT); if (accept_socket == INVALID_SOCKET) { SET_REQ_ERROR(req, WSAGetLastError()); uv__insert_pending_req(loop, (uv_req_t*)req); @@ -488,15 +487,6 @@ static void uv__tcp_queue_accept(uv_tcp_t* handle, uv_tcp_accept_t* req) { return; } - /* Make the socket non-inheritable */ - if (!SetHandleInformation((HANDLE) accept_socket, HANDLE_FLAG_INHERIT, 0)) { - SET_REQ_ERROR(req, GetLastError()); - uv__insert_pending_req(loop, (uv_req_t*)req); - handle->reqs_pending++; - closesocket(accept_socket); - return; - } - /* Prepare the overlapped structure. */ memset(&(req->u.io.overlapped), 0, sizeof(req->u.io.overlapped)); if (handle->flags & UV_HANDLE_EMULATE_IOCP) { @@ -867,9 +857,9 @@ static int uv__tcp_try_connect(uv_connect_t* req, * is not reachable, instead of waiting for 2s. We do not care if this fails. * This only works on Windows version 10.0.16299 and later. */ - if (minimal_windows10_version1709() && uv__is_loopback(&converted)) { + if (uv__windows10_version1709() && uv__is_loopback(&converted)) { memset(&retransmit_ioctl, 0, sizeof(retransmit_ioctl)); - retransmit_ioctl.Rtt = TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS; + retransmit_ioctl.Rtt = TCP_INITIAL_RTO_DEFAULT_RTT; retransmit_ioctl.MaxSynRetransmissions = TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS; WSAIoctl(handle->socket, SIO_TCP_INITIAL_RTO, @@ -958,6 +948,7 @@ int uv__tcp_write(uv_loop_t* loop, UV_REQ_INIT(req, UV_WRITE); req->handle = (uv_stream_t*) handle; req->cb = cb; + req->write_extra.nwritten = 0; /* Prepare the overlapped structure. */ memset(&(req->u.io.overlapped), 0, sizeof(req->u.io.overlapped)); @@ -1105,6 +1096,8 @@ void uv__process_tcp_read_req(uv_loop_t* loop, uv_tcp_t* handle, break; } assert(buf.base != NULL); + if (buf.len > UV__IO_MAX_BYTES) + buf.len = UV__IO_MAX_BYTES; flags = 0; if (WSARecv(handle->socket, @@ -1174,6 +1167,7 @@ void uv__process_tcp_write_req(uv_loop_t* loop, uv_tcp_t* handle, assert(handle->write_queue_size >= req->u.io.queued_bytes); handle->write_queue_size -= req->u.io.queued_bytes; + req->write_extra.nwritten += req->u.io.overlapped.InternalHigh; UNREGISTER_HANDLE_REQ(loop, handle); @@ -1349,7 +1343,7 @@ int uv__tcp_xfer_import(uv_tcp_t* tcp, FROM_PROTOCOL_INFO, &xfer_info->socket_info, 0, - WSA_FLAG_OVERLAPPED); + WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT); if (socket == INVALID_SOCKET) { return WSAGetLastError(); @@ -1654,8 +1648,6 @@ int uv_socketpair(int type, int protocol, uv_os_sock_t fds[2], int flags0, int f WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT); if (server == INVALID_SOCKET) goto wsaerror; - if (!SetHandleInformation((HANDLE) server, HANDLE_FLAG_INHERIT, 0)) - goto error; name.sin_family = AF_INET; name.sin_addr.s_addr = htonl(INADDR_LOOPBACK); name.sin_port = 0; @@ -1669,15 +1661,11 @@ int uv_socketpair(int type, int protocol, uv_os_sock_t fds[2], int flags0, int f client0 = WSASocketW(AF_INET, type, protocol, NULL, 0, client0_flags); if (client0 == INVALID_SOCKET) goto wsaerror; - if (!SetHandleInformation((HANDLE) client0, HANDLE_FLAG_INHERIT, 0)) - goto error; if (connect(client0, (SOCKADDR*) &name, sizeof(name)) != 0) goto wsaerror; client1 = WSASocketW(AF_INET, type, protocol, NULL, 0, client1_flags); if (client1 == INVALID_SOCKET) goto wsaerror; - if (!SetHandleInformation((HANDLE) client1, HANDLE_FLAG_INHERIT, 0)) - goto error; if (!uv__get_acceptex_function(server, &func_acceptex)) { err = WSAEAFNOSUPPORT; goto cleanup; @@ -1723,10 +1711,6 @@ int uv_socketpair(int type, int protocol, uv_os_sock_t fds[2], int flags0, int f err = WSAGetLastError(); goto cleanup; - error: - err = GetLastError(); - goto cleanup; - cleanup: if (server != INVALID_SOCKET) closesocket(server); diff --git a/deps/uv/src/win/thread.c b/deps/uv/src/win/thread.c index 9c38e3d8b3c..d4f6852fb25 100644 --- a/deps/uv/src/win/thread.c +++ b/deps/uv/src/win/thread.c @@ -115,9 +115,11 @@ int uv_thread_create_ex(uv_thread_t* tid, int err; HANDLE thread; SYSTEM_INFO sysinfo; + unsigned int flags; size_t stack_size; size_t pagesize; + flags = CREATE_SUSPENDED; stack_size = params->flags & UV_THREAD_HAS_STACK_SIZE ? params->stack_size : 0; @@ -129,6 +131,9 @@ int uv_thread_create_ex(uv_thread_t* tid, if ((unsigned)stack_size != stack_size) return UV_EINVAL; + + /* Let Windows commit stack pages as they are needed. */ + flags |= STACK_SIZE_PARAM_IS_A_RESERVATION; } ctx = uv__malloc(sizeof(*ctx)); @@ -144,7 +149,7 @@ int uv_thread_create_ex(uv_thread_t* tid, (unsigned)stack_size, uv__thread_start, ctx, - CREATE_SUSPENDED, + flags, NULL); if (thread == NULL) { err = errno; diff --git a/deps/uv/src/win/tty.c b/deps/uv/src/win/tty.c index 66ca99cda83..97ef9d61781 100644 --- a/deps/uv/src/win/tty.c +++ b/deps/uv/src/win/tty.c @@ -555,7 +555,8 @@ static DWORD CALLBACK uv_tty_line_read_thread(void* data) { NULL); if (read_console_success) { - read_bytes = bytes; + assert(bytes > 0); + read_bytes = bytes - 1; uv_utf16_to_wtf8(utf16, read_chars, &handle->tty.rd.read_line_buffer.base, @@ -997,13 +998,13 @@ void uv_process_tty_read_line_req(uv_loop_t* loop, uv_tty_t* handle, &buf); } } else { - if (!(handle->flags & UV_HANDLE_CANCELLATION_PENDING) && + if (!(handle->flags & UV_HANDLE_READ_CANCELLATION_PENDING) && req->u.io.overlapped.InternalHigh != 0) { /* Read successful. TODO: read unicode, convert to utf-8 */ DWORD bytes = req->u.io.overlapped.InternalHigh; handle->read_cb((uv_stream_t*) handle, bytes, &buf); } - handle->flags &= ~UV_HANDLE_CANCELLATION_PENDING; + handle->flags &= ~UV_HANDLE_READ_CANCELLATION_PENDING; } /* Wait for more input events. */ @@ -1086,13 +1087,13 @@ int uv__tty_read_stop(uv_tty_t* handle) { if (!WriteConsoleInputW(handle->handle, &record, 1, &written)) { return GetLastError(); } - } else if (!(handle->flags & UV_HANDLE_CANCELLATION_PENDING)) { + } else if (!(handle->flags & UV_HANDLE_READ_CANCELLATION_PENDING)) { /* Cancel line-buffered read if not already pending */ err = uv__cancel_read_console(handle); if (err) return err; - handle->flags |= UV_HANDLE_CANCELLATION_PENDING; + handle->flags |= UV_HANDLE_READ_CANCELLATION_PENDING; } return 0; @@ -1105,7 +1106,7 @@ static int uv__cancel_read_console(uv_tty_t* handle) { DWORD err = 0; LONG status; - assert(!(handle->flags & UV_HANDLE_CANCELLATION_PENDING)); + assert(!(handle->flags & UV_HANDLE_READ_CANCELLATION_PENDING)); /* Hold the output lock during the cancellation, to ensure that further writes don't interfere with the screen state. It will be the ReadConsole @@ -2208,6 +2209,7 @@ int uv__tty_write(uv_loop_t* loop, UV_REQ_INIT(req, UV_WRITE); req->handle = (uv_stream_t*) handle; req->cb = cb; + req->write_extra.nwritten = 0; handle->reqs_pending++; handle->stream.conn.write_reqs_pending++; @@ -2217,6 +2219,7 @@ int uv__tty_write(uv_loop_t* loop, if (!uv__tty_write_bufs(handle, bufs, nbufs, &error)) { SET_REQ_SUCCESS(req); + req->write_extra.nwritten = uv__count_bufs(bufs, nbufs); } else { SET_REQ_ERROR(req, error); } diff --git a/deps/uv/src/win/udp.c b/deps/uv/src/win/udp.c index 08e358a2cf1..2967b1527ae 100644 --- a/deps/uv/src/win/udp.c +++ b/deps/uv/src/win/udp.c @@ -69,11 +69,6 @@ static int uv__udp_set_socket(uv_loop_t* loop, uv_udp_t* handle, SOCKET socket, return WSAGetLastError(); } - /* Make the socket non-inheritable */ - if (!SetHandleInformation((HANDLE)socket, HANDLE_FLAG_INHERIT, 0)) { - return GetLastError(); - } - /* Associate it with the I/O completion port. Use uv_handle_t pointer as * completion key. */ if (CreateIoCompletionPort((HANDLE)socket, @@ -143,7 +138,8 @@ int uv__udp_init_ex(uv_loop_t* loop, SOCKET sock; DWORD err; - sock = socket(domain, SOCK_DGRAM, 0); + sock = WSASocketW(domain, SOCK_DGRAM, 0, NULL, 0, + WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT); if (sock == INVALID_SOCKET) { err = WSAGetLastError(); uv__queue_remove(&handle->handle_queue); @@ -212,7 +208,8 @@ static int uv__udp_maybe_bind(uv_udp_t* handle, } if (handle->socket == INVALID_SOCKET) { - SOCKET sock = socket(addr->sa_family, SOCK_DGRAM, 0); + SOCKET sock = WSASocketW(addr->sa_family, SOCK_DGRAM, 0, NULL, 0, + WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT); if (sock == INVALID_SOCKET) { return WSAGetLastError(); } @@ -1129,9 +1126,6 @@ int uv__udp_try_send(uv_udp_t* handle, struct sockaddr_storage converted; int err; - if (nbufs < 1) - return UV_EINVAL; - if (addr != NULL) { err = uv__convert_to_localhost_if_unspecified(addr, &converted); if (err) diff --git a/deps/uv/src/win/util.c b/deps/uv/src/win/util.c index fec07b269f7..d3a3836f142 100644 --- a/deps/uv/src/win/util.c +++ b/deps/uv/src/win/util.c @@ -508,8 +508,8 @@ int uv_uptime(double* uptime) { unsigned int uv_available_parallelism(void) { DWORD_PTR procmask; DWORD_PTR sysmask; - unsigned count; - unsigned i; + int count; + unsigned int i; /* TODO(bnoordhuis) Use GetLogicalProcessorInformationEx() to support systems * with > 64 CPUs? See https://github.com/libuv/libuv/pull/3458 @@ -627,7 +627,7 @@ int uv_cpu_info(uv_cpu_info_t** cpu_infos_ptr, int* cpu_count_ptr) { uv__convert_utf16_to_utf8(cpu_brand, cpu_brand_size / sizeof(WCHAR), - &(cpu_info->model)); + (char**) &(cpu_info->model)); } uv__free(sppi); @@ -639,12 +639,8 @@ int uv_cpu_info(uv_cpu_info_t** cpu_infos_ptr, int* cpu_count_ptr) { error: if (cpu_infos != NULL) { - /* This is safe because the cpu_infos array is zeroed on allocation. */ - for (i = 0; i < cpu_count; i++) - uv__free(cpu_infos[i].model); + uv_free_cpu_info(cpu_infos, cpu_count); } - - uv__free(cpu_infos); uv__free(sppi); return uv_translate_sys_error(err); diff --git a/deps/uv/src/win/winapi.h b/deps/uv/src/win/winapi.h index 2f25dc55fff..06661b3a3d9 100644 --- a/deps/uv/src/win/winapi.h +++ b/deps/uv/src/win/winapi.h @@ -4785,6 +4785,9 @@ typedef struct _TCP_INITIAL_RTO_PARAMETERS { UCHAR MaxSynRetransmissions; } TCP_INITIAL_RTO_PARAMETERS, *PTCP_INITIAL_RTO_PARAMETERS; +#ifndef TCP_INITIAL_RTO_DEFAULT_RTT +# define TCP_INITIAL_RTO_DEFAULT_RTT 0 +#endif #ifndef TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS # define TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS ((UCHAR) -2) #endif diff --git a/deps/uv/src/win/winsock.c b/deps/uv/src/win/winsock.c index a68b095366a..fcef8f7c95a 100644 --- a/deps/uv/src/win/winsock.c +++ b/deps/uv/src/win/winsock.c @@ -102,7 +102,8 @@ void uv__winsock_init(void) { /* Try to detect non-IFS LSPs */ uv_tcp_non_ifs_lsp_ipv4 = 1; - dummy = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + dummy = WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_IP, NULL, 0, + WSA_FLAG_NO_HANDLE_INHERIT); if (dummy != INVALID_SOCKET) { opt_len = (int) sizeof protocol_info; if (getsockopt(dummy, @@ -118,7 +119,8 @@ void uv__winsock_init(void) { /* Try to detect IPV6 support and non-IFS LSPs */ uv_tcp_non_ifs_lsp_ipv6 = 1; - dummy = socket(AF_INET6, SOCK_STREAM, IPPROTO_IP); + dummy = WSASocketW(AF_INET6, SOCK_STREAM, IPPROTO_IP, NULL, 0, + WSA_FLAG_NO_HANDLE_INHERIT); if (dummy != INVALID_SOCKET) { opt_len = (int) sizeof protocol_info; if (getsockopt(dummy, diff --git a/deps/uv/test/appcontainer.c b/deps/uv/test/appcontainer.c new file mode 100644 index 00000000000..e8fc761f45e --- /dev/null +++ b/deps/uv/test/appcontainer.c @@ -0,0 +1,610 @@ +/* Copyright libuv contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/* + * appcontainer.c - Run a program inside a Windows AppContainer. + * + * Usage: appcontainer.exe program.exe [args...] + * + * Creates an AppContainer profile, grants it read/write access to the + * current directory tree and temp directory, adds loopback exemption + * for localhost network access, launches the given program inside the + * AppContainer, waits for it to exit, and returns the child's exit code. + */ + +#include +#include +#include +#include +#include +#include +#include + +/* + * Well-known capability SIDs for network access. + * See https://devblogs.microsoft.com/oldnewthing/20220503-00/?p=106557 + */ +#define INTERNET_CLIENT_SID "S-1-15-3-1" +#define INTERNET_CLIENT_SERVER_SID "S-1-15-3-2" +#define PRIVATE_NETWORK_CLIENT_SERVER_SID "S-1-15-3-3" + +typedef LONG NTSTATUS; + +NTSTATUS NTAPI NtSetSecurityObject(HANDLE, SECURITY_INFORMATION, + PSECURITY_DESCRIPTOR); +ULONG NTAPI RtlNtStatusToDosError(NTSTATUS); + +/* Modify the DACL on the given path for the AppContainer SID. + * If grant, add an ACE with the given perms and inheritance. + * If !grant, remove the SID's ACE (perms and inheritance are ignored). + * + * For non-inheritable ACEs, uses NtSetSecurityObject to set the DACL + * on the single object without a tree walk. The Win32 wrapper + * SetNamedSecurityInfo recursively re-propagates every inheritable ACE + * to all descendants even when the ACE being added is non-inheritable, + * which is extremely slow on large directory trees. + * + * For inheritable ACEs, uses SetNamedSecurityInfo so that Windows + * propagates the new ACE to existing descendants. */ +static DWORD modify_access(PSID sid, const char* path, DWORD perms, + DWORD inheritance, int grant) { + EXPLICIT_ACCESSA ea; + PACL old_acl = NULL; + PACL new_acl = NULL; + PSECURITY_DESCRIPTOR sd = NULL; + SECURITY_DESCRIPTOR sd_new; + HANDLE h; + NTSTATUS status; + DWORD err; + ULONGLONG t1 = GetTickCount64(); + + /* Open the file or directory. FILE_FLAG_BACKUP_SEMANTICS is + * required to obtain a handle to a directory. */ + h = CreateFileA(path, + READ_CONTROL | WRITE_DAC, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + if (h == INVALID_HANDLE_VALUE) { + err = GetLastError(); + fprintf(stderr, "appcontainer: modify_access %s %s perms=0x%lx inherit=0x%lx: " + "CreateFile error %lu (%.3f s)\n", + grant ? "grant" : "revoke", path, perms, inheritance, + err, (GetTickCount64() - t1) / 1000.0); + return err; + } + + /* Read the existing DACL. */ + err = GetSecurityInfo(h, + SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION, + NULL, NULL, &old_acl, NULL, &sd); + if (err != ERROR_SUCCESS) { + fprintf(stderr, "appcontainer: modify_access %s %s perms=0x%lx inherit=0x%lx: " + "GetSecurityInfo error %lu (%.3f s)\n", + grant ? "grant" : "revoke", path, perms, inheritance, + err, (GetTickCount64() - t1) / 1000.0); + CloseHandle(h); + return err; + } + + /* Build updated ACL. */ + memset(&ea, 0, sizeof(ea)); + ea.grfAccessPermissions = grant ? perms : 0; + ea.grfAccessMode = grant ? SET_ACCESS : REVOKE_ACCESS; + ea.grfInheritance = grant ? inheritance : NO_INHERITANCE; + ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; + ea.Trustee.ptstrName = (LPSTR)sid; + + err = SetEntriesInAclA(1, &ea, old_acl, &new_acl); + if (err != ERROR_SUCCESS) { + fprintf(stderr, "appcontainer: modify_access %s %s perms=0x%lx inherit=0x%lx: " + "SetEntriesInAcl error %lu (%.3f s)\n", + grant ? "grant" : "revoke", path, perms, inheritance, + err, (GetTickCount64() - t1) / 1000.0); + LocalFree(sd); + CloseHandle(h); + return err; + } + + /* For non-inheritable ACEs (e.g. parent directory grants), use + * NtSetSecurityObject to set the DACL on this single object without + * the tree walk that SetNamedSecurityInfo performs. For inheritable + * ACEs, use SetNamedSecurityInfo so that Windows propagates the new + * ACE to existing descendants. */ + if (inheritance == NO_INHERITANCE) { + InitializeSecurityDescriptor(&sd_new, SECURITY_DESCRIPTOR_REVISION); + SetSecurityDescriptorDacl(&sd_new, TRUE, new_acl, FALSE); + status = NtSetSecurityObject(h, DACL_SECURITY_INFORMATION, &sd_new); + err = (status == 0) ? ERROR_SUCCESS : RtlNtStatusToDosError(status); + } else { + err = SetNamedSecurityInfoA((LPSTR)path, + SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION, + NULL, NULL, new_acl, NULL); + } + + fprintf(stderr, "appcontainer: modify_access %s %s perms=0x%lx inherit=0x%lx: " + "%s (%.3f s)\n", + grant ? "grant" : "revoke", path, perms, inheritance, + err == ERROR_SUCCESS ? "ok" : "error", + (GetTickCount64() - t1) / 1000.0); + + LocalFree(new_acl); + LocalFree(sd); + CloseHandle(h); + return err; +} + +/* Modify access on all parent directories up to the drive root. + * If grant is true, add read+execute; otherwise revoke. */ +static void modify_parents(PSID sid, const char* path, int grant) { + char parent[MAX_PATH]; + size_t len; + + strncpy(parent, path, MAX_PATH - 1); + parent[MAX_PATH - 1] = '\0'; + + for (;;) { + len = strlen(parent); + while (len > 0 && parent[len - 1] != '\\') + len--; + if (len == 0) + break; + /* Keep the trailing backslash only for drive roots (e.g. C:\). */ + if (len > 1 && parent[len - 2] != ':') + parent[len - 1] = '\0'; + else + parent[len] = '\0'; + + DWORD err = modify_access(sid, parent, + GENERIC_READ | GENERIC_EXECUTE, + NO_INHERITANCE, grant); + if (grant && err != ERROR_SUCCESS) + fprintf(stderr, "appcontainer: warning: grant parent %s: %lu\n", + parent, err); + + /* Stop at drive root (e.g. "C:\"). */ + if (len <= 3) + break; + parent[len - 1] = '\0'; + } +} + +/* Grant or revoke access to a directory and its parents. */ +static void modify_dir_and_parents(PSID sid, const char* path, int grant) { + ULONGLONG t1 = GetTickCount64(); + DWORD err = modify_access(sid, path, GENERIC_ALL, + CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, + grant); + if (grant && err != ERROR_SUCCESS) { + fprintf(stderr, "appcontainer: warning: grant %s: %lu\n", path, err); + return; + } + + modify_parents(sid, path, grant); + + fprintf(stderr, "appcontainer: %s tree %s: %.3f s\n", + grant ? "grant" : "revoke", path, + (GetTickCount64() - t1) / 1000.0); +} + +/* Grant or revoke the AppContainer SID's access to the NUL device. + * uv_spawn needs to open NUL for ignored stdio handles. + * NUL is a device object, so we must use handle-based + * GetSecurityInfo/SetSecurityInfo (the named variants silently + * fail on devices). */ +static void modify_nul_access(PSID sid, int grant) { + HANDLE h; + PACL old_acl = NULL; + PACL new_acl = NULL; + PSECURITY_DESCRIPTOR sd = NULL; + EXPLICIT_ACCESSA ea; + DWORD err; + ULONGLONG t1 = GetTickCount64(); + + /* Open NUL with permission to read and modify the DACL. */ + h = CreateFileA("\\\\.\\NUL", + READ_CONTROL | WRITE_DAC, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + 0, + NULL); + if (h == INVALID_HANDLE_VALUE) { + if (grant) + fprintf(stderr, "appcontainer: warning: open NUL: %lu\n", GetLastError()); + return; + } + + err = GetSecurityInfo(h, + SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION, + NULL, NULL, &old_acl, NULL, &sd); + if (err != ERROR_SUCCESS) { + if (grant) + fprintf(stderr, "appcontainer: warning: GetSecurityInfo NUL: %lu\n", err); + CloseHandle(h); + return; + } + + memset(&ea, 0, sizeof(ea)); + ea.grfAccessPermissions = grant ? (GENERIC_READ | GENERIC_WRITE) : 0; + ea.grfAccessMode = grant ? SET_ACCESS : REVOKE_ACCESS; + ea.grfInheritance = NO_INHERITANCE; + ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; + ea.Trustee.ptstrName = (LPSTR)sid; + + err = SetEntriesInAclA(1, &ea, old_acl, &new_acl); + if (err != ERROR_SUCCESS) { + if (grant) + fprintf(stderr, "appcontainer: warning: SetEntriesInAcl NUL: %lu\n", err); + LocalFree(sd); + CloseHandle(h); + return; + } + + err = SetSecurityInfo(h, + SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION, + NULL, NULL, new_acl, NULL); + if (grant) { + if (err != ERROR_SUCCESS) + fprintf(stderr, "appcontainer: warning: SetSecurityInfo NUL: %lu\n", err); + else + fprintf(stderr, "appcontainer: NUL device access granted\n"); + } + + fprintf(stderr, "appcontainer: %s NUL: %.3f s\n", + grant ? "grant" : "revoke", + (GetTickCount64() - t1) / 1000.0); + + LocalFree(new_acl); + LocalFree(sd); + CloseHandle(h); +} + +/* Add or remove the AppContainer SID from the loopback exemption list + * so that TCP/UDP tests can connect to localhost. */ +static void modify_loopback_exemption(PSID sid, int grant) { + /* Load NetworkIsolationGetAppContainerConfig and NetworkIsolationSetAppContainerConfig + * directly from FirewallAPI.dll, since netfw.h does not always contain their declarations. + * See https://learn.microsoft.com/en-us/windows/win32/api/netfw/nf-netfw-networkisolationsetappcontainerconfig */ + typedef DWORD (WINAPI *fnGetConfig)(DWORD*, PSID_AND_ATTRIBUTES*); + typedef DWORD (WINAPI *fnSetConfig)(DWORD, PSID_AND_ATTRIBUTES); + HMODULE hFirewall; + fnGetConfig getConfig; + fnSetConfig setConfig; + DWORD numSids = 0; + PSID_AND_ATTRIBUTES oldSids = NULL; + PSID_AND_ATTRIBUTES newSids = NULL; + DWORD newCount = 0; + DWORD err; + DWORD i; + ULONGLONG t1 = GetTickCount64(); + + hFirewall = LoadLibraryA("FirewallAPI.dll"); + if (!hFirewall) { + if (grant) + fprintf(stderr, "appcontainer: warning: LoadLibrary(FirewallAPI.dll) " + "failed: %lu\n", GetLastError()); + return; + } + + getConfig = (fnGetConfig)GetProcAddress(hFirewall, "NetworkIsolationGetAppContainerConfig"); + setConfig = (fnSetConfig)GetProcAddress(hFirewall, "NetworkIsolationSetAppContainerConfig"); + if (!getConfig || !setConfig) { + if (grant) + fprintf(stderr, "appcontainer: warning: NetworkIsolation API not found\n"); + FreeLibrary(hFirewall); + return; + } + + /* Get current exemption list. */ + err = getConfig(&numSids, &oldSids); + if (err != ERROR_SUCCESS) { + if (grant) + fprintf(stderr, "appcontainer: warning: NetworkIsolationGetAppContainerConfig: %lu\n", err); + FreeLibrary(hFirewall); + return; + } + + /* Build new list: copy existing entries (excluding our SID to avoid + * duplicates), then append our SID if granting. */ + newSids = (PSID_AND_ATTRIBUTES)malloc( + (numSids + 1) * sizeof(SID_AND_ATTRIBUTES)); + if (!newSids) { + FreeLibrary(hFirewall); + return; + } + + for (i = 0; i < numSids; i++) { + if (!EqualSid(oldSids[i].Sid, sid)) + newSids[newCount++] = oldSids[i]; + } + if (grant) { + newSids[newCount].Sid = sid; + newSids[newCount].Attributes = SE_GROUP_ENABLED; + newCount++; + } + + err = setConfig(newCount, newSids); + if (grant) { + if (err != ERROR_SUCCESS) + fprintf(stderr, "appcontainer: warning: NetworkIsolationSetAppContainerConfig: %lu\n", err); + else + fprintf(stderr, "appcontainer: loopback exemption added\n"); + } + + fprintf(stderr, "appcontainer: %s loopback exemption: %.3f s\n", + grant ? "grant" : "revoke", + (GetTickCount64() - t1) / 1000.0); + + free(newSids); + FreeLibrary(hFirewall); +} + +/* Launch a child process inside the AppContainer and return its + * exit code. */ +static int run_child(const char* abs_exe, const char* cmdline, + SECURITY_CAPABILITIES* sc) { + STARTUPINFOEXA si; + PROCESS_INFORMATION pi; + SIZE_T attr_size; + DWORD exit_code = 1; + + memset(&si, 0, sizeof(si)); + memset(&pi, 0, sizeof(pi)); + + /* Allocate the proc thread attribute list. */ + attr_size = 0; + InitializeProcThreadAttributeList(NULL, 1, 0, &attr_size); + si.StartupInfo.cb = sizeof(si); + si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)malloc(attr_size); + if (!si.lpAttributeList) { + fprintf(stderr, "malloc failed\n"); + return 1; + } + if (!InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, + &attr_size)) { + fprintf(stderr, "InitializeProcThreadAttributeList failed: %lu\n", + GetLastError()); + free(si.lpAttributeList); + return 1; + } + + if (!UpdateProcThreadAttribute(si.lpAttributeList, + 0, + PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES, + sc, + sizeof(*sc), + NULL, + NULL)) { + fprintf(stderr, "UpdateProcThreadAttribute failed: %lu\n", GetLastError()); + DeleteProcThreadAttributeList(si.lpAttributeList); + free(si.lpAttributeList); + return 1; + } + + /* Inherit handles so stdout/stderr flow through. */ + si.StartupInfo.dwFlags = STARTF_USESTDHANDLES; + si.StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + si.StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + si.StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); + + if (!CreateProcessA(abs_exe, + (LPSTR)cmdline, + NULL, + NULL, + TRUE, + EXTENDED_STARTUPINFO_PRESENT | CREATE_NO_WINDOW, + NULL, + NULL, + &si.StartupInfo, + &pi)) { + fprintf(stderr, "CreateProcessA failed: %lu\n", GetLastError()); + DeleteProcThreadAttributeList(si.lpAttributeList); + free(si.lpAttributeList); + return 1; + } + + WaitForSingleObject(pi.hProcess, INFINITE); + GetExitCodeProcess(pi.hProcess, &exit_code); + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + DeleteProcThreadAttributeList(si.lpAttributeList); + free(si.lpAttributeList); + return (int)exit_code; +} + +int main(int argc, char* argv[]) { + /* These must be wide strings: CreateAppContainerProfile has no A variant. */ + static const wchar_t profile_name[] = L"libuv-test-appcontainer"; + static const wchar_t profile_display[] = L"libuv test"; + static const wchar_t profile_desc[] = L"AppContainer for libuv tests"; + + PSID sid = NULL; + HRESULT hr; + char cwd[MAX_PATH]; + char abs_exe[MAX_PATH]; + char tmpdir[MAX_PATH]; + char cmdline[32768]; + SECURITY_CAPABILITIES sc; + LPSTR sid_str = NULL; + int i; + int exit_code; + size_t pos; + ULONGLONG start_time; + + /* Capability SIDs to add. */ + PSID net_sid; + SID_AND_ATTRIBUTES caps[1]; + int num_caps = 0; + + start_time = GetTickCount64(); + + if (argc < 2) { + fprintf(stderr, "usage: appcontainer.exe program.exe [args...]\n"); + return 1; + } + + /* Get the current directory. */ + if (!GetCurrentDirectoryA(MAX_PATH, cwd)) { + fprintf(stderr, "GetCurrentDirectoryA failed: %lu\n", GetLastError()); + return 1; + } + + /* Resolve the exe to an absolute path. */ + if (!GetFullPathNameA(argv[1], MAX_PATH, abs_exe, NULL)) { + fprintf(stderr, "GetFullPathNameA failed: %lu\n", GetLastError()); + return 1; + } + + /* Get the temp directory. */ + if (!GetTempPathA(MAX_PATH, tmpdir)) { + fprintf(stderr, "GetTempPathA failed: %lu\n", GetLastError()); + return 1; + } + { + size_t tlen = strlen(tmpdir); + if (tlen > 3 && tmpdir[tlen - 1] == '\\') + tmpdir[tlen - 1] = '\0'; + } + + fprintf(stderr, "appcontainer: cwd=%s\n", cwd); + fprintf(stderr, "appcontainer: exe=%s\n", abs_exe); + fprintf(stderr, "appcontainer: tmp=%s\n", tmpdir); + + /* Verify the exe exists. */ + if (GetFileAttributesA(abs_exe) == INVALID_FILE_ATTRIBUTES) { + fprintf(stderr, "appcontainer: exe not found: %s (error %lu)\n", + abs_exe, GetLastError()); + return 1; + } + + /* Create capability SIDs for network access. */ + if (!ConvertStringSidToSidA(INTERNET_CLIENT_SERVER_SID, &net_sid)) { + fprintf(stderr, "appcontainer: warning: ConvertStringSidToSidA(%s): %lu\n", + INTERNET_CLIENT_SERVER_SID, GetLastError()); + } else { + caps[num_caps].Sid = net_sid; + caps[num_caps].Attributes = SE_GROUP_ENABLED; + num_caps++; + } + + /* Delete any leftover profile from a previous run. */ + DeleteAppContainerProfile(profile_name); + + /* Create the AppContainer profile. */ + { + ULONGLONG t1 = GetTickCount64(); + hr = CreateAppContainerProfile(profile_name, + profile_display, + profile_desc, + num_caps > 0 ? caps : NULL, + num_caps, + &sid); + fprintf(stderr, "appcontainer: CreateAppContainerProfile: %.3f s\n", + (GetTickCount64() - t1) / 1000.0); + } + if (FAILED(hr)) { + fprintf(stderr, "CreateAppContainerProfile failed: 0x%08lx\n", hr); + return 1; + } + + if (ConvertSidToStringSidA(sid, &sid_str)) { + fprintf(stderr, "appcontainer: SID=%s\n", sid_str); + LocalFree(sid_str); + } + + /* Grant access. */ + { + ULONGLONG t1 = GetTickCount64(); + modify_dir_and_parents(sid, cwd, 1); + modify_dir_and_parents(sid, tmpdir, 1); + modify_nul_access(sid, 1); + modify_loopback_exemption(sid, 1); + fprintf(stderr, "appcontainer: total setup grants: %.3f s\n", + (GetTickCount64() - t1) / 1000.0); + } + + /* Set up SECURITY_CAPABILITIES. */ + memset(&sc, 0, sizeof(sc)); + sc.AppContainerSid = sid; + if (num_caps > 0) { + sc.Capabilities = caps; + sc.CapabilityCount = num_caps; + } + + /* Build the command line. */ + pos = 0; + cmdline[pos++] = '"'; + { + size_t elen = strlen(abs_exe); + memcpy(&cmdline[pos], abs_exe, elen); + pos += elen; + } + cmdline[pos++] = '"'; + + for (i = 2; i < argc; i++) { + size_t arglen = strlen(argv[i]); + cmdline[pos++] = ' '; + cmdline[pos++] = '"'; + memcpy(&cmdline[pos], argv[i], arglen); + pos += arglen; + cmdline[pos++] = '"'; + } + cmdline[pos] = '\0'; + + fprintf(stderr, "appcontainer: launching: %s\n", cmdline); + + { + ULONGLONG child_start = GetTickCount64(); + exit_code = run_child(abs_exe, cmdline, &sc); + fprintf(stderr, "appcontainer: child exited with code %d (%.3f s)\n", + exit_code, (GetTickCount64() - child_start) / 1000.0); + } + + /* Tear down the sandbox: revoke all grants so we don't leave + * stale SIDs on directories, the NUL device, or the firewall. */ + { + ULONGLONG t1 = GetTickCount64(); + modify_dir_and_parents(sid, cwd, 0); + modify_dir_and_parents(sid, tmpdir, 0); + modify_nul_access(sid, 0); + modify_loopback_exemption(sid, 0); + fprintf(stderr, "appcontainer: total teardown revokes: %.3f s\n", + (GetTickCount64() - t1) / 1000.0); + } + + if (net_sid) + LocalFree(net_sid); + FreeSid(sid); + DeleteAppContainerProfile(profile_name); + fprintf(stderr, "appcontainer: elapsed %.3f s\n", + (GetTickCount64() - start_time) / 1000.0); + return exit_code; +} diff --git a/deps/uv/test/run-tests.c b/deps/uv/test/run-tests.c index 9c2574ebf8a..4b0c961eaa6 100644 --- a/deps/uv/test/run-tests.c +++ b/deps/uv/test/run-tests.c @@ -211,25 +211,18 @@ static int maybe_run_test(int argc, char **argv) { if (strcmp(argv[1], "spawn_helper8") == 0) { uv_os_fd_t closed_fd; uv_os_fd_t open_fd; -#ifdef _WIN32 - DWORD flags; - HMODULE kernelbase_module; - union { - FARPROC proc; - sCompareObjectHandles pCompareObjectHandles; /* Windows >= 10 */ - } u; -#endif notify_parent_process(); ASSERT_EQ(sizeof(closed_fd), read(0, &closed_fd, sizeof(closed_fd))); ASSERT_EQ(sizeof(open_fd), read(0, &open_fd, sizeof(open_fd))); #ifdef _WIN32 + DWORD flags; ASSERT_GT((intptr_t) closed_fd, 0); ASSERT_GT((intptr_t) open_fd, 0); - ASSERT_NE(0, GetHandleInformation(open_fd, &flags)); - kernelbase_module = GetModuleHandleW(L"kernelbase.dll"); - u.proc = GetProcAddress(kernelbase_module, "CompareObjectHandles"); - if (u.pCompareObjectHandles != NULL) - ASSERT_EQ(FALSE, u.pCompareObjectHandles(open_fd, closed_fd)); + ASSERT_EQ(0, GetHandleInformation(open_fd, &flags)); + ASSERT_EQ(ERROR_INVALID_HANDLE, GetLastError()); + SetLastError(ERROR_SUCCESS); + ASSERT_EQ(0, GetHandleInformation(closed_fd, &flags)); + ASSERT_EQ(ERROR_INVALID_HANDLE, GetLastError()); #else ASSERT_GT(open_fd, 2); ASSERT_GT(closed_fd, 2); diff --git a/deps/uv/test/runner-win.c b/deps/uv/test/runner-win.c index 52e0f64debf..bd2471da9ab 100644 --- a/deps/uv/test/runner-win.c +++ b/deps/uv/test/runner-win.c @@ -70,7 +70,21 @@ void platform_init(int argc, char **argv) { } -int process_start(char *name, char *part, process_info_t *p, int is_helper) { +void notify_parent_process(void) { + HANDLE handle; + char* arg; + + arg = getenv("UV_TEST_RUNNER_FD"); + if (arg == NULL) + return; + + handle = (HANDLE)(uintptr_t)strtoull(arg, NULL, 10); + SetEnvironmentVariableA("UV_TEST_RUNNER_FD", NULL); + ASSERT_NE(CloseHandle(handle), 0); +} + + +int process_start(char* name, char* part, process_info_t* p, int is_helper) { HANDLE file = INVALID_HANDLE_VALUE; HANDLE nul = INVALID_HANDLE_VALUE; WCHAR path[MAX_PATH], filename[MAX_PATH]; @@ -79,10 +93,22 @@ int process_start(char *name, char *part, process_info_t *p, int is_helper) { STARTUPINFOW si; PROCESS_INFORMATION pi; DWORD result; + HANDLE fds[2]; + char fdstr[32]; + + fds[0] = fds[1] = INVALID_HANDLE_VALUE; - if (!is_helper) { - /* Give the helpers time to settle. Race-y, fix this. */ - uv_sleep(250); + if (is_helper) { + /* Create a pipe so the helper can signal when it is ready. */ + if (!CreatePipe(&fds[0], &fds[1], NULL, 0)) + goto error; + if (!SetHandleInformation(fds[0], HANDLE_FLAG_INHERIT, 0)) + goto error; + if (!SetHandleInformation(fds[1], HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) + goto error; + snprintf(fdstr, sizeof(fdstr), "%" PRIuPTR, (uintptr_t) fds[1]); + if (!SetEnvironmentVariableA("UV_TEST_RUNNER_FD", fdstr)) + goto error; } if (GetTempPathW(sizeof(path) / sizeof(WCHAR), (WCHAR*)&path) == 0) @@ -162,9 +188,41 @@ int process_start(char *name, char *part, process_info_t *p, int is_helper) { p->process = pi.hProcess; p->name = part; + if (!is_helper) + return 0; + + /* Close the write end in the parent and wait for the child to close its + * copy, which signals that the helper has finished starting up. */ + ASSERT_NE(CloseHandle(fds[1]), 0); + fds[1] = INVALID_HANDLE_VALUE; + SetEnvironmentVariableA("UV_TEST_RUNNER_FD", NULL); + + { + char buf[1]; + DWORD bytes; + if (ReadFile(fds[0], buf, sizeof(buf), &bytes, NULL)) { + if (bytes > 0) { + fprintf(stderr, "EOF expected but got data.\n"); + CloseHandle(fds[0]); + return -1; + } + } else if (GetLastError() != ERROR_BROKEN_PIPE) { + fprintf(stderr, "ReadFile: %lu\n", GetLastError()); + CloseHandle(fds[0]); + return -1; + } + } + + ASSERT_NE(CloseHandle(fds[0]), 0); return 0; error: + if (fds[0] != INVALID_HANDLE_VALUE) + CloseHandle(fds[0]); + if (fds[1] != INVALID_HANDLE_VALUE) { + CloseHandle(fds[1]); + SetEnvironmentVariableA("UV_TEST_RUNNER_FD", NULL); + } if (file != INVALID_HANDLE_VALUE) CloseHandle(file); if (nul != INVALID_HANDLE_VALUE) diff --git a/deps/uv/test/runner.c b/deps/uv/test/runner.c index 87e7db0b05c..5cfd8095cdf 100644 --- a/deps/uv/test/runner.c +++ b/deps/uv/test/runner.c @@ -28,7 +28,9 @@ #include "uv.h" /* Refs: https://github.com/libuv/libuv/issues/4369 */ -#if defined(__ANDROID__) +/* Refs: https://github.com/libuv/libuv/issues/5092 */ +#if defined(__ANDROID__) && __ANDROID_API__ >= __ANDROID_API_Q__ +#define USE_FDSAN #include #endif @@ -149,7 +151,7 @@ void log_tap_result(int test_count, void enable_fdsan(void) { /* Refs: https://github.com/libuv/libuv/issues/4369 */ -#if defined(__ANDROID__) +#if defined(USE_FDSAN) android_fdsan_set_error_level(ANDROID_FDSAN_ERROR_LEVEL_WARN_ALWAYS); #endif } diff --git a/deps/uv/test/task.h b/deps/uv/test/task.h index 45674ff1901..deef2847fd5 100644 --- a/deps/uv/test/task.h +++ b/deps/uv/test/task.h @@ -50,9 +50,9 @@ #define TEST_PORT_3 9125 #ifdef _WIN32 -# define TEST_PIPENAME "\\\\.\\pipe\\uv-test" -# define TEST_PIPENAME_2 "\\\\.\\pipe\\uv-test2" -# define TEST_PIPENAME_3 "\\\\.\\pipe\\uv-test3" +# define TEST_PIPENAME "\\\\.\\pipe\\LOCAL\\uv-test" +# define TEST_PIPENAME_2 "\\\\.\\pipe\\LOCAL\\uv-test2" +# define TEST_PIPENAME_3 "\\\\.\\pipe\\LOCAL\\uv-test3" #else # define TEST_PIPENAME "uv-test-sock" # define TEST_PIPENAME_2 "uv-test-sock2" @@ -304,6 +304,14 @@ typedef enum { /* Format big numbers nicely. */ char* fmt(char (*buf)[32], double d); +#if defined(__clang__) || \ + defined(__GNUC__) || \ + defined(__INTEL_COMPILER) +# define UNUSED __attribute__((unused)) +#else +# define UNUSED +#endif + /* Reserved test exit codes. */ enum test_status { TEST_OK = 0, @@ -322,6 +330,31 @@ enum test_status { return TEST_SKIP; \ } while (0) +#ifdef _WIN32 +UNUSED static int is_in_appcontainer(void) { + HANDLE token; + DWORD ac; + DWORD len; + int ok; + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) + return 0; + ac = 0; + len = sizeof(ac); + ok = GetTokenInformation(token, TokenIsAppContainer, &ac, len, &len); + CloseHandle(token); + return ok && ac != 0; +} + +# define RETURN_SKIP_IN_APPCONTAINER(reason) \ + do { \ + if (is_in_appcontainer()) \ + RETURN_SKIP("AppContainer: " reason); \ + } while (0) +#else +# define RETURN_SKIP_IN_APPCONTAINER(reason) do {} while (0) +#endif + #if !defined(_WIN32) # define TEST_FILE_LIMIT(num) \ @@ -343,19 +376,7 @@ enum test_status { extern int snprintf(char*, size_t, const char*, ...); #endif -#if defined(__clang__) || \ - defined(__GNUC__) || \ - defined(__INTEL_COMPILER) -# define UNUSED __attribute__((unused)) -#else -# define UNUSED -#endif - -#if defined(_WIN32) -#define notify_parent_process() ((void) 0) -#else extern void notify_parent_process(void); -#endif /* Fully close a loop */ static void close_walk_cb(uv_handle_t* handle, void* arg) { diff --git a/deps/uv/test/test-fork.c b/deps/uv/test/test-fork.c index fe42f03daf5..0bc84567737 100644 --- a/deps/uv/test/test-fork.c +++ b/deps/uv/test/test-fork.c @@ -161,6 +161,10 @@ TEST_IMPL(fork_socketpair) { ASSERT_EQ(1, socket_cb_called); } + /* uv_poll_init() does not take ownership of the socket. */ + ASSERT_OK(close(socket_fds[0])); + ASSERT_OK(close(socket_fds[1])); + MAKE_VALGRIND_HAPPY(uv_default_loop()); return 0; } @@ -229,6 +233,12 @@ TEST_IMPL(fork_socketpair_started) { ASSERT_OK(strcmp("hi\n", socket_cb_read_buf)); } + /* uv_poll_init() does not take ownership of the socket. */ + ASSERT_OK(close(socket_fds[0])); + ASSERT_OK(close(socket_fds[1])); + ASSERT_OK(close(sync_pipe[0])); + ASSERT_OK(close(sync_pipe[1])); + MAKE_VALGRIND_HAPPY(uv_default_loop()); return 0; } @@ -292,6 +302,9 @@ TEST_IMPL(fork_signal_to_child) { ASSERT_EQ(SIGUSR1, fork_signal_cb_called); } + ASSERT_OK(close(sync_pipe[0])); + ASSERT_OK(close(sync_pipe[1])); + MAKE_VALGRIND_HAPPY(uv_default_loop()); return 0; } @@ -371,6 +384,11 @@ TEST_IMPL(fork_signal_to_child_closed) { exit(0); } + ASSERT_OK(close(sync_pipe[0])); + ASSERT_OK(close(sync_pipe[1])); + ASSERT_OK(close(sync_pipe2[0])); + ASSERT_OK(close(sync_pipe2[1])); + MAKE_VALGRIND_HAPPY(uv_default_loop()); return 0; } diff --git a/deps/uv/test/test-fs-event.c b/deps/uv/test/test-fs-event.c index 103e74aed96..e02083d6b3f 100644 --- a/deps/uv/test/test-fs-event.c +++ b/deps/uv/test/test-fs-event.c @@ -153,14 +153,9 @@ static void fs_event_cb_del_dir(uv_fs_event_t* handle, ASSERT_PTR_EQ(handle, &fs_event); ASSERT_OK(status); ASSERT(events == UV_CHANGE || events == UV_RENAME); - /* There is a bug in the FreeBSD kernel where the filename is sometimes NULL. - * Refs: https://github.com/libuv/libuv/issues/4606 - */ - #if defined(__FreeBSD__) - ASSERT(filename == NULL || strcmp(filename, "watch_del_dir") == 0); - #else - ASSERT_OK(strcmp(filename, "watch_del_dir")); - #endif + /* The filename may be NULL when it cannot be determined. */ + if (filename != NULL) + ASSERT_STR_EQ(filename, "watch_del_dir"); ASSERT_OK(uv_fs_event_stop(handle)); uv_close((uv_handle_t*)handle, close_cb); } @@ -519,6 +514,51 @@ TEST_IMPL(fs_event_watch_delete_dir) { return 0; } +#ifdef _WIN32 +static int fs_event_cb_del_dir_perm_got_enoent; + +static void fs_event_cb_del_dir_perm(uv_fs_event_t* handle, + const char* filename, + int events, + int status) { + if (status == UV_ENOENT) { + fs_event_cb_del_dir_perm_got_enoent = 1; + uv_close((uv_handle_t*)handle, close_cb); + } +} + +TEST_IMPL(fs_event_watch_delete_dir_win) { + uv_loop_t* loop = uv_default_loop(); + int r; + + /* Setup */ + fs_event_cb_del_dir_perm_got_enoent = 0; + fs_event_unlink_files(NULL); + delete_dir("watch_del_dir/"); + create_dir("watch_del_dir"); + + r = uv_fs_event_init(loop, &fs_event); + ASSERT_OK(r); + r = uv_fs_event_start(&fs_event, fs_event_cb_del_dir_perm, "watch_del_dir", 0); + ASSERT_OK(r); + r = uv_timer_init(loop, &timer); + ASSERT_OK(r); + r = uv_timer_start(&timer, fs_event_del_dir, 100, 0); + ASSERT_OK(r); + + uv_run(loop, UV_RUN_DEFAULT); + + ASSERT_EQ(1, fs_event_cb_del_dir_perm_got_enoent); + ASSERT_EQ(2, close_cb_called); + + /* Cleanup */ + fs_event_unlink_files(NULL); + + MAKE_VALGRIND_HAPPY(loop); + return 0; +} +#endif + TEST_IMPL(fs_event_watch_dir_recursive) { #if defined(__APPLE__) && defined(__TSAN__) @@ -628,7 +668,7 @@ static int short_path_make(const char* parent, memcpy(watch_dir, req.path, pathlen + 1); uv_fs_req_cleanup(&req); - /* The caller appends "\\file1" to both watch_dir and short_dir. If there is + /* The caller appends "\file1" to both watch_dir and short_dir. If there is no room for that suffix, skip this location. */ if (pathlen + sizeof("\\file1") <= watch_n && MultiByteToWideChar(CP_UTF8, 0, watch_dir, -1, @@ -695,6 +735,7 @@ TEST_IMPL(fs_event_watch_dir_short_path) { ASSERT_EQ(1, fs_event_cb_called); ASSERT_EQ(1, timer_cb_called); ASSERT_EQ(1, close_cb_called); + /* Cleanup */ delete_file(watch_file); delete_dir(watch_dir); diff --git a/deps/uv/test/test-fs-readdir.c b/deps/uv/test/test-fs-readdir.c index 6ecfc077e15..d3202a8e080 100644 --- a/deps/uv/test/test-fs-readdir.c +++ b/deps/uv/test/test-fs-readdir.c @@ -524,6 +524,8 @@ TEST_FS_IMPL(fs_readdir_symlink) { uv_fs_t symlink_req; int r; + RETURN_SKIP_IN_APPCONTAINER("symlink creation requires elevated privilege"); + cleanup_symlink_test_files(); r = uv_fs_mkdir(uv_default_loop(), &mkdir_req, "test_symlink_dir", 0755, NULL); diff --git a/deps/uv/test/test-fs.c b/deps/uv/test/test-fs.c index 2a20baf67cd..22657bcc00f 100644 --- a/deps/uv/test/test-fs.c +++ b/deps/uv/test/test-fs.c @@ -35,6 +35,7 @@ # include # include # include +# include # ifndef ERROR_SYMLINK_NOT_SUPPORTED # define ERROR_SYMLINK_NOT_SUPPORTED 1464 # endif @@ -470,6 +471,8 @@ static void open_cb(uv_fs_t* req) { static void open_cb_simple(uv_fs_t* req) { + uv_fs_t close_req; + ASSERT_EQ(req->fs_type, UV_FS_OPEN); if (req->result < 0) { fprintf(stderr, "async open error: %d\n", (int) req->result); @@ -477,6 +480,8 @@ static void open_cb_simple(uv_fs_t* req) { } open_cb_count++; ASSERT(req->path); + ASSERT_OK(uv_fs_close(NULL, &close_req, (uv_file) req->result, NULL)); + uv_fs_req_cleanup(&close_req); uv_fs_req_cleanup(req); } @@ -1041,6 +1046,12 @@ TEST_FS_IMPL(fs_file_async) { static void fs_file_sync(int add_flags) { int r; +#ifdef _WIN32 + HANDLE report_file; + LARGE_INTEGER report_size; + _HFILE old_report_file; + int old_report_mode; +#endif /* Setup. */ unlink("test_file"); @@ -1066,6 +1077,36 @@ static void fs_file_sync(int add_flags) { ASSERT_OK(close_req.result); uv_fs_req_cleanup(&close_req); +#ifdef _WIN32 + /* Invalid descriptors must not trigger debug CRT assertions. */ + report_file = CreateFileA("test_crt_report", + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_TEMPORARY | + FILE_FLAG_DELETE_ON_CLOSE, + NULL); + ASSERT_PTR_NE(report_file, INVALID_HANDLE_VALUE); + old_report_mode = _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE); + old_report_file = _CrtSetReportFile(_CRT_ASSERT, (_HFILE) report_file); +#endif + + r = uv_fs_close(NULL, &close_req, open_req1.result, NULL); + +#ifdef _WIN32 + _CrtSetReportFile(_CRT_ASSERT, old_report_file); + _CrtSetReportMode(_CRT_ASSERT, old_report_mode); + ASSERT_NE(FlushFileBuffers(report_file), 0); + ASSERT_NE(GetFileSizeEx(report_file, &report_size), 0); + ASSERT_EQ(0, report_size.QuadPart); + ASSERT_NE(CloseHandle(report_file), 0); +#endif + + ASSERT_EQ(UV_EBADF, r); + ASSERT_EQ(UV_EBADF, close_req.result); + uv_fs_req_cleanup(&close_req); + r = uv_fs_open(NULL, &open_req1, "test_file", UV_FS_O_RDWR | add_flags, 0, NULL); ASSERT_GE(r, 0); @@ -1376,6 +1417,10 @@ static int test_sendfile(void (*setup)(int), uv_fs_cb cb, size_t expected_size) ASSERT_GE(req.result, 0); ASSERT_EQ(buf1[0], 'e'); /* 'e' from begin */ uv_fs_req_cleanup(&req); + + r = uv_fs_close(NULL, &close_req, open_req1.result, NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&close_req); } else { ASSERT_UINT64_EQ(s1.st_size, s2.st_size); } @@ -1676,6 +1721,8 @@ TEST_FS_IMPL(fs_fstat_st_dev) { char* test_file = "tmp_st_dev"; char* symlink_file = "tmp_st_dev_link"; + RETURN_SKIP_IN_APPCONTAINER("symlink creation requires elevated privilege"); + unlink(test_file); unlink(symlink_file); @@ -1685,6 +1732,8 @@ TEST_FS_IMPL(fs_fstat_st_dev) { ASSERT_GE(r, 0); ASSERT_GE(req.result, 0); uv_fs_req_cleanup(&req); + ASSERT_OK(uv_fs_close(NULL, &req, r, NULL)); + uv_fs_req_cleanup(&req); // Create a symlink r = uv_fs_symlink(loop, &req, test_file, symlink_file, 0, NULL); @@ -2612,6 +2661,7 @@ TEST_FS_IMPL(fs_symlink_dir) { } TEST_FS_IMPL(fs_symlink_junction) { + RETURN_SKIP_IN_APPCONTAINER("junction lstat not supported"); return test_symlink_dir_impl(UV_FS_SYMLINK_JUNCTION); } @@ -3090,6 +3140,8 @@ TEST_FS_IMPL(fs_futime) { ASSERT_EQ(1, futime_cb_count); /* Cleanup. */ + ASSERT_OK(uv_fs_close(NULL, &req, file, NULL)); + uv_fs_req_cleanup(&req); unlink(path); MAKE_VALGRIND_HAPPY(loop); @@ -4503,7 +4555,7 @@ TEST_FS_IMPL(fs_file_pos_append) { } #endif -TEST_FS_IMPL(fs_null_req) { +TEST_IMPL(fs_null_req) { /* Verify that all fs functions return UV_EINVAL when the request is NULL. */ int r; @@ -4724,6 +4776,8 @@ TEST_FS_IMPL(fs_open_readonly_acl) { uv_fs_t req; int r; + RETURN_SKIP_IN_APPCONTAINER("cannot modify file ACLs"); + /* Based on Node.js test from https://github.com/nodejs/node/commit/3ba81e34e86a5c32658e218cb6e65b13e8326bc5 @@ -4951,7 +5005,7 @@ TEST_FS_IMPL(fs_statfs) { return 0; } -TEST_FS_IMPL(fs_get_system_error) { +TEST_IMPL(fs_get_system_error) { uv_fs_t req; int r; int system_error; diff --git a/deps/uv/test/test-idna.c b/deps/uv/test/test-idna.c index c154bb4de78..ed8fad813a0 100644 --- a/deps/uv/test/test-idna.c +++ b/deps/uv/test/test-idna.c @@ -244,5 +244,52 @@ TEST_IMPL(wtf8) { ASSERT_GT(len, 0); ASSERT_LT(len, ARRAY_SIZE(buf)); uv_wtf8_to_utf16(input_max, buf, len); + + /* Invalid start byte for UTF-8/WTF-8 sequence */ + ASSERT_EQ(UV_EINVAL, uv_wtf8_length_as_utf16("\xFF")); + return 0; +} + +TEST_IMPL(utf16_to_wtf8_exact_fill) { + /* Regression test for the off-by-one NUL write in uv_utf16_to_wtf8(). + * + * The API contract says target_len_ptr excludes space for the NUL terminator. + * The caller must pass (buffer_size - 1) so that the NUL written at + * target[target_len] stays in bounds. + * + * U+4E2D encodes to 3 UTF-8 bytes (0xE4 0xB8 0xAD). With a buffer of size N + * (divisible by 3) and N/3 input characters, the worst-case output exactly + * fills the data portion. Passing target_len = N - 1 must keep the NUL inside + * the buffer, and passing target_len = N would write one byte past the end. + */ + static const size_t sizes[] = { 3, 6, 48, 96, 192 }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(sizes); i++) { + size_t buf_size = sizes[i]; + size_t num_chars = buf_size / 3; + char mem[200]; + uint16_t utf16[200]; + char* target; + size_t target_len; + size_t j; + + ASSERT_NOT_NULL(mem); + ASSERT_NOT_NULL(utf16); + + /* Fill entire region including canary with 0xAA. */ + memset(mem, 0xAA, buf_size + 1); + for (j = 0; j < num_chars; j++) + utf16[j] = 0x4E2D; /* U+4E2D (中) — 3-byte UTF-8 */ + + /* Correct usage: target_len = buf_size - 1 reserves space for NUL. */ + target = mem; + target_len = buf_size - 1; + uv_utf16_to_wtf8(utf16, num_chars, &target, &target_len); + + /* NUL must land inside the buffer; canary byte must be untouched. */ + ASSERT_EQ((unsigned char) mem[buf_size], 0xAA); + } + return 0; } diff --git a/deps/uv/test/test-io-64-safe.c b/deps/uv/test/test-io-64-safe.c new file mode 100644 index 00000000000..b58a8b5b11f --- /dev/null +++ b/deps/uv/test/test-io-64-safe.c @@ -0,0 +1,182 @@ +/* Copyright libuv contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/* Verify that passing INT32_MAX as a buffer length is rejected with UV_EINVAL + * at the various I/O entry points that enforce UV__IO_MAX_BYTES. + */ + +#include "uv.h" +#include "task.h" + +#include +#include +#include + +#define TEST_FILE "tmp_io_64_safe" + +static void on_udp_send(uv_udp_send_t* req, int status) { + /* Should never be called: uv_udp_send must reject synchronously. */ + ASSERT(0 && "on_udp_send callback must not be invoked"); + (void) req; + (void) status; +} + +TEST_IMPL(io_64_safe) { + uv_loop_t* loop; + uv_fs_t open_req; + uv_fs_t fs_req; + uv_write_t write_req; + uv_udp_t udp; + uv_tcp_t tcp; + uv_udp_send_t send_req; + struct sockaddr_in addr; + uv_buf_t* t2_bufs[1]; + unsigned int t2_nbufs[1]; + struct sockaddr* t2_addrs[1]; + uv_buf_t buf; + uv_buf_t bufs2[2]; + uv_file fd; + uv_file in_fd; + uv_file out_fd; + + loop = uv_default_loop(); + + /* A buf whose length just exceeds UV__IO_MAX_BYTES (0x7ffff000). */ + buf = uv_buf_init(NULL, INT32_MAX); + + /* Two buffers whose individual sizes are reasonable but whose sum exceeds + * UV__IO_MAX_BYTES (0x7ffff000). Each is 1 GiB + 1 byte. + */ + bufs2[0] = uv_buf_init(NULL, 0x40000001u); + bufs2[1] = uv_buf_init(NULL, 0x40000001u); + + /* ------------------------------------------------------------------ */ + /* uv_fs_write: reject synchronous filesystem write > UV__IO_MAX_BYTES. */ + /* ------------------------------------------------------------------ */ + { + fd = uv_fs_open(NULL, &open_req, TEST_FILE, + UV_FS_O_WRONLY | UV_FS_O_CREAT | UV_FS_O_TRUNC, S_IRUSR | S_IWUSR, NULL); + ASSERT_GE(fd, 0); + uv_fs_req_cleanup(&open_req); + + ASSERT_EQ(UV_EINVAL, uv_fs_write(NULL, &fs_req, fd, &buf, 1, 0, NULL)); + uv_fs_req_cleanup(&fs_req); + + /* nbufs > 1 where sum > UV__IO_MAX_BYTES */ + ASSERT_EQ(UV_EINVAL, uv_fs_write(NULL, &fs_req, fd, bufs2, 2, 0, NULL)); + uv_fs_req_cleanup(&fs_req); + + uv_fs_close(NULL, &fs_req, fd, NULL); + uv_fs_req_cleanup(&fs_req); + } + + /* ------------------------------------------------------------------ */ + /* uv_fs_sendfile: reject len > UV__IO_MAX_BYTES. */ + /* ------------------------------------------------------------------ */ + { + in_fd = uv_fs_open(NULL, &open_req, TEST_FILE, + UV_FS_O_RDONLY | UV_FS_O_CREAT, S_IRUSR | S_IWUSR, NULL); + ASSERT_GE(in_fd, 0); + uv_fs_req_cleanup(&open_req); + + out_fd = uv_fs_open(NULL, &open_req, TEST_FILE, + UV_FS_O_WRONLY | UV_FS_O_CREAT | UV_FS_O_APPEND, S_IRUSR | S_IWUSR, + NULL); + ASSERT_GE(out_fd, 0); + uv_fs_req_cleanup(&open_req); + + ASSERT_EQ(UV_EINVAL, + uv_fs_sendfile(NULL, &fs_req, out_fd, in_fd, 0, + (size_t) INT32_MAX, NULL)); + uv_fs_req_cleanup(&fs_req); + + uv_fs_close(NULL, &fs_req, in_fd, NULL); + uv_fs_req_cleanup(&fs_req); + uv_fs_close(NULL, &fs_req, out_fd, NULL); + uv_fs_req_cleanup(&fs_req); + } + + uv_fs_unlink(NULL, &fs_req, TEST_FILE, NULL); + uv_fs_req_cleanup(&fs_req); + + { + /* uv_write: reject stream write > UV__IO_MAX_BYTES before queuing. */ + ASSERT_OK(uv_tcp_init(loop, &tcp)); + ASSERT_EQ(UV_EINVAL, + uv_write(&write_req, (uv_stream_t*) &tcp, &buf, 1, NULL)); + + /* nbufs > 1 where sum > UV__IO_MAX_BYTES */ + ASSERT_EQ(UV_EINVAL, + uv_write(&write_req, (uv_stream_t*) &tcp, bufs2, 2, NULL)); + + /* uv_try_write: same check via the synchronous path. */ + ASSERT_EQ(UV_EINVAL, uv_try_write((uv_stream_t*) &tcp, &buf, 1)); + + /* nbufs > 1 via try_write */ + ASSERT_EQ(UV_EINVAL, uv_try_write((uv_stream_t*) &tcp, bufs2, 2)); + + uv_close((uv_handle_t*) &tcp, NULL); + } + + { + ASSERT_OK(uv_udp_init(loop, &udp)); + ASSERT_OK(uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); + + /* uv_udp_try_send: reject UDP send > UV__IO_MAX_BYTES. */ + ASSERT_EQ(UV_EINVAL, + uv_udp_try_send(&udp, &buf, 1, + (const struct sockaddr*) &addr)); + + /* nbufs > 1 via try_send */ + ASSERT_EQ(UV_EINVAL, + uv_udp_try_send(&udp, bufs2, 2, + (const struct sockaddr*) &addr)); + + /* uv_udp_send (async): reject synchronously before queuing. */ + ASSERT_EQ(UV_EINVAL, + uv_udp_send(&send_req, &udp, &buf, 1, + (const struct sockaddr*) &addr, on_udp_send)); + + /* nbufs > 1 via async send */ + ASSERT_EQ(UV_EINVAL, + uv_udp_send(&send_req, &udp, bufs2, 2, + (const struct sockaddr*) &addr, on_udp_send)); + + /* uv_udp_try_send2: reject per-batch size > UV__IO_MAX_BYTES. */ + t2_bufs[0] = &buf; + t2_nbufs[0] = 1; + t2_addrs[0] = (struct sockaddr*) &addr; + ASSERT_EQ(UV_EINVAL, + uv_udp_try_send2(&udp, 1, t2_bufs, t2_nbufs, t2_addrs, 0)); + + /* nbufs > 1 per batch via try_send2 */ + t2_bufs[0] = bufs2; + t2_nbufs[0] = 2; + ASSERT_EQ(UV_EINVAL, + uv_udp_try_send2(&udp, 1, t2_bufs, t2_nbufs, t2_addrs, 0)); + + uv_close((uv_handle_t*) &udp, NULL); + } + + uv_run(loop, UV_RUN_DEFAULT); + MAKE_VALGRIND_HAPPY(loop); + return 0; +} diff --git a/deps/uv/test/test-list.h b/deps/uv/test/test-list.h index b79a4ea74ed..0d9923f97ae 100644 --- a/deps/uv/test/test-list.h +++ b/deps/uv/test/test-list.h @@ -22,6 +22,7 @@ #include "uv.h" TEST_DECLARE (platform_output) +TEST_DECLARE (sizeof) TEST_DECLARE (close_order) TEST_DECLARE (run_once) TEST_DECLARE (run_nowait) @@ -111,7 +112,17 @@ TEST_DECLARE (tcp_write_fail) TEST_DECLARE (tcp_try_write) TEST_DECLARE (tcp_write_in_a_row) TEST_DECLARE (tcp_try_write_error) +TEST_DECLARE (tcp_write_in_read_cb_backend_timeout) TEST_DECLARE (tcp_write_queue_order) +TEST_DECLARE (tcp_write_cancel) +TEST_DECLARE (pipe_write_cancel) +TEST_DECLARE (pipe_write_cancel_all) +TEST_DECLARE (pipe_write_cancel_after_close) +TEST_DECLARE (pipe_write_cancel_overlapped) +TEST_DECLARE (pipe_write_cancel_all_overlapped) +TEST_DECLARE (pipe_write_cancel_ipc) +TEST_DECLARE (tcp_write_nwritten) +TEST_DECLARE (pipe_write_nwritten) TEST_DECLARE (tcp_open) TEST_DECLARE (tcp_open_twice) TEST_DECLARE (tcp_open_bound) @@ -181,6 +192,11 @@ TEST_DECLARE (udp_recvmsg_unreachable_error) TEST_DECLARE (udp_recvmsg_unreachable_error6) TEST_DECLARE (udp_send_pollerr_no_recv) TEST_DECLARE (udp_mmsg) +#ifndef _WIN32 +TEST_DECLARE (udp_mmsg_namelen_zero) +#endif +TEST_DECLARE (udp_mmsg_single_drain_cb) +TEST_DECLARE (udp_mmsg_small_buf) TEST_DECLARE (udp_multicast_join) TEST_DECLARE (udp_multicast_join6) TEST_DECLARE (udp_multicast_ttl) @@ -201,9 +217,11 @@ TEST_DECLARE (udp_reuseport) #ifndef _WIN32 TEST_DECLARE (udp_send_unix) #endif +TEST_DECLARE (udp_send_fail_nbufs) TEST_DECLARE (udp_sendmmsg_error) TEST_DECLARE (udp_try_send) TEST_DECLARE (pipe_bind_error_addrinuse) +TEST_DECLARE (pipe_bind_error_addrinuse_pending_instances) TEST_DECLARE (pipe_bind_error_addrnotavail) TEST_DECLARE (pipe_bind_error_inval) TEST_DECLARE (pipe_connect_close_multiple) @@ -218,6 +236,7 @@ TEST_DECLARE (pipe_getsockname) TEST_DECLARE (pipe_getsockname_abstract) TEST_DECLARE (pipe_getsockname_autobind) TEST_DECLARE (pipe_getsockname_blocking) +TEST_DECLARE (pipe_getsockname_long_path) TEST_DECLARE (pipe_pending_instances) TEST_DECLARE (pipe_sendmsg) TEST_DECLARE (pipe_server_close) @@ -281,6 +300,7 @@ TEST_DECLARE (pipe_close_stdout_read_stdin) #endif TEST_DECLARE (pipe_set_non_blocking) TEST_DECLARE (pipe_set_chmod) +TEST_DECLARE (pipe_write_trailing_empty_buf) TEST_DECLARE (process_ref) TEST_DECLARE (process_priority) TEST_DECLARE (has_ref) @@ -327,6 +347,7 @@ TEST_DECLARE (spawn_empty_env) TEST_DECLARE (spawn_exit_code) TEST_DECLARE (spawn_stdout) TEST_DECLARE (spawn_stdin) +TEST_DECLARE (spawn_stdio_socket_buffer_size) TEST_DECLARE (spawn_stdio_greater_than_3) TEST_DECLARE (spawn_ignored_stdio) TEST_DECLARE (spawn_and_kill) @@ -407,6 +428,9 @@ TEST_FS_DECLARE (fs_read_bufs) TEST_FS_DECLARE (fs_read_file_eof) TEST_DECLARE (fs_event_watch_dir) TEST_DECLARE (fs_event_watch_delete_dir) +#ifdef _WIN32 +TEST_DECLARE (fs_event_watch_delete_dir_win) +#endif TEST_DECLARE (fs_event_watch_dir_recursive) #ifdef _WIN32 TEST_DECLARE (fs_event_watch_dir_short_path) @@ -450,7 +474,7 @@ TEST_FS_DECLARE (fs_write_alotof_bufs_with_offset) TEST_FS_DECLARE (fs_partial_read) TEST_FS_DECLARE (fs_partial_write) TEST_FS_DECLARE (fs_file_pos_after_op_with_offset) -TEST_FS_DECLARE (fs_null_req) +TEST_DECLARE (fs_null_req) TEST_FS_DECLARE (fs_read_dir) #ifdef _WIN32 TEST_FS_DECLARE (fs_file_pos_write) @@ -462,7 +486,8 @@ TEST_FS_DECLARE (fs_fchmod_archive_readonly) TEST_FS_DECLARE (fs_invalid_mkdir_name) TEST_FS_DECLARE (fs_wtf) #endif -TEST_FS_DECLARE (fs_get_system_error) +TEST_DECLARE (fs_get_system_error) +TEST_DECLARE (io_64_safe) TEST_DECLARE (strscpy) TEST_DECLARE (strtok) TEST_DECLARE (threadpool_queue_work_simple) @@ -473,6 +498,7 @@ TEST_DECLARE (threadpool_cancel_getnameinfo) TEST_DECLARE (threadpool_cancel_random) TEST_DECLARE (threadpool_cancel_work) TEST_FS_DECLARE (threadpool_cancel_fs) +TEST_DECLARE (threadpool_cancel_fs_iouring_sync_cancel) TEST_DECLARE (threadpool_cancel_single) TEST_DECLARE (threadpool_cancel_when_busy) TEST_DECLARE (thread_detach) @@ -543,6 +569,7 @@ TEST_DECLARE (signal_pending_on_close) TEST_DECLARE (signal_close_loop_alive) #endif #ifdef __APPLE__ +TEST_DECLARE (osx_resident_set_memory) TEST_DECLARE (osx_select) TEST_DECLARE (osx_select_many_fds) #endif @@ -586,6 +613,7 @@ TEST_DECLARE (fork_threadpool_queue_work_simple) TEST_DECLARE (iouring_pollhup) TEST_DECLARE (wtf8) +TEST_DECLARE (utf16_to_wtf8_exact_fill) TEST_DECLARE (idna_toascii) TEST_DECLARE (utf8_decode1) TEST_DECLARE (utf8_decode1_overrun) @@ -600,6 +628,7 @@ TEST_DECLARE (metrics_idle_time_zero) TASK_LIST_START TEST_ENTRY_CUSTOM (platform_output, 0, 1, 5000) + TEST_ENTRY (sizeof) TEST_ENTRY (test_macros) TEST_ENTRY (close_order) TEST_ENTRY (run_once) @@ -727,7 +756,17 @@ TASK_LIST_START TEST_ENTRY (tcp_write_in_a_row) TEST_ENTRY (tcp_try_write_error) + TEST_ENTRY (tcp_write_in_read_cb_backend_timeout) TEST_ENTRY (tcp_write_queue_order) + TEST_ENTRY (tcp_write_cancel) + TEST_ENTRY (pipe_write_cancel) + TEST_ENTRY (pipe_write_cancel_all) + TEST_ENTRY (pipe_write_cancel_after_close) + TEST_ENTRY (pipe_write_cancel_overlapped) + TEST_ENTRY (pipe_write_cancel_all_overlapped) + TEST_ENTRY (pipe_write_cancel_ipc) + TEST_ENTRY (tcp_write_nwritten) + TEST_ENTRY (pipe_write_nwritten) TEST_ENTRY (tcp_open) TEST_HELPER (tcp_open, tcp4_echo_server) @@ -823,11 +862,17 @@ TASK_LIST_START TEST_ENTRY (udp_options6) TEST_ENTRY (udp_no_autobind) TEST_ENTRY (udp_mmsg) +#ifndef _WIN32 + TEST_ENTRY (udp_mmsg_namelen_zero) +#endif + TEST_ENTRY (udp_mmsg_single_drain_cb) + TEST_ENTRY (udp_mmsg_small_buf) TEST_ENTRY (udp_multicast_interface) TEST_ENTRY (udp_multicast_interface6) TEST_ENTRY (udp_multicast_join) TEST_ENTRY (udp_multicast_join6) TEST_ENTRY (udp_multicast_ttl) + TEST_ENTRY (udp_send_fail_nbufs) TEST_ENTRY (udp_sendmmsg_error) TEST_ENTRY (udp_try_send) TEST_ENTRY (udp_recv_in_a_row) @@ -842,6 +887,7 @@ TASK_LIST_START #endif TEST_ENTRY (pipe_bind_error_addrinuse) + TEST_ENTRY (pipe_bind_error_addrinuse_pending_instances) TEST_ENTRY (pipe_bind_error_addrnotavail) TEST_ENTRY (pipe_bind_error_inval) TEST_ENTRY (pipe_connect_close_multiple) @@ -853,8 +899,10 @@ TASK_LIST_START TEST_ENTRY (pipe_getsockname_abstract) TEST_ENTRY (pipe_getsockname_autobind) TEST_ENTRY (pipe_getsockname_blocking) + TEST_ENTRY (pipe_getsockname_long_path) TEST_ENTRY (pipe_pending_instances) TEST_ENTRY (pipe_sendmsg) + TEST_ENTRY (pipe_write_trailing_empty_buf) TEST_ENTRY (connection_fail) TEST_ENTRY (connection_fail_doesnt_auto_close) @@ -1016,6 +1064,9 @@ TASK_LIST_START TEST_ENTRY (spawn_exit_code) TEST_ENTRY (spawn_stdout) TEST_ENTRY (spawn_stdin) +#ifndef _WIN32 + TEST_ENTRY (spawn_stdio_socket_buffer_size) +#endif TEST_ENTRY (spawn_stdio_greater_than_3) TEST_ENTRY (spawn_ignored_stdio) TEST_ENTRY (spawn_and_kill) @@ -1081,6 +1132,7 @@ TASK_LIST_START #endif #ifdef __APPLE__ + TEST_ENTRY (osx_resident_set_memory) TEST_ENTRY (osx_select) TEST_ENTRY (osx_select_many_fds) #endif @@ -1135,6 +1187,9 @@ TASK_LIST_START TEST_FS_ENTRY (fs_file_open_append) TEST_ENTRY (fs_event_watch_dir) TEST_ENTRY (fs_event_watch_delete_dir) +#ifdef _WIN32 + TEST_ENTRY (fs_event_watch_delete_dir_win) +#endif TEST_ENTRY (fs_event_watch_dir_recursive) #ifdef _WIN32 TEST_ENTRY (fs_event_watch_dir_short_path) @@ -1176,7 +1231,7 @@ TASK_LIST_START TEST_FS_ENTRY (fs_partial_write) TEST_FS_ENTRY (fs_read_write_null_arguments) TEST_FS_ENTRY (fs_file_pos_after_op_with_offset) - TEST_FS_ENTRY (fs_null_req) + TEST_ENTRY (fs_null_req) TEST_FS_ENTRY (fs_read_dir) #ifdef _WIN32 TEST_FS_ENTRY (fs_file_pos_write) @@ -1188,9 +1243,10 @@ TASK_LIST_START TEST_FS_ENTRY (fs_invalid_mkdir_name) TEST_FS_ENTRY (fs_wtf) #endif - TEST_FS_ENTRY (fs_get_system_error) + TEST_ENTRY (fs_get_system_error) TEST_ENTRY (get_osfhandle_valid_handle) TEST_ENTRY (open_osfhandle_valid_handle) + TEST_ENTRY (io_64_safe) TEST_ENTRY (strscpy) TEST_ENTRY (strtok) TEST_ENTRY (threadpool_queue_work_simple) @@ -1201,6 +1257,7 @@ TASK_LIST_START TEST_ENTRY (threadpool_cancel_random) TEST_ENTRY (threadpool_cancel_work) TEST_FS_ENTRY (threadpool_cancel_fs) + TEST_ENTRY (threadpool_cancel_fs_iouring_sync_cancel) TEST_ENTRY (threadpool_cancel_single) TEST_ENTRY (threadpool_cancel_when_busy) TEST_ENTRY (thread_detach) @@ -1251,6 +1308,7 @@ TASK_LIST_START TEST_ENTRY (iouring_pollhup) TEST_ENTRY (wtf8) + TEST_ENTRY (utf16_to_wtf8_exact_fill) TEST_ENTRY (utf8_decode1) TEST_ENTRY (utf8_decode1_overrun) TEST_ENTRY (uname) diff --git a/deps/uv/test/test-metrics.c b/deps/uv/test/test-metrics.c index 361fcef5ced..f1ab2b8cc3a 100644 --- a/deps/uv/test/test-metrics.c +++ b/deps/uv/test/test-metrics.c @@ -307,6 +307,7 @@ static void fs_addrinfo_cb(uv_getaddrinfo_t* req, TEST_IMPL(metrics_pool_events) { uv_buf_t iov; + uv_fs_t close_req; uv_fs_t open_req; uv_fs_t stat1_req; uv_fs_t stat2_req; @@ -382,6 +383,9 @@ TEST_IMPL(metrics_pool_events) { ASSERT_GE(metrics.events_waiting, 4); ASSERT_EQ(pool_events_counter, -42); + ASSERT_OK(uv_fs_close(NULL, &close_req, fd, NULL)); + uv_fs_req_cleanup(&close_req); + uv_fs_unlink(NULL, &unlink_req, "test_file", NULL); uv_fs_req_cleanup(&unlink_req); diff --git a/deps/uv/test/test-osx-resident-set-memory.c b/deps/uv/test/test-osx-resident-set-memory.c new file mode 100644 index 00000000000..a3ee760ef1b --- /dev/null +++ b/deps/uv/test/test-osx-resident-set-memory.c @@ -0,0 +1,64 @@ +/* Copyright libuv project contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "uv.h" +#include "task.h" + +#ifdef __APPLE__ + +#include +#include + +TEST_IMPL(osx_resident_set_memory) { + size_t before; + size_t during; + size_t after; + size_t size; + char* mem; + + size = 64 << 20; + + ASSERT_OK(uv_resident_set_memory(&before)); + + mem = mmap(NULL, + size, + PROT_READ | PROT_WRITE, + MAP_ANON | MAP_PRIVATE, + -1, + 0); + ASSERT_PTR_NE(mem, MAP_FAILED); + memset(mem, 42, size); + + ASSERT_OK(uv_resident_set_memory(&during)); + ASSERT_GE(during, before + size / 2); + + /* How allocators free memory on macOS; the pages stay resident. */ + ASSERT_OK(madvise(mem, size, MADV_FREE_REUSABLE)); + + ASSERT_OK(uv_resident_set_memory(&after)); + ASSERT_LT(after, before + size / 2); + + ASSERT_OK(munmap(mem, size)); + + return 0; +} + +#endif /* __APPLE__ */ diff --git a/deps/uv/test/test-pipe-bind-error.c b/deps/uv/test/test-pipe-bind-error.c index 16164a7ee90..3f212258a03 100644 --- a/deps/uv/test/test-pipe-bind-error.c +++ b/deps/uv/test/test-pipe-bind-error.c @@ -73,6 +73,34 @@ TEST_IMPL(pipe_bind_error_addrinuse) { } +TEST_IMPL(pipe_bind_error_addrinuse_pending_instances) { + uv_pipe_t server1, server2; + int r; + + r = uv_pipe_init(uv_default_loop(), &server1, 0); + ASSERT_OK(r); + uv_pipe_pending_instances(&server1, 32); + r = uv_pipe_bind(&server1, TEST_PIPENAME); + ASSERT_OK(r); + + r = uv_pipe_init(uv_default_loop(), &server2, 0); + ASSERT_OK(r); + uv_pipe_pending_instances(&server2, 32); + r = uv_pipe_bind(&server2, TEST_PIPENAME); + ASSERT_EQ(r, UV_EADDRINUSE); + + uv_close((uv_handle_t*)&server1, close_cb); + uv_close((uv_handle_t*)&server2, close_cb); + + uv_run(uv_default_loop(), UV_RUN_DEFAULT); + + ASSERT_EQ(2, close_cb_called); + + MAKE_VALGRIND_HAPPY(uv_default_loop()); + return 0; +} + + TEST_IMPL(pipe_bind_error_addrnotavail) { uv_pipe_t server; int r; diff --git a/deps/uv/test/test-pipe-close-stdout-read-stdin.c b/deps/uv/test/test-pipe-close-stdout-read-stdin.c index 51bbf0f54e2..990cf16c611 100644 --- a/deps/uv/test/test-pipe-close-stdout-read-stdin.c +++ b/deps/uv/test/test-pipe-close-stdout-read-stdin.c @@ -81,6 +81,8 @@ TEST_IMPL(pipe_close_stdout_read_stdin) { close(0); r = dup(fd[0]); ASSERT_NE(r, -1); + /* fd 0 is the descriptor uv_pipe_open() takes over below. */ + ASSERT_OK(close(fd[0])); /* Create a stream that reads from the pipe. */ r = uv_pipe_init(uv_default_loop(), (uv_pipe_t *)&stdin_pipe, 0); diff --git a/deps/uv/test/test-pipe-connect-multiple.c b/deps/uv/test/test-pipe-connect-multiple.c index 69a09ec6cdd..fc06e7aea0d 100644 --- a/deps/uv/test/test-pipe-connect-multiple.c +++ b/deps/uv/test/test-pipe-connect-multiple.c @@ -73,6 +73,7 @@ TEST_IMPL(pipe_connect_multiple) { #if defined(NO_SELF_CONNECT) RETURN_SKIP(NO_SELF_CONNECT); #endif + RETURN_SKIP_IN_APPCONTAINER("flaky crash in child process spawning"); int i; int r; uv_loop_t* loop; @@ -139,6 +140,7 @@ TEST_IMPL(pipe_connect_close_multiple) { #if defined(NO_SELF_CONNECT) RETURN_SKIP(NO_SELF_CONNECT); #endif + RETURN_SKIP_IN_APPCONTAINER("flaky crash in child process spawning"); int i; int r; uv_loop_t* loop; diff --git a/deps/uv/test/test-pipe-getsockname.c b/deps/uv/test/test-pipe-getsockname.c index 3be30e67418..69f87cd140f 100644 --- a/deps/uv/test/test-pipe-getsockname.c +++ b/deps/uv/test/test-pipe-getsockname.c @@ -26,6 +26,8 @@ #include #ifndef _WIN32 +# include +# include # include /* close */ #else # include @@ -368,3 +370,53 @@ TEST_IMPL(pipe_getsockname_blocking) { MAKE_VALGRIND_HAPPY(uv_default_loop()); return 0; } + +#ifdef SOCK_MAXADDRLEN +static void long_path_connect_cb(uv_connect_t* req, int status) { + ASSERT_OK(status); + uv_close((uv_handle_t*) req->handle, NULL); +} +#endif + +TEST_IMPL(pipe_getsockname_long_path) { +#if !defined(SOCK_MAXADDRLEN) || defined(__FreeBSD__) + RETURN_SKIP("long unix paths not supported on this platform"); +#else + uv_loop_t* loop; + size_t len; + int r; + char path[SOCK_MAXADDRLEN - offsetof(struct sockaddr_un, sun_path) + 1]; + char name[SOCK_MAXADDRLEN - offsetof(struct sockaddr_un, sun_path) + 1]; + + loop = uv_default_loop(); + ASSERT_NOT_NULL(loop); + + r = uv_pipe_init(loop, &pipe_server, 0); + ASSERT_OK(r); + r = uv_pipe_init(loop, &pipe_client, 0); + ASSERT_OK(r); + + memset(path, 'x', sizeof(path) - 1); + path[sizeof(path) - 1] = '\0'; + + r = uv_pipe_bind(&pipe_server, path); + ASSERT_OK(r); + + len = sizeof(name); + r = uv_pipe_getsockname(&pipe_server, name, &len); + + ASSERT_OK(r); + ASSERT_EQ(len, strlen(path)); + ASSERT_MEM_EQ(path, name, len); + + r = uv_listen((uv_stream_t*) &pipe_server, 0, pipe_server_connection_cb); + ASSERT_OK(r); + + uv_pipe_connect(&connect_req, &pipe_client, path, long_path_connect_cb); + uv_close((uv_handle_t*) &pipe_server, NULL); + ASSERT_OK(uv_run(loop, UV_RUN_DEFAULT)); + + MAKE_VALGRIND_HAPPY(loop); + return 0; +#endif +} diff --git a/deps/uv/test/test-pipe-sendmsg.c b/deps/uv/test/test-pipe-sendmsg.c index 3958b05378c..749cc6a3135 100644 --- a/deps/uv/test/test-pipe-sendmsg.c +++ b/deps/uv/test/test-pipe-sendmsg.c @@ -156,7 +156,11 @@ TEST_IMPL(pipe_sendmsg) { uv_run(uv_default_loop(), UV_RUN_DEFAULT); ASSERT_EQ(ARRAY_SIZE(incoming), incoming_count); ASSERT_EQ(ARRAY_SIZE(incoming) + 1, close_called); - close(fds[0]); + + /* sendmsg() duplicates the descriptors, it does not consume them. */ + for (i = 0; i < ARRAY_SIZE(send_fds); i++) + ASSERT_OK(close(send_fds[i])); + ASSERT_OK(close(fds[0])); MAKE_VALGRIND_HAPPY(uv_default_loop()); return 0; diff --git a/deps/uv/test/test-pipe-write.c b/deps/uv/test/test-pipe-write.c new file mode 100644 index 00000000000..22ffa67ffca --- /dev/null +++ b/deps/uv/test/test-pipe-write.c @@ -0,0 +1,81 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "uv.h" +#include "task.h" + + +#ifndef _WIN32 +#include +#include +#endif + + +static int write_cb_called; +static int close_cb_called; + + +static void close_cb(uv_handle_t* handle) { + ASSERT_NOT_NULL(handle); + close_cb_called++; +} + + +static void write_cb(uv_write_t* req, int status) { + ASSERT_NOT_NULL(req); + ASSERT_OK(status); + write_cb_called++; + uv_close((uv_handle_t*) req->handle, close_cb); +} + + +TEST_IMPL(pipe_write_trailing_empty_buf) { +#ifdef _WIN32 + RETURN_SKIP("Unix only test"); +#else + uv_pipe_t pipe_handle; + uv_write_t write_req; + uv_buf_t bufs[2]; + int fd; + + fd = open("/dev/null", O_WRONLY); + ASSERT_GE(fd, 0); + + ASSERT_OK(uv_pipe_init(uv_default_loop(), &pipe_handle, 0)); + ASSERT_OK(uv_pipe_open(&pipe_handle, fd)); + fd = -1; /* fd is owned by pipe_handle now. */ + + bufs[0] = uv_buf_init("hello\n", 6); + bufs[1] = uv_buf_init(NULL, 0); + ASSERT_OK(uv_write(&write_req, + (uv_stream_t*) &pipe_handle, + bufs, + ARRAY_SIZE(bufs), + write_cb)); + + ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + ASSERT_EQ(1, write_cb_called); + ASSERT_EQ(1, close_cb_called); + + MAKE_VALGRIND_HAPPY(uv_default_loop()); + return 0; +#endif +} diff --git a/deps/uv/test/test-poll-close.c b/deps/uv/test/test-poll-close.c index 5843bf4516d..ed36457bd4d 100644 --- a/deps/uv/test/test-poll-close.c +++ b/deps/uv/test/test-poll-close.c @@ -41,6 +41,16 @@ static void close_cb(uv_handle_t* handle) { } +/* uv_poll_init_socket() does not take ownership of the socket. */ +static void close_socket(uv_os_sock_t sock) { +#ifdef _WIN32 + ASSERT_OK(closesocket(sock)); +#else + ASSERT_OK(close(sock)); +#endif +} + + TEST_IMPL(poll_close) { uv_os_sock_t sockets[NUM_SOCKETS]; uv_poll_t poll_handles[NUM_SOCKETS]; @@ -68,6 +78,9 @@ TEST_IMPL(poll_close) { ASSERT_EQ(close_cb_called, NUM_SOCKETS); + for (i = 0; i < NUM_SOCKETS; i++) + close_socket(sockets[i]); + MAKE_VALGRIND_HAPPY(uv_default_loop()); return 0; } diff --git a/deps/uv/test/test-poll-multiple-handles.c b/deps/uv/test/test-poll-multiple-handles.c index d9c4924dbfe..144c38b4c39 100644 --- a/deps/uv/test/test-poll-multiple-handles.c +++ b/deps/uv/test/test-poll-multiple-handles.c @@ -99,6 +99,13 @@ TEST_IMPL(poll_multiple_handles) { ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_DEFAULT)); ASSERT_EQ(2, close_cb_called); + /* uv_poll_init_socket() does not take ownership of the socket. */ +#ifdef _WIN32 + ASSERT_OK(closesocket(sock)); +#else + ASSERT_OK(close(sock)); +#endif + MAKE_VALGRIND_HAPPY(uv_default_loop()); return 0; } diff --git a/deps/uv/test/test-poll-oob.c b/deps/uv/test/test-poll-oob.c index 4b119d4dd76..cf4da7f2392 100644 --- a/deps/uv/test/test-poll-oob.c +++ b/deps/uv/test/test-poll-oob.c @@ -68,7 +68,7 @@ static void idle_cb(uv_idle_t* idle) { static void poll_cb(uv_poll_t* handle, int status, int events) { char buffer[5]; int n; - int fd; + uv_os_fd_t fd; ASSERT_OK(uv_fileno((uv_handle_t*)handle, &fd)); memset(buffer, 0, 5); @@ -203,6 +203,9 @@ TEST_IMPL(poll_oob) { */ ASSERT_EQ(1, srv_rd_check); + /* uv_poll_init() does not take ownership of the socket. */ + ASSERT_OK(close(client_fd)); + MAKE_VALGRIND_HAPPY(loop); return 0; } diff --git a/deps/uv/test/test-poll.c b/deps/uv/test/test-poll.c index e291e7377ff..46da49d7fa4 100644 --- a/deps/uv/test/test-poll.c +++ b/deps/uv/test/test-poll.c @@ -663,7 +663,7 @@ TEST_IMPL(poll_bad_fdtype) { #ifdef __APPLE__ ASSERT_OK(pipe(fd)); /* Pipes should be permitted in kqueue. */ - ASSERT_EQ(0, uv_poll_init(uv_default_loop(), &poll_handle, fd[0])); + ASSERT_OK(uv_poll_init(uv_default_loop(), &poll_handle, fd[0])); ASSERT_OK(close(fd[0])); ASSERT_OK(close(fd[1])); diff --git a/deps/uv/test/test-sizeof.c b/deps/uv/test/test-sizeof.c new file mode 100644 index 00000000000..7dbedf30948 --- /dev/null +++ b/deps/uv/test/test-sizeof.c @@ -0,0 +1,912 @@ +/* Copyright libuv contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/* Check that all public uv.h struct sizes stay stable across changes. + * Any deliberate ABI change requires updating the expected sizes here. + * Add a new platform block when porting to a new OS or architecture. + */ + +#include "task.h" +#include "uv.h" + +#if defined(__ANDROID__) + +# if defined(__x86_64__) +# define SIZEOF_UV_LOOP_T 848 +# define SIZEOF_UV_REQ_T 64 +# define SIZEOF_UV_HANDLE_T 96 +# define SIZEOF_UV_STREAM_T 248 +# define SIZEOF_UV_TCP_T 248 +# define SIZEOF_UV_UDP_T 216 +# define SIZEOF_UV_PIPE_T 264 +# define SIZEOF_UV_TTY_T 288 +# define SIZEOF_UV_POLL_T 160 +# define SIZEOF_UV_TIMER_T 152 +# define SIZEOF_UV_PREPARE_T 120 +# define SIZEOF_UV_CHECK_T 120 +# define SIZEOF_UV_IDLE_T 120 +# define SIZEOF_UV_ASYNC_T 128 +# define SIZEOF_UV_PROCESS_T 136 +# define SIZEOF_UV_FS_EVENT_T 136 +# define SIZEOF_UV_FS_POLL_T 104 +# define SIZEOF_UV_SIGNAL_T 152 +# define SIZEOF_UV_SHUTDOWN_T 80 +# define SIZEOF_UV_WRITE_T 192 +# define SIZEOF_UV_CONNECT_T 96 +# define SIZEOF_UV_UDP_SEND_T 320 +# define SIZEOF_UV_FS_T 440 +# define SIZEOF_UV_WORK_T 128 +# define SIZEOF_UV_GETADDRINFO_T 160 +# define SIZEOF_UV_GETNAMEINFO_T 1320 +# define SIZEOF_UV_RANDOM_T 144 +# define SIZEOF_UV_LIB_T 16 +# define SIZEOF_UV_BUF_T 16 +# define SIZEOF_UV_DIR_T 56 +# define SIZEOF_UV_STAT_T 160 +# define SIZEOF_UV_STATFS_T 88 +# define SIZEOF_UV_TIMESPEC_T 16 +# define SIZEOF_UV_TIMESPEC64_T 16 +# define SIZEOF_UV_TIMEVAL_T 16 +# define SIZEOF_UV_TIMEVAL64_T 16 +# define SIZEOF_UV_RUSAGE_T 144 +# define SIZEOF_UV_METRICS_T 128 +# define SIZEOF_UV_DIRENT_T 16 +# define SIZEOF_UV_PASSWD_T 40 +# define SIZEOF_UV_GROUP_T 24 +# define SIZEOF_UV_CPU_INFO_T 56 +# define SIZEOF_UV_INTERFACE_ADDRESS_T 80 +# define SIZEOF_UV_ENV_ITEM_T 16 +# define SIZEOF_UV_UTSNAME_T 1024 +# define SIZEOF_UV_THREAD_T 8 +# define SIZEOF_UV_MUTEX_T 40 +# define SIZEOF_UV_RWLOCK_T 56 +# define SIZEOF_UV_SEM_T 16 +# define SIZEOF_UV_COND_T 48 +# define SIZEOF_UV_ONCE_T 4 +# define SIZEOF_UV_KEY_T 4 +# define SIZEOF_UV_BARRIER_T 32 +# define SIZEOF_UV_THREAD_OPTIONS_T 16 + +# elif defined(__aarch64__) +# define SIZEOF_UV_LOOP_T 848 +# define SIZEOF_UV_REQ_T 64 +# define SIZEOF_UV_HANDLE_T 96 +# define SIZEOF_UV_STREAM_T 248 +# define SIZEOF_UV_TCP_T 248 +# define SIZEOF_UV_UDP_T 216 +# define SIZEOF_UV_PIPE_T 264 +# define SIZEOF_UV_TTY_T 288 +# define SIZEOF_UV_POLL_T 160 +# define SIZEOF_UV_TIMER_T 152 +# define SIZEOF_UV_PREPARE_T 120 +# define SIZEOF_UV_CHECK_T 120 +# define SIZEOF_UV_IDLE_T 120 +# define SIZEOF_UV_ASYNC_T 128 +# define SIZEOF_UV_PROCESS_T 136 +# define SIZEOF_UV_FS_EVENT_T 136 +# define SIZEOF_UV_FS_POLL_T 104 +# define SIZEOF_UV_SIGNAL_T 152 +# define SIZEOF_UV_SHUTDOWN_T 80 +# define SIZEOF_UV_WRITE_T 192 +# define SIZEOF_UV_CONNECT_T 96 +# define SIZEOF_UV_UDP_SEND_T 320 +# define SIZEOF_UV_FS_T 440 +# define SIZEOF_UV_WORK_T 128 +# define SIZEOF_UV_GETADDRINFO_T 160 +# define SIZEOF_UV_GETNAMEINFO_T 1320 +# define SIZEOF_UV_RANDOM_T 144 +# define SIZEOF_UV_LIB_T 16 +# define SIZEOF_UV_BUF_T 16 +# define SIZEOF_UV_DIR_T 56 +# define SIZEOF_UV_STAT_T 160 +# define SIZEOF_UV_STATFS_T 88 +# define SIZEOF_UV_TIMESPEC_T 16 +# define SIZEOF_UV_TIMESPEC64_T 16 +# define SIZEOF_UV_TIMEVAL_T 16 +# define SIZEOF_UV_TIMEVAL64_T 16 +# define SIZEOF_UV_RUSAGE_T 144 +# define SIZEOF_UV_METRICS_T 128 +# define SIZEOF_UV_DIRENT_T 16 +# define SIZEOF_UV_PASSWD_T 40 +# define SIZEOF_UV_GROUP_T 24 +# define SIZEOF_UV_CPU_INFO_T 56 +# define SIZEOF_UV_INTERFACE_ADDRESS_T 80 +# define SIZEOF_UV_ENV_ITEM_T 16 +# define SIZEOF_UV_UTSNAME_T 1024 +# define SIZEOF_UV_THREAD_T 8 +# define SIZEOF_UV_MUTEX_T 40 +# define SIZEOF_UV_RWLOCK_T 56 +# define SIZEOF_UV_SEM_T 16 +# define SIZEOF_UV_COND_T 48 +# define SIZEOF_UV_ONCE_T 4 +# define SIZEOF_UV_KEY_T 4 +# define SIZEOF_UV_BARRIER_T 32 +# define SIZEOF_UV_THREAD_OPTIONS_T 16 + +# elif defined(__arm__) +# define SIZEOF_UV_LOOP_T 456 +# define SIZEOF_UV_REQ_T 32 +# define SIZEOF_UV_HANDLE_T 48 +# define SIZEOF_UV_STREAM_T 132 +# define SIZEOF_UV_TCP_T 132 +# define SIZEOF_UV_UDP_T 112 +# define SIZEOF_UV_PIPE_T 140 +# define SIZEOF_UV_TTY_T 172 +# define SIZEOF_UV_POLL_T 84 +# define SIZEOF_UV_TIMER_T 88 +# define SIZEOF_UV_PREPARE_T 60 +# define SIZEOF_UV_CHECK_T 60 +# define SIZEOF_UV_IDLE_T 60 +# define SIZEOF_UV_ASYNC_T 64 +# define SIZEOF_UV_PROCESS_T 68 +# define SIZEOF_UV_FS_EVENT_T 68 +# define SIZEOF_UV_FS_POLL_T 52 +# define SIZEOF_UV_SIGNAL_T 80 +# define SIZEOF_UV_SHUTDOWN_T 40 +# define SIZEOF_UV_WRITE_T 100 +# define SIZEOF_UV_CONNECT_T 48 +# define SIZEOF_UV_UDP_SEND_T 224 +# define SIZEOF_UV_FS_T 296 +# define SIZEOF_UV_WORK_T 64 +# define SIZEOF_UV_GETADDRINFO_T 80 +# define SIZEOF_UV_GETNAMEINFO_T 1256 +# define SIZEOF_UV_RANDOM_T 72 +# define SIZEOF_UV_LIB_T 8 +# define SIZEOF_UV_BUF_T 8 +# define SIZEOF_UV_DIR_T 28 +# define SIZEOF_UV_STAT_T 128 +# define SIZEOF_UV_STATFS_T 88 +# define SIZEOF_UV_TIMESPEC_T 8 +# define SIZEOF_UV_TIMESPEC64_T 16 +# define SIZEOF_UV_TIMEVAL_T 8 +# define SIZEOF_UV_TIMEVAL64_T 16 +# define SIZEOF_UV_RUSAGE_T 128 +# define SIZEOF_UV_METRICS_T 80 +# define SIZEOF_UV_DIRENT_T 8 +# define SIZEOF_UV_PASSWD_T 20 +# define SIZEOF_UV_GROUP_T 12 +# define SIZEOF_UV_CPU_INFO_T 48 +# define SIZEOF_UV_INTERFACE_ADDRESS_T 72 +# define SIZEOF_UV_ENV_ITEM_T 8 +# define SIZEOF_UV_UTSNAME_T 1024 +# define SIZEOF_UV_THREAD_T 4 +# define SIZEOF_UV_MUTEX_T 4 +# define SIZEOF_UV_RWLOCK_T 40 +# define SIZEOF_UV_SEM_T 4 +# define SIZEOF_UV_COND_T 4 +# define SIZEOF_UV_ONCE_T 4 +# define SIZEOF_UV_KEY_T 4 +# define SIZEOF_UV_BARRIER_T 32 +# define SIZEOF_UV_THREAD_OPTIONS_T 8 + +# elif defined(__i386__) +# define SIZEOF_UV_LOOP_T 452 +# define SIZEOF_UV_REQ_T 32 +# define SIZEOF_UV_HANDLE_T 48 +# define SIZEOF_UV_STREAM_T 132 +# define SIZEOF_UV_TCP_T 132 +# define SIZEOF_UV_UDP_T 112 +# define SIZEOF_UV_PIPE_T 140 +# define SIZEOF_UV_TTY_T 172 +# define SIZEOF_UV_POLL_T 84 +# define SIZEOF_UV_TIMER_T 88 +# define SIZEOF_UV_PREPARE_T 60 +# define SIZEOF_UV_CHECK_T 60 +# define SIZEOF_UV_IDLE_T 60 +# define SIZEOF_UV_ASYNC_T 64 +# define SIZEOF_UV_PROCESS_T 68 +# define SIZEOF_UV_FS_EVENT_T 68 +# define SIZEOF_UV_FS_POLL_T 52 +# define SIZEOF_UV_SIGNAL_T 80 +# define SIZEOF_UV_SHUTDOWN_T 40 +# define SIZEOF_UV_WRITE_T 100 +# define SIZEOF_UV_CONNECT_T 48 +# define SIZEOF_UV_UDP_SEND_T 224 +# define SIZEOF_UV_FS_T 288 +# define SIZEOF_UV_WORK_T 64 +# define SIZEOF_UV_GETADDRINFO_T 80 +# define SIZEOF_UV_GETNAMEINFO_T 1256 +# define SIZEOF_UV_RANDOM_T 72 +# define SIZEOF_UV_LIB_T 8 +# define SIZEOF_UV_BUF_T 8 +# define SIZEOF_UV_DIR_T 28 +# define SIZEOF_UV_STAT_T 128 +# define SIZEOF_UV_STATFS_T 88 +# define SIZEOF_UV_TIMESPEC_T 8 +# define SIZEOF_UV_TIMESPEC64_T 12 +# define SIZEOF_UV_TIMEVAL_T 8 +# define SIZEOF_UV_TIMEVAL64_T 12 +# define SIZEOF_UV_RUSAGE_T 128 +# define SIZEOF_UV_METRICS_T 76 +# define SIZEOF_UV_DIRENT_T 8 +# define SIZEOF_UV_PASSWD_T 20 +# define SIZEOF_UV_GROUP_T 12 +# define SIZEOF_UV_CPU_INFO_T 48 +# define SIZEOF_UV_INTERFACE_ADDRESS_T 72 +# define SIZEOF_UV_ENV_ITEM_T 8 +# define SIZEOF_UV_UTSNAME_T 1024 +# define SIZEOF_UV_THREAD_T 4 +# define SIZEOF_UV_MUTEX_T 4 +# define SIZEOF_UV_RWLOCK_T 40 +# define SIZEOF_UV_SEM_T 4 +# define SIZEOF_UV_COND_T 4 +# define SIZEOF_UV_ONCE_T 4 +# define SIZEOF_UV_KEY_T 4 +# define SIZEOF_UV_BARRIER_T 32 +# define SIZEOF_UV_THREAD_OPTIONS_T 8 + +# else +# define UV_SIZEOF_SKIP "Android: sizes not recorded for this arch/pointer-size" +# endif /* arch */ + +#elif defined(__linux__) + +# if defined(__x86_64__) +# define SIZEOF_UV_LOOP_T 848 +# define SIZEOF_UV_REQ_T 64 +# define SIZEOF_UV_HANDLE_T 96 +# define SIZEOF_UV_STREAM_T 248 +# define SIZEOF_UV_TCP_T 248 +# define SIZEOF_UV_UDP_T 216 +# define SIZEOF_UV_PIPE_T 264 +# define SIZEOF_UV_TTY_T 312 +# define SIZEOF_UV_POLL_T 160 +# define SIZEOF_UV_TIMER_T 152 +# define SIZEOF_UV_PREPARE_T 120 +# define SIZEOF_UV_CHECK_T 120 +# define SIZEOF_UV_IDLE_T 120 +# define SIZEOF_UV_ASYNC_T 128 +# define SIZEOF_UV_PROCESS_T 136 +# define SIZEOF_UV_FS_EVENT_T 136 +# define SIZEOF_UV_FS_POLL_T 104 +# define SIZEOF_UV_SIGNAL_T 152 +# define SIZEOF_UV_SHUTDOWN_T 80 +# define SIZEOF_UV_WRITE_T 192 +# define SIZEOF_UV_CONNECT_T 96 +# define SIZEOF_UV_UDP_SEND_T 320 +# define SIZEOF_UV_FS_T 440 +# define SIZEOF_UV_WORK_T 128 +# define SIZEOF_UV_GETADDRINFO_T 160 +# define SIZEOF_UV_GETNAMEINFO_T 1320 +# define SIZEOF_UV_RANDOM_T 144 +# define SIZEOF_UV_LIB_T 16 +# define SIZEOF_UV_BUF_T 16 +# define SIZEOF_UV_DIR_T 56 +# define SIZEOF_UV_STAT_T 160 +# define SIZEOF_UV_STATFS_T 88 +# define SIZEOF_UV_TIMESPEC_T 16 +# define SIZEOF_UV_TIMESPEC64_T 16 +# define SIZEOF_UV_TIMEVAL_T 16 +# define SIZEOF_UV_TIMEVAL64_T 16 +# define SIZEOF_UV_RUSAGE_T 144 +# define SIZEOF_UV_METRICS_T 128 +# define SIZEOF_UV_DIRENT_T 16 +# define SIZEOF_UV_PASSWD_T 40 +# define SIZEOF_UV_GROUP_T 24 +# define SIZEOF_UV_CPU_INFO_T 56 +# define SIZEOF_UV_INTERFACE_ADDRESS_T 80 +# define SIZEOF_UV_ENV_ITEM_T 16 +# define SIZEOF_UV_UTSNAME_T 1024 +# define SIZEOF_UV_THREAD_T 8 +# define SIZEOF_UV_MUTEX_T 40 +# define SIZEOF_UV_RWLOCK_T 56 +# define SIZEOF_UV_SEM_T 32 +# define SIZEOF_UV_COND_T 48 +# define SIZEOF_UV_ONCE_T 4 +# define SIZEOF_UV_KEY_T 4 +# define SIZEOF_UV_BARRIER_T 32 +# define SIZEOF_UV_THREAD_OPTIONS_T 16 + +# elif defined(__i386__) +# define SIZEOF_UV_LOOP_T 464 +# define SIZEOF_UV_REQ_T 32 +# define SIZEOF_UV_HANDLE_T 48 +# define SIZEOF_UV_STREAM_T 132 +# define SIZEOF_UV_TCP_T 132 +# define SIZEOF_UV_UDP_T 112 +# define SIZEOF_UV_PIPE_T 140 +# define SIZEOF_UV_TTY_T 196 +# define SIZEOF_UV_POLL_T 84 +# define SIZEOF_UV_TIMER_T 88 +# define SIZEOF_UV_PREPARE_T 60 +# define SIZEOF_UV_CHECK_T 60 +# define SIZEOF_UV_IDLE_T 60 +# define SIZEOF_UV_ASYNC_T 64 +# define SIZEOF_UV_PROCESS_T 68 +# define SIZEOF_UV_FS_EVENT_T 68 +# define SIZEOF_UV_FS_POLL_T 52 +# define SIZEOF_UV_SIGNAL_T 80 +# define SIZEOF_UV_SHUTDOWN_T 40 +# define SIZEOF_UV_WRITE_T 100 +# define SIZEOF_UV_CONNECT_T 48 +# define SIZEOF_UV_UDP_SEND_T 224 +# define SIZEOF_UV_FS_T 292 +# define SIZEOF_UV_WORK_T 64 +# define SIZEOF_UV_GETADDRINFO_T 80 +# define SIZEOF_UV_GETNAMEINFO_T 1256 +# define SIZEOF_UV_RANDOM_T 72 +# define SIZEOF_UV_LIB_T 8 +# define SIZEOF_UV_BUF_T 8 +# define SIZEOF_UV_DIR_T 28 +# define SIZEOF_UV_STAT_T 128 +# define SIZEOF_UV_STATFS_T 88 +# define SIZEOF_UV_TIMESPEC_T 8 +# define SIZEOF_UV_TIMESPEC64_T 16 +# define SIZEOF_UV_TIMEVAL_T 8 +# define SIZEOF_UV_TIMEVAL64_T 16 +# define SIZEOF_UV_RUSAGE_T 128 +# define SIZEOF_UV_METRICS_T 76 +# define SIZEOF_UV_DIRENT_T 8 +# define SIZEOF_UV_PASSWD_T 20 +# define SIZEOF_UV_GROUP_T 12 +# define SIZEOF_UV_CPU_INFO_T 48 +# define SIZEOF_UV_INTERFACE_ADDRESS_T 72 +# define SIZEOF_UV_ENV_ITEM_T 8 +# define SIZEOF_UV_UTSNAME_T 1024 +# define SIZEOF_UV_THREAD_T 4 +# define SIZEOF_UV_MUTEX_T 24 +# define SIZEOF_UV_RWLOCK_T 32 +# define SIZEOF_UV_SEM_T 16 +# define SIZEOF_UV_COND_T 48 +# define SIZEOF_UV_ONCE_T 4 +# define SIZEOF_UV_KEY_T 4 +# define SIZEOF_UV_BARRIER_T 20 +# define SIZEOF_UV_THREAD_OPTIONS_T 8 + +# elif defined(__aarch64__) +# define SIZEOF_UV_LOOP_T 856 +# define SIZEOF_UV_REQ_T 64 +# define SIZEOF_UV_HANDLE_T 96 +# define SIZEOF_UV_STREAM_T 248 +# define SIZEOF_UV_TCP_T 248 +# define SIZEOF_UV_UDP_T 216 +# define SIZEOF_UV_PIPE_T 264 +# define SIZEOF_UV_TTY_T 312 +# define SIZEOF_UV_POLL_T 160 +# define SIZEOF_UV_TIMER_T 152 +# define SIZEOF_UV_PREPARE_T 120 +# define SIZEOF_UV_CHECK_T 120 +# define SIZEOF_UV_IDLE_T 120 +# define SIZEOF_UV_ASYNC_T 128 +# define SIZEOF_UV_PROCESS_T 136 +# define SIZEOF_UV_FS_EVENT_T 136 +# define SIZEOF_UV_FS_POLL_T 104 +# define SIZEOF_UV_SIGNAL_T 152 +# define SIZEOF_UV_SHUTDOWN_T 80 +# define SIZEOF_UV_WRITE_T 192 +# define SIZEOF_UV_CONNECT_T 96 +# define SIZEOF_UV_UDP_SEND_T 320 +# define SIZEOF_UV_FS_T 440 +# define SIZEOF_UV_WORK_T 128 +# define SIZEOF_UV_GETADDRINFO_T 160 +# define SIZEOF_UV_GETNAMEINFO_T 1320 +# define SIZEOF_UV_RANDOM_T 144 +# define SIZEOF_UV_LIB_T 16 +# define SIZEOF_UV_BUF_T 16 +# define SIZEOF_UV_DIR_T 56 +# define SIZEOF_UV_STAT_T 160 +# define SIZEOF_UV_STATFS_T 88 +# define SIZEOF_UV_TIMESPEC_T 16 +# define SIZEOF_UV_TIMESPEC64_T 16 +# define SIZEOF_UV_TIMEVAL_T 16 +# define SIZEOF_UV_TIMEVAL64_T 16 +# define SIZEOF_UV_RUSAGE_T 144 +# define SIZEOF_UV_METRICS_T 128 +# define SIZEOF_UV_DIRENT_T 16 +# define SIZEOF_UV_PASSWD_T 40 +# define SIZEOF_UV_GROUP_T 24 +# define SIZEOF_UV_CPU_INFO_T 56 +# define SIZEOF_UV_INTERFACE_ADDRESS_T 80 +# define SIZEOF_UV_ENV_ITEM_T 16 +# define SIZEOF_UV_UTSNAME_T 1024 +# define SIZEOF_UV_THREAD_T 8 +# define SIZEOF_UV_MUTEX_T 48 +# define SIZEOF_UV_RWLOCK_T 56 +# define SIZEOF_UV_SEM_T 32 +# define SIZEOF_UV_COND_T 48 +# define SIZEOF_UV_ONCE_T 4 +# define SIZEOF_UV_KEY_T 4 +# define SIZEOF_UV_BARRIER_T 32 +# define SIZEOF_UV_THREAD_OPTIONS_T 16 + +# elif defined(__riscv) && __riscv_xlen == 64 +# define SIZEOF_UV_LOOP_T 848 +# define SIZEOF_UV_REQ_T 64 +# define SIZEOF_UV_HANDLE_T 96 +# define SIZEOF_UV_STREAM_T 248 +# define SIZEOF_UV_TCP_T 248 +# define SIZEOF_UV_UDP_T 216 +# define SIZEOF_UV_PIPE_T 264 +# define SIZEOF_UV_TTY_T 312 +# define SIZEOF_UV_POLL_T 160 +# define SIZEOF_UV_TIMER_T 152 +# define SIZEOF_UV_PREPARE_T 120 +# define SIZEOF_UV_CHECK_T 120 +# define SIZEOF_UV_IDLE_T 120 +# define SIZEOF_UV_ASYNC_T 128 +# define SIZEOF_UV_PROCESS_T 136 +# define SIZEOF_UV_FS_EVENT_T 136 +# define SIZEOF_UV_FS_POLL_T 104 +# define SIZEOF_UV_SIGNAL_T 152 +# define SIZEOF_UV_SHUTDOWN_T 80 +# define SIZEOF_UV_WRITE_T 192 +# define SIZEOF_UV_CONNECT_T 96 +# define SIZEOF_UV_UDP_SEND_T 320 +# define SIZEOF_UV_FS_T 440 +# define SIZEOF_UV_WORK_T 128 +# define SIZEOF_UV_GETADDRINFO_T 160 +# define SIZEOF_UV_GETNAMEINFO_T 1320 +# define SIZEOF_UV_RANDOM_T 144 +# define SIZEOF_UV_LIB_T 16 +# define SIZEOF_UV_BUF_T 16 +# define SIZEOF_UV_DIR_T 56 +# define SIZEOF_UV_STAT_T 160 +# define SIZEOF_UV_STATFS_T 88 +# define SIZEOF_UV_TIMESPEC_T 16 +# define SIZEOF_UV_TIMESPEC64_T 16 +# define SIZEOF_UV_TIMEVAL_T 16 +# define SIZEOF_UV_TIMEVAL64_T 16 +# define SIZEOF_UV_RUSAGE_T 144 +# define SIZEOF_UV_METRICS_T 128 +# define SIZEOF_UV_DIRENT_T 16 +# define SIZEOF_UV_PASSWD_T 40 +# define SIZEOF_UV_GROUP_T 24 +# define SIZEOF_UV_CPU_INFO_T 56 +# define SIZEOF_UV_INTERFACE_ADDRESS_T 80 +# define SIZEOF_UV_ENV_ITEM_T 16 +# define SIZEOF_UV_UTSNAME_T 1024 +# define SIZEOF_UV_THREAD_T 8 +# define SIZEOF_UV_MUTEX_T 40 +# define SIZEOF_UV_RWLOCK_T 56 +# define SIZEOF_UV_SEM_T 32 +# define SIZEOF_UV_COND_T 48 +# define SIZEOF_UV_ONCE_T 4 +# define SIZEOF_UV_KEY_T 4 +# define SIZEOF_UV_BARRIER_T 32 +# define SIZEOF_UV_THREAD_OPTIONS_T 16 + +# elif defined(__arm__) +# define SIZEOF_UV_LOOP_T 472 +# define SIZEOF_UV_REQ_T 32 +# define SIZEOF_UV_HANDLE_T 48 +# define SIZEOF_UV_STREAM_T 132 +# define SIZEOF_UV_TCP_T 132 +# define SIZEOF_UV_UDP_T 112 +# define SIZEOF_UV_PIPE_T 140 +# define SIZEOF_UV_TTY_T 196 +# define SIZEOF_UV_POLL_T 84 +# define SIZEOF_UV_TIMER_T 88 +# define SIZEOF_UV_PREPARE_T 60 +# define SIZEOF_UV_CHECK_T 60 +# define SIZEOF_UV_IDLE_T 60 +# define SIZEOF_UV_ASYNC_T 64 +# define SIZEOF_UV_PROCESS_T 68 +# define SIZEOF_UV_FS_EVENT_T 68 +# define SIZEOF_UV_FS_POLL_T 52 +# define SIZEOF_UV_SIGNAL_T 80 +# define SIZEOF_UV_SHUTDOWN_T 40 +# define SIZEOF_UV_WRITE_T 100 +# define SIZEOF_UV_CONNECT_T 48 +# define SIZEOF_UV_UDP_SEND_T 224 +# define SIZEOF_UV_FS_T 296 +# define SIZEOF_UV_WORK_T 64 +# define SIZEOF_UV_GETADDRINFO_T 80 +# define SIZEOF_UV_GETNAMEINFO_T 1256 +# define SIZEOF_UV_RANDOM_T 72 +# define SIZEOF_UV_LIB_T 8 +# define SIZEOF_UV_BUF_T 8 +# define SIZEOF_UV_DIR_T 28 +# define SIZEOF_UV_STAT_T 128 +# define SIZEOF_UV_STATFS_T 88 +# define SIZEOF_UV_TIMESPEC_T 8 +# define SIZEOF_UV_TIMESPEC64_T 16 +# define SIZEOF_UV_TIMEVAL_T 8 +# define SIZEOF_UV_TIMEVAL64_T 16 +# define SIZEOF_UV_RUSAGE_T 128 +# define SIZEOF_UV_METRICS_T 80 +# define SIZEOF_UV_DIRENT_T 8 +# define SIZEOF_UV_PASSWD_T 20 +# define SIZEOF_UV_GROUP_T 12 +# define SIZEOF_UV_CPU_INFO_T 48 +# define SIZEOF_UV_INTERFACE_ADDRESS_T 72 +# define SIZEOF_UV_ENV_ITEM_T 8 +# define SIZEOF_UV_UTSNAME_T 1024 +# define SIZEOF_UV_THREAD_T 4 +# define SIZEOF_UV_MUTEX_T 24 +# define SIZEOF_UV_RWLOCK_T 32 +# define SIZEOF_UV_SEM_T 16 +# define SIZEOF_UV_COND_T 48 +# define SIZEOF_UV_ONCE_T 4 +# define SIZEOF_UV_KEY_T 4 +# define SIZEOF_UV_BARRIER_T 20 +# define SIZEOF_UV_THREAD_OPTIONS_T 8 + +# else +# define UV_SIZEOF_SKIP "Linux: sizes not recorded for this arch/pointer-size" +# endif /* arch */ + +#elif defined(__APPLE__) + +# if __SIZEOF_POINTER__ == 8 +# define SIZEOF_UV_LOOP_T 1072 +# define SIZEOF_UV_REQ_T 64 +# define SIZEOF_UV_HANDLE_T 96 +# define SIZEOF_UV_STREAM_T 264 +# define SIZEOF_UV_TCP_T 264 +# define SIZEOF_UV_UDP_T 224 +# define SIZEOF_UV_PIPE_T 280 +# define SIZEOF_UV_TTY_T 344 +# define SIZEOF_UV_POLL_T 168 +# define SIZEOF_UV_TIMER_T 152 +# define SIZEOF_UV_PREPARE_T 120 +# define SIZEOF_UV_CHECK_T 120 +# define SIZEOF_UV_IDLE_T 120 +# define SIZEOF_UV_ASYNC_T 128 +# define SIZEOF_UV_PROCESS_T 136 +# define SIZEOF_UV_FS_EVENT_T 304 +# define SIZEOF_UV_FS_POLL_T 104 +# define SIZEOF_UV_SIGNAL_T 152 +# define SIZEOF_UV_SHUTDOWN_T 80 +# define SIZEOF_UV_WRITE_T 192 +# define SIZEOF_UV_CONNECT_T 96 +# define SIZEOF_UV_UDP_SEND_T 320 +# define SIZEOF_UV_FS_T 440 +# define SIZEOF_UV_WORK_T 128 +# define SIZEOF_UV_GETADDRINFO_T 160 +# define SIZEOF_UV_GETNAMEINFO_T 1320 +# define SIZEOF_UV_RANDOM_T 144 +# define SIZEOF_UV_LIB_T 16 +# define SIZEOF_UV_BUF_T 16 +# define SIZEOF_UV_DIR_T 56 +# define SIZEOF_UV_STAT_T 160 +# define SIZEOF_UV_STATFS_T 88 +# define SIZEOF_UV_TIMESPEC_T 16 +# define SIZEOF_UV_TIMESPEC64_T 16 +# define SIZEOF_UV_TIMEVAL_T 16 +# define SIZEOF_UV_TIMEVAL64_T 16 +# define SIZEOF_UV_RUSAGE_T 144 +# define SIZEOF_UV_METRICS_T 128 +# define SIZEOF_UV_DIRENT_T 16 +# define SIZEOF_UV_PASSWD_T 40 +# define SIZEOF_UV_GROUP_T 24 +# define SIZEOF_UV_CPU_INFO_T 56 +# define SIZEOF_UV_INTERFACE_ADDRESS_T 80 +# define SIZEOF_UV_ENV_ITEM_T 16 +# define SIZEOF_UV_UTSNAME_T 1024 +# define SIZEOF_UV_THREAD_T 8 +# define SIZEOF_UV_MUTEX_T 64 +# define SIZEOF_UV_RWLOCK_T 200 +# define SIZEOF_UV_SEM_T 4 +# define SIZEOF_UV_COND_T 48 +# define SIZEOF_UV_ONCE_T 16 +# define SIZEOF_UV_KEY_T 8 +# define SIZEOF_UV_BARRIER_T 8 +# define SIZEOF_UV_THREAD_OPTIONS_T 16 +# else +# define UV_SIZEOF_SKIP "Darwin: unknown pointer size" +# endif + +#elif defined(__FreeBSD__) || defined(__OpenBSD__) || \ + defined(__NetBSD__) || defined(__DragonFly__) +// These skip all thread types since pthread_t/mutex/cond types vary +// significantly across FreeBSD, OpenBSD, NetBSD, and DragonFly. + +# if defined(__x86_64__) +# define SIZEOF_UV_LOOP_T 712 +# define SIZEOF_UV_REQ_T 64 +# define SIZEOF_UV_HANDLE_T 96 +# define SIZEOF_UV_STREAM_T 256 +# define SIZEOF_UV_TCP_T 256 +# define SIZEOF_UV_UDP_T 224 +# define SIZEOF_UV_PIPE_T 272 +# define SIZEOF_UV_TTY_T 304 +# define SIZEOF_UV_POLL_T 168 +# define SIZEOF_UV_TIMER_T 152 +# define SIZEOF_UV_PREPARE_T 120 +# define SIZEOF_UV_CHECK_T 120 +# define SIZEOF_UV_IDLE_T 120 +# define SIZEOF_UV_ASYNC_T 128 +# define SIZEOF_UV_PROCESS_T 136 +# define SIZEOF_UV_FS_EVENT_T 176 +# define SIZEOF_UV_FS_POLL_T 104 +# define SIZEOF_UV_SIGNAL_T 152 +# define SIZEOF_UV_SHUTDOWN_T 80 +# define SIZEOF_UV_WRITE_T 192 +# define SIZEOF_UV_CONNECT_T 96 +# define SIZEOF_UV_UDP_SEND_T 320 +# define SIZEOF_UV_FS_T 440 +# define SIZEOF_UV_WORK_T 128 +# define SIZEOF_UV_GETADDRINFO_T 160 +# define SIZEOF_UV_GETNAMEINFO_T 1320 +# define SIZEOF_UV_RANDOM_T 144 +# define SIZEOF_UV_LIB_T 16 +# define SIZEOF_UV_BUF_T 16 +# define SIZEOF_UV_DIR_T 56 +# define SIZEOF_UV_STAT_T 160 +# define SIZEOF_UV_STATFS_T 88 +# define SIZEOF_UV_TIMESPEC_T 16 +# define SIZEOF_UV_TIMESPEC64_T 16 +# define SIZEOF_UV_TIMEVAL_T 16 +# define SIZEOF_UV_TIMEVAL64_T 16 +# define SIZEOF_UV_RUSAGE_T 144 +# define SIZEOF_UV_METRICS_T 128 +# define SIZEOF_UV_DIRENT_T 16 +# define SIZEOF_UV_PASSWD_T 40 +# define SIZEOF_UV_GROUP_T 24 +# define SIZEOF_UV_CPU_INFO_T 56 +# define SIZEOF_UV_INTERFACE_ADDRESS_T 80 +# define SIZEOF_UV_ENV_ITEM_T 16 +# define SIZEOF_UV_UTSNAME_T 1024 +# define SIZEOF_UV_THREAD_OPTIONS_T 16 +# elif defined(__i386__) +# define SIZEOF_UV_LOOP_T 392 +# define SIZEOF_UV_REQ_T 32 +# define SIZEOF_UV_HANDLE_T 48 +# define SIZEOF_UV_STREAM_T 132 +# define SIZEOF_UV_TCP_T 132 +# define SIZEOF_UV_UDP_T 112 +# define SIZEOF_UV_PIPE_T 140 +# define SIZEOF_UV_TTY_T 176 +# define SIZEOF_UV_POLL_T 92 +# define SIZEOF_UV_TIMER_T 88 +# define SIZEOF_UV_PREPARE_T 60 +# define SIZEOF_UV_CHECK_T 60 +# define SIZEOF_UV_IDLE_T 60 +# define SIZEOF_UV_ASYNC_T 64 +# define SIZEOF_UV_PROCESS_T 68 +# define SIZEOF_UV_FS_EVENT_T 100 +# define SIZEOF_UV_FS_POLL_T 52 +# define SIZEOF_UV_SIGNAL_T 80 +# define SIZEOF_UV_SHUTDOWN_T 40 +# define SIZEOF_UV_WRITE_T 100 +# define SIZEOF_UV_CONNECT_T 48 +# define SIZEOF_UV_UDP_SEND_T 216 +# define SIZEOF_UV_FS_T 280 +# define SIZEOF_UV_WORK_T 64 +# define SIZEOF_UV_GETADDRINFO_T 80 +# define SIZEOF_UV_GETNAMEINFO_T 1256 +# define SIZEOF_UV_RANDOM_T 72 +# define SIZEOF_UV_LIB_T 8 +# define SIZEOF_UV_BUF_T 8 +# define SIZEOF_UV_DIR_T 28 +# define SIZEOF_UV_STAT_T 128 +# define SIZEOF_UV_STATFS_T 88 +# define SIZEOF_UV_TIMESPEC_T 8 +# define SIZEOF_UV_TIMESPEC64_T 16 +# define SIZEOF_UV_TIMEVAL_T 8 +# define SIZEOF_UV_TIMEVAL64_T 16 +# define SIZEOF_UV_RUSAGE_T 128 +# define SIZEOF_UV_METRICS_T 76 +# define SIZEOF_UV_DIRENT_T 8 +# define SIZEOF_UV_PASSWD_T 20 +# define SIZEOF_UV_GROUP_T 12 +# define SIZEOF_UV_CPU_INFO_T 48 +# define SIZEOF_UV_INTERFACE_ADDRESS_T 72 +# define SIZEOF_UV_ENV_ITEM_T 8 +# define SIZEOF_UV_UTSNAME_T 1024 +# define SIZEOF_UV_THREAD_OPTIONS_T 8 +# else +# define UV_SIZEOF_SKIP "BSD: sizes not recorded for this arch/pointer-size" +# endif + +#elif defined(_AIX) +# define UV_SIZEOF_SKIP "AIX: sizes not yet recorded" + +#elif defined(__sun) +# define UV_SIZEOF_SKIP "Solaris: sizes not yet recorded" + +#elif defined(_WIN64) +# define SIZEOF_UV_LOOP_T 472 +# define SIZEOF_UV_HANDLE_T 96 +# define SIZEOF_UV_STREAM_T 272 +# define SIZEOF_UV_TCP_T 320 +# define SIZEOF_UV_UDP_T 424 +# define SIZEOF_UV_PIPE_T 576 +# define SIZEOF_UV_TTY_T 344 +# define SIZEOF_UV_POLL_T 416 +# define SIZEOF_UV_TIMER_T 160 +# define SIZEOF_UV_PREPARE_T 120 +# define SIZEOF_UV_CHECK_T 120 +# define SIZEOF_UV_IDLE_T 120 +# define SIZEOF_UV_ASYNC_T 224 +# define SIZEOF_UV_PROCESS_T 264 +# define SIZEOF_UV_FS_EVENT_T 272 +# define SIZEOF_UV_FS_POLL_T 104 +# define SIZEOF_UV_SIGNAL_T 264 +# define SIZEOF_UV_REQ_T 112 +# define SIZEOF_UV_SHUTDOWN_T 128 +# define SIZEOF_UV_WRITE_T 176 +# define SIZEOF_UV_CONNECT_T 128 +# define SIZEOF_UV_UDP_SEND_T 128 +# define SIZEOF_UV_FS_T 456 +# define SIZEOF_UV_WORK_T 176 +# define SIZEOF_UV_GETADDRINFO_T 216 +# define SIZEOF_UV_GETNAMEINFO_T 1368 +# define SIZEOF_UV_RANDOM_T 192 +# define SIZEOF_UV_BUF_T 16 +# define SIZEOF_UV_LIB_T 16 +# define SIZEOF_UV_DIR_T 656 +# define SIZEOF_UV_STAT_T 128 +# define SIZEOF_UV_STATFS_T 88 +# define SIZEOF_UV_TIMESPEC_T 8 +# define SIZEOF_UV_TIMESPEC64_T 16 +# define SIZEOF_UV_TIMEVAL_T 8 +# define SIZEOF_UV_TIMEVAL64_T 16 +# define SIZEOF_UV_RUSAGE_T 128 +# define SIZEOF_UV_METRICS_T 128 +# define SIZEOF_UV_DIRENT_T 16 +# define SIZEOF_UV_PASSWD_T 32 +# define SIZEOF_UV_GROUP_T 24 +# define SIZEOF_UV_CPU_INFO_T 56 +# define SIZEOF_UV_INTERFACE_ADDRESS_T 80 +# define SIZEOF_UV_ENV_ITEM_T 16 +# define SIZEOF_UV_UTSNAME_T 1024 +# define SIZEOF_UV_THREAD_T 8 +# define SIZEOF_UV_MUTEX_T 40 +# define SIZEOF_UV_RWLOCK_T 80 +# define SIZEOF_UV_SEM_T 8 +# define SIZEOF_UV_COND_T 64 +# define SIZEOF_UV_ONCE_T 16 +# define SIZEOF_UV_KEY_T 4 +# define SIZEOF_UV_BARRIER_T 64 +# define SIZEOF_UV_THREAD_OPTIONS_T 16 + +#elif defined(_WIN32) +# define SIZEOF_UV_LOOP_T 264 +# define SIZEOF_UV_HANDLE_T 48 +# define SIZEOF_UV_STREAM_T 144 +# define SIZEOF_UV_TCP_T 168 +# define SIZEOF_UV_UDP_T 288 +# define SIZEOF_UV_PIPE_T 320 +# define SIZEOF_UV_TTY_T 196 +# define SIZEOF_UV_POLL_T 256 +# define SIZEOF_UV_TIMER_T 96 +# define SIZEOF_UV_PREPARE_T 60 +# define SIZEOF_UV_CHECK_T 60 +# define SIZEOF_UV_IDLE_T 60 +# define SIZEOF_UV_ASYNC_T 116 +# define SIZEOF_UV_PROCESS_T 136 +# define SIZEOF_UV_FS_EVENT_T 140 +# define SIZEOF_UV_FS_POLL_T 52 +# define SIZEOF_UV_SIGNAL_T 136 +# define SIZEOF_UV_REQ_T 60 +# define SIZEOF_UV_SHUTDOWN_T 68 +# define SIZEOF_UV_WRITE_T 92 +# define SIZEOF_UV_CONNECT_T 68 +# define SIZEOF_UV_UDP_SEND_T 68 +# define SIZEOF_UV_FS_T 312 +# define SIZEOF_UV_WORK_T 92 +# define SIZEOF_UV_GETADDRINFO_T 112 +# define SIZEOF_UV_GETNAMEINFO_T 1288 +# define SIZEOF_UV_RANDOM_T 100 +# define SIZEOF_UV_BUF_T 8 +# define SIZEOF_UV_LIB_T 8 +# define SIZEOF_UV_DIR_T 624 +# define SIZEOF_UV_STAT_T 128 +# define SIZEOF_UV_STATFS_T 88 +# define SIZEOF_UV_TIMESPEC_T 8 +# define SIZEOF_UV_TIMESPEC64_T 16 +# define SIZEOF_UV_TIMEVAL_T 8 +# define SIZEOF_UV_TIMEVAL64_T 16 +# define SIZEOF_UV_RUSAGE_T 128 +# define SIZEOF_UV_METRICS_T 80 +# define SIZEOF_UV_DIRENT_T 8 +# define SIZEOF_UV_PASSWD_T 20 +# define SIZEOF_UV_GROUP_T 12 +# define SIZEOF_UV_CPU_INFO_T 48 +# define SIZEOF_UV_INTERFACE_ADDRESS_T 72 +# define SIZEOF_UV_ENV_ITEM_T 8 +# define SIZEOF_UV_UTSNAME_T 1024 +# define SIZEOF_UV_THREAD_T 4 +# define SIZEOF_UV_MUTEX_T 24 +# define SIZEOF_UV_RWLOCK_T 48 +# define SIZEOF_UV_SEM_T 4 +# define SIZEOF_UV_COND_T 36 +# define SIZEOF_UV_ONCE_T 8 +# define SIZEOF_UV_KEY_T 4 +# define SIZEOF_UV_BARRIER_T 40 +# define SIZEOF_UV_THREAD_OPTIONS_T 8 + +#else +# define UV_SIZEOF_SKIP "unsupported platform" +#endif + + +#if defined(UV_SIZEOF_SKIP) +#define CHECK_SIZE(type, expected) \ + printf("# define %-28s%4u\n", #expected, (unsigned) sizeof(type)) +#else +#define CHECK_SIZE(type, expected) \ + ASSERT_EQ(sizeof(type), (expected)) +#endif + +TEST_IMPL(sizeof) { + CHECK_SIZE(uv_loop_t, SIZEOF_UV_LOOP_T); + + /* Handles */ + CHECK_SIZE(uv_handle_t, SIZEOF_UV_HANDLE_T); + CHECK_SIZE(uv_stream_t, SIZEOF_UV_STREAM_T); + CHECK_SIZE(uv_tcp_t, SIZEOF_UV_TCP_T); + CHECK_SIZE(uv_udp_t, SIZEOF_UV_UDP_T); + CHECK_SIZE(uv_pipe_t, SIZEOF_UV_PIPE_T); + CHECK_SIZE(uv_tty_t, SIZEOF_UV_TTY_T); + CHECK_SIZE(uv_poll_t, SIZEOF_UV_POLL_T); + CHECK_SIZE(uv_timer_t, SIZEOF_UV_TIMER_T); + CHECK_SIZE(uv_prepare_t, SIZEOF_UV_PREPARE_T); + CHECK_SIZE(uv_check_t, SIZEOF_UV_CHECK_T); + CHECK_SIZE(uv_idle_t, SIZEOF_UV_IDLE_T); + CHECK_SIZE(uv_async_t, SIZEOF_UV_ASYNC_T); + CHECK_SIZE(uv_process_t, SIZEOF_UV_PROCESS_T); + CHECK_SIZE(uv_fs_event_t, SIZEOF_UV_FS_EVENT_T); + CHECK_SIZE(uv_fs_poll_t, SIZEOF_UV_FS_POLL_T); + CHECK_SIZE(uv_signal_t, SIZEOF_UV_SIGNAL_T); + + /* Requests */ + CHECK_SIZE(uv_req_t, SIZEOF_UV_REQ_T); + CHECK_SIZE(uv_shutdown_t, SIZEOF_UV_SHUTDOWN_T); + CHECK_SIZE(uv_write_t, SIZEOF_UV_WRITE_T); + CHECK_SIZE(uv_connect_t, SIZEOF_UV_CONNECT_T); + CHECK_SIZE(uv_udp_send_t, SIZEOF_UV_UDP_SEND_T); + CHECK_SIZE(uv_fs_t, SIZEOF_UV_FS_T); + CHECK_SIZE(uv_work_t, SIZEOF_UV_WORK_T); + CHECK_SIZE(uv_getaddrinfo_t, SIZEOF_UV_GETADDRINFO_T); + CHECK_SIZE(uv_getnameinfo_t, SIZEOF_UV_GETNAMEINFO_T); + CHECK_SIZE(uv_random_t, SIZEOF_UV_RANDOM_T); + + /* Data types */ + CHECK_SIZE(uv_buf_t, SIZEOF_UV_BUF_T); + CHECK_SIZE(uv_lib_t, SIZEOF_UV_LIB_T); + CHECK_SIZE(uv_dir_t, SIZEOF_UV_DIR_T); + CHECK_SIZE(uv_stat_t, SIZEOF_UV_STAT_T); + CHECK_SIZE(uv_statfs_t, SIZEOF_UV_STATFS_T); + CHECK_SIZE(uv_timespec_t, SIZEOF_UV_TIMESPEC_T); + CHECK_SIZE(uv_timespec64_t, SIZEOF_UV_TIMESPEC64_T); + CHECK_SIZE(uv_timeval_t, SIZEOF_UV_TIMEVAL_T); + CHECK_SIZE(uv_timeval64_t, SIZEOF_UV_TIMEVAL64_T); + CHECK_SIZE(uv_rusage_t, SIZEOF_UV_RUSAGE_T); + CHECK_SIZE(uv_metrics_t, SIZEOF_UV_METRICS_T); + CHECK_SIZE(uv_dirent_t, SIZEOF_UV_DIRENT_T); + CHECK_SIZE(uv_passwd_t, SIZEOF_UV_PASSWD_T); + CHECK_SIZE(uv_group_t, SIZEOF_UV_GROUP_T); + CHECK_SIZE(uv_cpu_info_t, SIZEOF_UV_CPU_INFO_T); + CHECK_SIZE(uv_interface_address_t, SIZEOF_UV_INTERFACE_ADDRESS_T); + CHECK_SIZE(uv_env_item_t, SIZEOF_UV_ENV_ITEM_T); + CHECK_SIZE(uv_utsname_t, SIZEOF_UV_UTSNAME_T); + + /* Threading primitives */ +#if defined(UV_SIZEOF_SKIP) || defined(SIZEOF_UV_MUTEX_T) + CHECK_SIZE(uv_thread_t, SIZEOF_UV_THREAD_T); + CHECK_SIZE(uv_mutex_t, SIZEOF_UV_MUTEX_T); + CHECK_SIZE(uv_rwlock_t, SIZEOF_UV_RWLOCK_T); + CHECK_SIZE(uv_sem_t, SIZEOF_UV_SEM_T); + CHECK_SIZE(uv_cond_t, SIZEOF_UV_COND_T); + CHECK_SIZE(uv_once_t, SIZEOF_UV_ONCE_T); + CHECK_SIZE(uv_key_t, SIZEOF_UV_KEY_T); + CHECK_SIZE(uv_barrier_t, SIZEOF_UV_BARRIER_T); +#endif + CHECK_SIZE(uv_thread_options_t, SIZEOF_UV_THREAD_OPTIONS_T); + +#if defined(UV_SIZEOF_SKIP) + RETURN_SKIP(UV_SIZEOF_SKIP); +#else + return 0; +#endif +} diff --git a/deps/uv/test/test-spawn.c b/deps/uv/test/test-spawn.c index eaaa8bbeac3..9411551624e 100644 --- a/deps/uv/test/test-spawn.c +++ b/deps/uv/test/test-spawn.c @@ -426,6 +426,7 @@ TEST_IMPL(spawn_stdout_and_stderr_to_file) { TEST_IMPL(spawn_stdout_and_stderr_to_file2) { #ifndef _WIN32 int r; + int saved_stderr; uv_file file; uv_fs_t fs_req; uv_stdio_container_t stdio[3]; @@ -436,6 +437,13 @@ TEST_IMPL(spawn_stdout_and_stderr_to_file2) { init_process_options("spawn_helper6", exit_cb); + /* This test replaces fd 2, so stash the real stderr and put it back + * afterwards. Without that the process ends up with no stderr at all, which + * silently swallows the output of any later assertion failure. + */ + saved_stderr = dup(STDERR_FILENO); + ASSERT_NE(saved_stderr, -1); + /* Replace stderr with our file */ r = uv_fs_open(NULL, &fs_req, @@ -447,6 +455,9 @@ TEST_IMPL(spawn_stdout_and_stderr_to_file2) { uv_fs_req_cleanup(&fs_req); file = dup2(r, STDERR_FILENO); ASSERT_NE(file, -1); + /* dup2() put a copy on fd 2, the original descriptor is redundant now. */ + ASSERT_OK(uv_fs_close(NULL, &fs_req, r, NULL)); + uv_fs_req_cleanup(&fs_req); options.stdio = stdio; options.stdio[0].flags = UV_IGNORE; @@ -470,9 +481,9 @@ TEST_IMPL(spawn_stdout_and_stderr_to_file2) { ASSERT_EQ(27, r); uv_fs_req_cleanup(&fs_req); - r = uv_fs_close(NULL, &fs_req, file, NULL); - ASSERT_OK(r); - uv_fs_req_cleanup(&fs_req); + /* Putting the real stderr back also closes the file sitting on fd 2. */ + ASSERT_NE(-1, dup2(saved_stderr, STDERR_FILENO)); + ASSERT_OK(close(saved_stderr)); printf("output is: %s", output); ASSERT_OK(strcmp("hello world\nhello errworld\n", output)); @@ -491,6 +502,8 @@ TEST_IMPL(spawn_stdout_and_stderr_to_file2) { TEST_IMPL(spawn_stdout_and_stderr_to_file_swap) { #ifndef _WIN32 int r; + int saved_stdout; + int saved_stderr; uv_file stdout_file; uv_file stderr_file; uv_fs_t fs_req; @@ -503,6 +516,15 @@ TEST_IMPL(spawn_stdout_and_stderr_to_file_swap) { init_process_options("spawn_helper6", exit_cb); + /* This test replaces fds 1 and 2, so stash the real ones and put them back + * afterwards. Without that the process ends up with no stdout or stderr at + * all, which silently swallows anything printed from here on. + */ + saved_stdout = dup(STDOUT_FILENO); + ASSERT_NE(saved_stdout, -1); + saved_stderr = dup(STDERR_FILENO); + ASSERT_NE(saved_stderr, -1); + /* open 'stdout_file' and replace STDOUT_FILENO with it */ r = uv_fs_open(NULL, &fs_req, @@ -514,6 +536,9 @@ TEST_IMPL(spawn_stdout_and_stderr_to_file_swap) { uv_fs_req_cleanup(&fs_req); stdout_file = dup2(r, STDOUT_FILENO); ASSERT_NE(stdout_file, -1); + /* dup2() put a copy on fd 1, the original descriptor is redundant now. */ + ASSERT_OK(uv_fs_close(NULL, &fs_req, r, NULL)); + uv_fs_req_cleanup(&fs_req); /* open 'stderr_file' and replace STDERR_FILENO with it */ r = uv_fs_open(NULL, &fs_req, "stderr_file", O_CREAT | O_RDWR, @@ -522,6 +547,9 @@ TEST_IMPL(spawn_stdout_and_stderr_to_file_swap) { uv_fs_req_cleanup(&fs_req); stderr_file = dup2(r, STDERR_FILENO); ASSERT_NE(stderr_file, -1); + /* dup2() put a copy on fd 2, the original descriptor is redundant now. */ + ASSERT_OK(uv_fs_close(NULL, &fs_req, r, NULL)); + uv_fs_req_cleanup(&fs_req); /* now we're going to swap them: the child process' stdout will be our * stderr_file and vice versa */ @@ -549,9 +577,9 @@ TEST_IMPL(spawn_stdout_and_stderr_to_file_swap) { ASSERT_GE(r, 15); uv_fs_req_cleanup(&fs_req); - r = uv_fs_close(NULL, &fs_req, stdout_file, NULL); - ASSERT_OK(r); - uv_fs_req_cleanup(&fs_req); + /* Putting the real stdout back also closes the file sitting on fd 1. */ + ASSERT_NE(-1, dup2(saved_stdout, STDOUT_FILENO)); + ASSERT_OK(close(saved_stdout)); printf("output is: %s", output); ASSERT_OK(strncmp("hello errworld\n", output, 15)); @@ -561,9 +589,9 @@ TEST_IMPL(spawn_stdout_and_stderr_to_file_swap) { ASSERT_GE(r, 12); uv_fs_req_cleanup(&fs_req); - r = uv_fs_close(NULL, &fs_req, stderr_file, NULL); - ASSERT_OK(r); - uv_fs_req_cleanup(&fs_req); + /* Putting the real stderr back also closes the file sitting on fd 2. */ + ASSERT_NE(-1, dup2(saved_stderr, STDERR_FILENO)); + ASSERT_OK(close(saved_stderr)); printf("output is: %s", output); ASSERT_OK(strncmp("hello world\n", output, 12)); @@ -580,6 +608,73 @@ TEST_IMPL(spawn_stdout_and_stderr_to_file_swap) { } +#ifndef _WIN32 +/* The stdio "pipes" handed to a child are AF_UNIX stream socket pairs whose + * buffers are raised to at least 64 KiB, but never shrunk below what the + * platform hands out by default. + */ +TEST_IMPL(spawn_stdio_socket_buffer_size) { + uv_stdio_container_t stdio[2]; + uv_pipe_t in; + uv_pipe_t out; + uv_os_fd_t fd; + socklen_t len; + int defaults[2]; + int sndbuf; + int rcvbuf; + int sv[2]; + int r; + + /* What does a fresh socket pair get on this system? */ + ASSERT_OK(socketpair(AF_UNIX, SOCK_STREAM, 0, sv)); + len = sizeof(defaults[0]); + ASSERT_OK(getsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &defaults[0], &len)); + len = sizeof(defaults[1]); + ASSERT_OK(getsockopt(sv[0], SOL_SOCKET, SO_RCVBUF, &defaults[1], &len)); + close(sv[0]); + close(sv[1]); + + init_process_options("spawn_helper1", exit_cb); + + uv_pipe_init(uv_default_loop(), &out, 0); + uv_pipe_init(uv_default_loop(), &in, 0); + options.stdio = stdio; + options.stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; + options.stdio[0].data.stream = (uv_stream_t*) ∈ + options.stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; + options.stdio[1].data.stream = (uv_stream_t*) &out; + options.stdio_count = 2; + + r = uv_spawn(uv_default_loop(), &process, &options); + ASSERT_OK(r); + + ASSERT_OK(uv_fileno((uv_handle_t*) &in, &fd)); + len = sizeof(sndbuf); + ASSERT_OK(getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, &len)); + len = sizeof(rcvbuf); + ASSERT_OK(getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &len)); + + ASSERT_GE(sndbuf, 64 * 1024); + ASSERT_GE(rcvbuf, 64 * 1024); + ASSERT_GE(sndbuf, defaults[0]); + ASSERT_GE(rcvbuf, defaults[1]); + + r = uv_read_start((uv_stream_t*) &out, on_alloc, on_read); + ASSERT_OK(r); + uv_close((uv_handle_t*) &in, close_cb); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + ASSERT_OK(r); + + ASSERT_EQ(1, exit_cb_called); + ASSERT_EQ(3, close_cb_called); /* Once for process twice for the pipe. */ + + MAKE_VALGRIND_HAPPY(uv_default_loop()); + return 0; +} +#endif + + TEST_IMPL(spawn_stdin) { int r; uv_pipe_t out; @@ -1097,6 +1192,7 @@ TEST_IMPL(spawn_detect_pipe_name_collisions_on_windows) { uv_pipe_t out; char name[64]; HANDLE pipe_handle; + HANDLE pipe_handle_local; uv_stdio_container_t stdio[2]; init_process_options("spawn_helper2", exit_cb); @@ -1108,7 +1204,12 @@ TEST_IMPL(spawn_detect_pipe_name_collisions_on_windows) { options.stdio[1].data.stream = (uv_stream_t*) &out; options.stdio_count = 2; - /* Create a pipe that'll cause a collision. */ + /* Create pipes that might cause a collision. + * Inside an AppContainer, libuv uses \\?\pipe\LOCAL\uv\... and the + * non-LOCAL CreateNamedPipe will fail with access denied. + * Outside an AppContainer, libuv uses \\?\pipe\uv\... and both + * CreateNamedPipe calls succeed but only the non-LOCAL one can collide. + * Either way, uv_spawn should handle collisions gracefully. */ snprintf(name, sizeof(name), "\\\\.\\pipe\\uv\\%p-%lu", @@ -1122,7 +1223,24 @@ TEST_IMPL(spawn_detect_pipe_name_collisions_on_windows) { 65536, 0, NULL); - ASSERT_PTR_NE(pipe_handle, INVALID_HANDLE_VALUE); + + snprintf(name, + sizeof(name), + "\\\\.\\pipe\\LOCAL\\uv\\%p-%lu", + &out, + GetCurrentProcessId()); + pipe_handle_local = CreateNamedPipeA(name, + PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, + 10, + 65536, + 65536, + 0, + NULL); + + /* At least one must succeed. */ + ASSERT(pipe_handle != INVALID_HANDLE_VALUE || + pipe_handle_local != INVALID_HANDLE_VALUE); r = uv_spawn(uv_default_loop(), &process, &options); ASSERT_OK(r); @@ -1138,6 +1256,11 @@ TEST_IMPL(spawn_detect_pipe_name_collisions_on_windows) { printf("output is: %s", output); ASSERT_OK(strcmp("hello world\n", output)); + if (pipe_handle != INVALID_HANDLE_VALUE) + CloseHandle(pipe_handle); + if (pipe_handle_local != INVALID_HANDLE_VALUE) + CloseHandle(pipe_handle_local); + MAKE_VALGRIND_HAPPY(uv_default_loop()); return 0; } @@ -1677,15 +1800,12 @@ TEST_IMPL(spawn_fs_open) { uv_stdio_container_t stdio[1]; #ifdef _WIN32 const char dev_null[] = "NUL"; - HMODULE kernelbase_module; - union { - FARPROC proc; - sCompareObjectHandles pCompareObjectHandles; /* Windows >= 10 */ - } u; #else const char dev_null[] = "/dev/null"; #endif + RETURN_SKIP_IN_APPCONTAINER("child process crashes with invalid handle"); + r = uv_fs_open(NULL, &fs_req, dev_null, UV_FS_O_RDWR, 0, NULL); ASSERT_NE(r, -1); fd = uv_get_osfhandle((uv_file) fs_req.result); @@ -1704,10 +1824,6 @@ TEST_IMPL(spawn_fs_open) { #ifdef _WIN32 ASSERT_NE(0, DuplicateHandle(GetCurrentProcess(), fd, GetCurrentProcess(), &dup_fd, 0, /* inherit */ TRUE, DUPLICATE_SAME_ACCESS)); - kernelbase_module = GetModuleHandleW(L"kernelbase.dll"); - u.proc = GetProcAddress(kernelbase_module, "CompareObjectHandles"); - if (u.pCompareObjectHandles != NULL) - ASSERT_EQ(TRUE, u.pCompareObjectHandles(fd, dup_fd)); #else dup_fd = dup(fd); #endif @@ -1726,6 +1842,11 @@ TEST_IMPL(spawn_fs_open) { ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_DEFAULT)); ASSERT_OK(uv_fs_close(NULL, &fs_req, r, NULL)); +#ifdef _WIN32 + ASSERT_NE(0, CloseHandle(dup_fd)); +#else + ASSERT_OK(close(dup_fd)); +#endif ASSERT_EQ(1, exit_cb_called); ASSERT_EQ(2, close_cb_called); /* One for `in`, one for process */ @@ -2111,3 +2232,17 @@ TEST_IMPL(spawn_relative_path) { MAKE_VALGRIND_HAPPY(uv_default_loop()); return 0; } + +TEST_IMPL(spawn_empty_command_line) { + init_process_options("spawn_empty_command_line", exit_cb); + options.args[1] = NULL; + + ASSERT_OK(uv_spawn(uv_default_loop(), &process, &options)); + ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + + ASSERT_EQ(1, exit_cb_called); + ASSERT_EQ(1, close_cb_called); + + MAKE_VALGRIND_HAPPY(uv_default_loop()); + return 0; +} diff --git a/deps/uv/test/test-tcp-rst.c b/deps/uv/test/test-tcp-rst.c index cd4567037d4..103aad4802d 100644 --- a/deps/uv/test/test-tcp-rst.c +++ b/deps/uv/test/test-tcp-rst.c @@ -52,11 +52,6 @@ static void read_cb(uv_stream_t* t, ssize_t nread, const uv_buf_t* buf) { ASSERT_PTR_EQ((uv_tcp_t*) t, &tcp); ASSERT_EQ(nread, UV_ECONNRESET); - int fd; - ASSERT_OK(uv_fileno((uv_handle_t*) t, &fd)); - uv_handle_type type = uv_guess_handle(fd); - ASSERT_EQ(type, UV_TCP); - uv_close((uv_handle_t *) t, close_cb); free(buf->base); } diff --git a/deps/uv/test/test-tcp-write-fail.c b/deps/uv/test/test-tcp-write-fail.c index c273c61655f..42a55f71aa9 100644 --- a/deps/uv/test/test-tcp-write-fail.c +++ b/deps/uv/test/test-tcp-write-fail.c @@ -94,6 +94,8 @@ TEST_IMPL(tcp_write_fail) { uv_buf_t buf; int r; + RETURN_SKIP_IN_APPCONTAINER("child process crashes with invalid handle"); + ASSERT_OK(uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); r = uv_tcp_init(uv_default_loop(), &client); diff --git a/deps/uv/test/test-tcp-write-in-read-cb-backend-timeout.c b/deps/uv/test/test-tcp-write-in-read-cb-backend-timeout.c new file mode 100644 index 00000000000..d70280ca660 --- /dev/null +++ b/deps/uv/test/test-tcp-write-in-read-cb-backend-timeout.c @@ -0,0 +1,138 @@ +/* Copyright libuv project contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/* Regression test: a write that completes synchronously on a stream that is + * also being read must not leave a no-op io watcher update queued, because + * embedders that drive the loop with uv_backend_fd()/uv_backend_timeout() + * see that as "poll again immediately" and spin an extra iteration. + */ + +#include "uv.h" +#include "task.h" + +static uv_tcp_t server; +static uv_tcp_t client; +static uv_tcp_t incoming; +static uv_connect_t connect_req; +static uv_write_t ping_req; +static uv_write_t pong_req; +static char slab[64]; +static int pong_written; +static int pong_write_cb_called; +static int close_cb_called; + +static void close_cb(uv_handle_t* handle) { + close_cb_called++; +} + +static void alloc_cb(uv_handle_t* handle, size_t size, uv_buf_t* buf) { + *buf = uv_buf_init(slab, sizeof(slab)); +} + +static void pong_write_cb(uv_write_t* req, int status) { + ASSERT_OK(status); + pong_write_cb_called++; +} + +static void incoming_read_cb(uv_stream_t* stream, + ssize_t nread, + const uv_buf_t* buf) { + uv_buf_t pong; + + if (nread == 0) + return; + ASSERT_EQ(4, nread); + ASSERT_OK(pong_written); + /* We are inside uv__io_poll(). A small write on an idle loopback socket + * completes synchronously; its callback is deferred via the pending queue + * and runs before this loop iteration ends. */ + pong = uv_buf_init("pong", 4); + ASSERT_OK(uv_write(&pong_req, stream, &pong, 1, pong_write_cb)); + ASSERT_UINT64_EQ(0, stream->write_queue_size); + pong_written++; +} + +static void client_read_cb(uv_stream_t* stream, + ssize_t nread, + const uv_buf_t* buf) { + ASSERT_GE(nread, 0); +} + +static void ping_write_cb(uv_write_t* req, int status) { + ASSERT_OK(status); +} + +static void connect_cb(uv_connect_t* req, int status) { + uv_buf_t ping; + ASSERT_OK(status); + ASSERT_OK(uv_read_start((uv_stream_t*) &client, alloc_cb, client_read_cb)); + ping = uv_buf_init("ping", 4); + ASSERT_OK(uv_write(&ping_req, (uv_stream_t*) &client, &ping, 1, + ping_write_cb)); +} + +static void connection_cb(uv_stream_t* s, int status) { + ASSERT_OK(status); + ASSERT_OK(uv_tcp_init(s->loop, &incoming)); + ASSERT_OK(uv_accept(s, (uv_stream_t*) &incoming)); + ASSERT_OK(uv_read_start((uv_stream_t*) &incoming, + alloc_cb, + incoming_read_cb)); +} + +TEST_IMPL(tcp_write_in_read_cb_backend_timeout) { +#if defined(_WIN32) + RETURN_SKIP("Stream writes do not complete synchronously on Windows."); +#elif defined(__sun) + RETURN_SKIP("Event ports re-arm file descriptors on every iteration."); +#else + struct sockaddr_in addr; + uv_loop_t* loop; + + loop = uv_default_loop(); + ASSERT_OK(uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); + ASSERT_OK(uv_tcp_init(loop, &server)); + ASSERT_OK(uv_tcp_bind(&server, (struct sockaddr*) &addr, 0)); + ASSERT_OK(uv_listen((uv_stream_t*) &server, 1, connection_cb)); + ASSERT_OK(uv_tcp_init(loop, &client)); + ASSERT_OK(uv_tcp_connect(&connect_req, + &client, + (struct sockaddr*) &addr, + connect_cb)); + + while (pong_write_cb_called == 0) + ASSERT_EQ(1, uv_run(loop, UV_RUN_ONCE)); + + ASSERT_EQ(1, pong_written); + /* Three reading TCP handles, no timers, nothing pending: an embedder should + * now be told to block indefinitely, not to spin. */ + ASSERT_EQ(-1, uv_backend_timeout(loop)); + + uv_close((uv_handle_t*) &incoming, close_cb); + uv_close((uv_handle_t*) &client, close_cb); + uv_close((uv_handle_t*) &server, close_cb); + ASSERT_OK(uv_run(loop, UV_RUN_DEFAULT)); + ASSERT_EQ(3, close_cb_called); + + MAKE_VALGRIND_HAPPY(loop); + return 0; +#endif +} diff --git a/deps/uv/test/test-thread-priority.c b/deps/uv/test/test-thread-priority.c index acc33855130..769b485e980 100644 --- a/deps/uv/test/test-thread-priority.c +++ b/deps/uv/test/test-thread-priority.c @@ -99,11 +99,5 @@ TEST_IMPL(thread_priority) { uv_sem_destroy(&sem); - /* Now that the thread no longer exists, verify that the relevant error is returned */ -#if !defined(__ANDROID__) - ASSERT_EQ(UV_ESRCH, uv_thread_getpriority(task_id, &priority)); - ASSERT_EQ(UV_ESRCH, uv_thread_setpriority(task_id, UV_THREAD_PRIORITY_LOWEST)); -#endif - return 0; } diff --git a/deps/uv/test/test-threadpool-cancel.c b/deps/uv/test/test-threadpool-cancel.c index c5e6e9aa384..3609ba20612 100644 --- a/deps/uv/test/test-threadpool-cancel.c +++ b/deps/uv/test/test-threadpool-cancel.c @@ -23,6 +23,10 @@ #include "task.h" #include +#ifdef __linux__ +# include +#endif + #ifdef _WIN32 # define putenv _putenv #endif @@ -54,6 +58,12 @@ static unsigned timer_cb_called; static uv_work_t pause_reqs[4]; static uv_sem_t pause_sems[ARRAY_SIZE(pause_reqs)]; +#ifdef __linux__ +static uv_fs_t iouring_fs_req; +static ssize_t iouring_fs_result; +static int iouring_cancel_result; +#endif + static void work_cb(uv_work_t* req) { uv_sem_wait(pause_sems + (req - pause_reqs)); @@ -97,7 +107,7 @@ static int known_broken(uv_req_t* req) { return 0; #ifdef __linux__ - /* TODO(bnoordhuis) make cancellation work with io_uring */ + /* io_uring requests may complete before the cancel timer fires. */ switch (((uv_fs_t*) req)->fs_type) { case UV_FS_CLOSE: case UV_FS_FDATASYNC: @@ -131,6 +141,29 @@ static void fs_cb(uv_fs_t* req) { fs_cb_called++; } +#ifdef __linux__ +static void iouring_fs_cb(uv_fs_t* req) { + iouring_fs_result = req->result; + uv_fs_req_cleanup(req); + fs_cb_called++; +} + +static void iouring_cancel_cb(uv_timer_t* handle) { + int* fd; + + iouring_cancel_result = uv_cancel((uv_req_t*) &iouring_fs_req); + if (iouring_cancel_result == UV_EBUSY) { + fd = handle->data; + ASSERT_OK(close(*fd)); + *fd = -1; + } else { + ASSERT_OK(iouring_cancel_result); + } + + uv_close((uv_handle_t*) handle, NULL); +} +#endif + static void getaddrinfo_cb(uv_getaddrinfo_t* req, int status, @@ -369,6 +402,70 @@ TEST_FS_IMPL(threadpool_cancel_fs) { return 0; } +TEST_IMPL(threadpool_cancel_fs_iouring_sync_cancel) { +#ifdef __linux__ + char env[] = "UV_USE_IO_URING=1"; + char buf[1]; + uv_loop_t* loop; + uv_timer_t timer; + uv_buf_t iov; + int pipefd[2]; + + ASSERT_OK(putenv(env)); + + loop = uv_default_loop(); + ASSERT_OK(uv_loop_configure(loop, UV_LOOP_USE_IO_URING_SQPOLL)); + ASSERT_OK(pipe(pipefd)); + + iov = uv_buf_init(buf, sizeof(buf)); + + ASSERT_OK(uv_fs_read(loop, + &iouring_fs_req, + pipefd[0], + &iov, + 1, + -1, + iouring_fs_cb)); + + if (iouring_fs_req.work_req.done != NULL) { + ASSERT_OK(close(pipefd[1])); + ASSERT_OK(uv_run(loop, UV_RUN_DEFAULT)); + ASSERT_EQ(iouring_fs_result, 0); + ASSERT_EQ(fs_cb_called, 1); + ASSERT_OK(close(pipefd[0])); + + MAKE_VALGRIND_HAPPY(loop); + RETURN_SKIP("io_uring is not available."); + } + + ASSERT_OK(uv_timer_init(loop, &timer)); + timer.data = &pipefd[1]; + ASSERT_OK(uv_timer_start(&timer, iouring_cancel_cb, 10, 0)); + + ASSERT_OK(uv_run(loop, UV_RUN_DEFAULT)); + + if (iouring_cancel_result == UV_EBUSY) { + ASSERT_EQ(iouring_fs_result, 0); + ASSERT_EQ(fs_cb_called, 1); + ASSERT_OK(close(pipefd[0])); + + MAKE_VALGRIND_HAPPY(loop); + RETURN_SKIP("Synchronous io_uring cancellation is not available."); + } + + ASSERT_EQ(fs_cb_called, 1); + ASSERT_EQ(iouring_fs_result, UV_ECANCELED); + + ASSERT_OK(close(pipefd[0])); + ASSERT_OK(close(pipefd[1])); + + MAKE_VALGRIND_HAPPY(loop); + return 0; +#else + RETURN_SKIP("Not on Linux."); +#endif +} + TEST_IMPL(threadpool_cancel_single) { uv_loop_t* loop; diff --git a/deps/uv/test/test-tty.c b/deps/uv/test/test-tty.c index 9ecb3ae52ae..0f186f5e94b 100644 --- a/deps/uv/test/test-tty.c +++ b/deps/uv/test/test-tty.c @@ -28,12 +28,14 @@ #else /* Unix */ # include # include -# if defined(__linux__) && !defined(__ANDROID__) +# if defined(__linux__) && !defined(__ANDROID__) || defined(__GNU__) # include # elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) # include # elif defined(__FreeBSD__) || defined(__DragonFly__) # include +# elif defined(__HAIKU__) +# include # endif #endif @@ -119,6 +121,9 @@ TEST_IMPL(tty) { ASSERT_GT(width, 0); ASSERT_GT(height, 0); + r = uv_tty_set_mode(&tty_in, (uv_tty_mode_t) -1); + ASSERT_EQ(r, UV_EINVAL); + /* Turn on raw mode. */ r = uv_tty_set_mode(&tty_in, UV_TTY_MODE_RAW); ASSERT_OK(r); @@ -442,6 +447,10 @@ TEST_IMPL(tty_pty) { ASSERT(uv_is_writable((uv_stream_t*) &slave_tty)); ASSERT(uv_is_readable((uv_stream_t*) &master_tty)); ASSERT(uv_is_writable((uv_stream_t*) &master_tty)); + + r = uv_tty_set_mode(&slave_tty, (uv_tty_mode_t) -1); + ASSERT_EQ(r, UV_EINVAL); + /* Check if the file descriptor was reopened. If it is, * UV_HANDLE_BLOCKING_WRITES (value 0x100000) isn't set on flags. */ @@ -515,7 +524,7 @@ TEST_IMPL(tty_pty_partial) { /* This test is not 100% deterministic. If the bug it is testing for is * present, then it fails about 1 in 3 times, that's why it runs in a loop. */ - for (i = 0; i < 10; i++) { + for (i = 0; i < 50; i++) { if (openpty(&master_fd, &slave_fd, NULL, NULL, NULL)) RETURN_SKIP("No pty available, skipping."); diff --git a/deps/uv/test/test-udp-mmsg.c b/deps/uv/test/test-udp-mmsg.c index 73213c43d97..6ea9e549337 100644 --- a/deps/uv/test/test-udp-mmsg.c +++ b/deps/uv/test/test-udp-mmsg.c @@ -35,11 +35,21 @@ static uv_udp_t recver; static uv_udp_t sender; +static uv_timer_t timeout; static int recv_cb_called; static int received_datagrams; static int read_bytes; static int close_cb_called; static int alloc_cb_called; +static int terminal_data_cb_called; +static int terminal_free_cb_called; +static int terminal_drain_cb_called; +static int terminal_timeout_cb_called; +static int duplicate_terminal_cb; +#ifndef _WIN32 +static int zero_namelen_chunk_cb_called; +static int zero_namelen_free_cb_called; +#endif static void alloc_cb(uv_handle_t* handle, @@ -106,6 +116,165 @@ static void recv_cb(uv_udp_t* handle, } +static void small_alloc_cb(uv_handle_t* handle, + size_t suggested_size, + uv_buf_t* buf) { + CHECK_HANDLE(handle); + buf->len = 64; + buf->base = malloc(buf->len); + ASSERT_NOT_NULL(buf->base); + alloc_cb_called++; +} + + +static void small_recv_cb(uv_udp_t* handle, + ssize_t nread, + const uv_buf_t* rcvbuf, + const struct sockaddr* addr, + unsigned flags) { + CHECK_HANDLE(handle); + ASSERT_EQ(UV_EINVAL, nread); + uv_close((uv_handle_t*) &recver, close_cb); + uv_close((uv_handle_t*) &sender, close_cb); + free(rcvbuf->base); + recv_cb_called++; +} + + +static void terminal_alloc_cb(uv_handle_t* handle, + size_t suggested_size, + uv_buf_t* buf) { + CHECK_HANDLE(handle); + buf->base = malloc(suggested_size); + ASSERT_NOT_NULL(buf->base); + buf->len = suggested_size; +} + + +static void timeout_cb(uv_timer_t* handle) { + terminal_timeout_cb_called++; + + if (!uv_is_closing((uv_handle_t*) &recver)) + uv_close((uv_handle_t*) &recver, close_cb); + if (!uv_is_closing((uv_handle_t*) &sender)) + uv_close((uv_handle_t*) &sender, close_cb); + uv_close((uv_handle_t*) handle, NULL); +} + + +static void terminal_recv_cb(uv_udp_t* handle, + ssize_t nread, + const uv_buf_t* rcvbuf, + const struct sockaddr* addr, + unsigned flags) { + CHECK_HANDLE(handle); + + if (flags & UV_UDP_MMSG_FREE) { + ASSERT_OK(nread); + ASSERT_NULL(addr); + terminal_free_cb_called++; + free(rcvbuf->base); + return; + } + + if (nread > 0) { + ASSERT_EQ(4, nread); + ASSERT_NOT_NULL(addr); + ASSERT_MEM_EQ("PING", rcvbuf->base, nread); + terminal_data_cb_called++; + return; + } + + ASSERT_NULL(addr); + terminal_drain_cb_called++; + + if (terminal_drain_cb_called == 1) { + free(rcvbuf->base); + + if (!uv_is_closing((uv_handle_t*) &recver)) + uv_close((uv_handle_t*) &recver, close_cb); + if (!uv_is_closing((uv_handle_t*) &sender)) + uv_close((uv_handle_t*) &sender, close_cb); + if (!uv_is_closing((uv_handle_t*) &timeout)) + uv_close((uv_handle_t*) &timeout, NULL); + return; + } + + duplicate_terminal_cb = 1; +} + +#ifndef _WIN32 +static void zero_namelen_recv_cb(uv_udp_t* handle, + ssize_t nread, + const uv_buf_t* rcvbuf, + const struct sockaddr* addr, + unsigned flags) { + CHECK_HANDLE(handle); + + if (flags & UV_UDP_MMSG_FREE) { + ASSERT_EQ(0, nread); + ASSERT_NULL(addr); + zero_namelen_free_cb_called++; + free(rcvbuf->base); + return; + } + + ASSERT_EQ(0, nread); + ASSERT_NULL(addr); + zero_namelen_chunk_cb_called++; + + if (!uv_is_closing((uv_handle_t*) &recver)) + uv_close((uv_handle_t*) &recver, close_cb); + + /* Don't free if the buffer could be reused via mmsg */ + if (rcvbuf && !(flags & UV_UDP_MMSG_CHUNK)) + free(rcvbuf->base); +} + + +TEST_IMPL(udp_mmsg_namelen_zero) { + uv_loop_t* loop; + struct sockaddr_in recv_addr; + struct sockaddr_in send_addr; + uv_os_fd_t fd; + + loop = uv_default_loop(); + + ASSERT_OK(uv_udp_init_ex(loop, &recver, AF_UNSPEC | UV_UDP_RECVMMSG)); + if (!uv_udp_using_recvmmsg(&recver)) { + uv_close((uv_handle_t*) &recver, close_cb); + ASSERT_OK(uv_run(loop, UV_RUN_DEFAULT)); + ASSERT_EQ(1, close_cb_called); + MAKE_VALGRIND_HAPPY(loop); + return 0; + } + + ASSERT_OK(uv_ip4_addr("0.0.0.0", TEST_PORT, &recv_addr)); + ASSERT_OK(uv_udp_bind(&recver, (const struct sockaddr*) &recv_addr, 0)); + + ASSERT_OK(uv_ip4_addr("127.0.0.1", TEST_PORT_2, &send_addr)); + ASSERT_OK(uv_udp_connect(&recver, (const struct sockaddr*) &send_addr)); + + ASSERT_OK(uv_fileno((const uv_handle_t*) &recver, &fd)); + ASSERT_OK(shutdown(fd, SHUT_RD)); + + ASSERT_OK(uv_udp_recv_start(&recver, alloc_cb, zero_namelen_recv_cb)); + + ASSERT_OK(uv_run(loop, UV_RUN_DEFAULT)); + + /* On some platforms as FreeBSD, multiple chunks will be received, one per + * buffer */ + ASSERT_GE(zero_namelen_chunk_cb_called, 1); + /* On others such as Linux, the free callback won't be called as recvmmsg is + * returning EINVAL */ + ASSERT_LE(zero_namelen_free_cb_called, 1); + ASSERT_EQ(1, close_cb_called); + + MAKE_VALGRIND_HAPPY(loop); + return 0; +} +#endif + TEST_IMPL(udp_mmsg) { struct sockaddr_in addr; uv_buf_t buf; @@ -148,3 +317,80 @@ TEST_IMPL(udp_mmsg) { MAKE_VALGRIND_HAPPY(uv_default_loop()); return 0; } + + +TEST_IMPL(udp_mmsg_small_buf) { + struct sockaddr_in addr; + uv_loop_t* loop; + uv_buf_t buf; + + loop = uv_default_loop(); + ASSERT_OK(uv_udp_init_ex(loop, &recver, AF_UNSPEC | UV_UDP_RECVMMSG)); + if (uv_udp_using_recvmmsg(&recver)) { + ASSERT_OK(uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); + ASSERT_OK(uv_udp_bind(&recver, (const struct sockaddr*) &addr, 0)); + ASSERT_OK(uv_udp_recv_start(&recver, small_alloc_cb, small_recv_cb)); + ASSERT_OK(uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); + ASSERT_OK(uv_udp_init(loop, &sender)); + buf = uv_buf_init("PING", 4); + ASSERT_EQ(4, uv_udp_try_send(&sender, &buf, 1, (const struct sockaddr*) &addr)); + ASSERT_OK(uv_run(loop, UV_RUN_DEFAULT)); + ASSERT_EQ(1, recv_cb_called); + ASSERT_EQ(2, close_cb_called); + } else { + uv_close((uv_handle_t*) &recver, close_cb); + ASSERT_OK(uv_run(loop, UV_RUN_DEFAULT)); + ASSERT_EQ(1, close_cb_called); + } + MAKE_VALGRIND_HAPPY(loop); + return 0; +} + + +TEST_IMPL(udp_mmsg_single_drain_cb) { + struct sockaddr_in addr; + uv_loop_t* loop; + uv_buf_t buf; + + close_cb_called = 0; + terminal_data_cb_called = 0; + terminal_free_cb_called = 0; + terminal_drain_cb_called = 0; + terminal_timeout_cb_called = 0; + duplicate_terminal_cb = 0; + + loop = uv_default_loop(); + + ASSERT_OK(uv_udp_init_ex(loop, &recver, AF_UNSPEC | UV_UDP_RECVMMSG)); + if (!uv_udp_using_recvmmsg(&recver)) { + uv_close((uv_handle_t*) &recver, close_cb); + ASSERT_OK(uv_run(loop, UV_RUN_DEFAULT)); + ASSERT_EQ(1, close_cb_called); + MAKE_VALGRIND_HAPPY(loop); + return 0; + } + + ASSERT_OK(uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); + ASSERT_OK(uv_udp_bind(&recver, (const struct sockaddr*) &addr, 0)); + ASSERT_OK(uv_udp_recv_start(&recver, terminal_alloc_cb, terminal_recv_cb)); + + ASSERT_OK(uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); + ASSERT_OK(uv_udp_init(loop, &sender)); + ASSERT_OK(uv_timer_init(loop, &timeout)); + ASSERT_OK(uv_timer_start(&timeout, timeout_cb, 5000, 0)); + + buf = uv_buf_init("PING", 4); + ASSERT_EQ(4, uv_udp_try_send(&sender, &buf, 1, (const struct sockaddr*) &addr)); + + ASSERT_OK(uv_run(loop, UV_RUN_DEFAULT)); + + ASSERT_EQ(0, terminal_timeout_cb_called); + ASSERT_EQ(0, duplicate_terminal_cb); + ASSERT_EQ(1, terminal_data_cb_called); + ASSERT_EQ(1, terminal_free_cb_called); + ASSERT_EQ(1, terminal_drain_cb_called); + ASSERT_EQ(2, close_cb_called); + + MAKE_VALGRIND_HAPPY(loop); + return 0; +} diff --git a/deps/uv/test/test-udp-recvmsg-unreachable-error.c b/deps/uv/test/test-udp-recvmsg-unreachable-error.c index 48990cc6d48..4fa95e1c10c 100644 --- a/deps/uv/test/test-udp-recvmsg-unreachable-error.c +++ b/deps/uv/test/test-udp-recvmsg-unreachable-error.c @@ -84,6 +84,8 @@ TEST_IMPL(udp_recvmsg_unreachable_error) { udp_send_cb)); uv_run(uv_default_loop(), UV_RUN_DEFAULT); ASSERT_EQ(recv_cb_called, RECV_CB_MAX_CALL); + + MAKE_VALGRIND_HAPPY(uv_default_loop()); return 0; } @@ -91,6 +93,9 @@ TEST_IMPL(udp_recvmsg_unreachable_error6) { #if !defined(__linux__) RETURN_SKIP("This test is Linux-specific"); #endif + if (!can_ipv6()) + RETURN_SKIP("IPv6 not supported"); + struct sockaddr_in6 server_addr, client_addr; uv_udp_t client; uv_timer_t timer; @@ -121,5 +126,7 @@ TEST_IMPL(udp_recvmsg_unreachable_error6) { uv_run(uv_default_loop(), UV_RUN_DEFAULT); ASSERT_EQ(recv_cb_called, RECV_CB_MAX_CALL); + + MAKE_VALGRIND_HAPPY(uv_default_loop()); return 0; } diff --git a/deps/uv/test/test-udp-send-fail.c b/deps/uv/test/test-udp-send-fail.c new file mode 100644 index 00000000000..b3722e4ea23 --- /dev/null +++ b/deps/uv/test/test-udp-send-fail.c @@ -0,0 +1,70 @@ +/* Copyright libuv project contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "uv.h" +#include "task.h" + +#include + + +TEST_IMPL(udp_send_fail_nbufs) { + struct sockaddr_in addr; + uv_udp_send_t req; + uv_udp_t client; + uv_buf_t buf; + int r; + + ASSERT_OK(uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); + ASSERT_OK(uv_udp_init(uv_default_loop(), &client)); + + buf = uv_buf_init("PING", 4); + + /* nbufs=0 should be rejected. */ + r = uv_udp_send(&req, + &client, + &buf, + 0, + (const struct sockaddr*) &addr, + NULL); + ASSERT_EQ(UV_EINVAL, r); + + /* Negative nbufs undergoes sign conversion to a large unsigned value. */ + r = uv_udp_send(&req, + &client, + &buf, + -1, + (const struct sockaddr*) &addr, + NULL); + ASSERT_EQ(UV_EINVAL, r); + + /* Same checks for uv_udp_try_send. */ + r = uv_udp_try_send(&client, &buf, 0, (const struct sockaddr*) &addr); + ASSERT_EQ(UV_EINVAL, r); + + r = uv_udp_try_send(&client, &buf, -1, (const struct sockaddr*) &addr); + ASSERT_EQ(UV_EINVAL, r); + + uv_close((uv_handle_t*) &client, NULL); + uv_run(uv_default_loop(), UV_RUN_DEFAULT); + + MAKE_VALGRIND_HAPPY(uv_default_loop()); + return 0; +} diff --git a/deps/uv/test/test-udp-try-send.c b/deps/uv/test/test-udp-try-send.c index 6181fbbbffc..d423720b184 100644 --- a/deps/uv/test/test-udp-try-send.c +++ b/deps/uv/test/test-udp-try-send.c @@ -22,8 +22,6 @@ #include "uv.h" #include "task.h" -#include -#include #include #define CHECK_HANDLE(handle) \ @@ -86,55 +84,42 @@ TEST_IMPL(udp_try_send) { struct sockaddr_in addr; static char buffer[64 * 1024]; uv_buf_t buf; - int r; + uv_buf_t* bufs[] = {&buf, &buf}; + unsigned int nbufs[] = {1, 1}; + struct sockaddr* addrs[] = { + (struct sockaddr*) &addr, + (struct sockaddr*) &addr, + }; ASSERT_OK(uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); - - r = uv_udp_init(uv_default_loop(), &server); - ASSERT_OK(r); - - r = uv_udp_bind(&server, (const struct sockaddr*) &addr, 0); - ASSERT_OK(r); - - r = uv_udp_recv_start(&server, alloc_cb, sv_recv_cb); - ASSERT_OK(r); - + ASSERT_OK(uv_udp_init(uv_default_loop(), &server)); + ASSERT_OK(uv_udp_bind(&server, (const struct sockaddr*) &addr, 0)); + ASSERT_OK(uv_udp_recv_start(&server, alloc_cb, sv_recv_cb)); ASSERT_OK(uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); - - r = uv_udp_init(uv_default_loop(), &client); - ASSERT_OK(r); + ASSERT_OK(uv_udp_init(uv_default_loop(), &client)); buf = uv_buf_init(buffer, sizeof(buffer)); - r = uv_udp_try_send(&client, &buf, 0, (const struct sockaddr*) &addr); - ASSERT_EQ(r, UV_EINVAL); + ASSERT_EQ(uv_udp_try_send(&client, &buf, 0, (const struct sockaddr*) &addr), + UV_EINVAL); - r = uv_udp_try_send(&client, &buf, 1, (const struct sockaddr*) &addr); - ASSERT_EQ(r, UV_EMSGSIZE); - - uv_buf_t* bufs[] = {&buf, &buf}; - unsigned int nbufs[] = {1, 1}; - struct sockaddr* addrs[] = { - (struct sockaddr*) &addr, - (struct sockaddr*) &addr, - }; + ASSERT_EQ(uv_udp_try_send(&client, &buf, 1, (const struct sockaddr*) &addr), + UV_EMSGSIZE); ASSERT_EQ(0, sv_recv_cb_called); buf = uv_buf_init("HELO", 4); - r = uv_udp_try_send2(&client, 2, bufs, nbufs, addrs, /*flags*/0); - ASSERT_EQ(r, 2); + ASSERT_EQ(2, uv_udp_try_send2(&client, 2, bufs, nbufs, addrs, /*flags*/0)); - uv_run(uv_default_loop(), UV_RUN_DEFAULT); + ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_DEFAULT)); ASSERT_EQ(2, sv_recv_cb_called); - r = uv_udp_recv_start(&server, alloc_cb, sv_recv_cb); - ASSERT_OK(r); + ASSERT_OK(uv_udp_recv_start(&server, alloc_cb, sv_recv_cb)); buf = uv_buf_init("EXIT", 4); - r = uv_udp_try_send(&client, &buf, 1, (const struct sockaddr*) &addr); - ASSERT_EQ(4, r); + ASSERT_EQ(uv_udp_try_send(&client, &buf, 1, (const struct sockaddr*) &addr), + 4); uv_run(uv_default_loop(), UV_RUN_DEFAULT); diff --git a/deps/uv/test/test-write-cancel.c b/deps/uv/test/test-write-cancel.c new file mode 100644 index 00000000000..23d915d6029 --- /dev/null +++ b/deps/uv/test/test-write-cancel.c @@ -0,0 +1,539 @@ +/* Copyright libuv project contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include + +#include "uv.h" +#include "task.h" + +#define REQ_COUNT 100 + +static int close_cb_called; +static int write_cb_called; +static int cancelled_count; +static int connected; +static int closing; + +static void close_cb(uv_handle_t* handle) { + close_cb_called++; +} + + +/* + * tcp_write_cancel + */ +static uv_tcp_t server; +static uv_tcp_t client; +static uv_tcp_t incoming; +static uv_write_t write_reqs[REQ_COUNT]; +static char buf_data[16 * 1024]; + +static void connection_cb(uv_stream_t* tcp, int status) { + ASSERT_OK(status); + ASSERT_OK(uv_tcp_init(tcp->loop, &incoming)); + ASSERT_OK(uv_accept(tcp, (uv_stream_t*) &incoming)); + connected++; + ASSERT_EQ(1, connected); +} + +static void write_cb(uv_write_t* req, int status) { + write_cb_called++; + if (status == UV_ECANCELED && closing == 0) + cancelled_count++; + + if (cancelled_count >= 5 && closing == 0) { + closing++; + ASSERT_EQ(1, closing); + uv_close((uv_handle_t*) &client, close_cb); + uv_close((uv_handle_t*) &server, close_cb); + if (connected) + uv_close((uv_handle_t*) &incoming, close_cb); + } +} + +static void connect_cb(uv_connect_t* req, int status) { + uv_buf_t buf; + int r; + int i; + int cancel_count; + + ASSERT_OK(status); + + buf = uv_buf_init(buf_data, sizeof(buf_data)); + + /* Queue many writes to fill the socket buffer */ + for (i = 0; i < REQ_COUNT; i++) { + r = uv_write(&write_reqs[i], + req->handle, + &buf, + 1, + write_cb); + ASSERT_OK(r); + } + + /* Cancel the trailing writes which should be queued */ + cancel_count = 0; + for (i = REQ_COUNT - 5; i < REQ_COUNT; i++) { + r = uv_cancel((uv_req_t*) &write_reqs[i]); + ASSERT_OK(r); + cancel_count++; + } + + ASSERT_EQ(5, cancel_count); +} + +TEST_IMPL(tcp_write_cancel) { + uv_connect_t connect_req; + struct sockaddr_in addr; + uv_loop_t* loop; + int buffer_size; + + loop = uv_default_loop(); + + close_cb_called = 0; + write_cb_called = 0; + cancelled_count = 0; + connected = 0; + closing = 0; + buffer_size = sizeof(buf_data); + memset(buf_data, 'A', sizeof(buf_data)); + + ASSERT_OK(uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); + ASSERT_OK(uv_tcp_init(loop, &server)); + ASSERT_OK(uv_tcp_bind(&server, (struct sockaddr*) &addr, 0)); + ASSERT_OK(uv_listen((uv_stream_t*) &server, 128, connection_cb)); + + ASSERT_OK(uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); + ASSERT_OK(uv_tcp_init(loop, &client)); + ASSERT_OK(uv_tcp_connect(&connect_req, + &client, + (struct sockaddr*) &addr, + connect_cb)); + /* Set the send buffer size small to ensure that writes get queued in + * userspace rather than fitting entirely in the kernel's send buffer. + * Also set the receive buffer small so the receiver doesn't drain data + * and relieve backpressure. Note: Linux sets the actual buffer to twice + * the requested size. */ + ASSERT_OK(uv_send_buffer_size((uv_handle_t*) &client, &buffer_size)); + ASSERT_OK(uv_recv_buffer_size((uv_handle_t*) &client, &buffer_size)); + + ASSERT_OK(uv_run(loop, UV_RUN_DEFAULT)); + + /* The writes we cancelled should have gotten UV_ECANCELED callbacks */ + ASSERT_EQ(5, cancelled_count); + ASSERT_EQ(2 + connected, close_cb_called); + + MAKE_VALGRIND_HAPPY(loop); + return 0; +} + + +/* + * pipe_write_cancel / pipe_write_cancel_all + * + * uv_pipe() with no flags creates non-overlapped handles, so on Windows + * these exercise the synchronous (thread pool) write cancellation path. + * The _overlapped variants pass UV_NONBLOCK_PIPE so that on Windows the + * handles are overlapped and cancellation goes through CancelIoEx. Half + * the writes use multiple buffers to exercise the coalesced write path. + * The _ipc variant makes the writer an ipc pipe, whose framing writes are + * always coalesced. + */ +static uv_pipe_t pipe_cancel_writer; +static uv_pipe_t pipe_cancel_reader; +static uv_pipe_t pipe_cancel_listener; +static uv_connect_t pipe_cancel_connect_req; +static int pipe_cancel_connected; +static int pipe_cancel_accepted; +static uv_write_t pipe_cancel_write_reqs[REQ_COUNT]; +static char pipe_cancel_buf_data[64 * 1024]; +static char pipe_cancel_after_close_buf_data[1024 * 1024]; +static int cancel_target; +static uv_write_cb pipe_cancel_cb; + +static void pipe_cancel_connect_cb(uv_connect_t* req, int status) { + ASSERT_OK(status); + pipe_cancel_connected = 1; +} + +static void pipe_cancel_connection_cb(uv_stream_t* listener, int status) { + ASSERT_OK(status); + ASSERT_OK(uv_pipe_init(listener->loop, &pipe_cancel_reader, 0)); + ASSERT_OK(uv_accept(listener, (uv_stream_t*) &pipe_cancel_reader)); + pipe_cancel_accepted = 1; +} + +static void pipe_cancel_setup(uv_loop_t* loop, int pipe_flags, int ipc) { + uv_buf_t bufs[4]; + uv_file fds[2]; + int r; + int i; + + close_cb_called = 0; + write_cb_called = 0; + cancelled_count = 0; + closing = 0; + memset(pipe_cancel_buf_data, 'D', sizeof(pipe_cancel_buf_data)); + + if (ipc) { + /* uv_pipe() endpoints are unidirectional, and an ipc pipe must be + * duplex (uv_pipe_open() refuses anything else on Windows). Establish + * a loopback named-pipe connection instead. */ + pipe_cancel_connected = 0; + pipe_cancel_accepted = 0; + ASSERT_OK(uv_pipe_init(loop, &pipe_cancel_listener, 0)); + ASSERT_OK(uv_pipe_bind(&pipe_cancel_listener, TEST_PIPENAME)); + ASSERT_OK(uv_listen((uv_stream_t*) &pipe_cancel_listener, + 1, + pipe_cancel_connection_cb)); + ASSERT_OK(uv_pipe_init(loop, &pipe_cancel_writer, 1)); + uv_pipe_connect(&pipe_cancel_connect_req, + &pipe_cancel_writer, + TEST_PIPENAME, + pipe_cancel_connect_cb); + while (!pipe_cancel_connected || !pipe_cancel_accepted) + uv_run(loop, UV_RUN_ONCE); + uv_close((uv_handle_t*) &pipe_cancel_listener, NULL); + } else { + ASSERT_OK(uv_pipe(fds, pipe_flags, pipe_flags)); + + ASSERT_OK(uv_pipe_init(loop, &pipe_cancel_writer, 0)); + ASSERT_OK(uv_pipe_init(loop, &pipe_cancel_reader, 0)); + + ASSERT_OK(uv_pipe_open(&pipe_cancel_writer, fds[1])); + ASSERT_OK(uv_pipe_open(&pipe_cancel_reader, fds[0])); + } + + bufs[0] = uv_buf_init(pipe_cancel_buf_data, + sizeof(pipe_cancel_buf_data) / 4); + bufs[1] = uv_buf_init(pipe_cancel_buf_data + + sizeof(pipe_cancel_buf_data) / 4, + sizeof(pipe_cancel_buf_data) / 4); + bufs[2] = uv_buf_init(pipe_cancel_buf_data + + sizeof(pipe_cancel_buf_data) / 2, + sizeof(pipe_cancel_buf_data) / 4); + bufs[3] = uv_buf_init(pipe_cancel_buf_data + + 3 * sizeof(pipe_cancel_buf_data) / 4, + sizeof(pipe_cancel_buf_data) / 4); + + /* Queue many writes to fill the pipe buffer. Alternate between single-buffer + * and multi-buffer writes so the latter exercise the coalesced write path + * on Windows. */ + for (i = 0; i < REQ_COUNT; i++) { + if (i % 2 == 0) { + r = uv_write(&pipe_cancel_write_reqs[i], + (uv_stream_t*) &pipe_cancel_writer, + &bufs[0], + 1, + pipe_cancel_cb); + } else { + r = uv_write(&pipe_cancel_write_reqs[i], + (uv_stream_t*) &pipe_cancel_writer, + bufs, + 4, + pipe_cancel_cb); + } + ASSERT_OK(r); + } +} + +static void pipe_cancel_close(void) { + if (closing == 0) { + closing++; + ASSERT_EQ(1, closing); + uv_close((uv_handle_t*) &pipe_cancel_writer, close_cb); + uv_close((uv_handle_t*) &pipe_cancel_reader, close_cb); + } +} + +static void pipe_cancel_write_cb_tail(uv_write_t* req, int status) { + write_cb_called++; + if (status == UV_ECANCELED && closing == 0) + cancelled_count++; + + if (cancelled_count >= cancel_target && closing == 0) + pipe_cancel_close(); +} + +static int pipe_write_cancel_run(int pipe_flags, int ipc) { + uv_loop_t* loop; + int i; + + loop = uv_default_loop(); + cancel_target = 5; + pipe_cancel_cb = pipe_cancel_write_cb_tail; + pipe_cancel_setup(loop, pipe_flags, ipc); + + /* Cancel the trailing writes which should be queued. */ + for (i = REQ_COUNT - 5; i < REQ_COUNT; i++) + ASSERT_OK(uv_cancel((uv_req_t*) &pipe_cancel_write_reqs[i])); + + ASSERT_OK(uv_run(loop, UV_RUN_DEFAULT)); + + ASSERT_EQ(5, cancelled_count); + ASSERT_EQ(2, close_cb_called); + + MAKE_VALGRIND_HAPPY(loop); + return 0; +} + +TEST_IMPL(pipe_write_cancel) { + return pipe_write_cancel_run(0, 0); +} + +TEST_IMPL(pipe_write_cancel_overlapped) { + return pipe_write_cancel_run(UV_NONBLOCK_PIPE, 0); +} + +TEST_IMPL(pipe_write_cancel_ipc) { + return pipe_write_cancel_run(UV_NONBLOCK_PIPE, 1); +} + +static void pipe_cancel_write_cb_all(uv_write_t* req, int status) { + write_cb_called++; + if (status == UV_ECANCELED) + cancelled_count++; +} + +static int pipe_write_cancel_all_run(int pipe_flags, int ipc) { + uv_loop_t* loop; + int i; + + loop = uv_default_loop(); + pipe_cancel_cb = pipe_cancel_write_cb_all; + pipe_cancel_setup(loop, pipe_flags, ipc); + + /* Cancel all writes. The first few may have already completed; one may be + * currently blocking in the thread pool (on Windows). Both cases are + * exercised by cancelling every request in order. */ + for (i = 0; i < REQ_COUNT; i++) + uv_cancel((uv_req_t*) &pipe_cancel_write_reqs[i]); + + /* Close the handles so the event loop can drain. */ + uv_close((uv_handle_t*) &pipe_cancel_writer, close_cb); + uv_close((uv_handle_t*) &pipe_cancel_reader, close_cb); + + ASSERT_OK(uv_run(loop, UV_RUN_DEFAULT)); + + /* Every write must have gotten a callback. */ + ASSERT_EQ(REQ_COUNT, write_cb_called); + /* We don't know exactly how many were cancelled vs already completed, + * but at least the tail end should have been cancelled. */ + ASSERT_GT(cancelled_count, 0); + ASSERT_EQ(2, close_cb_called); + + MAKE_VALGRIND_HAPPY(loop); + return 0; +} + +TEST_IMPL(pipe_write_cancel_all) { + return pipe_write_cancel_all_run(0, 0); +} + +TEST_IMPL(pipe_write_cancel_all_overlapped) { + return pipe_write_cancel_all_run(UV_NONBLOCK_PIPE, 0); +} + +TEST_IMPL(pipe_write_cancel_after_close) { + uv_buf_t buf; + uv_file fds[2]; + uv_loop_t* loop; + uv_pipe_t reader; + uv_pipe_t writer; + uv_write_t req; + + loop = uv_default_loop(); + close_cb_called = 0; + write_cb_called = 0; + cancelled_count = 0; + memset(pipe_cancel_after_close_buf_data, + 'D', + sizeof(pipe_cancel_after_close_buf_data)); + + ASSERT_OK(uv_pipe(fds, 0, 0)); + ASSERT_OK(uv_pipe_init(loop, &writer, 0)); + ASSERT_OK(uv_pipe_init(loop, &reader, 0)); + ASSERT_OK(uv_pipe_open(&writer, fds[1])); + ASSERT_OK(uv_pipe_open(&reader, fds[0])); + + buf = uv_buf_init(pipe_cancel_after_close_buf_data, + sizeof(pipe_cancel_after_close_buf_data)); + ASSERT_OK(uv_write(&req, + (uv_stream_t*) &writer, + &buf, + 1, + pipe_cancel_write_cb_all)); + + uv_close((uv_handle_t*) &writer, close_cb); + ASSERT_EQ(UV_EBUSY, uv_cancel((uv_req_t*) &req)); + uv_close((uv_handle_t*) &reader, close_cb); + + ASSERT_OK(uv_run(loop, UV_RUN_DEFAULT)); + + ASSERT_EQ(1, write_cb_called); + ASSERT_EQ(1, cancelled_count); + ASSERT_EQ(2, close_cb_called); + + MAKE_VALGRIND_HAPPY(loop); + return 0; +} + + +/* + * tcp_write_nwritten + */ +static uv_tcp_t nwritten_server; +static uv_tcp_t nwritten_client; +static uv_tcp_t nwritten_incoming; +static uv_write_t nwritten_req; +static int nwritten_cb_called; +static int nwritten_accepted; +static size_t nwritten_value; +static char nwritten_buf_data[1024]; + +/* The client's write can complete before the server's connection callback + * has run (and thus before nwritten_incoming is initialized), and vice + * versa; tear down only once both have happened. */ +static void nwritten_maybe_close(void) { + if (nwritten_cb_called == 0 || !nwritten_accepted) + return; + uv_close((uv_handle_t*) &nwritten_client, close_cb); + uv_close((uv_handle_t*) &nwritten_server, close_cb); + uv_close((uv_handle_t*) &nwritten_incoming, close_cb); +} + +static void nwritten_write_cb(uv_write_t* req, int status) { + ASSERT_OK(status); + nwritten_cb_called++; + nwritten_value = uv_write_nwritten(req); + nwritten_maybe_close(); +} + +static void nwritten_connection_cb(uv_stream_t* tcp, int status) { + ASSERT_OK(status); + ASSERT_OK(uv_tcp_init(tcp->loop, &nwritten_incoming)); + ASSERT_OK(uv_accept(tcp, (uv_stream_t*) &nwritten_incoming)); + nwritten_accepted = 1; + nwritten_maybe_close(); +} + +static void nwritten_connect_cb(uv_connect_t* req, int status) { + uv_buf_t buf; + + ASSERT_OK(status); + + buf = uv_buf_init(nwritten_buf_data, sizeof(nwritten_buf_data)); + ASSERT_OK(uv_write(&nwritten_req, req->handle, &buf, 1, nwritten_write_cb)); +} + +TEST_IMPL(tcp_write_nwritten) { + uv_connect_t connect_req; + struct sockaddr_in addr; + uv_loop_t* loop; + + loop = uv_default_loop(); + + close_cb_called = 0; + nwritten_cb_called = 0; + nwritten_accepted = 0; + nwritten_value = 0; + memset(nwritten_buf_data, 'B', sizeof(nwritten_buf_data)); + + ASSERT_OK(uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); + ASSERT_OK(uv_tcp_init(loop, &nwritten_server)); + ASSERT_OK(uv_tcp_bind(&nwritten_server, (struct sockaddr*) &addr, 0)); + ASSERT_OK(uv_listen((uv_stream_t*) &nwritten_server, 128, nwritten_connection_cb)); + + ASSERT_OK(uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); + ASSERT_OK(uv_tcp_init(loop, &nwritten_client)); + ASSERT_OK(uv_tcp_connect(&connect_req, + &nwritten_client, + (struct sockaddr*) &addr, + nwritten_connect_cb)); + + ASSERT_OK(uv_run(loop, UV_RUN_DEFAULT)); + + ASSERT_EQ(1, nwritten_cb_called); + ASSERT_EQ(sizeof(nwritten_buf_data), nwritten_value); + ASSERT_EQ(3, close_cb_called); + + MAKE_VALGRIND_HAPPY(loop); + return 0; +} + + +/* + * pipe_write_nwritten + */ +static uv_pipe_t pipe_client; +static uv_pipe_t pipe_server; +static uv_write_t pipe_write_req; +static int pipe_cb_called; +static size_t pipe_nwritten_value; +static char pipe_buf_data[1024]; + +static void pipe_write_cb(uv_write_t* req, int status) { + ASSERT_OK(status); + pipe_cb_called++; + pipe_nwritten_value = uv_write_nwritten(req); + + uv_close((uv_handle_t*) &pipe_client, close_cb); + uv_close((uv_handle_t*) &pipe_server, close_cb); +} + +TEST_IMPL(pipe_write_nwritten) { + uv_loop_t* loop; + uv_buf_t buf; + uv_file fds[2]; + + loop = uv_default_loop(); + + close_cb_called = 0; + pipe_cb_called = 0; + pipe_nwritten_value = 0; + memset(pipe_buf_data, 'C', sizeof(pipe_buf_data)); + + ASSERT_OK(uv_pipe(fds, 0, 0)); + + ASSERT_OK(uv_pipe_init(loop, &pipe_client, 0)); + ASSERT_OK(uv_pipe_init(loop, &pipe_server, 0)); + + ASSERT_OK(uv_pipe_open(&pipe_client, fds[1])); + ASSERT_OK(uv_pipe_open(&pipe_server, fds[0])); + + buf = uv_buf_init(pipe_buf_data, sizeof(pipe_buf_data)); + ASSERT_OK(uv_write(&pipe_write_req, + (uv_stream_t*) &pipe_client, + &buf, + 1, + pipe_write_cb)); + + ASSERT_OK(uv_run(loop, UV_RUN_DEFAULT)); + + ASSERT_EQ(1, pipe_cb_called); + ASSERT_EQ(sizeof(pipe_buf_data), pipe_nwritten_value); + ASSERT_EQ(2, close_cb_called); + + MAKE_VALGRIND_HAPPY(loop); + return 0; +}