diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..89297cb --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +test/fixtures/lorem_ipsum.txt text eol=lf diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 543dc9f..4253879 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -4,6 +4,9 @@ If you want to report a bug, you are in the right place! If you need help or have a question, go here: https://github.com/libuv/help/issues/new +If you are reporting a libuv test failure, please ensure that you are not +running the test as root. + Please include code that demonstrates the bug and keep it short and simple. --> * **Version**: diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000..d0ba3a4 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,24 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 21 +# Number of days of inactivity before a stale issue is closed +# Set to false to disable. If disabled, issues still need to be closed +# manually, but will remain marked as stale. +daysUntilClose: 120 +# Issues with these labels will never be considered stale +exemptLabels: + - v2 + - enhancement + - good first issue + - feature-request + - doc + - bug + - not-stale +# Label to use when marking an issue as stale +staleLabel: stale +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..008de82 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,54 @@ +name: CI + +on: [push, pull_request] + +jobs: + build-cross-qemu: + runs-on: ubuntu-latest + name: build-cross-qemu-${{ matrix.config.target }} + + strategy: + fail-fast: false + matrix: + config: + - {target: arm, toolchain: gcc-arm-linux-gnueabi, cc: arm-linux-gnueabi-gcc, qemu: qemu-arm-static } + - {target: armhf, toolchain: gcc-arm-linux-gnueabihf, cc: arm-linux-gnueabihf-gcc, qemu: qemu-arm-static } + - {target: aarch64, toolchain: gcc-aarch64-linux-gnu, cc: aarch64-linux-gnu-gcc, qemu: qemu-aarch64-static } + - {target: riscv64, toolchain: gcc-riscv64-linux-gnu, cc: riscv64-linux-gnu-gcc, qemu: qemu-riscv64-static } + - {target: ppc, toolchain: gcc-powerpc-linux-gnu, cc: powerpc-linux-gnu-gcc, qemu: qemu-ppc-static } + - {target: ppc64, toolchain: gcc-powerpc64-linux-gnu, cc: powerpc64-linux-gnu-gcc, qemu: qemu-ppc64-static } + - {target: ppc64le, toolchain: gcc-powerpc64le-linux-gnu, cc: powerpc64le-linux-gnu-gcc, qemu: qemu-ppc64le-static } + - {target: s390x, toolchain: gcc-s390x-linux-gnu, cc: s390x-linux-gnu-gcc, qemu: qemu-s390x-static } + - {target: mips, toolchain: gcc-mips-linux-gnu, cc: mips-linux-gnu-gcc, qemu: qemu-mips-static } + - {target: mips64, toolchain: gcc-mips64-linux-gnuabi64, cc: mips64-linux-gnuabi64-gcc, qemu: qemu-mips64-static } + - {target: mipsel, toolchain: gcc-mipsel-linux-gnu, cc: mipsel-linux-gnu-gcc, qemu: qemu-mipsel-static } + - {target: mips64el,toolchain: gcc-mips64el-linux-gnuabi64, cc: mips64el-linux-gnuabi64-gcc,qemu: qemu-mips64el-static } + - {target: alpha, toolchain: gcc-alpha-linux-gnu, cc: alpha-linux-gnu-gcc, qemu: qemu-alpha-static } + - {target: arm (u64 slots), toolchain: gcc-arm-linux-gnueabi, cc: arm-linux-gnueabi-gcc, qemu: qemu-arm-static} + - {target: aarch64 (u64 slots), toolchain: gcc-aarch64-linux-gnu, cc: aarch64-linux-gnu-gcc, qemu: qemu-aarch64-static} + - {target: ppc (u64 slots), toolchain: gcc-powerpc-linux-gnu, cc: powerpc-linux-gnu-gcc, qemu: qemu-ppc-static} + - {target: ppc64 (u64 slots), toolchain: gcc-powerpc64-linux-gnu, cc: powerpc64-linux-gnu-gcc, qemu: qemu-ppc64-static} + + steps: + - uses: actions/checkout@v2 + - name: Install QEMU + # this ensure install latest qemu on ubuntu, apt get version is old + env: + QEMU_SRC: "http://archive.ubuntu.com/ubuntu/pool/universe/q/qemu" + QEMU_VER: "qemu-user-static_4\\.2-.*_amd64.deb$" + run: | + DEB=`curl -s $QEMU_SRC/ | grep -o -E 'href="([^"#]+)"' | cut -d'"' -f2 | grep $QEMU_VER` + wget $QEMU_SRC/$DEB + sudo dpkg -i $DEB + - name: Install ${{ matrix.config.toolchain }} + run: | + sudo apt install ${{ matrix.config.toolchain }} -y + - name: Build + run: | + mkdir build + cd build && cmake .. -DBUILD_TESTING=ON -DQEMU=ON -DCMAKE_C_COMPILER=${{ matrix.config.cc }} + cmake --build . + ls -lh + - name: Test + run: | + ${{ matrix.config.qemu }} build/uv_run_tests_a diff --git a/.gitignore b/.gitignore index 7536abd..7eb4932 100644 --- a/.gitignore +++ b/.gitignore @@ -37,12 +37,7 @@ vgcore.* Makefile Makefile.in -# Generated by gyp for android -*.target.mk -/android-toolchain - -/out/ -/build/gyp +/build/ /test/.libs/ /test/run-tests @@ -51,6 +46,7 @@ Makefile.in /test/run-benchmarks /test/run-benchmarks.exe /test/run-benchmarks.dSYM +test_file_* *.sln *.sln.cache @@ -71,6 +67,7 @@ ipch # Clion / IntelliJ project files /.idea/ +cmake-build-debug/ *.xcodeproj *.xcworkspace diff --git a/.mailmap b/.mailmap index 5bbe5b2..2ae2968 100644 --- a/.mailmap +++ b/.mailmap @@ -17,6 +17,7 @@ Imran Iqbal Isaac Z. Schlueter Jason Williams Jesse Gorzinski +Jesse Gorzinski Justin Venus Keno Fischer Keno Fischer @@ -38,6 +39,7 @@ Sam Roberts San-Tai Hsu Santiago Gimeno Saúl Ibarra Corretgé +Saúl Ibarra Corretgé Shigeki Ohtsu Timothy J. Fontaine Yasuhiro Matsumoto @@ -45,3 +47,5 @@ Yazhong Liu Yuki Okumura jBarz jBarz +ptlomholt +zlargon diff --git a/AUTHORS b/AUTHORS index 6504800..222367e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -351,3 +351,84 @@ Jeremiah Senkpiel Andy Zhang dmabupt Ryan Liptak +Ali Ijaz Sheikh +hitesh +Svante Signell +Samuel Thibault +Jeremy Studer +damon-kwok <563066990@qq.com> +Damon Kwok +Ashe Connor +Rick +Ivan Krylov +Michael Meier +ptlomholt +Victor Costan +sid +Kevin Adler +Stephen Belanger +yeyuanfeng +erw7 +Thomas Karl Pietrowski +evgley +Andreas Rohner +Rich Trott +Milad Farazmand +zlargon +Yury Selivanov +Oscar Waddell +FX Coudert +George Zhao +Kyle Edwards +ken-cunningham-webuse +Kelvin Jin +Leorize +Vlad A +Niels Lohmann +Jenil Christo +Evgeny Ermakov +gengjiawen +Leo Chung +Javier Blazquez +Mustafa M +Zach Bjornson +Nan Xiao +Ben Davies +Nhan Khong +Crunkle +Tomas Krizek +Konstantin Podsvirov +seny +Vladimir Karnushin +MaYuming +Eneas U de Queiroz +Daniel Hahler +Yang Yu +David Carlier +Calvin Hill +Isabella Muerte <63051+slurps-mad-rips@users.noreply.github.com> +Ouyang Yadong +ZYSzys +Carl Lei +Stefan Bender +nia +virtualyw +Witold Kręcicki +Dominique Dumont +Manuel BACHMANN +Marek Vavrusa +TK-one +Irek Fakhrutdinov +Lin Zhang +毛毛 +Sk Sajidul Kadir +twosee +Rikard Falkeborn +Yash Ladha +James Ross +Colin Finck +Shohei YOSHIDA +Philip Chimento +Michal Artazov +Jeroen Roovers +MasterDuke17 diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f13efc..0496d36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,179 +1,113 @@ -# TODO: determine CMAKE_SYSTEM_NAME on OS/390. Currently assumes "OS/390". -cmake_minimum_required(VERSION 3.0) -project(libuv) -enable_testing() - -if(MSVC) - list(APPEND uv_cflags /W4) -elseif(CMAKE_C_COMPILER_ID MATCHES "AppleClang|Clang|GNU") - list(APPEND uv_cflags -fvisibility=hidden --std=gnu89) - list(APPEND uv_cflags -Wall -Wextra -Wstrict-prototypes) - list(APPEND uv_cflags -Wno-unused-parameter) +cmake_minimum_required(VERSION 3.4) +project(libuv LANGUAGES C) + +cmake_policy(SET CMP0057 NEW) # Enable IN_LIST operator +cmake_policy(SET CMP0064 NEW) # Support if (TEST) operator + +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") + +include(CMakePackageConfigHelpers) +include(CMakeDependentOption) +include(CheckCCompilerFlag) +include(GNUInstallDirs) +include(CTest) + +set(CMAKE_C_VISIBILITY_PRESET hidden) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS ON) +set(CMAKE_C_STANDARD 90) + +cmake_dependent_option(LIBUV_BUILD_TESTS + "Build the unit tests when BUILD_TESTING is enabled and we are the root project" ON + "BUILD_TESTING;CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR" OFF) +cmake_dependent_option(LIBUV_BUILD_BENCH + "Build the benchmarks when building unit tests and we are the root project" ON + "LIBUV_BUILD_TESTS" OFF) + +# Qemu Build +option(QEMU "build for qemu" OFF) +if(QEMU) + add_definitions(-D__QEMU__=1) endif() +# Compiler check +string(CONCAT is-msvc $, + $ +>) + +check_c_compiler_flag(/W4 UV_LINT_W4) +check_c_compiler_flag(/wd4100 UV_LINT_NO_UNUSED_PARAMETER_MSVC) +check_c_compiler_flag(/wd4127 UV_LINT_NO_CONDITIONAL_CONSTANT_MSVC) +check_c_compiler_flag(/wd4201 UV_LINT_NO_NONSTANDARD_MSVC) +check_c_compiler_flag(/wd4206 UV_LINT_NO_NONSTANDARD_EMPTY_TU_MSVC) +check_c_compiler_flag(/wd4210 UV_LINT_NO_NONSTANDARD_FILE_SCOPE_MSVC) +check_c_compiler_flag(/wd4232 UV_LINT_NO_NONSTANDARD_NONSTATIC_DLIMPORT_MSVC) +check_c_compiler_flag(/wd4456 UV_LINT_NO_HIDES_LOCAL) +check_c_compiler_flag(/wd4457 UV_LINT_NO_HIDES_PARAM) +check_c_compiler_flag(/wd4459 UV_LINT_NO_HIDES_GLOBAL) +check_c_compiler_flag(/wd4706 UV_LINT_NO_CONDITIONAL_ASSIGNMENT_MSVC) +check_c_compiler_flag(/wd4996 UV_LINT_NO_UNSAFE_MSVC) + +check_c_compiler_flag(-Wall UV_LINT_WALL) # DO NOT use this under MSVC + +# TODO: Place these into its own function +check_c_compiler_flag(-Wno-unused-parameter UV_LINT_NO_UNUSED_PARAMETER) +check_c_compiler_flag(-Wstrict-prototypes UV_LINT_STRICT_PROTOTYPES) +check_c_compiler_flag(-Wextra UV_LINT_EXTRA) + +set(lint-no-unused-parameter $<$:-Wno-unused-parameter>) +set(lint-strict-prototypes $<$:-Wstrict-prototypes>) +set(lint-extra $<$:-Wextra>) +set(lint-w4 $<$:/W4>) +set(lint-no-unused-parameter-msvc $<$:/wd4100>) +set(lint-no-conditional-constant-msvc $<$:/wd4127>) +set(lint-no-nonstandard-msvc $<$:/wd4201>) +set(lint-no-nonstandard-empty-tu-msvc $<$:/wd4206>) +set(lint-no-nonstandard-file-scope-msvc $<$:/wd4210>) +set(lint-no-nonstandard-nonstatic-dlimport-msvc $<$:/wd4232>) +set(lint-no-hides-local-msvc $<$:/wd4456>) +set(lint-no-hides-param-msvc $<$:/wd4457>) +set(lint-no-hides-global-msvc $<$:/wd4459>) +set(lint-no-conditional-assignment-msvc $<$:/wd4706>) +set(lint-no-unsafe-msvc $<$:/wd4996>) +# Unfortunately, this one is complicated because MSVC and clang-cl support -Wall +# but using it is like calling -Weverything +string(CONCAT lint-default $< + $,$>:-Wall +>) + +list(APPEND uv_cflags ${lint-strict-prototypes} ${lint-extra} ${lint-default} ${lint-w4}) +list(APPEND uv_cflags ${lint-no-unused-parameter}) +list(APPEND uv_cflags ${lint-no-unused-parameter-msvc}) +list(APPEND uv_cflags ${lint-no-conditional-constant-msvc}) +list(APPEND uv_cflags ${lint-no-nonstandard-msvc}) +list(APPEND uv_cflags ${lint-no-nonstandard-empty-tu-msvc}) +list(APPEND uv_cflags ${lint-no-nonstandard-file-scope-msvc}) +list(APPEND uv_cflags ${lint-no-nonstandard-nonstatic-dlimport-msvc}) +list(APPEND uv_cflags ${lint-no-hides-local-msvc}) +list(APPEND uv_cflags ${lint-no-hides-param-msvc}) +list(APPEND uv_cflags ${lint-no-hides-global-msvc}) +list(APPEND uv_cflags ${lint-no-conditional-assignment-msvc}) +list(APPEND uv_cflags ${lint-no-unsafe-msvc}) + set(uv_sources src/fs-poll.c + src/idna.c src/inet.c + src/random.c + src/strscpy.c src/threadpool.c src/timer.c src/uv-common.c src/uv-data-getter-setters.c src/version.c) -set(uv_test_sources - test/blackhole-server.c - test/echo-server.c - test/run-tests.c - test/runner.c - test/test-active.c - test/test-async-null-cb.c - test/test-async.c - test/test-barrier.c - test/test-callback-order.c - test/test-callback-stack.c - test/test-close-fd.c - test/test-close-order.c - test/test-condvar.c - test/test-connect-unspecified.c - test/test-connection-fail.c - test/test-cwd-and-chdir.c - test/test-default-loop-close.c - test/test-delayed-accept.c - test/test-dlerror.c - test/test-eintr-handling.c - test/test-embed.c - test/test-emfile.c - test/test-env-vars.c - test/test-error.c - test/test-fail-always.c - test/test-fork.c - test/test-fs-copyfile.c - test/test-fs-event.c - test/test-fs-poll.c - test/test-fs.c - test/test-get-currentexe.c - test/test-get-loadavg.c - test/test-get-memory.c - test/test-get-passwd.c - test/test-getaddrinfo.c - test/test-gethostname.c - test/test-getnameinfo.c - test/test-getsockname.c - test/test-getters-setters.c - test/test-handle-fileno.c - test/test-homedir.c - test/test-hrtime.c - test/test-idle.c - test/test-ip4-addr.c - test/test-ip6-addr.c - test/test-ip6-addr.c - test/test-ipc-heavy-traffic-deadlock-bug.c - test/test-ipc-send-recv.c - test/test-ipc.c - test/test-loop-alive.c - test/test-loop-close.c - test/test-loop-configure.c - test/test-loop-handles.c - test/test-loop-stop.c - test/test-loop-time.c - test/test-multiple-listen.c - test/test-mutexes.c - test/test-osx-select.c - test/test-pass-always.c - test/test-ping-pong.c - test/test-pipe-bind-error.c - test/test-pipe-close-stdout-read-stdin.c - test/test-pipe-connect-error.c - test/test-pipe-connect-multiple.c - test/test-pipe-connect-prepare.c - test/test-pipe-getsockname.c - test/test-pipe-pending-instances.c - test/test-pipe-sendmsg.c - test/test-pipe-server-close.c - test/test-pipe-set-fchmod.c - test/test-pipe-set-non-blocking.c - test/test-platform-output.c - test/test-poll-close-doesnt-corrupt-stack.c - test/test-poll-close.c - test/test-poll-closesocket.c - test/test-poll-oob.c - test/test-poll.c - test/test-process-priority.c - test/test-process-title-threadsafe.c - test/test-process-title.c - test/test-queue-foreach-delete.c - test/test-ref.c - test/test-run-nowait.c - test/test-run-once.c - test/test-semaphore.c - test/test-shutdown-close.c - test/test-shutdown-eof.c - test/test-shutdown-twice.c - test/test-signal-multiple-loops.c - test/test-signal.c - test/test-socket-buffer-size.c - test/test-spawn.c - test/test-stdio-over-pipes.c - test/test-tcp-alloc-cb-fail.c - test/test-tcp-bind-error.c - test/test-tcp-bind6-error.c - test/test-tcp-close-accept.c - test/test-tcp-close-while-connecting.c - test/test-tcp-close.c - test/test-tcp-connect-error-after-write.c - test/test-tcp-connect-error.c - test/test-tcp-connect-timeout.c - test/test-tcp-connect6-error.c - test/test-tcp-create-socket-early.c - test/test-tcp-flags.c - test/test-tcp-oob.c - test/test-tcp-open.c - test/test-tcp-read-stop.c - test/test-tcp-shutdown-after-write.c - test/test-tcp-try-write.c - test/test-tcp-unexpected-read.c - test/test-tcp-write-after-connect.c - test/test-tcp-write-fail.c - test/test-tcp-write-queue-order.c - test/test-tcp-write-to-half-open-connection.c - test/test-tcp-writealot.c - test/test-thread-equal.c - test/test-thread.c - test/test-threadpool-cancel.c - test/test-threadpool.c - test/test-timer-again.c - test/test-timer-from-check.c - test/test-timer.c - test/test-tmpdir.c - test/test-tty.c - test/test-udp-alloc-cb-fail.c - test/test-udp-bind.c - test/test-udp-create-socket-early.c - test/test-udp-dgram-too-big.c - test/test-udp-ipv6.c - test/test-udp-multicast-interface.c - test/test-udp-multicast-interface6.c - test/test-udp-multicast-join.c - test/test-udp-multicast-join6.c - test/test-udp-multicast-ttl.c - test/test-udp-open.c - test/test-udp-options.c - test/test-udp-send-and-recv.c - test/test-udp-send-hang-loop.c - test/test-udp-send-immediate.c - test/test-udp-send-unreachable.c - test/test-udp-try-send.c - test/test-walk-handles.c - test/test-watcher-cross-stop.c) - if(WIN32) list(APPEND uv_defines WIN32_LEAN_AND_MEAN _WIN32_WINNT=0x0600) list(APPEND uv_libraries - advapi32 - iphlpapi psapi - shell32 - user32 + iphlpapi userenv ws2_32) list(APPEND uv_sources @@ -193,7 +127,6 @@ if(WIN32) src/win/poll.c src/win/process.c src/win/process-stdio.c - src/win/req.c src/win/signal.c src/win/snprintf.c src/win/stream.c @@ -207,7 +140,12 @@ if(WIN32) list(APPEND uv_test_sources src/win/snprintf.c test/runner-win.c) else() list(APPEND uv_defines _FILE_OFFSET_BITS=64 _LARGEFILE_SOURCE) - list(APPEND uv_libraries pthread) + if(NOT CMAKE_SYSTEM_NAME MATCHES "Android|OS390") + # TODO: This should be replaced with find_package(Threads) if possible + # Android has pthread as part of its c library, not as a separate + # libpthread.so. + list(APPEND uv_libraries pthread) + endif() list(APPEND uv_sources src/unix/async.c src/unix/core.c @@ -220,6 +158,7 @@ else() src/unix/pipe.c src/unix/poll.c src/unix/process.c + src/unix/random-devurandom.c src/unix/signal.c src/unix/stream.c src/unix/tcp.c @@ -234,13 +173,16 @@ if(CMAKE_SYSTEM_NAME STREQUAL "AIX") _ALL_SOURCE _LINUX_SOURCE_COMPAT _THREAD_SAFE - _XOPEN_SOURCE=500) + _XOPEN_SOURCE=500 + HAVE_SYS_AHAFS_EVPRODS_H) list(APPEND uv_libraries perfstat) - list(APPEND uv_sources src/unix/aix.c) + list(APPEND uv_sources + src/unix/aix.c + src/unix/aix-common.c) endif() if(CMAKE_SYSTEM_NAME STREQUAL "Android") - list(APPEND uv_libs dl) + list(APPEND uv_libraries dl) list(APPEND uv_sources src/unix/android-ifaddrs.c src/unix/linux-core.c @@ -248,11 +190,13 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Android") src/unix/linux-syscalls.c src/unix/procfs-exepath.c src/unix/pthread-fixes.c - src/unix/sysinfo-loadavg.c - src/unix/sysinfo-memory.c) + src/unix/random-getentropy.c + src/unix/random-getrandom.c + src/unix/random-sysctl-linux.c + src/unix/sysinfo-loadavg.c) endif() -if(CMAKE_SYSTEM_NAME MATCHES "Android|Darwin|Linux|OS/390") +if(APPLE OR CMAKE_SYSTEM_NAME MATCHES "Android|Linux|OS390") list(APPEND uv_sources src/unix/proctitle.c) endif() @@ -261,15 +205,23 @@ if(CMAKE_SYSTEM_NAME MATCHES "DragonFly|FreeBSD") endif() if(CMAKE_SYSTEM_NAME MATCHES "DragonFly|FreeBSD|NetBSD|OpenBSD") - list(APPEND uv_sources src/unix/posix-hrtime.c) + list(APPEND uv_sources src/unix/posix-hrtime.c src/unix/bsd-proctitle.c) list(APPEND uv_libraries kvm) endif() -if(CMAKE_SYSTEM_NAME MATCHES "Darwin|DragonFly|FreeBSD|NetBSD|OpenBSD") +if(APPLE OR CMAKE_SYSTEM_NAME MATCHES "DragonFly|FreeBSD|NetBSD|OpenBSD") list(APPEND uv_sources src/unix/bsd-ifaddrs.c src/unix/kqueue.c) endif() -if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") +if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + list(APPEND uv_sources src/unix/random-getrandom.c) +endif() + +if(APPLE OR CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") + list(APPEND uv_sources src/unix/random-getentropy.c) +endif() + +if(APPLE) list(APPEND uv_defines _DARWIN_UNLIMITED_SELECT=1 _DARWIN_USE_64_BIT_INODE=1) list(APPEND uv_sources src/unix/darwin-proctitle.c @@ -285,8 +237,9 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux") src/unix/linux-inotify.c src/unix/linux-syscalls.c src/unix/procfs-exepath.c - src/unix/sysinfo-loadavg.c - src/unix/sysinfo-memory.c) + src/unix/random-getrandom.c + src/unix/random-sysctl-linux.c + src/unix/sysinfo-loadavg.c) endif() if(CMAKE_SYSTEM_NAME STREQUAL "NetBSD") @@ -297,14 +250,16 @@ if(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") list(APPEND uv_sources src/unix/openbsd.c) endif() -if(CMAKE_SYSTEM_NAME STREQUAL "OS/390") +if(CMAKE_SYSTEM_NAME STREQUAL "OS390") list(APPEND uv_defines PATH_MAX=255) list(APPEND uv_defines _AE_BIMODAL) list(APPEND uv_defines _ALL_SOURCE) + list(APPEND uv_defines _ISOC99_SOURCE) list(APPEND uv_defines _LARGE_TIME_API) list(APPEND uv_defines _OPEN_MSGQ_EXT) list(APPEND uv_defines _OPEN_SYS_FILE_EXT) list(APPEND uv_defines _OPEN_SYS_IF_EXT) + list(APPEND uv_defines _OPEN_SYS_SOCK_EXT3) list(APPEND uv_defines _OPEN_SYS_SOCK_IPV6) list(APPEND uv_defines _UNIX03_SOURCE) list(APPEND uv_defines _UNIX03_THREADS) @@ -312,9 +267,25 @@ if(CMAKE_SYSTEM_NAME STREQUAL "OS/390") list(APPEND uv_defines _XOPEN_SOURCE_EXTENDED) list(APPEND uv_sources src/unix/pthread-fixes.c - src/unix/pthread-barrier.c src/unix/os390.c src/unix/os390-syscalls.c) + list(APPEND uv_cflags -Wc,DLL -Wc,exportall -Wc,xplink) + list(APPEND uv_libraries -Wl,xplink) + list(APPEND uv_test_libraries -Wl,xplink) +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "OS400") + list(APPEND uv_defines + _ALL_SOURCE + _LINUX_SOURCE_COMPAT + _THREAD_SAFE + _XOPEN_SOURCE=500) + list(APPEND uv_sources + src/unix/aix-common.c + src/unix/ibmi.c + src/unix/no-fsevents.c + src/unix/no-proctitle.c + src/unix/posix-poll.c) endif() if(CMAKE_SYSTEM_NAME STREQUAL "SunOS") @@ -323,37 +294,248 @@ if(CMAKE_SYSTEM_NAME STREQUAL "SunOS") list(APPEND uv_sources src/unix/no-proctitle.c src/unix/sunos.c) endif() -if(CMAKE_SYSTEM_NAME MATCHES "Darwin|DragonFly|FreeBSD|Linux|NetBSD|OpenBSD") +if(APPLE OR CMAKE_SYSTEM_NAME MATCHES "DragonFly|FreeBSD|Linux|NetBSD|OpenBSD") list(APPEND uv_test_libraries util) endif() add_library(uv SHARED ${uv_sources}) -target_compile_definitions(uv PRIVATE ${uv_defines} BUILDING_UV_SHARED=1) +target_compile_definitions(uv + INTERFACE + USING_UV_SHARED=1 + PRIVATE + BUILDING_UV_SHARED=1 + ${uv_defines}) target_compile_options(uv PRIVATE ${uv_cflags}) -target_include_directories(uv PRIVATE include src) +target_include_directories(uv + PUBLIC + $ + $ + PRIVATE + $) target_link_libraries(uv ${uv_libraries}) add_library(uv_a STATIC ${uv_sources}) target_compile_definitions(uv_a PRIVATE ${uv_defines}) target_compile_options(uv_a PRIVATE ${uv_cflags}) -target_include_directories(uv_a PRIVATE include src) +target_include_directories(uv_a + PUBLIC + $ + $ + PRIVATE + $) target_link_libraries(uv_a ${uv_libraries}) -if(BUILD_TESTING) - include(CTest) - add_executable(uv_run_tests ${uv_test_sources}) - target_compile_definitions(uv_run_tests PRIVATE ${uv_defines}) +if(LIBUV_BUILD_TESTS) + # Small hack: use ${uv_test_sources} now to get the runner skeleton, + # before the actual tests are added. + add_executable( + uv_run_benchmarks_a + ${uv_test_sources} + test/benchmark-async-pummel.c + test/benchmark-async.c + test/benchmark-fs-stat.c + test/benchmark-getaddrinfo.c + test/benchmark-loop-count.c + test/benchmark-million-async.c + test/benchmark-million-timers.c + test/benchmark-multi-accept.c + test/benchmark-ping-pongs.c + test/benchmark-ping-udp.c + test/benchmark-pound.c + test/benchmark-pump.c + test/benchmark-sizes.c + test/benchmark-spawn.c + test/benchmark-tcp-write-batch.c + test/benchmark-thread.c + test/benchmark-udp-pummel.c + test/blackhole-server.c + test/dns-server.c + test/echo-server.c + test/run-benchmarks.c + test/runner.c) + target_compile_definitions(uv_run_benchmarks_a PRIVATE ${uv_defines}) + target_compile_options(uv_run_benchmarks_a PRIVATE ${uv_cflags}) + target_link_libraries(uv_run_benchmarks_a uv_a ${uv_test_libraries}) + + list(APPEND uv_test_sources + test/blackhole-server.c + test/echo-server.c + test/run-tests.c + test/runner.c + test/test-active.c + test/test-async-null-cb.c + test/test-async.c + test/test-barrier.c + test/test-callback-order.c + test/test-callback-stack.c + test/test-close-fd.c + test/test-close-order.c + test/test-condvar.c + test/test-connect-unspecified.c + test/test-connection-fail.c + test/test-cwd-and-chdir.c + test/test-default-loop-close.c + test/test-delayed-accept.c + test/test-dlerror.c + test/test-eintr-handling.c + test/test-embed.c + test/test-emfile.c + test/test-env-vars.c + test/test-error.c + test/test-fail-always.c + test/test-fork.c + test/test-fs-copyfile.c + test/test-fs-event.c + test/test-fs-poll.c + test/test-fs.c + test/test-fs-readdir.c + test/test-fs-fd-hash.c + test/test-fs-open-flags.c + test/test-get-currentexe.c + test/test-get-loadavg.c + test/test-get-memory.c + test/test-get-passwd.c + test/test-getaddrinfo.c + test/test-gethostname.c + test/test-getnameinfo.c + test/test-getsockname.c + test/test-getters-setters.c + test/test-gettimeofday.c + test/test-handle-fileno.c + test/test-homedir.c + test/test-hrtime.c + test/test-idle.c + test/test-idna.c + test/test-ip4-addr.c + test/test-ip6-addr.c + test/test-ipc-heavy-traffic-deadlock-bug.c + test/test-ipc-send-recv.c + test/test-ipc.c + test/test-loop-alive.c + test/test-loop-close.c + test/test-loop-configure.c + test/test-loop-handles.c + test/test-loop-stop.c + test/test-loop-time.c + test/test-multiple-listen.c + test/test-mutexes.c + test/test-osx-select.c + test/test-pass-always.c + test/test-ping-pong.c + test/test-pipe-bind-error.c + test/test-pipe-close-stdout-read-stdin.c + test/test-pipe-connect-error.c + test/test-pipe-connect-multiple.c + test/test-pipe-connect-prepare.c + test/test-pipe-getsockname.c + test/test-pipe-pending-instances.c + test/test-pipe-sendmsg.c + test/test-pipe-server-close.c + test/test-pipe-set-fchmod.c + test/test-pipe-set-non-blocking.c + test/test-platform-output.c + test/test-poll-close-doesnt-corrupt-stack.c + test/test-poll-close.c + test/test-poll-closesocket.c + test/test-poll-oob.c + test/test-poll.c + test/test-process-priority.c + test/test-process-title-threadsafe.c + test/test-process-title.c + test/test-queue-foreach-delete.c + test/test-random.c + test/test-ref.c + test/test-run-nowait.c + test/test-run-once.c + test/test-semaphore.c + test/test-shutdown-close.c + test/test-shutdown-eof.c + test/test-shutdown-twice.c + test/test-signal-multiple-loops.c + test/test-signal-pending-on-close.c + test/test-signal.c + test/test-socket-buffer-size.c + test/test-spawn.c + test/test-stdio-over-pipes.c + test/test-strscpy.c + test/test-tcp-alloc-cb-fail.c + test/test-tcp-bind-error.c + test/test-tcp-bind6-error.c + test/test-tcp-close-accept.c + test/test-tcp-close-while-connecting.c + test/test-tcp-close.c + test/test-tcp-close-reset.c + test/test-tcp-connect-error-after-write.c + test/test-tcp-connect-error.c + test/test-tcp-connect-timeout.c + test/test-tcp-connect6-error.c + test/test-tcp-create-socket-early.c + test/test-tcp-flags.c + test/test-tcp-oob.c + test/test-tcp-open.c + test/test-tcp-read-stop.c + test/test-tcp-shutdown-after-write.c + test/test-tcp-try-write.c + test/test-tcp-try-write-error.c + test/test-tcp-unexpected-read.c + test/test-tcp-write-after-connect.c + test/test-tcp-write-fail.c + test/test-tcp-write-queue-order.c + test/test-tcp-write-to-half-open-connection.c + test/test-tcp-writealot.c + test/test-thread-equal.c + test/test-thread.c + test/test-threadpool-cancel.c + test/test-threadpool.c + test/test-timer-again.c + test/test-timer-from-check.c + test/test-timer.c + test/test-tmpdir.c + test/test-tty-duplicate-key.c + test/test-tty-escape-sequence-processing.c + test/test-tty.c + test/test-udp-alloc-cb-fail.c + test/test-udp-bind.c + test/test-udp-connect.c + test/test-udp-create-socket-early.c + test/test-udp-dgram-too-big.c + test/test-udp-ipv6.c + test/test-udp-multicast-interface.c + test/test-udp-multicast-interface6.c + test/test-udp-multicast-join.c + test/test-udp-multicast-join6.c + test/test-udp-multicast-ttl.c + test/test-udp-open.c + test/test-udp-options.c + test/test-udp-send-and-recv.c + test/test-udp-send-hang-loop.c + test/test-udp-send-immediate.c + test/test-udp-send-unreachable.c + test/test-udp-try-send.c + test/test-uname.c + test/test-walk-handles.c + test/test-watcher-cross-stop.c) + + add_executable(uv_run_tests ${uv_test_sources} uv_win_longpath.manifest) + target_compile_definitions(uv_run_tests + PRIVATE ${uv_defines} USING_UV_SHARED=1) target_compile_options(uv_run_tests PRIVATE ${uv_cflags}) - target_include_directories(uv_run_tests PRIVATE include) target_link_libraries(uv_run_tests uv ${uv_test_libraries}) add_test(NAME uv_test COMMAND uv_run_tests WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) - add_executable(uv_run_tests_a ${uv_test_sources}) + if(CMAKE_SYSTEM_NAME STREQUAL "OS390") + set_tests_properties(uv_test PROPERTIES ENVIRONMENT + "LIBPATH=${CMAKE_BINARY_DIR}:$ENV{LIBPATH}") + endif() + add_executable(uv_run_tests_a ${uv_test_sources} uv_win_longpath.manifest) target_compile_definitions(uv_run_tests_a PRIVATE ${uv_defines}) target_compile_options(uv_run_tests_a PRIVATE ${uv_cflags}) - target_include_directories(uv_run_tests_a PRIVATE include) - target_link_libraries(uv_run_tests_a uv_a ${uv_test_libraries}) + if(QEMU) + target_link_libraries(uv_run_tests_a uv_a ${uv_test_libraries} -static) + else() + target_link_libraries(uv_run_tests_a uv_a ${uv_test_libraries}) + endif() add_test(NAME uv_test_a COMMAND uv_run_tests_a WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) @@ -361,21 +543,43 @@ endif() if(UNIX) # Now for some gibbering horrors from beyond the stars... - include(GNUInstallDirs) - foreach(x ${uv_libraries}) - set(LIBS "${LIBS} -l${x}") - endforeach(x) + foreach(lib IN LISTS uv_libraries) + list(APPEND LIBS "-l${lib}") + endforeach() + string(REPLACE ";" " " LIBS "${LIBS}") + # Consider setting project version via project() call? file(STRINGS configure.ac configure_ac REGEX ^AC_INIT) - string(REGEX MATCH [0-9]+[.][0-9]+[.][0-9]+ PACKAGE_VERSION "${configure_ac}") + string(REGEX MATCH "([0-9]+)[.][0-9]+[.][0-9]+" PACKAGE_VERSION "${configure_ac}") + set(UV_VERSION_MAJOR "${CMAKE_MATCH_1}") + # The version in the filename is mirroring the behaviour of autotools. + set_target_properties(uv PROPERTIES + VERSION ${UV_VERSION_MAJOR}.0.0 + SOVERSION ${UV_VERSION_MAJOR}) set(includedir ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}) set(libdir ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}) set(prefix ${CMAKE_INSTALL_PREFIX}) - configure_file(libuv.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libuv.pc @ONLY) + configure_file(libuv.pc.in libuv.pc @ONLY) install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR}) - install(FILES LICENSE ${CMAKE_CURRENT_BINARY_DIR}/libuv.pc + install(FILES ${PROJECT_BINARY_DIR}/libuv.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) install(TARGETS uv LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) install(TARGETS uv_a ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif() + +if(WIN32) + install(DIRECTORY include/ DESTINATION include) + install(FILES LICENSE DESTINATION .) + install(TARGETS uv uv_a + RUNTIME DESTINATION lib/$ + ARCHIVE DESTINATION lib/$) +endif() + +message(STATUS "summary of build options: + Install prefix: ${CMAKE_INSTALL_PREFIX} + Target system: ${CMAKE_SYSTEM_NAME} + Compiler: + C compiler: ${CMAKE_C_COMPILER} + CFLAGS: ${CMAKE_C_FLAGS_${_build_type}} ${CMAKE_C_FLAGS} +") diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f22e124..b0abe3b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,11 +48,11 @@ the [Google C/C++ style guide]. Some of the key points, as well as some additional guidelines, are enumerated below. * Code that is specific to unix-y platforms should be placed in `src/unix`, and - declarations go into `include/uv-unix.h`. + declarations go into `include/uv/unix.h`. * Source code that is Windows-specific goes into `src/win`, and related publicly exported types, functions and macro declarations should generally - be declared in `include/uv-win.h`. + be declared in `include/uv/win.h`. * Names should be descriptive and concise. @@ -142,7 +142,6 @@ Bug fixes and features should come with tests. Add your tests in the If you add a new test file, it needs to be registered in three places: - `CMakeLists.txt`: add the file's name to the `uv_test_sources` list. - `Makefile.am`: add the file's name to the `test_run_tests_SOURCES` list. -- `uv.gyp`: add the file's name to the `sources` list in the `run-tests` target. Look at other tests to see how they should be structured (license boilerplate, the way entry points are declared, etc.). diff --git a/ChangeLog b/ChangeLog index 7b67e66..b5e8684 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,995 @@ -2018.09.22, Version 1.23.1 (Stable) +2020.05.18, Version 1.38.0 (Stable) + +Changes since version 1.37.0: + +* test: skip poll_duplex and poll_unidirectional on PASE (Xu Meng) + +* linux: make cpu_times consistently be milliseconds (James Ross) + +* win: DRY uv_poll_start() and uv_poll_stop() (Ben Noordhuis) + +* win: DRY uv_poll_close() (Ben Noordhuis) + +* unix,win: add uv_library_shutdown() (Ben Noordhuis) + +* unix: yield cpu when spinlocking on async handle (Ben Noordhuis) + +* win: remove dep on GetQueuedCompletionStatusEx (Colin Finck) + +* doc: correct source lines (Shohei YOSHIDA) + +* build,android: fix typo (twosee) + +* doc: uv_cancel() handles uv_random_t requests (Philip Chimento) + +* doc: fix unescaped character (Philip Chimento) + +* build,cmake: fix compilation on old MinGW (erw7) + +* build: remove unnessesary MSVC warnings (Bartosz Sosnowski) + +* win: make uv_udp_init_ex() accept UV_UDP_RECVMMSG (Ben Noordhuis) + +* unix: simplify uv__udp_init_ex() (Ben Noordhuis) + +* win: remove MAX_PATH limitations (Bartosz Sosnowski) + +* build, win: add long path aware manifest (Bartosz Sosnowski) + +* doc: check/idle/prepare functions always succeed (Ben Noordhuis) + +* darwin: fix build with non-apple compilers (Ben Noordhuis) + +* win: support environment variables > 32767 chars (Ben Noordhuis) + +* unix: fully initialize struct msghdr (Ben Noordhuis) + +* doc: add uv_replace_allocator thread safety warning (twosee) + +* unix: fix int overflow when copying large files (Michal Artazov) + +* fs: report original error (Bartosz Sosnowski) + +* win, fs: add IO_REPARSE_TAG_APPEXECLINK support (Bartosz Sosnowski) + +* doc: fix formatting (Ben Noordhuis) + +* unix: fix memory leak when uv_loop_init() fails (Anna Henningsen) + +* unix: shrink uv_udp_set_source_membership() stack (Ben Noordhuis) + +* unix,win: fix wrong sizeof argument to memcpy() (Ben Noordhuis) + +* build: check for libraries not provided by libc (Jeroen Roovers) + +* doc: fix the order of arguments to calloc() (MasterDuke17) + +* unix: don't abort when getrlimit() fails (Ben Noordhuis) + +* test: support common user profile on IBMi (Xu Meng) + +* build: test on more platforms via QEMU in CI (gengjiawen) + + +2020.04.20, Version 1.37.0 (Stable), 02a9e1be252b623ee032a3137c0b0c94afbe6809 + +Changes since version 1.36.0: + +* timer: remove redundant check in heap compare (Yash Ladha) + +* udp: add flag to enable recvmmsg(2) explicitly (Saúl Ibarra Corretgé) + + +2020.04.16, Version 1.36.0 (Stable), 533b738838ad8407032e14b6772b29ef9af63cfa + +Changes since version 1.35.0: + +* build: add aix-common.c for AIX cmake build (Jesse Gorzinski) + +* zos: explicitly mark message queue events (Irek Fakhrutdinov) + +* zos: move mq check out of loop to save cpu cycles (Irek Fakhrutdinov) + +* zos: add checks to ensure behavior of epoll_wait (Irek Fakhrutdinov) + +* src: add uv__reallocf() (Ben Noordhuis) + +* build: ibmi support for cmake (Jesse Gorzinski) + +* build: fix gyp build for Android API >= 28 (Lin Zhang) + +* udp: return recvmmsg-ed datagrams in order (Saúl Ibarra Corretgé) + +* zos,test: fix spawn_empty_env for shared library build (Richard Lau) + +* zos: fix non-Release builds (Richard Lau) + +* zos: fix return value on expired nanosleep() call (Richard Lau) + +* build: fix z/OS cmake build (Richard Lau) + +* test: add a bunch of ASSERT macros (Santiago Gimeno) + +* test: remove unused extern declaration (Ben Noordhuis) + +* test: canonicalize argv[0] in exepath test (Ben Noordhuis) + +* test: simplify platform_init() (Ben Noordhuis) + +* ibmi: Fix isatty EBADF handling and refactor (Kevin Adler) + +* test: Test EBADF tty handling (Kevin Adler) + +* build: make cmake build benchmarks (Ben Noordhuis) + +* win: use RtlGenRandom from advapi32.dll directly (Ben Noordhuis) + +* android: fix OOB write in uv_interface_addresses() (Lin Zhang) + +* test: pass test when hostname is single character (毛毛) + +* ibmi: set the highest process priority to -10 (Xu Meng) + +* build: remove support for gyp (Ben Noordhuis) + +* doc: add note to README on cross-compiling (Ben Noordhuis) + +* fs: add uv_fs_lutime() (Sk Sajidul Kadir) + +* unix: implement cpu_relax() for arm (David Carlier) + +* linux: fix uv__accept4() (twosee) + +* win: handle file paths in uv_fs_statfs() (erw7) + +* unix: fix uv_os_environ() null pointer check (Rikard Falkeborn) + +* win: fix uv_os_environ() null pointer check (Rikard Falkeborn) + +* unix: fix compilation on macOS 32-bit architectures (Brad King) + +* win: replace alloca() with stack-based array (Ben Noordhuis) + + +2020.03.12, Version 1.35.0 (Stable), e45f1ec38db882f8dc17b51f51a6684027034609 + +Changes since version 1.34.2: + +* src: android build fix (David Carlier) + +* build: make code compilable for iOS on Xcode (ssrlive) + +* ibmi: skip unsupported fs test cases (Xu Meng) + +* ibmi: ensure that pipe backlog is not zero (Xu Meng) + +* test,udp6: fix udp_ipv6 test flakiness (Jameson Nash) + +* test: fix fs_event_watch_dir_recursive flakiness (Santiago Gimeno) + +* pipe: disallow listening on an IPC pipe (Witold Kręcicki) + +* build,cmake: improve buil experience (Isabella Muerte) + +* unix: remove support for FreeBSD < 10 (Saúl Ibarra Corretgé) + +* linux: simplify uv__accept() (Ben Noordhuis) + +* linux: assume presence of SOCK_CLOEXEC flag (Ben Noordhuis) + +* linux: simplify uv__dup2_cloexec() (Ben Noordhuis) + +* freebsd,linux: simplify uv__make_socketpair() (Ben Noordhuis) + +* unix: fix error handling in uv__make_socketpair() (Ben Noordhuis) + +* freebsd,linux: simplify uv__make_pipe() (Ben Noordhuis) + +* unix: fix error handling in uv__make_pipe() (Ben Noordhuis) + +* linux: simplify uv__async_eventfd() (Ben Noordhuis) + +* linux: assume the presence of inotify system calls (Ben Noordhuis) + +* doc: strip ICC profile from 2 jpg files (Dominique Dumont) + +* unix: make uv_tcp_keepalive predictable (Manuel BACHMANN) + +* docs: uv_setup_args() may take ownership of argv (Ben Noordhuis) + +* unix: fix error path in uv_setup_args() (Ben Noordhuis) + +* unix: fix size check in uv_get_process_title() (Ben Noordhuis) + +* doc: add erw7 to maintainers (erw7) + +* test: fixed udp4_echo_server implementation (Marek Vavrusa) + +* test: added udp ping benchmark (1,10,100 pingers) (Marek Vavrusa) + +* freebsd,linux: add recvmmsg() + sendmmsg() udp implementation (Marek Vavrusa) + +* win,pipe: DRY/simplify some code paths (Jameson Nash) + +* win: address some style nits (Jameson Nash) + +* win,pipe: ensure `req->event_handle` is defined (Elliot Saba) + +* win,pipe: consolidate overlapped initialization (Elliot Saba) + +* win,pipe: erase event_handle after deleting pointer (Jameson Nash) + +* build: fix android cmake build, build missing file (Ben Noordhuis) + +* test: skip some UDP tests on IBMi (Xu Meng) + +* test: skip some spawn test cases on IBMi (Xu Meng) + +* src: fix wrong method name in comment (TK-one) + +* test: add UV_TIMEOUT_MULTIPLIER environment var (Ben Noordhuis) + +* unix: fix uv_cpu_info always returning UV_ENOTDIR on OpenBSD (Ben Davies) + +* test: skip the pwd_shell test on IBMi (Xu Meng) + +* win,tty: Change to restore cursor shape with uv_tty_reset() (erw7) + +* win,tty: Added set cursor style to CSI sequences (erw7) + +* test: handle EINTR, fix EOF check in poll test (Ben Noordhuis) + +* unix: use socklen_t instead of size_t (Ben Noordhuis) + +* doc: fix header file location (TK-one) + +* unix: fix signal handle closing deferral (Ben Noordhuis) + +* ibmi: set the amount of memory in use to zero (Xu Meng) + +* zos: return on realloc failure in scandir() (Milad Farazmand) + +* zos: fix scandir() error path NULL pointer deref (Ben Noordhuis) + + +2020.01.24, Version 1.34.2 (Stable), f868c9ab0c307525a16fff99fd21e32a6ebc3837 + +Changes since version 1.34.1: + +* misc: adjust stalebot deadlines (Jameson Nash) + +* test: fix env-vars flakiness (cjihrig) + +* test: avoid truncating output lines (Jameson Nash) + +* darwin: stop calling SetApplicationIsDaemon() (Ben Noordhuis) + +* ibmi: implement uv_interface_addresses() (Xu Meng) + +* osx,fsevent: fix race during uv_loop_close (Jameson Nash) + +* osx,fsevent: clear pointer when deleting it [NFCI] (Jameson Nash) + +* Revert "aix: replace ECONNRESET with EOF if already closed" (Jameson Nash) + +* unix: handle uv__open_cloexec return value correctly (Anna Henningsen) + + +2020.01.13, Version 1.34.1 (Stable), 8aa5636ec72990bb2856f81e14c95813024a5c2b + +Changes since version 1.34.0: + +* unix: fix -Wstrict-aliasing compiler warning (Ben Noordhuis) + +* unix: cache address of dlsym("mkostemp") (Ben Noordhuis) + +* build: remove -pedantic from compiler flags (Ben Noordhuis) + +* Revert "darwin: assume pthread_setname_np() is available" (Ben Noordhuis) + +* Revert "darwin: speed up uv_set_process_title()" (Ben Noordhuis) + +* darwin: assume pthread_setname_np() is available (Ben Noordhuis) + +* ibmi: fix the false isatty() issue on IBMi (Xu Meng) + +* test: fix test failure under NetBSD and OpenBSD (David Carlier) + +* test: skip some test cases on IBMi (Xu Meng) + +* test: skip uv_(get|set)_process_title on IBMi (Xu Meng) + +* doc: remove binaries for Windows from README (Richard Lau) + +* unix: fix -Wunused-but-set-variable warning (George Zhao) + +* unix: pass sysctl size arg using ARRAY_SIZE macro (David Carlier) + +* test: disallow running the test suite as root (cjihrig) + +* unix: suppress -Waddress-of-packed-member warning (Ben Noordhuis) + +* misc: make more tags "not-stale" (Jameson Nash) + +* test: fix pthread memory leak (Trevor Norris) + +* docs: delete socks5-proxy sample (Jameson Nash) + +* ibmi: fix the CMSG length issue (Xu Meng) + +* docs: fix formatting (Jameson Nash) + +* unix: squelch fchmod() EPERM on CIFS share (Ben Noordhuis) + +* docs: fix linkcheck (Jameson Nash) + +* docs: switch from linux.die.net to man7.org (Jameson Nash) + +* win: remove abort when non-IFS LSP detection fails (virtualyw) + +* docs: clarify that uv_pipe_t is a pipe (Jameson Nash) + +* win,tty: avoid regressions in utf-8 handling (Jameson Nash) + +* win: remove bad assert in uv_loop_close (Jameson Nash) + +* test: fix -fno-common build errors (Ben Noordhuis) + +* build: turn on -fno-common to catch regressions (Ben Noordhuis) + +* test: fix fs birth time test failure (Ben Noordhuis) + +* tty,unix: avoid affecting controlling TTY (Jameson Nash) + + +2019.12.05, Version 1.34.0 (Stable), 15ae750151ac9341e5945eb38f8982d59fb99201 + +Changes since version 1.33.1: + +* unix: move random-sysctl to random-sysctl-linux (nia) + +* netbsd: use KERN_ARND sysctl to get entropy (nia) + +* unix: refactor uv__fs_copyfile() logic (cjihrig) + +* build: fix android build, add missing sources (Ben Noordhuis) + +* build: fix android build, fix symbol redefinition (Ben Noordhuis) + +* build: fix android autotools build (Ben Noordhuis) + +* fs: handle non-functional statx system call (Milad Farazmand) + +* unix,win: add uv_sleep() (cjihrig) + +* doc: add richardlau to maintainers (Richard Lau) + +* aix: fix netmask for IPv6 (Richard Lau) + +* aix: clean up after errors in uv_interface_addresses() (Richard Lau) + +* aix: fix setting of physical addresses (Richard Lau) + +* fs: add uv_fs_mkstemp (Saúl Ibarra Corretgé) + +* unix: switch uv_sleep() to nanosleep() (Ben Noordhuis) + +* unix: retry on EINTR in uv_sleep() (Ben Noordhuis) + +* zos: fix nanosleep() emulation (Ben Noordhuis) + + +2019.10.20, Version 1.33.1 (Stable), 07ad32138f4d2285ba2226b5e20462b27b091a59 + +Changes since version 1.33.0: + +* linux: fix arm64 SYS__sysctl build breakage (Ben Noordhuis) + + +2019.10.17, Version 1.33.0 (Stable), e56e42e9310e4437e1886dbd6771792c14c0a5f3 + +Changes since version 1.32.0: + +* Revert "linux: drop code path for epoll_pwait-less kernels" (Yang Yu) + +* build: fix build error with __ANDROID_API__ < 21 (Yang Yu) + +* win: fix reading hidden env vars (Anna Henningsen) + +* unix,win: add uv_random() (Ben Noordhuis) + +* win: simplify mkdtemp (Saúl Ibarra Corretgé) + +* docs: fix literal-includes in User Guide (Nhan Khong) + +* win, tty: fix problem of receiving unexpected SIGWINCH (erw7) + +* unix: fix {Net,Open}BSD build (David Carlier) + +* win,mingw: Fix undefined MCAST_* constants (Crunkle) + +* build: Add link for test/fixtures/lorem_ipsum.txt (Andrew Paprocki) + +* fs: use statvfs in uv__fs_statfs() for Haiku (Calvin Hill) + +* fsevents: stop using fsevents to watch files (Jameson Nash) + +* fsevents: regression in watching / (Jameson Nash) + +* build,cmake: don't try to detect a C++ compiler (Isabella Muerte) + +* build: fix build warning on cygwin (MaYuming) + +* unix: set sin_len and sin6_len (Ouyang Yadong) + +* test: fix order of operations in test (cjihrig) + +* doc: improve uv_fs_readdir() cleanup docs (cjihrig) + +* build: remove duplicated test in build files (ZYSzys) + +* android: enable getentropy on Android >= 28 (David Carlier) + +* android: fix build (David Carlier) + +* darwin: speed up uv_set_process_title() (Ben Noordhuis) + +* darwin: assume pthread_setname_np() is available (Ben Noordhuis) + +* unix,udp: ensure addr is non-null (Jameson Nash) + +* win,tty: add uv_tty_{get,set}_vterm_state (erw7) + +* win: fix uv_statfs_t leak in uv_fs_statfs() (Ryan Liptak) + +* build: install files on windows via cmake (Carl Lei) + +* darwin,test: include AvailabilityMacros.h (Saúl Ibarra Corretgé) + +* darwin,test: update loop time after sleeping (Saúl Ibarra Corretgé) + +* doc: remove old FreeBSD 9 related note (Saúl Ibarra Corretgé) + +* doc: improve uv_{send,recv}_buffer_size() docs (Ryan Liptak) + +* build: move -Wno-long-long check to configure time (Ben Noordhuis) + +* unix: update uv_fs_copyfile() fallback logic (Stefan Bender) + +* win: cast setsockopt struct to const char* (Shelley Vohr) + + +2019.09.10, Version 1.32.0 (Stable), 697bea87b3a0b0e9b4e5ff86b39d1dedb70ee46d + +Changes since version 1.31.0: + +* misc: enable stalebot (Saúl Ibarra Corretgé) + +* win: map ERROR_ENVVAR_NOT_FOUND to UV_ENOENT (cjihrig) + +* win: use L'\0' as UTF-16 null terminator (cjihrig) + +* win: support retrieving empty env variables (cjihrig) + +* unix,stream: fix returned error codes (Santiago Gimeno) + +* test: fix typo in DYLD_LIBRARY_PATH (Ben Noordhuis) + +* unix,signal: keep handle active if pending signal (Santiago Gimeno) + +* openbsd: fix uv_cpu_info (Santiago Gimeno) + +* src: move uv_free_cpu_info to uv-common.c (Santiago Gimeno) + +* tcp: add uv_tcp_close_reset method (Santiago Gimeno) + +* test: fix udp-multicast-join tests (Santiago Gimeno) + +* test: remove assertion in fs_statfs test (cjihrig) + +* doc: clarify uv_buf_t usage in uv_alloc_cb (Tomas Krizek) + +* win: fix typo in preprocessor expression (Konstantin Podsvirov) + +* timer: fix uv_timer_start on closing timer (seny) + +* udp: add source-specific multicast support (Vladimir Karnushin) + +* udp: fix error return values (Santiago Gimeno) + +* udp: drop IPV6_SSM_SUPPORT macro (Santiago Gimeno) + +* udp: fix uv__udp_set_source_membership6 (Santiago Gimeno) + +* udp: use sockaddr_storage instead of union (Santiago Gimeno) + +* build,zos: add _OPEN_SYS_SOCK_EXT3 flag (Santiago Gimeno) + +* test: add specific source multicast tests (Santiago Gimeno) + +* include: map EILSEQ error code (cjihrig) + +* win, tty: improve SIGWINCH performance (Bartosz Sosnowski) + +* build: fix ios build error (MaYuming) + +* aix: replace ECONNRESET with EOF if already closed (Milad Farazmand) + +* build: add cmake library VERSION, SOVERSION (Eneas U de Queiroz) + +* build: make include/ public in CMakeLists.txt (Ben Noordhuis) + +* build: export USING_UV_SHARED=1 to cmake deps (Ben Noordhuis) + +* build: cmake_minimum_required(VERSION 2.8.12) (Daniel Hahler) + +* aix: Fix broken cmpxchgi() XL C++ specialization. (Andrew Paprocki) + +* test: fix -Wsign-compare warning (Ben Noordhuis) + +* unix: simplify open(O_CLOEXEC) feature detection (Ben Noordhuis) + +* unix: fix UV_FS_O_DIRECT definition on Linux (Joran Dirk Greef) + +* doc: uv_handle_t documentation suggestion (Daniel Bevenius) + + +2019.08.10, Version 1.31.0 (Stable), 0a6771cee4c15184c924bfe9d397bdd0c3b206ba + +Changes since version 1.30.1: + +* win,fs: don't modify global file translation mode (Javier Blazquez) + +* win: fix uv_os_tmpdir when env var is 260 chars (Mustafa M) + +* win: prevent tty event explosion machine hang (Javier Blazquez) + +* win: add UV_FS_O_FILEMAP (João Reis) + +* win, fs: mkdir return UV_EINVAL for invalid names (Bartosz Sosnowski) + +* github: add root warning to template (cjihrig) + +* win: misc fs cleanup (cjihrig) + +* unix,win: add uv_fs_statfs() (cjihrig) + +* test: avoid AF_LOCAL (Carlo Marcelo Arenas Belón) + +* unix,win: add ability to retrieve all env variables (Saúl Ibarra Corretgé) + +* Revert "darwin: speed up uv_set_process_title()" (Ben Noordhuis) + +* doc: add %p to valgrind log-file arg (Zach Bjornson) + +* doc: fix typo in basics.rst (Nan Xiao) + +* ibmi: support Makefile build for IBM i (Xu Meng) + +* OpenBSD: only get active CPU core count (Ben Davies) + +* test: fix gcc 8 warnings for tests (Nhan Khong) + +* ibmi: use correct header files (Xu Meng) + +* unix: clear UV_HANDLE_READING flag before callback (zyxwvu Shi) + +* unix: fix unused-function warning on BSD (Nhan Khong) + +* test: fix test runner on MinGW (Crunkle) + +* win: remove try-except outside MSVC (Crunkle) + +* win: fix uv_spawn() ENOMEM on empty env (Ben Noordhuis) + + +2019.07.03, Version 1.30.1 (Stable), 1551969c84c2f546a429dac169c7fdac3e38115e + +Changes since version 1.30.0: + +* doc: fix incorrect versionchanged (cjihrig) + +* test: allow UV_ECONNRESET in tcp_try_write_error (cjihrig) + +* unix: add uv_get_constrained_memory() cygwin stub (cjihrig) + +* build: fix android cmake build (Ben Noordhuis) + +* unix: squelch -Wcast-function-type warning (Ben Noordhuis) + +* build: fix compile error with uClibc (zlargon) + + +2019.06.28, Version 1.30.0 (Stable), 365b6f2a0eacda1ff52be8e57ab9381cfddc5dbb + +Changes since version 1.29.1: + +* darwin: fall back to F_BARRIERFSYNC (Ben Noordhuis) + +* darwin: add 32 bit close$NOCANCEL implementation (ken-cunningham-webuse) + +* build, core, unix: add support for Haiku (Leorize) + +* darwin,linux: more conservative minimum stack size (Ben Noordhuis) + +* threadpool: increase UV_THREADPOOL_SIZE limit (Vlad A) + +* unix: return actual error from `uv_try_write()` (Anna Henningsen) + +* darwin: fix build error with macos 10.10 (Ben Noordhuis) + +* unix: make uv_cwd() report UV_ENOBUFS (Ben Noordhuis) + +* unix: make uv_fs_read() fill all buffers (Ben Noordhuis) + +* test: give hrtime test a custom 10s timeout (Ben Noordhuis) + +* fs: fix uv_fs_copyfile if same src and dst (Santiago Gimeno) + +* build: add cmake option to skip building tests (Niels Lohmann) + +* doc: add link to nodejs.org (Jenil Christo) + +* unix: fix a comment typo in signal.c (Evgeny Ermakov) + +* unix: remove redundant cast in process.c (gengjiawen) + +* doc: fix wrong mutex function prototypes (Leo Chung) + + +2019.05.22, Version 1.29.1 (Stable), d16e6094e1eb3b0b5981ef1dd7e03ec4d466944d + +Changes since version 1.29.0: + +* unix: simplify uv/posix.h include logic (cjihrig) + +* test: increase test timeout (cjihrig) + +* linux: fix sscanf() overflows reading from /proc (Ben Noordhuis) + + +2019.05.16, Version 1.29.0 (Stable), 43957efd92c167b352b4c948b617ca7afbee0ed1 + +Changes since version 1.28.0: + +* ibmi: read memory and CPU usage info (Xu Meng) + +* doc: update the cmake testing instruction (zlargon) + +* unix: fix race condition in uv_async_send() (Ben Noordhuis) + +* linux: use O_CLOEXEC instead of EPOLL_CLOEXEC (Ben Noordhuis) + +* doc: mark uv_async_send() as async-signal-safe (Ben Noordhuis) + +* linux: init st_flags and st_gen when using statx (Oscar Waddell) + +* linux: read free/total memory from /proc/meminfo (Ben Noordhuis) + +* test: test zero-sized uv_fs_sendfile() writes (Ben Noordhuis) + +* unix: don't assert on UV_PROCESS_WINDOWS_* flags (Ben Noordhuis) + +* linux: set correct mac address for IP-aliases (Santiago Gimeno) + +* win,util: fix null pointer dereferencing (Tobias Nießen) + +* unix,win: fix `uv_fs_poll_stop()` when active (Anna Henningsen) + +* doc: add missing uv_fs_type entries (Michele Caini) + +* doc: fix build with sphinx 2.x (FX Coudert) + +* unix: don't make statx system call on Android (George Zhao) + +* unix: fix clang scan-build warning (Kyle Edwards) + +* unix: fall back to kqueue on older macOS systems (ken-cunningham-webuse) + +* unix,win: add uv_get_constrained_memory() (Kelvin Jin) + +* darwin: fix thread cancellation fd leak (Ben Noordhuis) + +* linux: fix thread cancellation fd leak (Ben Noordhuis) + + +2019.04.16, Version 1.28.0 (Stable), 7bf8fabfa934660ee0fe889f78e151198a1165fc + +Changes since version 1.27.0: + +* unix,win: add uv_gettimeofday() (cjihrig) + +* unix,win: add uv_fs_{open,read,close}dir() (cjihrig) + +* unix: fix uv_interface_addresses() (Andreas Rohner) + +* fs: remove macOS-specific copyfile(3) (Rich Trott) + +* fs: add test for copyfile() respecting permissions (Rich Trott) + +* build: partially revert 5234b1c43a (Ben Noordhuis) + +* zos: fix setsockopt error when using AF_UNIX (Milad Farazmand) + +* unix: suppress EINTR/EINPROGRESS in uv_fs_close() (Ben Noordhuis) + +* build: use cmake APPLE variable to detect platform (zlargon) + +* distcheck: remove duplicate test/ entry (Jameson Nash) + +* unix: remove unused cmpxchgl() function (Ben Noordhuis) + +* unix: support sockaddr_un in uv_udp_send() (Yury Selivanov) + +* unix: guard use of PTHREAD_STACK_MIN (Kamil Rytarowski) + +* unix,win: introduce uv_timeval64_t (cjihrig) + +* doc: document uv_timeval_t and uv_timeval64_t (cjihrig) + + +2019.03.17, Version 1.27.0 (Stable), a4fc9a66cc35256dbc4dcd67c910174f05b6daa6 + +Changes since version 1.26.0: + +* doc: describe unix signal handling better (Vladimír Čunát) + +* linux: use statx() to obtain file birth time (Ben Noordhuis) + +* src: fill sockaddr_in6.sin6_len when it's defined (Santiago Gimeno) + +* test: relax uv_hrtime() test assumptions (Ben Noordhuis) + +* build: make cmake install LICENSE only once (Thomas Karl Pietrowski) + +* bsd: plug uv_fs_event_start() error path fd leak (Ben Noordhuis) + +* unix: fix __FreeBSD_kernel__ typo (cjihrig) + +* doc: add note about uv_run() not being reentrant (Ben Noordhuis) + +* unix, win: make fs-poll close wait for resource cleanup (Anna Henningsen) + +* doc: fix typo in uv_thread_options_t definition (Ryan Liptak) + +* win: skip winsock initialization in safe mode (evgley) + +* unix: refactor getsockname/getpeername methods (Santiago Gimeno) + +* win,udp: allow to use uv_udp_open on bound sockets (Santiago Gimeno) + +* udp: add support for UDP connected sockets (Santiago Gimeno) + +* build: fix uv_test shared uv Windows cmake build (ptlomholt) + +* build: add android-configure scripts to EXTRA_DIST (Ben Noordhuis) + +* build: add missing header (cjihrig) + +* sunos: add perror() output prior to abort() (Andrew Paprocki) + +* test,sunos: disable UV_DISCONNECT handling (Andrew Paprocki) + +* sunos: disable __attribute__((unused)) (Andrew Paprocki) + +* test,sunos: use unistd.h code branch (Andrew Paprocki) + +* build,sunos: better handling of non-GCC compiler (Andrew Paprocki) + +* test,sunos: fix statement not reached warnings (Andrew Paprocki) + +* sunos: fix argument/prototype mismatch in atomics (Andrew Paprocki) + +* test,sunos: test-ipc.c lacks newline at EOF (Andrew Paprocki) + +* test: change spawn_stdin_stdout return to void (Andrew Paprocki) + +* test: remove call to floor() in test driver (Andrew Paprocki) + + +2019.02.11, Version 1.26.0 (Stable), 8669d8d3e93cddb62611b267ef62a3ddb5ba3ca0 + +Changes since version 1.25.0: + +* doc: fix uv_get_free_memory doc (Stephen Belanger) + +* unix: fix epoll cpu 100% issue (yeyuanfeng) + +* openbsd,tcp: special handling of EINVAL on connect (ptlomholt) + +* win: simplify registry closing in uv_cpu_info() (cjihrig) + +* src,include: define UV_MAXHOSTNAMESIZE (cjihrig) + +* win: return product name in uv_os_uname() version (cjihrig) + +* thread: allow specifying stack size for new thread (Anna Henningsen) + +* win: fix duplicate tty vt100 fn key (erw7) + +* unix: don't attempt to invalidate invalid fd (Ben Noordhuis) + + +2019.01.19, Version 1.25.0 (Stable), 4a10a9d425863330af199e4b74bd688e62d945f1 + +Changes since version 1.24.1: + +* Revert "win,fs: retry if uv_fs_rename fails" (Ben Noordhuis) + +* aix: manually trigger fs event monitoring (Gireesh Punathil) + +* unix: rename WRITE_RETRY_ON_ERROR macro (Ben Noordhuis) + +* darwin: DRY platform-specific error check (Ben Noordhuis) + +* unix: refactor uv__write() (Ben Noordhuis) + +* unix: don't send handle twice on partial write (Ben Noordhuis) + +* tty,win: fix Alt+key under WSL (Bartosz Sosnowski) + +* build: support running tests in out-of-tree builds (Jameson Nash) + +* fsevents: really watch files with fsevents on macos 10.7+ (Jameson Nash) + +* thread,mingw64: need intrin.h header for SSE2 MemoryBarrier (Jameson Nash) + +* win: fix sizeof-pointer-div warning (cjihrig) + +* unix,win: add uv_os_uname() (cjihrig) + +* win, tty: fix CreateFileW() return value check (Bartosz Sosnowski) + +* unix: enable IPv6 tests on OpenBSD (ptlomholt) + +* test: fix test-ipc spawn_helper exit_cb (Santiago Gimeno) + +* test: fix test-ipc tests (Santiago Gimeno) + +* unix: better handling of unsupported F_FULLFSYNC (Victor Costan) + +* win,test: de-flake fs_event_watch_dir_short_path (Refael Ackermann) + +* win: fix msvc warning (sid) + +* openbsd: switch to libuv's barrier implementation (ptlomholt) + +* unix,stream: fix zero byte writes (Santiago Gimeno) + +* ibmi: return EISDIR on read from directory fd (Kevin Adler) + +* build: wrap long lines in Makefile.am (cjihrig) + + +2018.12.17, Version 1.24.1 (Stable), 274f2bd3b70847cadd9a3965577a87e666ab9ac3 + +Changes since version 1.24.0: + +* test: fix platform_output test on cygwin (damon-kwok) + +* gitignore: ignore build/ directory (Damon Kwok) + +* unix: zero epoll_event before use (Ashe Connor) + +* darwin: use runtime check for file cloning (Ben Noordhuis) + +* doc: replace deprecated build command on macOS (Rick) + +* warnings: fix code that emits compiler warnings (Jameson Nash) + +* doc: clarify expected memory management strategy (Ivan Krylov) + +* test: add uv_inet_ntop(AF_INET) coverage (Ben Noordhuis) + +* unix: harden string copying, introduce strscpy() (Ben Noordhuis) + +* linux: get rid of strncpy() call (Ben Noordhuis) + +* aix: get rid of strcat() calls (Ben Noordhuis) + +* aix: fix data race in uv_fs_event_start() (Ben Noordhuis) + +* win: fs: fix `FILE_FLAG_NO_BUFFERING` for writes (Joran Dirk Greef) + +* build: don't link against -lpthread on Android (Michael Meier) + + +2018.11.14, Version 1.24.0 (Stable), 2d427ee0083d1baf995df4ebf79a3f8890e9a3e1 + +Changes since version 1.23.2: + +* unix: do not require PATH_MAX to be defined (Brad King) + +* win,doc: path encoding in uv_fs_XX is UTF-8 (hitesh) + +* unix: add missing link dependency on kFreeBSD (Svante Signell) + +* unix: add support for GNU/Hurd (Samuel Thibault) + +* test: avoid memory leak for test_output (Carlo Marcelo Arenas Belón) + +* zos: avoid UB with NULL pointer arithmetic (Carlo Marcelo Arenas Belón) + +* doc: add vtjnash to maintainers (Jameson Nash) + +* unix: restore skipping of phys_addr copy (cjihrig) + +* unix,win: make uv_interface_addresses() consistent (cjihrig) + +* unix: remove unnecessary linebreaks (cjihrig) + +* unix,win: handle zero-sized allocations uniformly (Ben Noordhuis) + +* unix: remove unused uv__dup() function (Ben Noordhuis) + +* core,bsd: refactor process_title functions (Santiago Gimeno) + +* win: Redefine NSIG to consider SIGWINCH (Jeremy Studer) + +* test: make sure that reading a directory fails (Sakthipriyan Vairamani) + +* win, tty: remove zero-size read callbacks (Bartosz Sosnowski) + +* test: fix test runner getenv async-signal-safety (Ben Noordhuis) + +* test: fix test runner execvp async-signal-safety (Ben Noordhuis) + +* test,unix: fix race in test runner (Ben Noordhuis) + +* unix,win: support IDNA 2008 in uv_getaddrinfo() (Ben Noordhuis) + +* win, tcp: avoid starving the loop (Bartosz Sosnowski) + +* win, dl: proper error messages on some systems (Bartosz Sosnowski) + +* win,fs: retry if uv_fs_rename fails (Bartosz Sosnowski) + +* darwin: speed up uv_set_process_title() (Ben Noordhuis) + +* aix: fix race in uv_get_process_title() (Gireesh Punathil) + +* win: support more fine-grained windows hiding (Bartosz Sosnowski) + + +2018.10.09, Version 1.23.2 (Stable), 34c12788d2e7308f3ac506c0abcbf74c0d6abd20 + +Changes since version 1.23.1: + +* unix: return 0 retrieving rss on cygwin (cjihrig) + +* unix: initialize uv_interface_address_t.phys_addr (cjihrig) + +* test: handle uv_os_setpriority() windows edge case (cjihrig) + +* tty, win: fix read stop for raw mode (Bartosz Sosnowski) + +* Revert "Revert "unix,fs: fix for potential partial reads/writes"" (Jameson + Nash) + +* unix,readv: always permit partial reads to return (Jameson Nash) + +* win,tty: fix uv_tty_close() (Bartosz Sosnowski) + +* doc: remove extraneous "on" (Ben Noordhuis) + +* unix,win: fix threadpool race condition (Anna Henningsen) + +* unix: rework thread barrier implementation (Ben Noordhuis) + +* aix: switch to libuv's own thread barrier impl (Ben Noordhuis) + +* unix: signal done to last thread barrier waiter (Ben Noordhuis) + +* test: add uv_barrier_wait serial thread test (Ali Ijaz Sheikh) + +* unix: optimize uv_fs_readlink() memory allocation (Ben Noordhuis) + +* win: remove req.c and other cleanup (Carlo Marcelo Arenas Belón) + +* aix: don't EISDIR on read from directory fd (Ben Noordhuis) + + +2018.09.22, Version 1.23.1 (Stable), d2282b3d67821dc53c907c2155fa8c5c6ce25180 Changes since version 1.23.0: diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 543dc3c..268251e 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -15,7 +15,12 @@ libuv is currently managed by the following individuals: - GPG key: AF2E EA41 EC34 47BF DD86 FED9 D706 3CCE 19B7 E890 (pubkey-indutny) * **Imran Iqbal** ([@imran-iq](https://github.com/imran-iq)) - GPG key: 9DFE AA5F 481B BF77 2D90 03CE D592 4925 2F8E C41A (pubkey-iwuzhere) +* **Jameson Nash** ([@vtjnash](https://github.com/vtjnash)) * **John Barboza** ([@jbarz](https://github.com/jbarz)) +* **Kaoru Takanashi** ([@erw7](https://github.com/erw7)) + - GPG Key: 5804 F999 8A92 2AFB A398 47A0 7183 5090 6134 887F (pubkey-erw7) +* **Richard Lau** ([@richardlau](https://github.com/richardlau)) + - GPG key: C82F A3AE 1CBE DC6B E46B 9360 C43C EC45 C17A B93C (pubkey-richardlau) * **Santiago Gimeno** ([@santigimeno](https://github.com/santigimeno)) - GPG key: 612F 0EAD 9401 6223 79DF 4402 F28C 3C8D A33C 03BE (pubkey-santigimeno) * **Saúl Ibarra Corretgé** ([@saghul](https://github.com/saghul)) diff --git a/Makefile.am b/Makefile.am index a217faa..d9d2f3d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -20,7 +20,9 @@ AM_CPPFLAGS = -I$(top_srcdir)/include \ include_HEADERS=include/uv.h uvincludedir = $(includedir)/uv -uvinclude_HEADERS=include/uv/errno.h include/uv/threadpool.h include/uv/version.h +uvinclude_HEADERS = include/uv/errno.h \ + include/uv/threadpool.h \ + include/uv/version.h CLEANFILES = @@ -29,8 +31,13 @@ libuv_la_CFLAGS = @CFLAGS@ libuv_la_LDFLAGS = -no-undefined -version-info 1:0:0 libuv_la_SOURCES = src/fs-poll.c \ src/heap-inl.h \ + src/idna.c \ + src/idna.h \ src/inet.c \ src/queue.h \ + src/random.c \ + src/strscpy.c \ + src/strscpy.h \ src/threadpool.c \ src/timer.c \ src/uv-data-getter-setters.c \ @@ -68,7 +75,6 @@ libuv_la_SOURCES += src/win/async.c \ src/win/poll.c \ src/win/process-stdio.c \ src/win/process.c \ - src/win/req.c \ src/win/req-inl.h \ src/win/signal.c \ src/win/stream.c \ @@ -100,6 +106,7 @@ libuv_la_SOURCES += src/unix/async.c \ src/unix/pipe.c \ src/unix/poll.c \ src/unix/process.c \ + src/unix/random-devurandom.c \ src/unix/signal.c \ src/unix/spinlock.h \ src/unix/stream.c \ @@ -112,30 +119,19 @@ endif # WINNT EXTRA_DIST = test/fixtures/empty_file \ test/fixtures/load_error.node \ + test/fixtures/lorem_ipsum.txt \ include \ - test \ docs \ img \ - samples \ - android-configure \ CONTRIBUTING.md \ LICENSE \ - README.md \ - checksparse.sh \ - vcbuild.bat \ - common.gypi \ - gyp_uv.py \ - uv.gyp + README.md TESTS = test/run-tests check_PROGRAMS = test/run-tests -if OS390 test_run_tests_CFLAGS = -else -test_run_tests_CFLAGS = -Wno-long-long -endif if SUNOS # Can't be turned into a CC_CHECK_CFLAGS in configure.ac, it makes compilers @@ -176,6 +172,9 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-fs-event.c \ test/test-fs-poll.c \ test/test-fs.c \ + test/test-fs-readdir.c \ + test/test-fs-fd-hash.c \ + test/test-fs-open-flags.c \ test/test-fork.c \ test/test-getters-setters.c \ test/test-get-currentexe.c \ @@ -186,10 +185,12 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-gethostname.c \ test/test-getnameinfo.c \ test/test-getsockname.c \ + test/test-gettimeofday.c \ test/test-handle-fileno.c \ test/test-homedir.c \ test/test-hrtime.c \ test/test-idle.c \ + test/test-idna.c \ test/test-ip4-addr.c \ test/test-ip6-addr.c \ test/test-ipc-heavy-traffic-deadlock-bug.c \ @@ -228,6 +229,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-process-title.c \ test/test-process-title-threadsafe.c \ test/test-queue-foreach-delete.c \ + test/test-random.c \ test/test-ref.c \ test/test-run-nowait.c \ test/test-run-once.c \ @@ -236,16 +238,19 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-shutdown-eof.c \ test/test-shutdown-twice.c \ test/test-signal-multiple-loops.c \ + test/test-signal-pending-on-close.c \ test/test-signal.c \ test/test-socket-buffer-size.c \ test/test-spawn.c \ test/test-stdio-over-pipes.c \ + test/test-strscpy.c \ test/test-tcp-alloc-cb-fail.c \ test/test-tcp-bind-error.c \ test/test-tcp-bind6-error.c \ test/test-tcp-close-accept.c \ test/test-tcp-close-while-connecting.c \ test/test-tcp-close.c \ + test/test-tcp-close-reset.c \ test/test-tcp-create-socket-early.c \ test/test-tcp-connect-error-after-write.c \ test/test-tcp-connect-error.c \ @@ -262,6 +267,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-tcp-writealot.c \ test/test-tcp-write-fail.c \ test/test-tcp-try-write.c \ + test/test-tcp-try-write-error.c \ test/test-tcp-write-queue-order.c \ test/test-thread-equal.c \ test/test-thread.c \ @@ -271,9 +277,12 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-timer-from-check.c \ test/test-timer.c \ test/test-tmpdir.c \ + test/test-tty-duplicate-key.c \ + test/test-tty-escape-sequence-processing.c \ test/test-tty.c \ test/test-udp-alloc-cb-fail.c \ test/test-udp-bind.c \ + test/test-udp-connect.c \ test/test-udp-create-socket-early.c \ test/test-udp-dgram-too-big.c \ test/test-udp-ipv6.c \ @@ -289,6 +298,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-udp-send-immediate.c \ test/test-udp-send-unreachable.c \ test/test-udp-try-send.c \ + test/test-uname.c \ test/test-walk-handles.c \ test/test-watcher-cross-stop.c test_run_tests_LDADD = libuv.la @@ -302,7 +312,19 @@ test_run_tests_SOURCES += test/runner-unix.c \ endif if AIX -test_run_tests_CFLAGS += -D_ALL_SOURCE -D_XOPEN_SOURCE=500 -D_LINUX_SOURCE_COMPAT +test_run_tests_CFLAGS += -D_ALL_SOURCE \ + -D_XOPEN_SOURCE=500 \ + -D_LINUX_SOURCE_COMPAT +endif + +if OS400 +test_run_tests_CFLAGS += -D_ALL_SOURCE \ + -D_XOPEN_SOURCE=500 \ + -D_LINUX_SOURCE_COMPAT +endif + +if HAIKU +test_run_tests_CFLAGS += -D_BSD_SOURCE endif if LINUX @@ -310,11 +332,14 @@ test_run_tests_CFLAGS += -D_GNU_SOURCE endif if SUNOS -test_run_tests_CFLAGS += -D__EXTENSIONS__ -D_XOPEN_SOURCE=500 +test_run_tests_CFLAGS += -D__EXTENSIONS__ \ + -D_XOPEN_SOURCE=500 \ + -D_REENTRANT endif if OS390 -test_run_tests_CFLAGS += -D_UNIX03_THREADS \ +test_run_tests_CFLAGS += -D_ISOC99_SOURCE \ + -D_UNIX03_THREADS \ -D_UNIX03_SOURCE \ -D_OPEN_SYS_IF_EXT=1 \ -D_OPEN_SYS_SOCK_IPV6 \ @@ -339,11 +364,30 @@ uvinclude_HEADERS += include/uv/aix.h libuv_la_SOURCES += src/unix/aix.c src/unix/aix-common.c endif +if OS400 +libuv_la_CFLAGS += -D_ALL_SOURCE \ + -D_XOPEN_SOURCE=500 \ + -D_LINUX_SOURCE_COMPAT \ + -D_THREAD_SAFE +uvinclude_HEADERS += include/uv/posix.h +libuv_la_SOURCES += src/unix/aix-common.c \ + src/unix/ibmi.c \ + src/unix/posix-poll.c \ + src/unix/no-fsevents.c \ + src/unix/no-proctitle.c +endif + if ANDROID -uvinclude_HEADERS += include/uv/android-ifaddrs.h \ - include/uv/pthread-barrier.h +uvinclude_HEADERS += include/uv/android-ifaddrs.h libuv_la_SOURCES += src/unix/android-ifaddrs.c \ - src/unix/pthread-fixes.c + src/unix/linux-core.c \ + src/unix/linux-inotify.c \ + src/unix/linux-syscalls.c \ + src/unix/procfs-exepath.c \ + src/unix/pthread-fixes.c \ + src/unix/random-getrandom.c \ + src/unix/random-sysctl-linux.c \ + src/unix/sysinfo-loadavg.c endif if CYGWIN @@ -361,22 +405,24 @@ libuv_la_SOURCES += src/unix/cygwin.c \ endif if DARWIN -uvinclude_HEADERS += include/uv/darwin.h \ - include/uv/pthread-barrier.h +uvinclude_HEADERS += include/uv/darwin.h libuv_la_CFLAGS += -D_DARWIN_USE_64_BIT_INODE=1 libuv_la_CFLAGS += -D_DARWIN_UNLIMITED_SELECT=1 libuv_la_SOURCES += src/unix/bsd-ifaddrs.c \ - src/unix/darwin.c \ src/unix/darwin-proctitle.c \ + src/unix/darwin-stub.h \ + src/unix/darwin.c \ src/unix/fsevents.c \ src/unix/kqueue.c \ - src/unix/proctitle.c + src/unix/proctitle.c \ + src/unix/random-getentropy.c test_run_tests_LDFLAGS += -lutil endif if DRAGONFLY uvinclude_HEADERS += include/uv/bsd.h libuv_la_SOURCES += src/unix/bsd-ifaddrs.c \ + src/unix/bsd-proctitle.c \ src/unix/freebsd.c \ src/unix/kqueue.c \ src/unix/posix-hrtime.c @@ -386,12 +432,32 @@ endif if FREEBSD uvinclude_HEADERS += include/uv/bsd.h libuv_la_SOURCES += src/unix/bsd-ifaddrs.c \ + src/unix/bsd-proctitle.c \ src/unix/freebsd.c \ src/unix/kqueue.c \ - src/unix/posix-hrtime.c + src/unix/posix-hrtime.c \ + src/unix/random-getrandom.c test_run_tests_LDFLAGS += -lutil endif +if HAIKU +uvinclude_HEADERS += include/uv/posix.h +libuv_la_CFLAGS += -D_BSD_SOURCE +libuv_la_SOURCES += src/unix/bsd-ifaddrs.c \ + src/unix/haiku.c \ + src/unix/no-fsevents.c \ + src/unix/no-proctitle.c \ + src/unix/posix-hrtime.c \ + src/unix/posix-poll.c +endif + +if HURD +uvinclude_HEADERS += include/uv/posix.h +libuv_la_SOURCES += src/unix/no-fsevents.c \ + src/unix/posix-hrtime.c \ + src/unix/posix-poll.c +endif + if LINUX uvinclude_HEADERS += include/uv/linux.h libuv_la_CFLAGS += -D_GNU_SOURCE @@ -401,8 +467,9 @@ libuv_la_SOURCES += src/unix/linux-core.c \ src/unix/linux-syscalls.h \ src/unix/procfs-exepath.c \ src/unix/proctitle.c \ - src/unix/sysinfo-loadavg.c \ - src/unix/sysinfo-memory.c + src/unix/random-getrandom.c \ + src/unix/random-sysctl-linux.c \ + src/unix/sysinfo-loadavg.c test_run_tests_LDFLAGS += -lutil endif @@ -422,6 +489,7 @@ endif if NETBSD uvinclude_HEADERS += include/uv/bsd.h libuv_la_SOURCES += src/unix/bsd-ifaddrs.c \ + src/unix/bsd-proctitle.c \ src/unix/kqueue.c \ src/unix/netbsd.c \ src/unix/posix-hrtime.c @@ -431,21 +499,24 @@ endif if OPENBSD uvinclude_HEADERS += include/uv/bsd.h libuv_la_SOURCES += src/unix/bsd-ifaddrs.c \ + src/unix/bsd-proctitle.c \ src/unix/kqueue.c \ src/unix/openbsd.c \ - src/unix/posix-hrtime.c + src/unix/posix-hrtime.c \ + src/unix/random-getentropy.c test_run_tests_LDFLAGS += -lutil endif if SUNOS uvinclude_HEADERS += include/uv/sunos.h -libuv_la_CFLAGS += -D__EXTENSIONS__ -D_XOPEN_SOURCE=500 +libuv_la_CFLAGS += -D__EXTENSIONS__ \ + -D_XOPEN_SOURCE=500 \ + -D_REENTRANT libuv_la_SOURCES += src/unix/no-proctitle.c \ src/unix/sunos.c endif if OS390 -uvinclude_HEADERS += include/uv/pthread-barrier.h libuv_la_CFLAGS += -D_UNIX03_THREADS \ -D_UNIX03_SOURCE \ -D_OPEN_SYS_IF_EXT=1 \ @@ -453,6 +524,7 @@ libuv_la_CFLAGS += -D_UNIX03_THREADS \ -D_XOPEN_SOURCE_EXTENDED \ -D_ALL_SOURCE \ -D_LARGE_TIME_API \ + -D_OPEN_SYS_SOCK_EXT3 \ -D_OPEN_SYS_SOCK_IPV6 \ -D_OPEN_SYS_FILE_EXT \ -DUV_PLATFORM_SEM_T=int \ diff --git a/README.md b/README.md index b24b722..98007c5 100644 --- a/README.md +++ b/README.md @@ -116,9 +116,6 @@ libuv can be downloaded either from the [GitHub repository](https://github.com/libuv/libuv) or from the [downloads site](http://dist.libuv.org/dist/). -Starting with libuv 1.7.0, binaries for Windows are also provided. This is to -be considered EXPERIMENTAL. - Before verifying the git tags or signature files, importing the relevant keys is necessary. Key IDs are listed in the [MAINTAINERS](https://github.com/libuv/libuv/blob/master/MAINTAINERS.md) @@ -155,44 +152,14 @@ $ gpg --verify libuv-1.7.0.tar.gz.sign ## Build Instructions -For GCC there are two build methods: via autotools or via [GYP][]. -GYP is a meta-build system which can generate MSVS, Makefile, and XCode -backends. It is best used for integration into other projects. - -To build with autotools: - -```bash -$ sh autogen.sh -$ ./configure -$ make -$ make check -$ make install -``` - -To build with [CMake](https://cmake.org/): - -```bash -$ mkdir -p out/cmake ; cd out/cmake ; cmake -DBUILD_TESTING=ON ../.. -$ make all test -# Or manually: -$ ./uv_run_tests # shared library build -$ ./uv_run_tests_a # static library build -``` - -To build with GYP, first run: +For UNIX-like platforms, including macOS, there are two build methods: +autotools or [CMake][]. -```bash -$ git clone https://chromium.googlesource.com/external/gyp build/gyp -``` +For Windows, [CMake][] is the only supported build method and has the +following prerequisites: -### Windows +
-Prerequisites: - -* [Python 2.6 or 2.7][] as it is required - by [GYP][]. - If python is not in your path, set the environment variable `PYTHON` to its - location. For example: `set PYTHON=C:\Python27\python.exe` * One of: * [Visual C++ Build Tools][] * [Visual Studio 2015 Update 3][], all editions @@ -205,68 +172,44 @@ Prerequisites: [Git for Windows][] includes Git Bash and tools which can be included in the global `PATH`. -To build, launch a git shell (e.g. Cmd or PowerShell), run `vcbuild.bat` -(to build with VS2017 you need to explicitly add a `vs2017` argument), -which will checkout the GYP code into `build/gyp`, generate `uv.sln` -as well as the necesery related project files, and start building. +
-```console -> vcbuild -``` - -Or: - -```console -> vcbuild vs2017 -``` - -To run the tests: - -```console -> vcbuild test -``` - -To see all the options that could passed to `vcbuild`: - -```console -> vcbuild help -vcbuild.bat [debug/release] [test/bench] [clean] [noprojgen] [nobuild] [vs2017] [x86/x64] [static/shared] -Examples: - vcbuild.bat : builds debug build - vcbuild.bat test : builds debug build and runs tests - vcbuild.bat release bench: builds release build and runs benchmarks -``` - - -### Unix - -For Debug builds (recommended) run: +To build with autotools: ```bash -$ ./gyp_uv.py -f make -$ make -C out +$ sh autogen.sh +$ ./configure +$ make +$ make check +$ make install ``` -For Release builds run: +To build with [CMake][]: ```bash -$ ./gyp_uv.py -f make -$ BUILDTYPE=Release make -C out -``` +$ mkdir -p build -Run `./gyp_uv.py -f make -Dtarget_arch=x32` to build [x32][] binaries. +$ (cd build && cmake .. -DBUILD_TESTING=ON) # generate project with tests +$ cmake --build build # add `-j ` with cmake >= 3.12 -### OS X +# Run tests: +$ (cd build && ctest -C Debug --output-on-failure) -Run: +# Or manually run tests: +$ build/uv_run_tests # shared library build +$ build/uv_run_tests_a # static library build +``` + +To cross-compile with [CMake][] (unsupported but generally works): ```bash -$ ./gyp_uv.py -f xcode -$ xcodebuild -ARCHS="x86_64" -project uv.xcodeproj \ - -configuration Release -target All +$ cmake ../.. \ + -DCMAKE_SYSTEM_NAME=Windows \ + -DCMAKE_SYSTEM_VERSION=6.1 \ + -DCMAKE_C_COMPILER=i686-w64-mingw32-gcc ``` -Using Homebrew: +### Install with Homebrew ```bash $ brew install --HEAD libuv @@ -278,103 +221,48 @@ Make sure that you specify the architecture you wish to build for in the "ARCHS" flag. You can specify more than one by delimiting with a space (e.g. "x86_64 i386"). -### Android - -Run: - -For arm - -```bash -$ source ./android-configure-arm NDK_PATH gyp [API_LEVEL] -$ make -C out -``` - -or for arm64 - -```bash -$ source ./android-configure-arm64 NDK_PATH gyp [API_LEVEL] -$ make -C out -``` - -or for x86 - -```bash -$ source ./android-configure-x86 NDK_PATH gyp [API_LEVEL] -$ make -C out -``` - -or for x86_64 - -```bash -$ source ./android-configure-x86_64 NDK_PATH gyp [API_LEVEL] -$ make -C out -``` - -The default API level is 24, but a different one can be selected as follows: - -```bash -$ source ./android-configure ~/android-ndk-r15b gyp 21 -$ make -C out -``` - -Note for UNIX users: compile your project with `-D_LARGEFILE_SOURCE` and -`-D_FILE_OFFSET_BITS=64`. GYP builds take care of that automatically. - -### Using Ninja - -To use ninja for build on ninja supported platforms, run: - -```bash -$ ./gyp_uv.py -f ninja -$ ninja -C out/Debug #for debug build OR -$ ninja -C out/Release -``` - - ### Running tests -#### Build - -Build (includes tests): +Some tests are timing sensitive. Relaxing test timeouts may be necessary +on slow or overloaded machines: ```bash -$ ./gyp_uv.py -f make -$ make -C out -``` - -#### Run all tests - -```bash -$ ./out/Debug/run-tests +$ env UV_TEST_TIMEOUT_MULTIPLIER=2 build/uv_run_tests # 10s instead of 5s ``` #### Run one test The list of all tests is in `test/test-list.h`. -This invocation will cause the `run-tests` driver to fork and execute `TEST_NAME` in a child process: +This invocation will cause the test driver to fork and execute `TEST_NAME` in +a child process: ```bash -$ ./out/Debug/run-tests TEST_NAME +$ build/uv_run_tests_a TEST_NAME ``` -This invocation will cause the `run-tests` driver to execute the test within the `run-tests` process: +This invocation will cause the test driver to execute the test in +the same process: ```bash -$ ./out/Debug/run-tests TEST_NAME TEST_NAME +$ build/uv_run_tests_a TEST_NAME TEST_NAME ``` #### Debugging tools -When running the test from within the `run-tests` process (`run-tests TEST_NAME TEST_NAME`), tools like gdb and valgrind work normally. -When running the test from a child of the `run-tests` process (`run-tests TEST_NAME`), use these tools in a fork-aware manner. +When running the test from within the test driver process +(`build/uv_run_tests_a TEST_NAME TEST_NAME`), tools like gdb and valgrind +work normally. + +When running the test from a child of the test driver process +(`build/uv_run_tests_a TEST_NAME`), use these tools in a fork-aware manner. ##### Fork-aware gdb Use the [follow-fork-mode](https://sourceware.org/gdb/onlinedocs/gdb/Forks.html) setting: ``` -$ gdb --args out/Debug/run-tests TEST_NAME +$ gdb --args build/uv_run_tests_a TEST_NAME (gdb) set follow-fork-mode child ... @@ -385,13 +273,14 @@ $ gdb --args out/Debug/run-tests TEST_NAME Use the `--trace-children=yes` parameter: ```bash -$ valgrind --trace-children=yes -v --tool=memcheck --leak-check=full --track-origins=yes --leak-resolution=high --show-reachable=yes --log-file=memcheck.log out/Debug/run-tests TEST_NAME +$ valgrind --trace-children=yes -v --tool=memcheck --leak-check=full --track-origins=yes --leak-resolution=high --show-reachable=yes --log-file=memcheck-%p.log build/uv_run_tests_a TEST_NAME ``` ### Running benchmarks See the section on running tests. -The benchmark driver is `out/Debug/run-benchmarks` and the benchmarks are listed in `test/benchmark-list.h`. +The benchmark driver is `./uv_run_benchmarks_a` and the benchmarks are +listed in `test/benchmark-list.h`. ## Supported Platforms @@ -399,14 +288,14 @@ Check the [SUPPORTED_PLATFORMS file](SUPPORTED_PLATFORMS.md). ### AIX Notes +AIX compilation using IBM XL C/C++ requires version 12.1 or greater. + AIX support for filesystem events requires the non-default IBM `bos.ahafs` package to be installed. This package provides the AIX Event Infrastructure that is detected by `autoconf`. [IBM documentation](http://www.ibm.com/developerworks/aix/library/au-aix_event_infrastructure/) describes the package in more detail. -AIX support for filesystem events is not compiled when building with `gyp`. - ### z/OS Notes z/OS creates System V semaphores and message queues. These persist on the system @@ -418,12 +307,10 @@ Use the `ipcrm` command to manually clear up System V resources. See the [guidelines for contributing][]. +[CMake]: https://cmake.org/ [node.js]: http://nodejs.org/ -[GYP]: http://code.google.com/p/gyp/ [guidelines for contributing]: https://github.com/libuv/libuv/blob/master/CONTRIBUTING.md [libuv_banner]: https://raw.githubusercontent.com/libuv/libuv/master/img/banner.png -[x32]: https://en.wikipedia.org/wiki/X32_ABI -[Python 2.6 or 2.7]: https://www.python.org/downloads/ [Visual C++ Build Tools]: https://visualstudio.microsoft.com/visual-cpp-build-tools/ [Visual Studio 2015 Update 3]: https://www.visualstudio.com/vs/older-downloads/ [Visual Studio 2017]: https://www.visualstudio.com/downloads/ diff --git a/SUPPORTED_PLATFORMS.md b/SUPPORTED_PLATFORMS.md index 0771910..72e054e 100644 --- a/SUPPORTED_PLATFORMS.md +++ b/SUPPORTED_PLATFORMS.md @@ -5,7 +5,7 @@ | GNU/Linux | Tier 1 | Linux >= 2.6.32 with glibc >= 2.12 | | | macOS | Tier 1 | macOS >= 10.7 | | | Windows | Tier 1 | >= Windows 7 | MSVC 2008 and later are supported | -| FreeBSD | Tier 1 | >= 9 (see note) | | +| FreeBSD | Tier 1 | >= 10 | | | AIX | Tier 2 | >= 6 | Maintainers: @libuv/aix | | z/OS | Tier 2 | >= V2R2 | Maintainers: @libuv/zos | | Linux with musl | Tier 2 | musl >= 1.0 | | @@ -16,11 +16,6 @@ | SunOS | Tier 3 | Solaris 121 and later | | | Other | Tier 3 | N/A | | -#### Note on FreeBSD 9 - -While FreeBSD is supported as Tier 1, FreeBSD 9 will get Tier 2 support until -it reaches end of life, in December 2016. - ## Support types * **Tier 1**: Officially supported and tested with CI. Any contributed patch @@ -52,8 +47,9 @@ All functionality related to the new platform must be implemented in its own file inside ``src/unix/`` unless it's already done in a common file, in which case adding an `ifdef` is fine. -Two build systems are supported: autotools and GYP. Ideally both need to be -supported, but if GYP does not support the new platform it can be left out. +Two build systems are supported: autotools and cmake. Ideally both need to be +supported, but if one of the two does not support the new platform it can be +left out. ### Windows diff --git a/configure.ac b/configure.ac index ce307b1..6616972 100644 --- a/configure.ac +++ b/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.23.1], [https://github.com/libuv/libuv/issues]) +AC_INIT([libuv], [1.38.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]) @@ -24,14 +24,12 @@ AC_ENABLE_SHARED AC_ENABLE_STATIC AC_PROG_CC AM_PROG_CC_C_O -AS_IF([AS_CASE([$host_os],[openedition*], [false], [true])], [ - CC_CHECK_CFLAGS_APPEND([-pedantic]) -]) CC_FLAG_VISIBILITY #[-fvisibility=hidden] CC_CHECK_CFLAGS_APPEND([-g]) CC_CHECK_CFLAGS_APPEND([-std=gnu89]) CC_CHECK_CFLAGS_APPEND([-Wall]) CC_CHECK_CFLAGS_APPEND([-Wextra]) +CC_CHECK_CFLAGS_APPEND([-Wno-long-long]) CC_CHECK_CFLAGS_APPEND([-Wno-unused-parameter]) CC_CHECK_CFLAGS_APPEND([-Wstrict-prototypes]) # AM_PROG_AR is not available in automake v0.11 but it's essential in v0.12. @@ -40,15 +38,17 @@ m4_ifdef([AM_PROG_AR], [AM_PROG_AR]) AC_PROG_LIBTOOL m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) LT_INIT -# TODO(bnoordhuis) Check for -pthread vs. -pthreads +AX_PTHREAD([ + LIBS="$LIBS $PTHREAD_LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" +]) AC_CHECK_LIB([dl], [dlopen]) -AC_CHECK_LIB([kstat], [kstat_lookup]) -AC_CHECK_LIB([nsl], [gethostbyname]) -AC_CHECK_LIB([perfstat], [perfstat_cpu]) -AC_CHECK_LIB([pthread], [pthread_mutex_init]) -AC_CHECK_LIB([rt], [clock_gettime]) -AC_CHECK_LIB([sendfile], [sendfile]) -AC_CHECK_LIB([socket], [socket]) +AC_SEARCH_LIBS([kstat_lookup], [kstat]) +AC_SEARCH_LIBS([gethostbyname], [nsl]) +AC_SEARCH_LIBS([perfstat_cpu], [perfstat]) +AC_SEARCH_LIBS([clock_gettime], [rt]) +AC_SEARCH_LIBS([sendfile], [sendfile]) +AC_SEARCH_LIBS([socket], [socket]) AC_SYS_LARGEFILE AM_CONDITIONAL([AIX], [AS_CASE([$host_os],[aix*], [true], [false])]) AM_CONDITIONAL([ANDROID], [AS_CASE([$host_os],[linux-android*],[true], [false])]) @@ -56,17 +56,29 @@ AM_CONDITIONAL([CYGWIN], [AS_CASE([$host_os],[cygwin*], [true], [false]) AM_CONDITIONAL([DARWIN], [AS_CASE([$host_os],[darwin*], [true], [false])]) AM_CONDITIONAL([DRAGONFLY],[AS_CASE([$host_os],[dragonfly*], [true], [false])]) AM_CONDITIONAL([FREEBSD], [AS_CASE([$host_os],[*freebsd*], [true], [false])]) +AM_CONDITIONAL([HAIKU], [AS_CASE([$host_os],[haiku], [true], [false])]) +AM_CONDITIONAL([HURD], [AS_CASE([$host_os],[gnu*], [true], [false])]) AM_CONDITIONAL([LINUX], [AS_CASE([$host_os],[linux*], [true], [false])]) AM_CONDITIONAL([MSYS], [AS_CASE([$host_os],[msys*], [true], [false])]) AM_CONDITIONAL([NETBSD], [AS_CASE([$host_os],[netbsd*], [true], [false])]) AM_CONDITIONAL([OPENBSD], [AS_CASE([$host_os],[openbsd*], [true], [false])]) AM_CONDITIONAL([OS390], [AS_CASE([$host_os],[openedition*], [true], [false])]) +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 -lshell32 -luserenv -luser32" ]) AS_CASE([$host_os], [netbsd*], [AC_CHECK_LIB([kvm], [kvm_open])]) +AS_CASE([$host_os], [kfreebsd*], [ + LIBS="$LIBS -lfreebsd-glue" +]) +AS_CASE([$host_os], [haiku], [ + LIBS="$LIBS -lnetwork" +]) AC_CHECK_HEADERS([sys/ahafs_evProds.h]) AC_CONFIG_FILES([Makefile libuv.pc]) +AC_CONFIG_LINKS([test/fixtures/empty_file:test/fixtures/empty_file]) +AC_CONFIG_LINKS([test/fixtures/load_error.node:test/fixtures/load_error.node]) +AC_CONFIG_LINKS([test/fixtures/lorem_ipsum.txt:test/fixtures/lorem_ipsum.txt]) AC_OUTPUT diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d23c694 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,183 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXAUTOBUILD = sphinx-autobuild +PAPER = +BUILDDIR = build +SRCDIR = src + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SRCDIR) +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SRCDIR) + +.PHONY: help clean html livehtml dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " livehtml to make standalone HTML files and live reload them" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +livehtml: html + $(SPHINXAUTOBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/libuv.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/libuv.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/libuv" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/libuv" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/code/multi-echo-server/main.c b/docs/code/multi-echo-server/main.c index 25f4961..b938a7d 100644 --- a/docs/code/multi-echo-server/main.c +++ b/docs/code/multi-echo-server/main.c @@ -70,7 +70,7 @@ void setup_workers() { child_worker_count = cpu_count; - workers = calloc(sizeof(struct child_worker), cpu_count); + workers = calloc(cpu_count, sizeof(struct child_worker)); while (cpu_count--) { struct child_worker *worker = &workers[cpu_count]; uv_pipe_init(loop, &worker->pipe, 1); diff --git a/docs/src/async.rst b/docs/src/async.rst index 02e6a58..bf61169 100644 --- a/docs/src/async.rst +++ b/docs/src/async.rst @@ -50,6 +50,10 @@ API It's safe to call this function from any thread. The callback will be called on the loop thread. + .. note:: + :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 diff --git a/docs/src/check.rst b/docs/src/check.rst index 36c93cf..33aab55 100644 --- a/docs/src/check.rst +++ b/docs/src/check.rst @@ -33,14 +33,22 @@ API .. c:function:: int uv_check_init(uv_loop_t* loop, uv_check_t* check) - Initialize the handle. + Initialize the handle. This function always succeeds. + + :returns: 0 .. c:function:: int uv_check_start(uv_check_t* check, uv_check_cb cb) - Start the handle with the given callback. + Start the handle with the given callback. This function always succeeds, + except when `cb` is `NULL`. + + :returns: 0 on success, or `UV_EINVAL` when `cb == NULL`. .. c:function:: int uv_check_stop(uv_check_t* check) Stop the handle, the callback will no longer be called. + This function always succeeds. + + :returns: 0 .. seealso:: The :c:type:`uv_handle_t` API functions also apply. diff --git a/docs/src/design.rst b/docs/src/design.rst index 487d08b..a23e33a 100644 --- a/docs/src/design.rst +++ b/docs/src/design.rst @@ -4,9 +4,11 @@ Design overview =============== -libuv is cross-platform support library which was originally written for NodeJS. It's designed +libuv is cross-platform support library which was originally written for `Node.js`_. It's designed around the event-driven asynchronous I/O model. +.. _Node.js: https://nodejs.org + The library provides much more than a simple abstraction over different I/O polling mechanisms: 'handles' and 'streams' provide a high level abstraction for sockets and other entities; cross-platform file I/O and threading functionality is also provided, amongst other things. @@ -124,9 +126,9 @@ Unlike network I/O, there are no platform-specific file I/O primitives libuv cou so the current approach is to run blocking file I/O operations in a thread pool. For a thorough explanation of the cross-platform file I/O landscape, checkout -`this post `_. +`this post `_. -libuv currently uses a global thread pool on which all loops can queue work on. 3 types of +libuv currently uses a global thread pool on which all loops can queue work. 3 types of operations are currently run on this pool: * File system operations diff --git a/docs/src/fs.rst b/docs/src/fs.rst index fcf70a3..73666f3 100644 --- a/docs/src/fs.rst +++ b/docs/src/fs.rst @@ -12,6 +12,8 @@ otherwise it will be performed asynchronously. All file operations are run on the threadpool. See :ref:`threadpool` for information on the threadpool size. +.. note:: + On Windows `uv_fs_*` functions use utf-8 encoding. Data types ---------- @@ -94,9 +96,32 @@ Data types UV_FS_FCHOWN, UV_FS_REALPATH, UV_FS_COPYFILE, - UV_FS_LCHOWN + UV_FS_LCHOWN, + UV_FS_OPENDIR, + UV_FS_READDIR, + UV_FS_CLOSEDIR, + UV_FS_MKSTEMP, + UV_FS_LUTIME } uv_fs_type; +.. c:type:: uv_statfs_t + + Reduced cross platform equivalent of ``struct statfs``. + Used in :c:func:`uv_fs_statfs`. + + :: + + typedef struct uv_statfs_s { + uint64_t f_type; + uint64_t f_bsize; + uint64_t f_blocks; + uint64_t f_bfree; + uint64_t f_bavail; + uint64_t f_files; + uint64_t f_ffree; + uint64_t f_spare[4]; + } uv_statfs_t; + .. c:type:: uv_dirent_t Cross platform (reduced) equivalent of ``struct dirent``. @@ -120,6 +145,21 @@ Data types uv_dirent_type_t type; } uv_dirent_t; +.. 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 + `uv_dirent_t`s used to hold results. `nentries` is the user provided maximum + array size of `dirents`. + + :: + + typedef struct uv_dir_s { + uv_dirent_t* dirents; + size_t nentries; + } uv_dir_t; + Public members ^^^^^^^^^^^^^^ @@ -180,6 +220,11 @@ API Equivalent to :man:`preadv(2)`. + .. warning:: + On Windows, under non-MSVC environments (e.g. when GCC or Clang is used + to build libuv), files opened using ``UV_FS_O_FILEMAP`` may cause a fatal + crash if the memory mapped read operation fails. + .. c:function:: int uv_fs_unlink(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) Equivalent to :man:`unlink(2)`. @@ -188,6 +233,11 @@ API Equivalent to :man:`pwritev(2)`. + .. warning:: + On Windows, under non-MSVC environments (e.g. when GCC or Clang is used + to build libuv), files opened using ``UV_FS_O_FILEMAP`` may cause a fatal + crash if the memory mapped write operation fails. + .. c:function:: int uv_fs_mkdir(uv_loop_t* loop, uv_fs_t* req, const char* path, int mode, uv_fs_cb cb) Equivalent to :man:`mkdir(2)`. @@ -197,15 +247,63 @@ API .. c:function:: int uv_fs_mkdtemp(uv_loop_t* loop, uv_fs_t* req, const char* tpl, uv_fs_cb cb) - Equivalent to :man:`mkdtemp(3)`. + Equivalent to :man:`mkdtemp(3)`. The result can be found as a null terminated string at `req->path`. - .. note:: - The result can be found as a null terminated string at `req->path`. +.. c:function:: int uv_fs_mkstemp(uv_loop_t* loop, uv_fs_t* req, const char* tpl, uv_fs_cb cb) + + Equivalent to :man:`mkstemp(3)`. The created file path can be found as a null terminated string at `req->path`. + The file descriptor can be found as an integer at `req->result`. + + .. versionadded:: 1.34.0 .. c:function:: int uv_fs_rmdir(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) Equivalent to :man:`rmdir(2)`. +.. c:function:: int uv_fs_opendir(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) + + Opens `path` as a directory stream. On success, a `uv_dir_t` is allocated + and returned via `req->ptr`. This memory is not freed by + `uv_fs_req_cleanup()`, although `req->ptr` is set to `NULL`. The allocated + memory must be freed by calling `uv_fs_closedir()`. On failure, no memory + is allocated. + + The contents of the directory can be iterated over by passing the resulting + `uv_dir_t` to `uv_fs_readdir()`. + + .. versionadded:: 1.28.0 + +.. c:function:: int uv_fs_closedir(uv_loop_t* loop, uv_fs_t* req, uv_dir_t* dir, uv_fs_cb cb) + + Closes the directory stream represented by `dir` and frees the memory + allocated by `uv_fs_opendir()`. + + .. versionadded:: 1.28.0 + +.. c:function:: int uv_fs_readdir(uv_loop_t* loop, uv_fs_t* req, uv_dir_t* dir, uv_fs_cb cb) + + Iterates over the directory stream, `dir`, returned by a successful + `uv_fs_opendir()` call. Prior to invoking `uv_fs_readdir()`, the caller + must set `dir->dirents` and `dir->nentries`, representing the array of + :c:type:`uv_dirent_t` elements used to hold the read directory entries and + its size. + + On success, the result is an integer >= 0 representing the number of entries + read from the stream. + + .. versionadded:: 1.28.0 + + .. warning:: + `uv_fs_readdir()` is not thread safe. + + .. note:: + This function does not return the "." and ".." entries. + + .. note:: + On success this function allocates memory that must be freed using + `uv_fs_req_cleanup()`. `uv_fs_req_cleanup()` must be called before + closing the directory with `uv_fs_closedir()`. + .. c:function:: int uv_fs_scandir(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags, uv_fs_cb cb) .. c:function:: int uv_fs_scandir_next(uv_fs_t* req, uv_dirent_t* ent) @@ -227,6 +325,17 @@ API Equivalent to :man:`stat(2)`, :man:`fstat(2)` and :man:`lstat(2)` respectively. +.. c:function:: int uv_fs_statfs(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) + + Equivalent to :man:`statfs(2)`. On success, a `uv_statfs_t` is allocated + and returned via `req->ptr`. This memory is freed by `uv_fs_req_cleanup()`. + + .. note:: + Any fields in the resulting `uv_statfs_t` that are not supported by the + underlying operating system are set to zero. + + .. versionadded:: 1.31.0 + .. c:function:: int uv_fs_rename(uv_loop_t* loop, uv_fs_t* req, const char* path, const char* new_path, uv_fs_cb cb) Equivalent to :man:`rename(2)`. @@ -256,10 +365,13 @@ API is to overwrite the destination if it exists. - `UV_FS_COPYFILE_FICLONE`: If present, `uv_fs_copyfile()` will attempt to create a copy-on-write reflink. If the underlying platform does not - support copy-on-write, then a fallback copy mechanism is used. + 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. - `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, then an error is returned. + not support copy-on-write, or an error occurs while attempting to use + copy-on-write, then an error is returned. .. warning:: If the destination path is created, but an error occurs while copying @@ -272,6 +384,10 @@ API .. versionchanged:: 1.20.0 `UV_FS_COPYFILE_FICLONE` and `UV_FS_COPYFILE_FICLONE_FORCE` are supported. + .. versionchanged:: 1.33.0 If an error occurs while using + `UV_FS_COPYFILE_FICLONE_FORCE`, that error is returned. Previously, + all errors were mapped to `UV_ENOTSUP`. + .. c:function:: int uv_fs_sendfile(uv_loop_t* loop, uv_fs_t* req, uv_file out_fd, uv_file in_fd, int64_t in_offset, size_t length, uv_fs_cb cb) Limited equivalent to :man:`sendfile(2)`. @@ -287,12 +403,17 @@ API .. c:function:: int uv_fs_utime(uv_loop_t* loop, uv_fs_t* req, const char* path, double atime, double mtime, uv_fs_cb cb) .. c:function:: int uv_fs_futime(uv_loop_t* loop, uv_fs_t* req, uv_file file, double atime, double mtime, uv_fs_cb cb) +.. c:function:: int uv_fs_lutime(uv_loop_t* loop, uv_fs_t* req, const char* path, double atime, double mtime, uv_fs_cb cb) - Equivalent to :man:`utime(2)` and :man:`futime(2)` respectively. + Equivalent to :man:`utime(2)`, :man:`futimes(3)` and :man:`lutimes(3)` respectively. .. note:: - AIX: This function only works for AIX 7.1 and newer. It can still be called on older - versions but will return ``UV_ENOSYS``. + z/OS: `uv_fs_lutime()` is not implemented for z/OS. It can still be called but will return + ``UV_ENOSYS``. + + .. note:: + AIX: `uv_fs_futime()` and `uv_fs_lutime()` functions only work for AIX 7.1 and newer. + They can still be called on older versions but will return ``UV_ENOSYS``. .. versionchanged:: 1.10.0 sub-second precission is supported on Windows @@ -320,7 +441,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 `GetFinalPathNameByHandle `_. + Equivalent to :man:`realpath(3)` on Unix. Windows uses `GetFinalPathNameByHandle `_. The resulting string is stored in `req->ptr`. .. warning:: @@ -371,6 +492,13 @@ API .. versionadded:: 1.19.0 +.. c:function:: int uv_fs_get_system_error(const uv_fs_t* req) + + Returns the platform specific error code - `GetLastError()` value on Windows + and `-(req->result)` on other platforms. + + .. versionadded:: 1.38.0 + .. c:function:: void* uv_fs_get_ptr(const uv_fs_t* req) Returns `req->ptr`. @@ -397,7 +525,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. @@ -406,7 +534,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 the return value is still owned by the CRT, any attempts to close it or to use it after closing the handle may lead to malfunction. @@ -432,7 +560,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 @@ -449,7 +577,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 @@ -471,6 +599,14 @@ File open constants .. versionchanged:: 1.17.0 support is added for Windows. +.. c:macro:: UV_FS_O_FILEMAP + + Use a memory file mapping to access the file. When using this flag, the + file cannot be open multiple times concurrently. + + .. note:: + `UV_FS_O_FILEMAP` is only supported on Windows. + .. c:macro:: UV_FS_O_NOATIME Do not update the file access time when the file is read. @@ -508,7 +644,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 @@ -525,7 +661,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 @@ -533,7 +669,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 @@ -546,7 +682,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 @@ -554,7 +690,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/docs/src/fs_event.rst b/docs/src/fs_event.rst index bd076aa..e28ec62 100644 --- a/docs/src/fs_event.rst +++ b/docs/src/fs_event.rst @@ -23,7 +23,7 @@ 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: http://www.ibm.com/developerworks/aix/library/au-aix_event_infrastructure/ + .. _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 diff --git a/docs/src/guide/about.rst b/docs/src/guide/about.rst index 2de658d..731d1a4 100644 --- a/docs/src/guide/about.rst +++ b/docs/src/guide/about.rst @@ -1,7 +1,7 @@ About ===== -`Nikhil Marathe `_ started writing this book one +`Nikhil Marathe `_ started writing this book one afternoon (June 16, 2012) when he didn't feel like programming. He had recently been stung by the lack of good documentation on libuv while working on `node-taglib `_. Although reference @@ -13,8 +13,8 @@ Nikhil is indebted to Marc Lehmann's comprehensive `man page `_ about libev which describes much of the semantics of the two libraries. -This book was made using `Sphinx `_ and `vim -`_. +This book was made using `Sphinx `_ and `vim +`_. .. note:: In 2017 the libuv project incorporated the Nikhil's work into the official diff --git a/docs/src/guide/basics.rst b/docs/src/guide/basics.rst index 91fa6a6..1da8d5e 100644 --- a/docs/src/guide/basics.rst +++ b/docs/src/guide/basics.rst @@ -82,7 +82,7 @@ libuv's event loop is explained in more detail in the `documentation Hello World ----------- -With the basics out of the way, lets write our first libuv program. It does +With the basics out of the way, let's write our first libuv program. It does nothing, except start a loop which will exit immediately. .. rubric:: helloworld/main.c @@ -136,8 +136,39 @@ Handles are opaque structs named as ``uv_TYPE_t`` where type signifies what the handle is used for. .. rubric:: libuv watchers -.. literalinclude:: ../../../include/uv.h - :lines: 197-230 +.. code-block:: c + + /* Handle types. */ + typedef struct uv_loop_s uv_loop_t; + typedef struct uv_handle_s uv_handle_t; + typedef struct uv_dir_s uv_dir_t; + typedef struct uv_stream_s uv_stream_t; + typedef struct uv_tcp_s uv_tcp_t; + typedef struct uv_udp_s uv_udp_t; + typedef struct uv_pipe_s uv_pipe_t; + typedef struct uv_tty_s uv_tty_t; + typedef struct uv_poll_s uv_poll_t; + typedef struct uv_timer_s uv_timer_t; + typedef struct uv_prepare_s uv_prepare_t; + typedef struct uv_check_s uv_check_t; + typedef struct uv_idle_s uv_idle_t; + typedef struct uv_async_s uv_async_t; + typedef struct uv_process_s uv_process_t; + typedef struct uv_fs_event_s uv_fs_event_t; + typedef struct uv_fs_poll_s uv_fs_poll_t; + typedef struct uv_signal_s uv_signal_t; + + /* Request types. */ + typedef struct uv_req_s uv_req_t; + typedef struct uv_getaddrinfo_s uv_getaddrinfo_t; + typedef struct uv_getnameinfo_s uv_getnameinfo_t; + typedef struct uv_shutdown_s uv_shutdown_t; + typedef struct uv_write_s uv_write_t; + typedef struct uv_connect_s uv_connect_t; + typedef struct uv_udp_send_s uv_udp_send_t; + typedef struct uv_fs_s uv_fs_t; + typedef struct uv_work_s uv_work_t; + Handles represent long-lived objects. Async operations on such handles are identified using **requests**. A request is short-lived (usually used across diff --git a/docs/src/guide/filesystem.rst b/docs/src/guide/filesystem.rst index 6129303..63dbbe4 100644 --- a/docs/src/guide/filesystem.rst +++ b/docs/src/guide/filesystem.rst @@ -118,8 +118,43 @@ same patterns as the read/write/open calls, returning the result in the ``uv_fs_t.result`` field. The full list: .. rubric:: Filesystem operations -.. literalinclude:: ../../../include/uv.h - :lines: 1084-1195 +.. code-block:: c + + int uv_fs_close(uv_loop_t* loop, uv_fs_t* req, uv_file file, uv_fs_cb cb); + int uv_fs_open(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags, int mode, uv_fs_cb cb); + int uv_fs_read(uv_loop_t* loop, uv_fs_t* req, uv_file file, const uv_buf_t bufs[], unsigned int nbufs, int64_t offset, uv_fs_cb cb); + int uv_fs_unlink(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb); + int uv_fs_write(uv_loop_t* loop, uv_fs_t* req, uv_file file, const uv_buf_t bufs[], unsigned int nbufs, int64_t offset, uv_fs_cb cb); + int uv_fs_copyfile(uv_loop_t* loop, uv_fs_t* req, const char* path, const char* new_path, int flags, uv_fs_cb cb); + int uv_fs_mkdir(uv_loop_t* loop, uv_fs_t* req, const char* path, int mode, uv_fs_cb cb); + int uv_fs_mkdtemp(uv_loop_t* loop, uv_fs_t* req, const char* tpl, uv_fs_cb cb); + int uv_fs_rmdir(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb); + int uv_fs_scandir(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags, uv_fs_cb cb); + int uv_fs_scandir_next(uv_fs_t* req, uv_dirent_t* ent); + int uv_fs_opendir(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb); + int uv_fs_readdir(uv_loop_t* loop, uv_fs_t* req, uv_dir_t* dir, uv_fs_cb cb); + int uv_fs_closedir(uv_loop_t* loop, uv_fs_t* req, uv_dir_t* dir, uv_fs_cb cb); + int uv_fs_stat(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb); + int uv_fs_fstat(uv_loop_t* loop, uv_fs_t* req, uv_file file, uv_fs_cb cb); + int uv_fs_rename(uv_loop_t* loop, uv_fs_t* req, const char* path, const char* new_path, uv_fs_cb cb); + int uv_fs_fsync(uv_loop_t* loop, uv_fs_t* req, uv_file file, uv_fs_cb cb); + int uv_fs_fdatasync(uv_loop_t* loop, uv_fs_t* req, uv_file file, uv_fs_cb cb); + int uv_fs_ftruncate(uv_loop_t* loop, uv_fs_t* req, uv_file file, int64_t offset, uv_fs_cb cb); + int uv_fs_sendfile(uv_loop_t* loop, uv_fs_t* req, uv_file out_fd, uv_file in_fd, int64_t in_offset, size_t length, uv_fs_cb cb); + int uv_fs_access(uv_loop_t* loop, uv_fs_t* req, const char* path, int mode, uv_fs_cb cb); + int uv_fs_chmod(uv_loop_t* loop, uv_fs_t* req, const char* path, int mode, uv_fs_cb cb); + int uv_fs_utime(uv_loop_t* loop, uv_fs_t* req, const char* path, double atime, double mtime, uv_fs_cb cb); + int uv_fs_futime(uv_loop_t* loop, uv_fs_t* req, uv_file file, double atime, double mtime, uv_fs_cb cb); + int uv_fs_lstat(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb); + int uv_fs_link(uv_loop_t* loop, uv_fs_t* req, const char* path, const char* new_path, uv_fs_cb cb); + int uv_fs_symlink(uv_loop_t* loop, uv_fs_t* req, const char* path, const char* new_path, int flags, uv_fs_cb cb); + int uv_fs_readlink(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb); + int uv_fs_realpath(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb); + int uv_fs_fchmod(uv_loop_t* loop, uv_fs_t* req, uv_file file, int mode, uv_fs_cb cb); + int uv_fs_chown(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_uid_t uid, uv_gid_t gid, uv_fs_cb cb); + int uv_fs_fchown(uv_loop_t* loop, uv_fs_t* req, uv_file file, uv_uid_t uid, uv_gid_t gid, uv_fs_cb cb); + int uv_fs_lchown(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_uid_t uid, uv_gid_t gid, uv_fs_cb cb); + .. _buffers-and-streams: @@ -254,8 +289,16 @@ The file change notification is started using ``uv_fs_event_init()``: The third argument is the actual file or directory to monitor. The last argument, ``flags``, can be: -.. literalinclude:: ../../../include/uv.h - :lines: 1299, 1308, 1315 +.. code-block:: c + + /* + * Flags to be passed to uv_fs_event_start(). + */ + enum uv_fs_event_flags { + UV_FS_EVENT_WATCH_ENTRY = 1, + UV_FS_EVENT_STAT = 2, + UV_FS_EVENT_RECURSIVE = 4 + }; ``UV_FS_EVENT_WATCH_ENTRY`` and ``UV_FS_EVENT_STAT`` don't do anything (yet). ``UV_FS_EVENT_RECURSIVE`` will start watching subdirectories as well on diff --git a/docs/src/guide/introduction.rst b/docs/src/guide/introduction.rst index f57cdd9..0a3af43 100644 --- a/docs/src/guide/introduction.rst +++ b/docs/src/guide/introduction.rst @@ -66,10 +66,10 @@ There is no need to ``make install``. To build the examples run ``make`` in the .. _Clone: https://github.com/nikhilm/uvbook .. _Download: https://github.com/nikhilm/uvbook/downloads .. _v1.3.0: https://github.com/libuv/libuv/tags -.. _V8: http://code.google.com/p/v8/ +.. _V8: https://v8.dev .. _libev: http://software.schmorp.de/pkg/libev.html .. _libuv: https://github.com/libuv/libuv -.. _node.js: http://www.nodejs.org +.. _node.js: https://www.nodejs.org .. _libev was removed: https://github.com/joyent/libuv/issues/485 -.. _Rust: http://rust-lang.org +.. _Rust: https://www.rust-lang.org .. _variety: https://github.com/libuv/libuv/wiki/Projects-that-use-libuv diff --git a/docs/src/guide/networking.rst b/docs/src/guide/networking.rst index 8d3c87b..e61f899 100644 --- a/docs/src/guide/networking.rst +++ b/docs/src/guide/networking.rst @@ -167,14 +167,15 @@ Multicast A socket can (un)subscribe to a multicast group using: -.. literalinclude:: ../../../include/uv.h - :lines: 594-597 +.. code::block:: c + + int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, const char* interface_addr, uv_membership membership); where ``membership`` is ``UV_JOIN_GROUP`` or ``UV_LEAVE_GROUP``. The concepts of multicasting are nicely explained in `this guide`_. -.. _this guide: http://www.tldp.org/HOWTO/Multicast-HOWTO-2.html +.. _this guide: https://www.tldp.org/HOWTO/Multicast-HOWTO-2.html Local loopback of multicast packets is enabled by default [#]_, use ``uv_udp_set_multicast_loop`` to switch it off. @@ -233,17 +234,17 @@ useful to allow your service to bind to IP addresses when it starts. interface has multiple IPv4/IPv6 addresses, the name will be reported multiple times, with each address being reported once. -.. _c-ares: http://c-ares.haxx.se -.. _getaddrinfo: http://www.kernel.org/doc/man-pages/online/pages/man3/getaddrinfo.3.html +.. _c-ares: https://c-ares.haxx.se +.. _getaddrinfo: https://www.kernel.org/doc/man-pages/online/pages/man3/getaddrinfo.3.html -.. _User Datagram Protocol: http://en.wikipedia.org/wiki/User_Datagram_Protocol -.. _DHCP: http://tools.ietf.org/html/rfc2131 +.. _User Datagram Protocol: https://en.wikipedia.org/wiki/User_Datagram_Protocol +.. _DHCP: https://tools.ietf.org/html/rfc2131 ---- -.. [#] http://beej.us/guide/bgnet/output/html/multipage/advanced.html#broadcast +.. [#] https://beej.us/guide/bgnet/html/#broadcast-packetshello-world .. [#] on Windows only supported on Windows Vista and later. -.. [#] http://www.tldp.org/HOWTO/Multicast-HOWTO-6.html#ss6.1 +.. [#] https://www.tldp.org/HOWTO/Multicast-HOWTO-6.html#ss6.1 .. [#] libuv use the system ``getaddrinfo`` in the libuv threadpool. libuv v0.8.0 and earlier also included c-ares_ as an alternative, but this has been removed in v0.9.0. diff --git a/docs/src/guide/processes.rst b/docs/src/guide/processes.rst index 19ce057..a780627 100644 --- a/docs/src/guide/processes.rst +++ b/docs/src/guide/processes.rst @@ -42,12 +42,10 @@ exits. This is achieved using ``uv_spawn``. The ``uv_process_t`` struct only acts as the handle, all options are set via ``uv_process_options_t``. To simply launch a process, you need to set only the ``file`` and ``args`` fields. ``file`` is the program to execute. Since -``uv_spawn`` uses execvp_ internally, there is no need to supply the full +``uv_spawn`` uses :man:`execvp(3)` internally, there is no need to supply the full path. Finally as per underlying conventions, **the arguments array has to be one larger than the number of arguments, with the last element being NULL**. -.. _execvp: http://www.kernel.org/doc/man-pages/online/pages/man3/exec.3.html - After the call to ``uv_spawn``, ``uv_process_t.pid`` will contain the process ID of the child process. @@ -181,8 +179,16 @@ The file descriptors of the child process are set using the ``stdio`` field in file descriptors being set. ``uv_process_options_t.stdio`` is an array of ``uv_stdio_container_t``, which is: -.. literalinclude:: ../../../include/uv.h - :lines: 826-834 +.. code-block:: c + + typedef struct uv_stdio_container_s { + uv_stdio_flags flags; + + union { + uv_stream_t* stream; + int fd; + } data; + } uv_stdio_container_t; where flags can have several values. Use ``UV_IGNORE`` if it isn't going to be used. If the first three ``stdio`` fields are marked as ``UV_IGNORE`` they'll @@ -205,7 +211,7 @@ to ``UV_INHERIT_STREAM`` and setting ``data.stream`` to the stream in the parent process, the child process can treat that stream as standard I/O. This can be used to implement something like CGI_. -.. _CGI: http://en.wikipedia.org/wiki/Common_Gateway_Interface +.. _CGI: https://en.wikipedia.org/wiki/Common_Gateway_Interface A sample CGI script/executable is: @@ -237,26 +243,35 @@ is great. Just be warned that creating processes is a costly task. .. _pipes: -Pipes ------ - -libuv's ``uv_pipe_t`` structure is slightly confusing to Unix programmers, -because it immediately conjures up ``|`` and `pipe(7)`_. But ``uv_pipe_t`` is -not related to anonymous pipes, rather it is an IPC mechanism. ``uv_pipe_t`` -can be backed by a `Unix Domain Socket`_ or `Windows Named Pipe`_ to allow -multiple processes to communicate. This is discussed below. - -.. _pipe(7): http://www.kernel.org/doc/man-pages/online/pages/man7/pipe.7.html -.. _Unix Domain Socket: http://www.kernel.org/doc/man-pages/online/pages/man7/unix.7.html -.. _Windows Named Pipe: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365590(v=vs.85).aspx - Parent-child IPC -++++++++++++++++ +---------------- A parent and child can have one or two way communication over a pipe created by settings ``uv_stdio_container_t.flags`` to a bit-wise combination of ``UV_CREATE_PIPE`` and ``UV_READABLE_PIPE`` or ``UV_WRITABLE_PIPE``. The -read/write flag is from the perspective of the child process. +read/write flag is from the perspective of the child process. In this case, +the ``uv_stream_t* stream`` field must be set to point to an initialized, +unopened ``uv_pipe_t`` instance. + +New stdio Pipes ++++++++++++++++ + +The ``uv_pipe_t`` structure represents more than just `pipe(7)`_ (or ``|``), +but supports any streaming file-like objects. On Windows, the only object of +that description is the `Named Pipe`_. On Unix, this could be any of `Unix +Domain Socket`_, or derived from `mkfifo(1)`_, or it could actually be a +`pipe(7)`_. When ``uv_spawn`` initializes a ``uv_pipe_t`` due to the +`UV_CREATE_PIPE` flag, it opts for creating a `socketpair(2)`_. + +This is intended for the purpose of allowing multiple libuv processes to +communicate with IPC. This is discussed below. + +.. _pipe(7): http://man7.org/linux/man-pages/man7/pipe.7.html +.. _mkfifo(1): http://man7.org/linux/man-pages/man1/mkfifo.1.html +.. _socketpair(2): http://man7.org/linux/man-pages/man2/socketpair.2.html +.. _Unix Domain Socket: http://man7.org/linux/man-pages/man7/unix.7.html +.. _Named Pipe: https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes + Arbitrary process IPC +++++++++++++++++++++ @@ -268,7 +283,7 @@ notification. Various applications can then react when a contact comes online or new hardware is detected. The MySQL server also runs a domain socket on which clients can interact with it. -.. _D-BUS: http://www.freedesktop.org/wiki/Software/dbus +.. _D-BUS: https://www.freedesktop.org/wiki/Software/dbus When using domain sockets, a client-server pattern is usually followed with the creator/owner of the socket acting as the server. After the initial setup, diff --git a/docs/src/guide/threads.rst b/docs/src/guide/threads.rst index b6a4fd8..c610d47 100644 --- a/docs/src/guide/threads.rst +++ b/docs/src/guide/threads.rst @@ -12,7 +12,7 @@ asynchronously that is actually blocking, by spawning a thread and collecting the result when it is done. Today there are two predominant thread libraries: the Windows threads -implementation and POSIX's `pthreads`_. libuv's thread API is analogous to +implementation and POSIX's :man:`pthreads(7)`. libuv's thread API is analogous to the pthreads API and often has similar semantics. A notable aspect of libuv's thread facilities is that it is a self contained @@ -68,7 +68,7 @@ Synchronization Primitives This section is purposely spartan. This book is not about threads, so I only catalogue any surprises in the libuv APIs here. For the rest you can look at -the pthreads `man pages `_. +the :man:`pthreads(7)` man pages. Mutexes ~~~~~~~ @@ -76,8 +76,14 @@ Mutexes The mutex functions are a **direct** map to the pthread equivalents. .. rubric:: libuv mutex functions -.. literalinclude:: ../../../include/uv.h - :lines: 1355-1360 +.. code-block:: c + + int uv_mutex_init(uv_mutex_t* handle); + int uv_mutex_init_recursive(uv_mutex_t* handle); + void uv_mutex_destroy(uv_mutex_t* handle); + void uv_mutex_lock(uv_mutex_t* handle); + int uv_mutex_trylock(uv_mutex_t* handle); + void uv_mutex_unlock(uv_mutex_t* handle); The ``uv_mutex_init()``, ``uv_mutex_init_recursive()`` and ``uv_mutex_trylock()`` functions will return 0 on success, and an error code otherwise. @@ -135,9 +141,9 @@ Others libuv also supports semaphores_, `condition variables`_ and barriers_ with APIs very similar to their pthread counterparts. -.. _semaphores: http://en.wikipedia.org/wiki/Semaphore_(programming) -.. _condition variables: http://en.wikipedia.org/wiki/Condition_variable#Waiting_and_signaling -.. _barriers: http://en.wikipedia.org/wiki/Barrier_(computer_science) +.. _semaphores: https://en.wikipedia.org/wiki/Semaphore_(programming) +.. _condition variables: https://en.wikipedia.org/wiki/Monitor_(synchronization)#Condition_variables_2 +.. _barriers: https://en.wikipedia.org/wiki/Barrier_(computer_science) In addition, libuv provides a convenience function ``uv_once()``. Multiple threads can attempt to call ``uv_once()`` with a given guard and a function @@ -287,7 +293,7 @@ informing the user of the status of running downloads. .. rubric:: progress/main.c .. literalinclude:: ../../code/progress/main.c :linenos: - :lines: 7-8,34- + :lines: 7-8,35- :emphasize-lines: 2,11 The async thread communication works *on loops* so although any thread can be @@ -312,7 +318,7 @@ with the async watcher whenever it receives a message. .. rubric:: progress/main.c .. literalinclude:: ../../code/progress/main.c :linenos: - :lines: 10-23 + :lines: 10-24 :emphasize-lines: 7-8 In the download function, we modify the progress indicator and queue the message @@ -322,7 +328,7 @@ non-blocking and will return immediately. .. rubric:: progress/main.c .. literalinclude:: ../../code/progress/main.c :linenos: - :lines: 30-33 + :lines: 31-34 The callback is a standard libuv pattern, extracting the data from the watcher. @@ -331,7 +337,7 @@ Finally it is important to remember to clean up the watcher. .. rubric:: progress/main.c .. literalinclude:: ../../code/progress/main.c :linenos: - :lines: 25-28 + :lines: 26-29 :emphasize-lines: 3 After this example, which showed the abuse of the ``data`` field, bnoordhuis_ @@ -373,9 +379,7 @@ which binds a third party library. It may go something like this: 4. The async callback, invoked in the main loop thread, which is the v8 thread, then interacts with v8 to invoke the JavaScript callback. -.. _pthreads: http://man7.org/linux/man-pages/man7/pthreads.7.html - ---- -.. _node.js is cancer: http://teddziuba.github.io/2011/10/node-js-is-cancer.html +.. _node.js is cancer: http://widgetsandshit.com/teddziuba/2011/10/node-js-is-cancer.html .. _bnoordhuis: https://github.com/bnoordhuis diff --git a/docs/src/guide/utilities.rst b/docs/src/guide/utilities.rst index 4d58838..a9fe56c 100644 --- a/docs/src/guide/utilities.rst +++ b/docs/src/guide/utilities.rst @@ -196,7 +196,7 @@ to access the underlying file descriptors and provide functions that process tasks in small increments as decided by your application. Some libraries though will not allow such access, providing only a standard blocking function which will perform the entire I/O transaction and only then return. It is unwise to -use these in the event loop thread, use the :ref:`libuv-work-queue` instead. Of +use these in the event loop thread, use the :ref:`threadpool` instead. Of course, this will also mean losing granular control on the library. The ``uv_poll`` section of libuv simply watches file descriptors using the @@ -210,8 +210,8 @@ download files. Rather than give all control to libcurl, we'll instead be using the libuv event loop, and use the non-blocking, async multi_ interface to progress with the download whenever libuv notifies of I/O readiness. -.. _libcurl: http://curl.haxx.se/libcurl/ -.. _multi: http://curl.haxx.se/libcurl/c/libcurl-multi.html +.. _libcurl: https://curl.haxx.se/libcurl/ +.. _multi: https://curl.haxx.se/libcurl/c/libcurl-multi.html .. rubric:: uvwget/main.c - The setup .. literalinclude:: ../../code/uvwget/main.c @@ -352,7 +352,7 @@ to get the error message. argument. ``init_plugin_function`` is a function pointer to the sort of function we are looking for in the application's plugins. -.. _shared libraries: http://en.wikipedia.org/wiki/Shared_library#Shared_libraries +.. _shared libraries: https://en.wikipedia.org/wiki/Shared_library#Shared_libraries TTY --- @@ -365,7 +365,7 @@ implement the ANSI escape codes across all platforms. By this I mean that libuv converts ANSI codes to the Windows equivalent, and provides functions to get terminal information. -.. _pretty standardised: http://en.wikipedia.org/wiki/ANSI_escape_sequences +.. _pretty standardised: https://en.wikipedia.org/wiki/ANSI_escape_sequences The first thing to do is to initialize a ``uv_tty_t`` with the file descriptor it reads/writes from. This is achieved with:: @@ -422,7 +422,7 @@ As you can see this is very useful to produce nicely formatted output, or even console based arcade games if that tickles your fancy. For fancier control you can try `ncurses`_. -.. _ncurses: http://www.gnu.org/software/ncurses/ncurses.html +.. _ncurses: https://www.gnu.org/software/ncurses/ncurses.html .. versionchanged:: 1.23.1: the `readable` parameter is now unused and ignored. The appropriate value will now be auto-detected from the kernel. @@ -431,7 +431,7 @@ can try `ncurses`_. .. [#] I was first introduced to the term baton in this context, in Konstantin Käfer's excellent slides on writing node.js bindings -- - http://kkaefer.github.com/node-cpp-modules/#baton + https://kkaefer.com/node-cpp-modules/#baton .. [#] mfp is My Fancy Plugin .. _libev man page: http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#COMMON_OR_USEFUL_IDIOMS_OR_BOTH diff --git a/docs/src/handle.rst b/docs/src/handle.rst index 86fa811..943c51d 100644 --- a/docs/src/handle.rst +++ b/docs/src/handle.rst @@ -60,6 +60,9 @@ Data types a ``UV_ENOBUFS`` error will be triggered in the :c:type:`uv_udp_recv_cb` or the :c:type:`uv_read_cb` callback. + Each buffer is used only once and the user is responsible for freeing it in the + :c:type:`uv_udp_recv_cb` or the :c:type:`uv_read_cb` callback. + A suggested size (65536 at the moment in most cases) is provided, but it's just an indication, not related in any way to the pending data to be read. The user is free to allocate the amount of memory they decide. @@ -87,7 +90,7 @@ Public members .. c:member:: uv_loop_t* uv_handle_t.loop - Pointer to the :c:type:`uv_loop_t` where the handle is running on. Readonly. + Pointer to the :c:type:`uv_loop_t` the handle is running on. Readonly. .. c:member:: uv_handle_type uv_handle_t.type @@ -140,6 +143,8 @@ API Request handle to be closed. `close_cb` will be called asynchronously after this call. This MUST be called on each handle before memory is released. + Moreover, the memory can only be released in `close_cb` or after it has + returned. Handles that wrap file descriptors are closed immediately but `close_cb` will still be deferred to the next iteration of the event loop. @@ -185,8 +190,11 @@ just for some handle types. Gets or sets the size of the send buffer that the operating system uses for the socket. - If `*value` == 0, it will return the current send buffer size, - otherwise it will use `*value` to set the new send buffer size. + If `*value` == 0, then it will set `*value` to the current send buffer size. + If `*value` > 0 then it will use `*value` to set the new send buffer size. + + On success, zero is returned. On error, a negative result is + returned. This function works for TCP, pipe and UDP handles on Unix and for TCP and UDP handles on Windows. @@ -199,8 +207,11 @@ just for some handle types. Gets or sets the size of the receive buffer that the operating system uses for the socket. - If `*value` == 0, it will return the current receive buffer size, - otherwise it will use `*value` to set the new receive buffer size. + If `*value` == 0, then it will set `*value` to the current receive buffer size. + If `*value` > 0 then it will use `*value` to set the new receive buffer size. + + On success, zero is returned. On error, a negative result is + returned. This function works for TCP, pipe and UDP handles on Unix and for TCP and UDP handles on Windows. diff --git a/docs/src/idle.rst b/docs/src/idle.rst index 1f51c4a..b7a0507 100644 --- a/docs/src/idle.rst +++ b/docs/src/idle.rst @@ -41,14 +41,22 @@ API .. c:function:: int uv_idle_init(uv_loop_t* loop, uv_idle_t* idle) - Initialize the handle. + Initialize the handle. This function always succeeds. + + :returns: 0 .. c:function:: int uv_idle_start(uv_idle_t* idle, uv_idle_cb cb) - Start the handle with the given callback. + Start the handle with the given callback. This function always succeeds, + except when `cb` is `NULL`. + + :returns: 0 on success, or `UV_EINVAL` when `cb == NULL`. .. c:function:: int uv_idle_stop(uv_idle_t* idle) Stop the handle, the callback will no longer be called. + This function always succeeds. + + :returns: 0 .. seealso:: The :c:type:`uv_handle_t` API functions also apply. diff --git a/docs/src/index.rst b/docs/src/index.rst index 5ec2beb..f696dc1 100644 --- a/docs/src/index.rst +++ b/docs/src/index.rst @@ -13,9 +13,9 @@ was primarily developed for use by `Node.js`_, but it's also used by `Luvit`_, In case you find errors in this documentation you can help by sending `pull requests `_! -.. _Node.js: http://nodejs.org -.. _Luvit: http://luvit.io -.. _Julia: http://julialang.org +.. _Node.js: https://nodejs.org +.. _Luvit: https://luvit.io +.. _Julia: https://julialang.org .. _pyuv: https://github.com/saghul/pyuv .. _others: https://github.com/libuv/libuv/wiki/Projects-that-use-libuv @@ -52,7 +52,7 @@ Documentation Downloads --------- -libuv can be downloaded from `here `_. +libuv can be downloaded from `here `_. Installation diff --git a/docs/src/loop.rst b/docs/src/loop.rst index 86a99ad..d642ac1 100644 --- a/docs/src/loop.rst +++ b/docs/src/loop.rst @@ -107,6 +107,8 @@ API or requests left), or non-zero if more callbacks are expected (meaning you should run the event loop again sometime in the future). + :c:func:`uv_run` is not reentrant. It must not be called from a callback. + .. c:function:: int uv_loop_alive(const uv_loop_t* loop) Returns non-zero if there are referenced active handles, active diff --git a/docs/src/misc.rst b/docs/src/misc.rst index cf4a789..906ca8f 100644 --- a/docs/src/misc.rst +++ b/docs/src/misc.rst @@ -46,6 +46,12 @@ Data types Replacement function for :man:`free(3)`. See :c:func:`uv_replace_allocator`. +.. c:type:: void (*uv_random_cb)(uv_random_t* req, int status, void* buf, size_t buflen) + + Callback passed to :c:func:`uv_random`. `status` is non-zero in case of + error. The `buf` pointer is the same pointer that was passed to + :c:func:`uv_random`. + .. c:type:: uv_file Cross platform representation of a file handle. @@ -65,6 +71,28 @@ Data types .. versionadded:: 1.16.0 +.. c:type:: uv_timeval_t + + Data type for storing times. + + :: + + typedef struct { + long tv_sec; + long tv_usec; + } uv_timeval_t; + +.. c:type:: uv_timeval64_t + + Alternative data type for storing times. + + :: + + typedef struct { + int64_t tv_sec; + int32_t tv_usec; + } uv_timeval64_t; + .. c:type:: uv_rusage_t Data type for resource usage results. @@ -103,11 +131,11 @@ Data types char* model; int speed; struct uv_cpu_times_s { - uint64_t user; - uint64_t nice; - uint64_t sys; - uint64_t idle; - uint64_t irq; + uint64_t user; /* milliseconds */ + uint64_t nice; /* milliseconds */ + uint64_t sys; /* milliseconds */ + uint64_t idle; /* milliseconds */ + uint64_t irq; /* milliseconds */ } cpu_times; } uv_cpu_info_t; @@ -145,6 +173,33 @@ Data types char* homedir; } uv_passwd_t; +.. c:type:: uv_utsname_t + + Data type for operating system name and version information. + + :: + + typedef struct uv_utsname_s { + char sysname[256]; + char release[256]; + char version[256]; + char machine[256]; + } uv_utsname_t; + +.. c:type:: uv_env_item_t + + Data type for environment variable storage. + + :: + + typedef struct uv_env_item_s { + char* name; + char* value; + } uv_env_item_t; + +.. c:type:: uv_random_t + + Random data request type. API --- @@ -178,6 +233,24 @@ API sure the allocator is changed while no memory was allocated with the previous allocator, or that they are compatible. + .. warning:: Allocator must be thread-safe. + +.. c:function:: void uv_library_shutdown(void); + + .. versionadded:: 1.38.0 + + Release any global state that libuv is holding onto. Libuv will normally + 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:: 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:function:: uv_buf_t uv_buf_init(char* base, unsigned int len) Constructor for :c:type:`uv_buf_t`. @@ -189,6 +262,15 @@ API .. c:function:: char** uv_setup_args(int argc, char** argv) Store the program arguments. Required for getting / setting the process title. + Libuv may take ownership of the memory that `argv` points to. This function + should be called exactly once, at program start-up. + + Example: + + :: + + argv = uv_setup_args(argc, argv); /* May return a copy of argv. */ + .. c:function:: int uv_get_process_title(char* buffer, size_t size) @@ -259,7 +341,7 @@ API .. c:function:: void uv_loadavg(double avg[3]) - Gets the load average. See: ``_ + Gets the load average. See: ``_ .. note:: Returns [0,0,0] on Windows (i.e., it's not implemented). @@ -418,11 +500,27 @@ API .. versionadded:: 1.9.0 -.. uint64_t uv_get_free_memory(void) +.. c:function:: uint64_t uv_get_free_memory(void) + + Gets memory information (in bytes). + .. c:function:: uint64_t uv_get_total_memory(void) Gets memory information (in bytes). +.. c:function:: uint64_t uv_get_constrained_memory(void) + + Gets the amount of memory available to the process (in bytes) based on + limits imposed by the OS. If there is no such constraint, or the constraint + is unknown, `0` is returned. Note that it is not unusual for this value to + be less than or greater than :c:func:`uv_get_total_memory`. + + .. note:: + This function currently only returns a non-zero value on Linux, based + on cgroups if it is present. + + .. versionadded:: 1.29.0 + .. c:function:: uint64_t uv_hrtime(void) Returns the current high-resolution real time. This is expressed in @@ -472,6 +570,23 @@ API .. versionadded:: 1.8.0 +.. c:function:: int uv_os_environ(uv_env_item_t** envitems, int* count) + + Retrieves all environment variables. This function will allocate memory + which must be freed by calling :c:func:`uv_os_free_environ`. + + .. warning:: + This function is not thread safe. + + .. versionadded:: 1.31.0 + +.. c:function:: void uv_os_free_environ(uv_env_item_t* envitems, int count); + + Frees the memory allocated for the environment variables by + :c:func:`uv_os_environ`. + + .. versionadded:: 1.31.0 + .. c:function:: int uv_os_getenv(const char* name, char* buffer, size_t* size) Retrieves the environment variable specified by `name`, copies its value @@ -518,6 +633,10 @@ API .. versionadded:: 1.12.0 + .. versionchanged:: 1.26.0 `UV_MAXHOSTNAMESIZE` is available and represents + the maximum `buffer` size required to store a + hostname and terminating `nul` character. + .. c:function:: int uv_os_getpriority(uv_pid_t pid, int* priority) Retrieves the scheduling priority of the process specified by `pid`. The @@ -548,4 +667,73 @@ API On Windows, setting `PRIORITY_HIGHEST` will only work for elevated user, for others it will be silently reduced to `PRIORITY_HIGH`. + .. note:: + On IBM i PASE, the highest process priority is -10. The constant + `UV_PRIORITY_HIGHEST` is -10, `UV_PRIORITY_HIGH` is -7, + `UV_PRIORITY_ABOVE_NORMAL` is -4, `UV_PRIORITY_NORMAL` is 0, + `UV_PRIORITY_BELOW_NORMAL` is 15 and `UV_PRIORITY_LOW` is 39. + + .. note:: + On IBM i PASE, you are not allowed to change your priority unless you + have the \*JOBCTL special authority (even to lower it). + .. versionadded:: 1.23.0 + +.. c:function:: int uv_os_uname(uv_utsname_t* buffer) + + Retrieves system information in `buffer`. The populated data includes the + operating system name, release, version, and machine. On non-Windows + systems, `uv_os_uname()` is a thin wrapper around :man:`uname(2)`. Returns + zero on success, and a non-zero error value otherwise. + + .. versionadded:: 1.25.0 + +.. c:function:: int uv_gettimeofday(uv_timeval64_t* tv) + + Cross-platform implementation of :man:`gettimeofday(2)`. The timezone + argument to `gettimeofday()` is not supported, as it is considered obsolete. + + .. versionadded:: 1.28.0 + +.. c:function:: int uv_random(uv_loop_t* loop, uv_random_t* req, void* buf, size_t buflen, unsigned int flags, uv_random_cb cb) + + Fill `buf` with exactly `buflen` cryptographically strong random bytes + acquired from the system CSPRNG. `flags` is reserved for future extension + and must currently be 0. + + Short reads are not possible. When less than `buflen` random bytes are + available, a non-zero error value is returned or passed to the callback. + + The synchronous version may block indefinitely when not enough entropy + is available. The asynchronous version may not ever finish when the system + is low on entropy. + + Sources of entropy: + + - Windows: `RtlGenRandom _`. + - Linux, Android: :man:`getrandom(2)` if available, or :man:`urandom(4)` + after reading from `/dev/random` once, or the `KERN_RANDOM` + :man:`sysctl(2)`. + - FreeBSD: `getrandom(2) _`, + or `/dev/urandom` after reading from `/dev/random` once. + - NetBSD: `KERN_ARND` `sysctl(3) _` + - macOS, OpenBSD: `getentropy(2) _` + if available, or `/dev/urandom` after reading from `/dev/random` once. + - AIX: `/dev/random`. + - IBM i: `/dev/urandom`. + - Other UNIX: `/dev/urandom` after reading from `/dev/random` once. + + :returns: 0 on success, or an error code < 0 on failure. The contents of + `buf` is undefined after an error. + + .. note:: + When using the synchronous version, both `loop` and `req` parameters + are not used and can be set to `NULL`. + + .. versionadded:: 1.33.0 + +.. c:function:: void uv_sleep(unsigned int msec) + + Causes the calling thread to sleep for `msec` milliseconds. + + .. versionadded:: 1.34.0 diff --git a/docs/src/pipe.rst b/docs/src/pipe.rst index 376d611..6437a9d 100644 --- a/docs/src/pipe.rst +++ b/docs/src/pipe.rst @@ -4,8 +4,8 @@ :c:type:`uv_pipe_t` --- Pipe handle =================================== -Pipe handles provide an abstraction over local domain sockets on Unix and named -pipes on Windows. +Pipe handles provide an abstraction over streaming files on Unix (including +local domain sockets, pipes, and FIFOs) and named pipes on Windows. :c:type:`uv_pipe_t` is a 'subclass' of :c:type:`uv_stream_t`. @@ -24,6 +24,8 @@ Public members .. c:member:: int uv_pipe_t.ipc Whether this pipe is suitable for handle passing between processes. + Only a connected pipe that will be passing the handles should have this flag + set, not the listening pipe that uv_accept is called on. .. seealso:: The :c:type:`uv_stream_t` members also apply. @@ -34,7 +36,10 @@ API .. c:function:: int uv_pipe_init(uv_loop_t* loop, uv_pipe_t* handle, int ipc) Initialize a pipe handle. The `ipc` argument is a boolean to indicate if - this pipe will be used for handle passing between processes. + this pipe will be used for handle passing between processes (which may + change the bytes on the wire). Only a connected pipe that will be + passing the handles should have this flag set, not the listening pipe + that uv_accept is called on. .. c:function:: int uv_pipe_open(uv_pipe_t* handle, uv_file file) diff --git a/docs/src/prepare.rst b/docs/src/prepare.rst index aca5815..5e0d247 100644 --- a/docs/src/prepare.rst +++ b/docs/src/prepare.rst @@ -33,14 +33,22 @@ API .. c:function:: int uv_prepare_init(uv_loop_t* loop, uv_prepare_t* prepare) - Initialize the handle. + Initialize the handle. This function always succeeds. + + :returns: 0 .. c:function:: int uv_prepare_start(uv_prepare_t* prepare, uv_prepare_cb cb) - Start the handle with the given callback. + Start the handle with the given callback. This function always succeeds, + except when `cb` is `NULL`. + + :returns: 0 on success, or `UV_EINVAL` when `cb == NULL`. .. c:function:: int uv_prepare_stop(uv_prepare_t* prepare) Stop the handle, the callback will no longer be called. + This function always succeeds. + + :returns: 0 .. seealso:: The :c:type:`uv_handle_t` API functions also apply. diff --git a/docs/src/process.rst b/docs/src/process.rst index bc96855..f2b3be2 100644 --- a/docs/src/process.rst +++ b/docs/src/process.rst @@ -70,11 +70,22 @@ Data types */ UV_PROCESS_DETACHED = (1 << 3), /* - * Hide the subprocess console window that would normally be created. This + * Hide the subprocess window that would normally be created. This option is + * only meaningful on Windows systems. On Unix it is silently ignored. + */ + UV_PROCESS_WINDOWS_HIDE = (1 << 4), + /* + * Hide the subprocess console window that would normally be created. This + * option is only meaningful on Windows systems. On Unix it is silently + * ignored. + */ + UV_PROCESS_WINDOWS_HIDE_CONSOLE = (1 << 5), + /* + * Hide the subprocess GUI window that would normally be created. This * option is only meaningful on Windows systems. On Unix it is silently * ignored. */ - UV_PROCESS_WINDOWS_HIDE = (1 << 4) + UV_PROCESS_WINDOWS_HIDE_GUI = (1 << 6) }; .. c:type:: uv_stdio_container_t @@ -217,6 +228,9 @@ API setgid specified, or not having enough memory to allocate for the new process. + .. versionchanged:: 1.24.0 Added `UV_PROCESS_WINDOWS_HIDE_CONSOLE` and + `UV_PROCESS_WINDOWS_HIDE_GUI` flags. + .. c:function:: int uv_process_kill(uv_process_t* handle, int signum) Sends the specified signal to the given process handle. Check the documentation diff --git a/docs/src/request.rst b/docs/src/request.rst index 5603828..5807ccb 100644 --- a/docs/src/request.rst +++ b/docs/src/request.rst @@ -69,8 +69,8 @@ API 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` and :c:type:`uv_work_t` requests is - currently supported. + :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 @@ -80,8 +80,9 @@ API * A :c:type:`uv_fs_t` request has its req->result field set to `UV_ECANCELED`. - * A :c:type:`uv_work_t`, :c:type:`uv_getaddrinfo_t` or c:type:`uv_getnameinfo_t` - request has its callback invoked with status == `UV_ECANCELED`. + * A :c:type:`uv_work_t`, :c:type:`uv_getaddrinfo_t`, + :c:type:`uv_getnameinfo_t` or :c:type:`uv_random_t` request has its + callback invoked with status == `UV_ECANCELED`. .. c:function:: size_t uv_req_size(uv_req_type type) diff --git a/docs/src/signal.rst b/docs/src/signal.rst index f52b647..eeadb95 100644 --- a/docs/src/signal.rst +++ b/docs/src/signal.rst @@ -6,7 +6,10 @@ Signal handles implement Unix style signal handling on a per-event loop bases. -Reception of some signals is emulated on Windows: +Windows notes +------------- + +Reception of some signals is emulated: * SIGINT is normally delivered when the user presses CTRL+C. However, like on Unix, it is not generated when terminal raw mode is enabled. @@ -17,6 +20,15 @@ Reception of some signals is emulated on Windows: program is given approximately 10 seconds to perform cleanup. After that Windows will unconditionally terminate it. +* SIGWINCH is raised whenever libuv detects that the console has been + resized. When a libuv app is running under a console emulator, or when a + 32-bit libuv app is running on 64-bit system, SIGWINCH will be emulated. In + such cases SIGWINCH signals may not always be delivered in a timely manner. + For a writable :c:type:`uv_tty_t` handle libuv will only detect size changes + when the cursor is moved. When a readable :c:type:`uv_tty_t` handle is used, + resizing of the console buffer will be detected only if the handle is in raw + mode and is being read. + * Watchers for other signals can be successfully created, but these signals are never received. These signals are: `SIGILL`, `SIGABRT`, `SIGFPE`, `SIGSEGV`, `SIGTERM` and `SIGKILL.` @@ -24,12 +36,23 @@ Reception of some signals is emulated on Windows: * Calls to raise() or abort() to programmatically raise a signal are not detected by libuv; these will not trigger a signal watcher. -.. note:: - On Linux SIGRT0 and SIGRT1 (signals 32 and 33) are used by the NPTL pthreads library to - manage threads. Installing watchers for those signals will lead to unpredictable behavior - and is strongly discouraged. Future versions of libuv may simply reject them. - .. versionchanged:: 1.15.0 SIGWINCH support on Windows was improved. +.. versionchanged:: 1.31.0 32-bit libuv SIGWINCH support on 64-bit Windows was + rolled back to old implementation. + +Unix notes +---------- + +* SIGKILL and SIGSTOP are impossible to catch. + +* Handling SIGBUS, SIGFPE, SIGILL or SIGSEGV via libuv results into undefined behavior. + +* SIGABRT will not be caught by libuv if generated by `abort()`, e.g. through `assert()`. + +* On Linux SIGRT0 and SIGRT1 (signals 32 and 33) are used by the NPTL pthreads library to + manage threads. Installing watchers for those signals will lead to unpredictable behavior + and is strongly discouraged. Future versions of libuv may simply reject them. + Data types ---------- diff --git a/docs/src/sphinx-plugins/manpage.py b/docs/src/sphinx-plugins/manpage.py index 1d1dc37..672b002 100644 --- a/docs/src/sphinx-plugins/manpage.py +++ b/docs/src/sphinx-plugins/manpage.py @@ -18,7 +18,7 @@ from string import Template def make_link_node(rawtext, app, name, manpage_num, options): ref = app.config.man_url_regex if not ref: - ref = "http://linux.die.net/man/%s/%s" % (manpage_num, name) + ref = "http://man7.org/linux/man-pages/man%s/%s.%s.html" %(manpage_num, name, manpage_num) else: s = Template(ref) ref = s.substitute(num=manpage_num, topic=name) @@ -39,7 +39,6 @@ def man_role(name, rawtext, text, lineno, inliner, options={}, content=[]): def setup(app): - app.info('Initializing manpage plugin') app.add_role('man', man_role) app.add_config_value('man_url_regex', None, 'env') return diff --git a/docs/src/static/diagrams.key/Data/st0-311.jpg b/docs/src/static/diagrams.key/Data/st0-311.jpg index 439f581..08f23a9 100644 Binary files a/docs/src/static/diagrams.key/Data/st0-311.jpg and b/docs/src/static/diagrams.key/Data/st0-311.jpg differ diff --git a/docs/src/static/diagrams.key/Data/st1-475.jpg b/docs/src/static/diagrams.key/Data/st1-475.jpg index ffb21ff..26e676a 100644 Binary files a/docs/src/static/diagrams.key/Data/st1-475.jpg and b/docs/src/static/diagrams.key/Data/st1-475.jpg differ diff --git a/docs/src/stream.rst b/docs/src/stream.rst index 6a70436..2ccb59b 100644 --- a/docs/src/stream.rst +++ b/docs/src/stream.rst @@ -50,8 +50,8 @@ Data types from the stream again is undefined. The callee is responsible for freeing the buffer, libuv does not reuse it. - The buffer may be a null buffer (where buf->base=NULL and buf->len=0) on - error. + The buffer may be a null buffer (where `buf->base` == NULL and `buf->len` == 0) + on error. .. c:type:: void (*uv_write_cb)(uv_write_t* req, int status) diff --git a/docs/src/tcp.rst b/docs/src/tcp.rst index d20a636..3cc8efa 100644 --- a/docs/src/tcp.rst +++ b/docs/src/tcp.rst @@ -60,6 +60,11 @@ API Enable / disable TCP keep-alive. `delay` is the initial delay in seconds, ignored when `enable` is zero. + After `delay` has been reached, 10 successive probes, each spaced 1 second + from the previous one, will still happen. If the connection is still lost + at the end of this procedure, then the handle is destroyed with a + ``UV_ETIMEDOUT`` error passed to the corresponding callback. + .. c:function:: int uv_tcp_simultaneous_accepts(uv_tcp_t* handle, int enable) Enable / disable simultaneous asynchronous accept requests that are @@ -113,3 +118,13 @@ API mapping .. seealso:: The :c:type:`uv_stream_t` API functions also apply. + +.. c:function:: int uv_tcp_close_reset(uv_tcp_t* handle, uv_close_cb close_cb) + + Resets a TCP connection by sending a RST packet. This is accomplished by + setting the `SO_LINGER` socket option with a linger interval of zero and + then calling :c:func:`uv_close`. + Due to some platform inconsistencies, mixing of :c:func:`uv_shutdown` and + :c:func:`uv_tcp_close_reset` calls is not allowed. + + .. versionadded:: 1.32.0 diff --git a/docs/src/threading.rst b/docs/src/threading.rst index 89bb4a6..7ca1d4b 100644 --- a/docs/src/threading.rst +++ b/docs/src/threading.rst @@ -55,10 +55,39 @@ API Threads ^^^^^^^ +.. c:type:: uv_thread_options_t + + Options for spawning a new thread (passed to :c:func:`uv_thread_create_ex`). + + :: + + typedef struct uv_thread_options_s { + enum { + UV_THREAD_NO_FLAGS = 0x00, + UV_THREAD_HAS_STACK_SIZE = 0x01 + } flags; + size_t stack_size; + } uv_thread_options_t; + + More fields may be added to this struct at any time, so its exact + layout and size should not be relied upon. + + .. versionadded:: 1.26.0 + .. c:function:: int uv_thread_create(uv_thread_t* tid, uv_thread_cb entry, void* arg) .. versionchanged:: 1.4.1 returns a UV_E* error code on failure +.. c:function:: int uv_thread_create_ex(uv_thread_t* tid, const uv_thread_options_t* params, uv_thread_cb entry, void* arg) + + Like :c:func:`uv_thread_create`, but additionally specifies options for creating a new thread. + + If `UV_THREAD_HAS_STACK_SIZE` is set, `stack_size` specifies a stack size for the new thread. + `0` indicates that the default value should be used, i.e. behaves as if the flag was not set. + Other values will be rounded up to the nearest page boundary. + + .. versionadded:: 1.26.0 + .. c:function:: uv_thread_t uv_thread_self(void) .. c:function:: int uv_thread_join(uv_thread_t *tid) .. c:function:: int uv_thread_equal(const uv_thread_t* t1, const uv_thread_t* t2) diff --git a/docs/src/threadpool.rst b/docs/src/threadpool.rst index 93bd236..cf6cdc1 100644 --- a/docs/src/threadpool.rst +++ b/docs/src/threadpool.rst @@ -10,7 +10,9 @@ operations, as well as getaddrinfo and getnameinfo requests. Its default size is 4, but it can be changed at startup time by setting the ``UV_THREADPOOL_SIZE`` environment variable to any value (the absolute maximum -is 128). +is 1024). + +.. versionchanged:: 1.30.0 the maximum UV_THREADPOOL_SIZE allowed was increased from 128 to 1024. The threadpool is global and shared across all event loops. When a particular function makes use of the threadpool (i.e. when using :c:func:`uv_queue_work`) diff --git a/docs/src/tty.rst b/docs/src/tty.rst index 9889a0a..ad379da 100644 --- a/docs/src/tty.rst +++ b/docs/src/tty.rst @@ -33,6 +33,23 @@ Data types UV_TTY_MODE_IO } uv_tty_mode_t; +.. c:type:: uv_tty_vtermstate_t + Console virtual terminal mode type: + + :: + + typedef enum { + /* + * The console supports handling of virtual terminal sequences + * (Windows10 new console, ConEmu) + */ + UV_TTY_SUPPORTED, + /* The console cannot process virtual terminal sequences. (Legacy + * console) + */ + UV_TTY_UNSUPPORTED + } uv_tty_vtermstate_t + Public members @@ -98,3 +115,25 @@ API Gets the current Window size. On success it returns 0. .. seealso:: The :c:type:`uv_stream_t` API functions also apply. + +.. c:function:: void uv_tty_set_vterm_state(uv_tty_vtermstate_t state) + + Controls whether console virtual terminal sequences are processed by libuv + or console. + Useful in particular for enabling ConEmu support of ANSI X3.64 and Xterm + 256 colors. Otherwise Windows10 consoles are usually detected automatically. + + This function is only meaningful on Windows systems. On Unix it is silently + ignored. + + .. versionadded:: 1.33.0 + +.. c:function:: int uv_tty_get_vterm_state(uv_tty_vtermstate_t* state) + + Get the current state of whether console virtual terminal sequences are + handled by libuv or the console. + + This function is not implemented on Unix, where it returns ``UV_ENOTSUP``. + + .. versionadded:: 1.33.0 + diff --git a/docs/src/udp.rst b/docs/src/udp.rst index 8148828..6be2034 100644 --- a/docs/src/udp.rst +++ b/docs/src/udp.rst @@ -41,7 +41,16 @@ Data types * (provided they all set the flag) but only the last one to bind will receive * any traffic, in effect "stealing" the port from the previous listener. */ - UV_UDP_REUSEADDR = 4 + UV_UDP_REUSEADDR = 4, + /* + * Indicates that the message was received by recvmmsg, so the buffer provided + * must not be freed by the recv_cb callback. + */ + UV_UDP_MMSG_CHUNK = 8, + /* + * Indicates that recvmmsg should be used, if available. + */ + UV_UDP_RECVMMSG = 256 }; .. c:type:: void (*uv_udp_send_cb)(uv_udp_send_t* req, int status) @@ -56,15 +65,23 @@ Data types * `handle`: UDP handle * `nread`: Number of bytes that have been received. - 0 if there is no more data to read. You may discard or repurpose - the read buffer. Note that 0 may also mean that an empty datagram - was received (in this case `addr` is not NULL). < 0 if a transmission - error was detected. + 0 if there is no more data to read. Note that 0 may also mean that an + empty datagram was received (in this case `addr` is not NULL). < 0 if + a transmission error was detected. * `buf`: :c:type:`uv_buf_t` with the received data. * `addr`: ``struct sockaddr*`` containing the address of the sender. Can be NULL. Valid for the duration of the callback only. - * `flags`: One or more or'ed UV_UDP_* constants. Right now only - ``UV_UDP_PARTIAL`` is used. + * `flags`: One or more or'ed UV_UDP_* constants. + + The callee is responsible for freeing the buffer, libuv does not reuse it. + The buffer may be a null buffer (where `buf->base` == NULL and `buf->len` == 0) + on error. + + When using :man:`recvmmsg(2)`, chunks will have the `UV_UDP_MMSG_CHUNK` flag set, + those must not be freed. There will be a final callback with `nread` set to 0, + `addr` set to NULL and the buffer pointing at the initially allocated data with + the `UV_UDP_MMSG_CHUNK` flag cleared. This is a good chance for the callee to + free the provided buffer. .. note:: The receive callback will be called with `nread` == 0 and `addr` == NULL when there is @@ -112,12 +129,17 @@ API .. c:function:: int uv_udp_init_ex(uv_loop_t* loop, uv_udp_t* handle, unsigned int flags) - Initialize the handle with the specified flags. At the moment the lower 8 bits - of the `flags` parameter are used as the socket domain. A socket will be created - for the given domain. If the specified domain is ``AF_UNSPEC`` no socket is created, - just like :c:func:`uv_udp_init`. + Initialize the handle with the specified flags. The lower 8 bits of the `flags` + parameter are used as the socket domain. A socket will be created for the given domain. + If the specified domain is ``AF_UNSPEC`` no socket is created, just like :c:func:`uv_udp_init`. + + 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. .. versionadded:: 1.7.0 + .. versionchanged:: 1.37.0 added the `UV_UDP_RECVMMSG` flag. .. c:function:: int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock) @@ -150,6 +172,44 @@ API :returns: 0 on success, or an error code < 0 on failure. +.. c:function:: int uv_udp_connect(uv_udp_t* handle, const struct sockaddr* addr) + + Associate the UDP handle to a remote address and port, so every + message sent by this handle is automatically sent to that destination. + Calling this function with a `NULL` `addr` disconnects the handle. + Trying to call `uv_udp_connect()` on an already connected handle will result + in an `UV_EISCONN` error. Trying to disconnect a handle that is not + connected will return an `UV_ENOTCONN` error. + + :param handle: UDP handle. Should have been initialized with + :c:func:`uv_udp_init`. + + :param addr: `struct sockaddr_in` or `struct sockaddr_in6` + with the address and port to associate to. + + :returns: 0 on success, or an error code < 0 on failure. + + .. versionadded:: 1.27.0 + +.. c:function:: int uv_udp_getpeername(const uv_udp_t* handle, struct sockaddr* name, int* namelen) + + Get the remote IP and port of the UDP handle on connected UDP handles. + On unconnected handles, it returns `UV_ENOTCONN`. + + :param handle: UDP handle. Should have been initialized with + :c:func:`uv_udp_init` and bound. + + :param name: Pointer to the structure to be filled with the address data. + In order to support IPv4 and IPv6 `struct sockaddr_storage` should be + used. + + :param namelen: On input it indicates the data of the `name` field. On + output it indicates how much of it was filled. + + :returns: 0 on success, or an error code < 0 on failure + + .. versionadded:: 1.27.0 + .. c:function:: int uv_udp_getsockname(const uv_udp_t* handle, struct sockaddr* name, int* namelen) Get the local IP and port of the UDP handle. @@ -181,6 +241,25 @@ API :returns: 0 on success, or an error code < 0 on failure. +.. c:function:: int uv_udp_set_source_membership(uv_udp_t* handle, const char* multicast_addr, const char* interface_addr, const char* source_addr, uv_membership membership) + + Set membership for a source-specific multicast group. + + :param handle: UDP handle. Should have been initialized with + :c:func:`uv_udp_init`. + + :param multicast_addr: Multicast address to set membership for. + + :param interface_addr: Interface address. + + :param source_addr: Source address. + + :param membership: Should be ``UV_JOIN_GROUP`` or ``UV_LEAVE_GROUP``. + + :returns: 0 on success, or an error code < 0 on failure. + + .. versionadded:: 1.32.0 + .. c:function:: int uv_udp_set_multicast_loop(uv_udp_t* handle, int on) Set IP multicast loop flag. Makes multicast packets loop back to @@ -247,6 +326,12 @@ API (``0.0.0.0`` or ``::``) it will be changed to point to ``localhost``. This is done to match the behavior of Linux systems. + For connected UDP handles, `addr` must be set to `NULL`, otherwise it will + return `UV_EISCONN` error. + + For connectionless UDP handles, `addr` cannot be `NULL`, otherwise it will + return `UV_EDESTADDRREQ` error. + :param req: UDP request handle. Need not be initialized. :param handle: UDP handle. Should have been initialized with @@ -266,15 +351,25 @@ API .. versionchanged:: 1.19.0 added ``0.0.0.0`` and ``::`` to ``localhost`` mapping + .. versionchanged:: 1.27.0 added support for connected sockets + .. c:function:: int uv_udp_try_send(uv_udp_t* handle, const uv_buf_t bufs[], unsigned int nbufs, const struct sockaddr* addr) Same as :c:func:`uv_udp_send`, but won't queue a send request if it can't be completed immediately. + For connected UDP handles, `addr` must be set to `NULL`, otherwise it will + return `UV_EISCONN` error. + + For connectionless UDP handles, `addr` cannot be `NULL`, otherwise it will + return `UV_EDESTADDRREQ` error. + :returns: >= 0: number of bytes sent (it matches the given buffer size). < 0: negative error code (``UV_EAGAIN`` is returned when the message can't be sent immediately). + .. versionchanged:: 1.27.0 added support for connected sockets + .. c:function:: int uv_udp_recv_start(uv_udp_t* handle, uv_alloc_cb alloc_cb, uv_udp_recv_cb recv_cb) Prepare for receiving data. If the socket has not previously been bound @@ -290,6 +385,13 @@ API :returns: 0 on success, or an error code < 0 on failure. + .. 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`. + .. 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`. + .. c:function:: int uv_udp_recv_stop(uv_udp_t* handle) Stop listening for incoming datagrams. diff --git a/docs/src/version.rst b/docs/src/version.rst index e1715b2..13b0940 100644 --- a/docs/src/version.rst +++ b/docs/src/version.rst @@ -10,7 +10,7 @@ a major release. In this section you'll find all macros and functions that will allow you to write or compile code conditionally, in order to work with multiple libuv versions. -.. _semantic versioning: http://semver.org +.. _semantic versioning: https://semver.org Macros diff --git a/include/uv.h b/include/uv.h index 717c2e5..fec6631 100644 --- a/include/uv.h +++ b/include/uv.h @@ -27,6 +27,10 @@ extern "C" { #endif +#if defined(BUILDING_UV_SHARED) && defined(USING_UV_SHARED) +#error "Define either BUILDING_UV_SHARED or USING_UV_SHARED, not both." +#endif + #ifdef _WIN32 /* Windows - set up dll import/export decorators. */ # if defined(BUILDING_UV_SHARED) @@ -143,6 +147,7 @@ extern "C" { XX(EREMOTEIO, "remote I/O error") \ XX(ENOTTY, "inappropriate ioctl for device") \ XX(EFTYPE, "inappropriate file type or format") \ + XX(EILSEQ, "illegal byte sequence") \ #define UV_HANDLE_TYPE_MAP(XX) \ XX(ASYNC, async) \ @@ -172,6 +177,7 @@ extern "C" { XX(WORK, work) \ XX(GETADDRINFO, getaddrinfo) \ XX(GETNAMEINFO, getnameinfo) \ + XX(RANDOM, random) \ typedef enum { #define XX(code, _) UV_ ## code = UV__ ## code, @@ -202,6 +208,7 @@ typedef enum { /* Handle types. */ typedef struct uv_loop_s uv_loop_t; typedef struct uv_handle_s uv_handle_t; +typedef struct uv_dir_s uv_dir_t; typedef struct uv_stream_s uv_stream_t; typedef struct uv_tcp_s uv_tcp_t; typedef struct uv_udp_s uv_udp_t; @@ -228,12 +235,16 @@ typedef struct uv_connect_s uv_connect_t; typedef struct uv_udp_send_s uv_udp_send_t; typedef struct uv_fs_s uv_fs_t; typedef struct uv_work_s uv_work_t; +typedef struct uv_random_s uv_random_t; /* None of the above. */ +typedef struct uv_env_item_s uv_env_item_t; typedef struct uv_cpu_info_s uv_cpu_info_t; typedef struct uv_interface_address_s uv_interface_address_t; typedef struct uv_dirent_s uv_dirent_t; typedef struct uv_passwd_s uv_passwd_t; +typedef struct uv_utsname_s uv_utsname_t; +typedef struct uv_statfs_s uv_statfs_t; typedef enum { UV_LOOP_BLOCK_SIGNAL @@ -254,6 +265,8 @@ typedef void* (*uv_realloc_func)(void* ptr, size_t size); typedef void* (*uv_calloc_func)(size_t count, size_t size); typedef void (*uv_free_func)(void* ptr); +UV_EXTERN void uv_library_shutdown(void); + UV_EXTERN int uv_replace_allocator(uv_malloc_func malloc_func, uv_realloc_func realloc_func, uv_calloc_func calloc_func, @@ -321,6 +334,10 @@ typedef void (*uv_getnameinfo_cb)(uv_getnameinfo_t* req, int status, const char* hostname, const char* service); +typedef void (*uv_random_cb)(uv_random_t* req, + int status, + void* buf, + size_t buflen); typedef struct { long tv_sec; @@ -555,6 +572,7 @@ UV_EXTERN int uv_tcp_getsockname(const uv_tcp_t* handle, UV_EXTERN int uv_tcp_getpeername(const uv_tcp_t* handle, struct sockaddr* name, int* namelen); +UV_EXTERN int uv_tcp_close_reset(uv_tcp_t* handle, uv_close_cb close_cb); UV_EXTERN int uv_tcp_connect(uv_connect_t* req, uv_tcp_t* handle, const struct sockaddr* addr, @@ -589,7 +607,17 @@ enum uv_udp_flags { * (provided they all set the flag) but only the last one to bind will receive * any traffic, in effect "stealing" the port from the previous listener. */ - UV_UDP_REUSEADDR = 4 + UV_UDP_REUSEADDR = 4, + /* + * Indicates that the message was received by recvmmsg, so the buffer provided + * must not be freed by the recv_cb callback. + */ + UV_UDP_MMSG_CHUNK = 8, + + /* + * Indicates that recvmmsg should be used, if available. + */ + UV_UDP_RECVMMSG = 256 }; typedef void (*uv_udp_send_cb)(uv_udp_send_t* req, int status); @@ -629,7 +657,11 @@ UV_EXTERN int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock); UV_EXTERN int uv_udp_bind(uv_udp_t* handle, const struct sockaddr* addr, unsigned int flags); +UV_EXTERN int uv_udp_connect(uv_udp_t* handle, const struct sockaddr* addr); +UV_EXTERN int uv_udp_getpeername(const uv_udp_t* handle, + struct sockaddr* name, + int* namelen); UV_EXTERN int uv_udp_getsockname(const uv_udp_t* handle, struct sockaddr* name, int* namelen); @@ -637,6 +669,11 @@ UV_EXTERN int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, const char* interface_addr, uv_membership membership); +UV_EXTERN int uv_udp_set_source_membership(uv_udp_t* handle, + const char* multicast_addr, + const char* interface_addr, + const char* source_addr, + uv_membership membership); UV_EXTERN int uv_udp_set_multicast_loop(uv_udp_t* handle, int on); UV_EXTERN int uv_udp_set_multicast_ttl(uv_udp_t* handle, int ttl); UV_EXTERN int uv_udp_set_multicast_interface(uv_udp_t* handle, @@ -681,10 +718,25 @@ typedef enum { UV_TTY_MODE_IO } uv_tty_mode_t; +typedef enum { + /* + * The console supports handling of virtual terminal sequences + * (Windows10 new console, ConEmu) + */ + UV_TTY_SUPPORTED, + /* The console cannot process the virtual terminal sequence. (Legacy + * console) + */ + UV_TTY_UNSUPPORTED +} uv_tty_vtermstate_t; + + UV_EXTERN int uv_tty_init(uv_loop_t*, uv_tty_t*, uv_file fd, int readable); UV_EXTERN int uv_tty_set_mode(uv_tty_t*, uv_tty_mode_t mode); UV_EXTERN int uv_tty_reset_mode(void); UV_EXTERN int uv_tty_get_winsize(uv_tty_t*, int* width, int* height); +UV_EXTERN void uv_tty_set_vterm_state(uv_tty_vtermstate_t state); +UV_EXTERN int uv_tty_get_vterm_state(uv_tty_vtermstate_t* state); #ifdef __cplusplus extern "C++" { @@ -963,11 +1015,22 @@ enum uv_process_flags { */ UV_PROCESS_DETACHED = (1 << 3), /* + * Hide the subprocess window that would normally be created. This option is + * only meaningful on Windows systems. On Unix it is silently ignored. + */ + UV_PROCESS_WINDOWS_HIDE = (1 << 4), + /* * Hide the subprocess console window that would normally be created. This * option is only meaningful on Windows systems. On Unix it is silently * ignored. */ - UV_PROCESS_WINDOWS_HIDE = (1 << 4) + UV_PROCESS_WINDOWS_HIDE_CONSOLE = (1 << 5), + /* + * Hide the subprocess GUI window that would normally be created. This + * option is only meaningful on Windows systems. On Unix it is silently + * ignored. + */ + UV_PROCESS_WINDOWS_HIDE_GUI = (1 << 6) }; /* @@ -1008,11 +1071,11 @@ UV_EXTERN int uv_cancel(uv_req_t* req); struct uv_cpu_times_s { - uint64_t user; - uint64_t nice; - uint64_t sys; - uint64_t idle; - uint64_t irq; + uint64_t user; /* milliseconds */ + uint64_t nice; /* milliseconds */ + uint64_t sys; /* milliseconds */ + uint64_t idle; /* milliseconds */ + uint64_t irq; /* milliseconds */ }; struct uv_cpu_info_s { @@ -1043,6 +1106,27 @@ struct uv_passwd_s { char* homedir; }; +struct uv_utsname_s { + char sysname[256]; + char release[256]; + char version[256]; + char machine[256]; + /* This struct does not contain the nodename and domainname fields present in + the utsname type. domainname is a GNU extension. Both fields are referred + to as meaningless in the docs. */ +}; + +struct uv_statfs_s { + uint64_t f_type; + uint64_t f_bsize; + uint64_t f_blocks; + uint64_t f_bfree; + uint64_t f_bavail; + uint64_t f_files; + uint64_t f_ffree; + uint64_t f_spare[4]; +}; + typedef enum { UV_DIRENT_UNKNOWN, UV_DIRENT_FILE, @@ -1073,6 +1157,11 @@ typedef struct { } uv_timeval_t; typedef struct { + int64_t tv_sec; + int32_t tv_usec; +} uv_timeval64_t; + +typedef struct { uv_timeval_t ru_utime; /* user CPU time used */ uv_timeval_t ru_stime; /* system CPU time used */ uint64_t ru_maxrss; /* maximum resident set size */ @@ -1100,12 +1189,22 @@ UV_EXTERN void uv_os_free_passwd(uv_passwd_t* pwd); UV_EXTERN uv_pid_t uv_os_getpid(void); UV_EXTERN uv_pid_t uv_os_getppid(void); -#define UV_PRIORITY_LOW 19 -#define UV_PRIORITY_BELOW_NORMAL 10 -#define UV_PRIORITY_NORMAL 0 -#define UV_PRIORITY_ABOVE_NORMAL -7 -#define UV_PRIORITY_HIGH -14 -#define UV_PRIORITY_HIGHEST -20 +#if defined(__PASE__) +/* On IBM i PASE, the highest process priority is -10 */ +# define UV_PRIORITY_LOW 39 // RUNPTY(99) +# define UV_PRIORITY_BELOW_NORMAL 15 // RUNPTY(50) +# define UV_PRIORITY_NORMAL 0 // RUNPTY(20) +# define UV_PRIORITY_ABOVE_NORMAL -4 // RUNTY(12) +# define UV_PRIORITY_HIGH -7 // RUNPTY(6) +# define UV_PRIORITY_HIGHEST -10 // RUNPTY(1) +#else +# define UV_PRIORITY_LOW 19 +# define UV_PRIORITY_BELOW_NORMAL 10 +# define UV_PRIORITY_NORMAL 0 +# define UV_PRIORITY_ABOVE_NORMAL -7 +# define UV_PRIORITY_HIGH -14 +# define UV_PRIORITY_HIGHEST -20 +#endif UV_EXTERN int uv_os_getpriority(uv_pid_t pid, int* priority); UV_EXTERN int uv_os_setpriority(uv_pid_t pid, int priority); @@ -1118,12 +1217,32 @@ UV_EXTERN int uv_interface_addresses(uv_interface_address_t** addresses, UV_EXTERN void uv_free_interface_addresses(uv_interface_address_t* addresses, int count); +struct uv_env_item_s { + char* name; + char* value; +}; + +UV_EXTERN int uv_os_environ(uv_env_item_t** envitems, int* count); +UV_EXTERN void uv_os_free_environ(uv_env_item_t* envitems, int count); UV_EXTERN int uv_os_getenv(const char* name, char* buffer, size_t* size); UV_EXTERN int uv_os_setenv(const char* name, const char* value); UV_EXTERN int uv_os_unsetenv(const char* name); +#ifdef MAXHOSTNAMELEN +# define UV_MAXHOSTNAMESIZE (MAXHOSTNAMELEN + 1) +#else + /* + Fallback for the maximum hostname size, including the null terminator. The + Windows gethostname() documentation states that 256 bytes will always be + large enough to hold the null-terminated hostname. + */ +# define UV_MAXHOSTNAMESIZE 256 +#endif + UV_EXTERN int uv_os_gethostname(char* buffer, size_t* size); +UV_EXTERN int uv_os_uname(uv_utsname_t* buffer); + typedef enum { UV_FS_UNKNOWN = -1, @@ -1157,9 +1276,22 @@ typedef enum { UV_FS_FCHOWN, UV_FS_REALPATH, UV_FS_COPYFILE, - UV_FS_LCHOWN + UV_FS_LCHOWN, + UV_FS_OPENDIR, + UV_FS_READDIR, + UV_FS_CLOSEDIR, + UV_FS_STATFS, + UV_FS_MKSTEMP, + UV_FS_LUTIME } uv_fs_type; +struct uv_dir_s { + uv_dirent_t* dirents; + size_t nentries; + void* reserved[4]; + UV_DIR_PRIVATE_FIELDS +}; + /* uv_fs_t is a subclass of uv_req_t. */ struct uv_fs_s { UV_REQ_FIELDS @@ -1175,6 +1307,7 @@ struct uv_fs_s { UV_EXTERN uv_fs_type uv_fs_get_type(const uv_fs_t*); UV_EXTERN ssize_t uv_fs_get_result(const uv_fs_t*); +UV_EXTERN int uv_fs_get_system_error(const uv_fs_t*); UV_EXTERN void* uv_fs_get_ptr(const uv_fs_t*); UV_EXTERN const char* uv_fs_get_path(const uv_fs_t*); UV_EXTERN uv_stat_t* uv_fs_get_statbuf(uv_fs_t*); @@ -1241,6 +1374,10 @@ UV_EXTERN int uv_fs_mkdtemp(uv_loop_t* loop, uv_fs_t* req, const char* tpl, uv_fs_cb cb); +UV_EXTERN int uv_fs_mkstemp(uv_loop_t* loop, + uv_fs_t* req, + const char* tpl, + uv_fs_cb cb); UV_EXTERN int uv_fs_rmdir(uv_loop_t* loop, uv_fs_t* req, const char* path, @@ -1252,6 +1389,18 @@ UV_EXTERN int uv_fs_scandir(uv_loop_t* loop, uv_fs_cb cb); UV_EXTERN int uv_fs_scandir_next(uv_fs_t* req, uv_dirent_t* ent); +UV_EXTERN int uv_fs_opendir(uv_loop_t* loop, + uv_fs_t* req, + const char* path, + uv_fs_cb cb); +UV_EXTERN int uv_fs_readdir(uv_loop_t* loop, + uv_fs_t* req, + uv_dir_t* dir, + uv_fs_cb cb); +UV_EXTERN int uv_fs_closedir(uv_loop_t* loop, + uv_fs_t* req, + uv_dir_t* dir, + uv_fs_cb cb); UV_EXTERN int uv_fs_stat(uv_loop_t* loop, uv_fs_t* req, const char* path, @@ -1307,6 +1456,12 @@ UV_EXTERN int uv_fs_futime(uv_loop_t* loop, double atime, double mtime, uv_fs_cb cb); +UV_EXTERN int uv_fs_lutime(uv_loop_t* loop, + uv_fs_t* req, + const char* path, + double atime, + double mtime, + uv_fs_cb cb); UV_EXTERN int uv_fs_lstat(uv_loop_t* loop, uv_fs_t* req, const char* path, @@ -1366,6 +1521,10 @@ UV_EXTERN int uv_fs_lchown(uv_loop_t* loop, uv_uid_t uid, uv_gid_t gid, uv_fs_cb cb); +UV_EXTERN int uv_fs_statfs(uv_loop_t* loop, + uv_fs_t* req, + const char* path, + uv_fs_cb cb); enum uv_fs_event { @@ -1471,6 +1630,26 @@ UV_EXTERN int uv_ip6_name(const struct sockaddr_in6* src, char* dst, size_t size UV_EXTERN int uv_inet_ntop(int af, const void* src, char* dst, size_t size); UV_EXTERN int uv_inet_pton(int af, const char* src, void* dst); + +struct uv_random_s { + UV_REQ_FIELDS + /* read-only */ + uv_loop_t* loop; + /* private */ + int status; + void* buf; + size_t buflen; + uv_random_cb cb; + struct uv__work work_req; +}; + +UV_EXTERN int uv_random(uv_loop_t* loop, + uv_random_t* req, + void *buf, + size_t buflen, + unsigned flags, /* For future extension; must be 0. */ + uv_random_cb cb); + #if defined(IF_NAMESIZE) # define UV_IF_NAMESIZE (IF_NAMESIZE + 1) #elif defined(IFNAMSIZ) @@ -1494,8 +1673,10 @@ UV_EXTERN int uv_chdir(const char* dir); UV_EXTERN uint64_t uv_get_free_memory(void); UV_EXTERN uint64_t uv_get_total_memory(void); +UV_EXTERN uint64_t uv_get_constrained_memory(void); UV_EXTERN uint64_t uv_hrtime(void); +UV_EXTERN void uv_sleep(unsigned int msec); UV_EXTERN void uv_disable_stdio_inheritance(void); @@ -1547,9 +1728,29 @@ UV_EXTERN void uv_key_delete(uv_key_t* key); UV_EXTERN void* uv_key_get(uv_key_t* key); UV_EXTERN void uv_key_set(uv_key_t* key, void* value); +UV_EXTERN int uv_gettimeofday(uv_timeval64_t* tv); + typedef void (*uv_thread_cb)(void* arg); UV_EXTERN int uv_thread_create(uv_thread_t* tid, uv_thread_cb entry, void* arg); + +typedef enum { + UV_THREAD_NO_FLAGS = 0x00, + UV_THREAD_HAS_STACK_SIZE = 0x01 +} uv_thread_create_flags; + +struct uv_thread_options_s { + unsigned int flags; + size_t stack_size; + /* More fields may be added at any time. */ +}; + +typedef struct uv_thread_options_s uv_thread_options_t; + +UV_EXTERN int uv_thread_create_ex(uv_thread_t* tid, + const uv_thread_options_t* params, + uv_thread_cb entry, + void* arg); UV_EXTERN uv_thread_t uv_thread_self(void); UV_EXTERN int uv_thread_join(uv_thread_t *tid); UV_EXTERN int uv_thread_equal(const uv_thread_t* t1, const uv_thread_t* t2); diff --git a/include/uv/errno.h b/include/uv/errno.h index 8eeb95d..165fd11 100644 --- a/include/uv/errno.h +++ b/include/uv/errno.h @@ -439,5 +439,10 @@ # define UV__EFTYPE (-4028) #endif +#if defined(EILSEQ) && !defined(_WIN32) +# define UV__EILSEQ UV__ERR(EILSEQ) +#else +# define UV__EILSEQ (-4027) +#endif #endif /* UV_ERRNO_H_ */ diff --git a/include/uv/unix.h b/include/uv/unix.h index 74a0d64..3a13163 100644 --- a/include/uv/unix.h +++ b/include/uv/unix.h @@ -31,13 +31,14 @@ #include #include #include -#include +#include /* MAXHOSTNAMELEN on Solaris */ #include #include #if !defined(__MVS__) #include +#include /* MAXHOSTNAMELEN on Linux and the BSDs */ #endif #include #include @@ -48,8 +49,8 @@ # include "uv/linux.h" #elif defined (__MVS__) # include "uv/os390.h" -#elif defined(__PASE__) -# include "uv/posix.h" +#elif defined(__PASE__) /* __PASE__ and _AIX are both defined on IBM i */ +# include "uv/posix.h" /* IBM i needs uv/posix.h, not uv/aix.h */ #elif defined(_AIX) # include "uv/aix.h" #elif defined(__sun) @@ -62,12 +63,12 @@ defined(__OpenBSD__) || \ defined(__NetBSD__) # include "uv/bsd.h" -#elif defined(__CYGWIN__) || defined(__MSYS__) +#elif defined(__CYGWIN__) || \ + defined(__MSYS__) || \ + defined(__GNU__) +# include "uv/posix.h" +#elif defined(__HAIKU__) # include "uv/posix.h" -#endif - -#ifndef PTHREAD_BARRIER_SERIAL_THREAD -# include "uv/pthread-barrier.h" #endif #ifndef NI_MAXHOST @@ -136,8 +137,30 @@ typedef pthread_rwlock_t uv_rwlock_t; typedef UV_PLATFORM_SEM_T uv_sem_t; typedef pthread_cond_t uv_cond_t; typedef pthread_key_t uv_key_t; -typedef pthread_barrier_t uv_barrier_t; +/* Note: guard clauses should match uv_barrier_init's in src/unix/thread.c. */ +#if defined(_AIX) || \ + defined(__OpenBSD__) || \ + !defined(PTHREAD_BARRIER_SERIAL_THREAD) +/* TODO(bnoordhuis) Merge into uv_barrier_t in v2. */ +struct _uv_barrier { + uv_mutex_t mutex; + uv_cond_t cond; + unsigned threshold; + unsigned in; + unsigned out; +}; + +typedef struct { + struct _uv_barrier* b; +# if defined(PTHREAD_BARRIER_SERIAL_THREAD) + /* TODO(bnoordhuis) Remove padding in v2. */ + char pad[sizeof(pthread_barrier_t) - sizeof(struct _uv_barrier*)]; +# endif +} uv_barrier_t; +#else +typedef pthread_barrier_t uv_barrier_t; +#endif /* Platform-specific definitions for uv_spawn support. */ typedef gid_t uv_gid_t; @@ -145,6 +168,9 @@ typedef uid_t uv_uid_t; typedef struct dirent uv__dirent_t; +#define UV_DIR_PRIVATE_FIELDS \ + DIR* dir; + #if defined(DT_UNKNOWN) # define HAVE_DIRENT_TYPES # if defined(DT_REG) @@ -379,11 +405,25 @@ typedef struct { #else # define UV_FS_O_CREAT 0 #endif -#if defined(O_DIRECT) + +#if defined(__linux__) && defined(__arm__) +# define UV_FS_O_DIRECT 0x10000 +#elif defined(__linux__) && defined(__m68k__) +# define UV_FS_O_DIRECT 0x10000 +#elif defined(__linux__) && defined(__mips__) +# 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(O_DIRECT) # define UV_FS_O_DIRECT O_DIRECT #else # define UV_FS_O_DIRECT 0 #endif + #if defined(O_DIRECTORY) # define UV_FS_O_DIRECTORY O_DIRECTORY #else @@ -456,6 +496,7 @@ typedef struct { #endif /* fs open() flags supported on other platforms: */ +#define UV_FS_O_FILEMAP 0 #define UV_FS_O_RANDOM 0 #define UV_FS_O_SHORT_LIVED 0 #define UV_FS_O_SEQUENTIAL 0 diff --git a/include/uv/version.h b/include/uv/version.h index 9db3d13..486658c 100644 --- a/include/uv/version.h +++ b/include/uv/version.h @@ -26,13 +26,13 @@ * Versions with the same major number are ABI stable. API is allowed to * evolve between minor releases, but only in a backwards compatible way. * Make sure you update the -soname directives in configure.ac - * and uv.gyp whenever you bump UV_VERSION_MAJOR or UV_VERSION_MINOR (but + * whenever you bump UV_VERSION_MAJOR or UV_VERSION_MINOR (but * not UV_VERSION_PATCH.) */ #define UV_VERSION_MAJOR 1 -#define UV_VERSION_MINOR 23 -#define UV_VERSION_PATCH 1 +#define UV_VERSION_MINOR 38 +#define UV_VERSION_PATCH 0 #define UV_VERSION_IS_RELEASE 1 #define UV_VERSION_SUFFIX "" diff --git a/include/uv/win.h b/include/uv/win.h index d6b8b3a..f5f1d3a 100644 --- a/include/uv/win.h +++ b/include/uv/win.h @@ -25,6 +25,7 @@ #if !defined(_SSIZE_T_) && !defined(_SSIZE_T_DEFINED) typedef intptr_t ssize_t; +# define SSIZE_MAX INTPTR_MAX # define _SSIZE_T_ # define _SSIZE_T_DEFINED #endif @@ -86,6 +87,14 @@ typedef struct pollfd { #define SIGKILL 9 #define SIGWINCH 28 +/* Redefine NSIG to take SIGWINCH into consideration */ +#if defined(NSIG) && NSIG <= SIGWINCH +# undef NSIG +#endif +#ifndef NSIG +# define NSIG SIGWINCH + 1 +#endif + /* The CRT defines SIGABRT_COMPAT as 6, which equals SIGABRT on many unix-like * platforms. However MinGW doesn't define it, so we do. */ #ifndef SIGABRT_COMPAT @@ -292,6 +301,11 @@ typedef struct uv__dirent_s { char d_name[1]; } uv__dirent_t; +#define UV_DIR_PRIVATE_FIELDS \ + HANDLE dir_handle; \ + WIN32_FIND_DATAW find_data; \ + BOOL need_find_call; + #define HAVE_DIRENT_TYPES #define UV__DT_DIR UV_DIRENT_DIR #define UV__DT_FILE UV_DIRENT_FILE @@ -503,7 +517,7 @@ typedef struct { /* eol conversion state */ \ unsigned char previous_eol; \ /* ansi parser state */ \ - unsigned char ansi_parser_state; \ + unsigned short ansi_parser_state; \ unsigned char ansi_csi_argc; \ unsigned short ansi_csi_argv[4]; \ COORD saved_position; \ @@ -654,6 +668,7 @@ typedef struct { #define UV_FS_O_APPEND _O_APPEND #define UV_FS_O_CREAT _O_CREAT #define UV_FS_O_EXCL _O_EXCL +#define UV_FS_O_FILEMAP 0x20000000 #define UV_FS_O_RANDOM _O_RANDOM #define UV_FS_O_RDONLY _O_RDONLY #define UV_FS_O_RDWR _O_RDWR diff --git a/m4/ax_pthread.m4 b/m4/ax_pthread.m4 new file mode 100644 index 0000000..5fbf9fe --- /dev/null +++ b/m4/ax_pthread.m4 @@ -0,0 +1,485 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_pthread.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) +# +# DESCRIPTION +# +# This macro figures out how to build C programs using POSIX threads. It +# sets the PTHREAD_LIBS output variable to the threads library and linker +# flags, and the PTHREAD_CFLAGS output variable to any special C compiler +# flags that are needed. (The user can also force certain compiler +# flags/libs to be tested by setting these environment variables.) +# +# Also sets PTHREAD_CC to any special C compiler that is needed for +# multi-threaded programs (defaults to the value of CC otherwise). (This +# is necessary on AIX to use the special cc_r compiler alias.) +# +# NOTE: You are assumed to not only compile your program with these flags, +# but also to link with them as well. For example, you might link with +# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS +# +# If you are only building threaded programs, you may wish to use these +# variables in your default LIBS, CFLAGS, and CC: +# +# LIBS="$PTHREAD_LIBS $LIBS" +# CFLAGS="$CFLAGS $PTHREAD_CFLAGS" +# CC="$PTHREAD_CC" +# +# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant +# has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to +# that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX). +# +# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the +# PTHREAD_PRIO_INHERIT symbol is defined when compiling with +# PTHREAD_CFLAGS. +# +# ACTION-IF-FOUND is a list of shell commands to run if a threads library +# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it +# is not found. If ACTION-IF-FOUND is not specified, the default action +# will define HAVE_PTHREAD. +# +# Please let the authors know if this macro fails on any platform, or if +# you have any other suggestions or comments. This macro was based on work +# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help +# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by +# Alejandro Forero Cuervo to the autoconf macro repository. We are also +# grateful for the helpful feedback of numerous users. +# +# Updated for Autoconf 2.68 by Daniel Richard G. +# +# LICENSE +# +# Copyright (c) 2008 Steven G. Johnson +# Copyright (c) 2011 Daniel Richard G. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 24 + +AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) +AC_DEFUN([AX_PTHREAD], [ +AC_REQUIRE([AC_CANONICAL_HOST]) +AC_REQUIRE([AC_PROG_CC]) +AC_REQUIRE([AC_PROG_SED]) +AC_LANG_PUSH([C]) +ax_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on Tru64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then + ax_pthread_save_CC="$CC" + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"]) + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS]) + AC_LINK_IFELSE([AC_LANG_CALL([], [pthread_join])], [ax_pthread_ok=yes]) + AC_MSG_RESULT([$ax_pthread_ok]) + if test "x$ax_pthread_ok" = "xno"; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + CC="$ax_pthread_save_CC" + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items starting with a "-" are +# C compiler flags, and other items are library names, except for "none" +# which indicates that we try without any flags at all, and "pthread-config" +# which is a program returning the flags for the Pth emulation library. + +ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64 +# (Note: HP C rejects this with "bad form for `-t' option") +# -pthreads: Solaris/gcc (Note: HP C also rejects) +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads and +# -D_REENTRANT too), HP C (must be checked before -lpthread, which +# is present but should not be used directly; and before -mthreads, +# because the compiler interprets this as "-mt" + "-hreads") +# -mthreads: Mingw32/gcc, Lynx/gcc +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case $host_os in + + freebsd*) + + # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) + # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) + + ax_pthread_flags="-kthread lthread $ax_pthread_flags" + ;; + + hpux*) + + # From the cc(1) man page: "[-mt] Sets various -D flags to enable + # multi-threading and also sets -lpthread." + + ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags" + ;; + + openedition*) + + # IBM z/OS requires a feature-test macro to be defined in order to + # enable POSIX threads at all, so give the user a hint if this is + # not set. (We don't define these ourselves, as they can affect + # other portions of the system API in unpredictable ways.) + + AC_EGREP_CPP([AX_PTHREAD_ZOS_MISSING], + [ +# if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS) + AX_PTHREAD_ZOS_MISSING +# endif + ], + [AC_MSG_WARN([IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support.])]) + ;; + + solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (N.B.: The stubs are missing + # pthread_cleanup_push, or rather a function called by this macro, + # so we could check for that, but who knows whether they'll stub + # that too in a future libc.) So we'll check first for the + # standard Solaris way of linking pthreads (-mt -lpthread). + + ax_pthread_flags="-mt,pthread pthread $ax_pthread_flags" + ;; +esac + +# GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC) + +AS_IF([test "x$GCC" = "xyes"], + [ax_pthread_flags="-pthread -pthreads $ax_pthread_flags"]) + +# The presence of a feature test macro requesting re-entrant function +# definitions is, on some systems, a strong hint that pthreads support is +# correctly enabled + +case $host_os in + darwin* | hpux* | linux* | osf* | solaris*) + ax_pthread_check_macro="_REENTRANT" + ;; + + aix*) + ax_pthread_check_macro="_THREAD_SAFE" + ;; + + *) + ax_pthread_check_macro="--" + ;; +esac +AS_IF([test "x$ax_pthread_check_macro" = "x--"], + [ax_pthread_check_cond=0], + [ax_pthread_check_cond="!defined($ax_pthread_check_macro)"]) + +# Are we compiling with Clang? + +AC_CACHE_CHECK([whether $CC is Clang], + [ax_cv_PTHREAD_CLANG], + [ax_cv_PTHREAD_CLANG=no + # Note that Autoconf sets GCC=yes for Clang as well as GCC + if test "x$GCC" = "xyes"; then + AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG], + [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */ +# if defined(__clang__) && defined(__llvm__) + AX_PTHREAD_CC_IS_CLANG +# endif + ], + [ax_cv_PTHREAD_CLANG=yes]) + fi + ]) +ax_pthread_clang="$ax_cv_PTHREAD_CLANG" + +ax_pthread_clang_warning=no + +# Clang needs special handling, because older versions handle the -pthread +# option in a rather... idiosyncratic way + +if test "x$ax_pthread_clang" = "xyes"; then + + # Clang takes -pthread; it has never supported any other flag + + # (Note 1: This will need to be revisited if a system that Clang + # supports has POSIX threads in a separate library. This tends not + # to be the way of modern systems, but it's conceivable.) + + # (Note 2: On some systems, notably Darwin, -pthread is not needed + # to get POSIX threads support; the API is always present and + # active. We could reasonably leave PTHREAD_CFLAGS empty. But + # -pthread does define _REENTRANT, and while the Darwin headers + # ignore this macro, third-party headers might not.) + + PTHREAD_CFLAGS="-pthread" + PTHREAD_LIBS= + + ax_pthread_ok=yes + + # However, older versions of Clang make a point of warning the user + # that, in an invocation where only linking and no compilation is + # taking place, the -pthread option has no effect ("argument unused + # during compilation"). They expect -pthread to be passed in only + # when source code is being compiled. + # + # Problem is, this is at odds with the way Automake and most other + # C build frameworks function, which is that the same flags used in + # compilation (CFLAGS) are also used in linking. Many systems + # supported by AX_PTHREAD require exactly this for POSIX threads + # support, and in fact it is often not straightforward to specify a + # flag that is used only in the compilation phase and not in + # linking. Such a scenario is extremely rare in practice. + # + # Even though use of the -pthread flag in linking would only print + # a warning, this can be a nuisance for well-run software projects + # that build with -Werror. So if the active version of Clang has + # this misfeature, we search for an option to squash it. + + AC_CACHE_CHECK([whether Clang needs flag to prevent "argument unused" warning when linking with -pthread], + [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG], + [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown + # Create an alternate version of $ac_link that compiles and + # links in two steps (.c -> .o, .o -> exe) instead of one + # (.c -> exe), because the warning occurs only in the second + # step + ax_pthread_save_ac_link="$ac_link" + ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g' + ax_pthread_link_step=`$as_echo "$ac_link" | sed "$ax_pthread_sed"` + ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)" + ax_pthread_save_CFLAGS="$CFLAGS" + for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do + AS_IF([test "x$ax_pthread_try" = "xunknown"], [break]) + CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS" + ac_link="$ax_pthread_save_ac_link" + AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], + [ac_link="$ax_pthread_2step_ac_link" + AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], + [break]) + ]) + done + ac_link="$ax_pthread_save_ac_link" + CFLAGS="$ax_pthread_save_CFLAGS" + AS_IF([test "x$ax_pthread_try" = "x"], [ax_pthread_try=no]) + ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try" + ]) + + case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in + no | unknown) ;; + *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;; + esac + +fi # $ax_pthread_clang = yes + +if test "x$ax_pthread_ok" = "xno"; then +for ax_pthread_try_flag in $ax_pthread_flags; do + + case $ax_pthread_try_flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + -mt,pthread) + AC_MSG_CHECKING([whether pthreads work with -mt -lpthread]) + PTHREAD_CFLAGS="-mt" + PTHREAD_LIBS="-lpthread" + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag]) + PTHREAD_CFLAGS="$ax_pthread_try_flag" + ;; + + pthread-config) + AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) + AS_IF([test "x$ax_pthread_config" = "xno"], [continue]) + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag]) + PTHREAD_LIBS="-l$ax_pthread_try_flag" + ;; + esac + + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include +# if $ax_pthread_check_cond +# error "$ax_pthread_check_macro must be defined" +# endif + static void routine(void *a) { a = 0; } + static void *start_routine(void *a) { return a; }], + [pthread_t th; pthread_attr_t attr; + pthread_create(&th, 0, start_routine, 0); + pthread_join(th, 0); + pthread_attr_init(&attr); + pthread_cleanup_push(routine, 0); + pthread_cleanup_pop(0) /* ; */])], + [ax_pthread_ok=yes], + []) + + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" + + AC_MSG_RESULT([$ax_pthread_ok]) + AS_IF([test "x$ax_pthread_ok" = "xyes"], [break]) + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + +# Various other checks: +if test "x$ax_pthread_ok" = "xyes"; then + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + + # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. + AC_CACHE_CHECK([for joinable pthread attribute], + [ax_cv_PTHREAD_JOINABLE_ATTR], + [ax_cv_PTHREAD_JOINABLE_ATTR=unknown + for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], + [int attr = $ax_pthread_attr; return attr /* ; */])], + [ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break], + []) + done + ]) + AS_IF([test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \ + test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \ + test "x$ax_pthread_joinable_attr_defined" != "xyes"], + [AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], + [$ax_cv_PTHREAD_JOINABLE_ATTR], + [Define to necessary symbol if this constant + uses a non-standard name on your system.]) + ax_pthread_joinable_attr_defined=yes + ]) + + AC_CACHE_CHECK([whether more special flags are required for pthreads], + [ax_cv_PTHREAD_SPECIAL_FLAGS], + [ax_cv_PTHREAD_SPECIAL_FLAGS=no + case $host_os in + solaris*) + ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS" + ;; + esac + ]) + AS_IF([test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \ + test "x$ax_pthread_special_flags_added" != "xyes"], + [PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS" + ax_pthread_special_flags_added=yes]) + + AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], + [ax_cv_PTHREAD_PRIO_INHERIT], + [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[int i = PTHREAD_PRIO_INHERIT;]])], + [ax_cv_PTHREAD_PRIO_INHERIT=yes], + [ax_cv_PTHREAD_PRIO_INHERIT=no]) + ]) + AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \ + test "x$ax_pthread_prio_inherit_defined" != "xyes"], + [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.]) + ax_pthread_prio_inherit_defined=yes + ]) + + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" + + # More AIX lossage: compile with *_r variant + if test "x$GCC" != "xyes"; then + case $host_os in + aix*) + AS_CASE(["x/$CC"], + [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], + [#handle absolute path differently from PATH based program lookup + AS_CASE(["x$CC"], + [x/*], + [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])], + [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])]) + ;; + esac + fi +fi + +test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" + +AC_SUBST([PTHREAD_LIBS]) +AC_SUBST([PTHREAD_CFLAGS]) +AC_SUBST([PTHREAD_CC]) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test "x$ax_pthread_ok" = "xyes"; then + ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) + : +else + ax_pthread_ok=no + $2 +fi +AC_LANG_POP +])dnl AX_PTHREAD diff --git a/src/fs-poll.c b/src/fs-poll.c index 6c82dfc..89864e2 100644 --- a/src/fs-poll.c +++ b/src/fs-poll.c @@ -22,12 +22,20 @@ #include "uv.h" #include "uv-common.h" +#ifdef _WIN32 +#include "win/internal.h" +#include "win/handle-inl.h" +#define uv__make_close_pending(h) uv_want_endgame((h)->loop, (h)) +#else +#include "unix/internal.h" +#endif + #include #include #include struct poll_ctx { - uv_fs_poll_t* parent_handle; /* NULL if parent has been stopped or closed */ + uv_fs_poll_t* parent_handle; int busy_polling; unsigned int interval; uint64_t start_time; @@ -36,6 +44,7 @@ struct poll_ctx { uv_timer_t timer_handle; uv_fs_t fs_req; /* TODO(bnoordhuis) mark fs_req internal */ uv_stat_t statbuf; + struct poll_ctx* previous; /* context from previous start()..stop() period */ char path[1]; /* variable length */ }; @@ -49,6 +58,7 @@ static uv_stat_t zero_statbuf; int uv_fs_poll_init(uv_loop_t* loop, uv_fs_poll_t* handle) { uv__handle_init(loop, (uv_handle_t*)handle, UV_FS_POLL); + handle->poll_ctx = NULL; return 0; } @@ -62,7 +72,7 @@ int uv_fs_poll_start(uv_fs_poll_t* handle, size_t len; int err; - if (uv__is_active(handle)) + if (uv_is_active((uv_handle_t*)handle)) return 0; loop = handle->loop; @@ -90,6 +100,8 @@ int uv_fs_poll_start(uv_fs_poll_t* handle, if (err < 0) goto error; + if (handle->poll_ctx != NULL) + ctx->previous = handle->poll_ctx; handle->poll_ctx = ctx; uv__handle_start(handle); @@ -104,19 +116,17 @@ error: int uv_fs_poll_stop(uv_fs_poll_t* handle) { struct poll_ctx* ctx; - if (!uv__is_active(handle)) + if (!uv_is_active((uv_handle_t*)handle)) return 0; ctx = handle->poll_ctx; assert(ctx != NULL); - assert(ctx->parent_handle != NULL); - ctx->parent_handle = NULL; - handle->poll_ctx = NULL; + assert(ctx->parent_handle == handle); /* Close the timer if it's active. If it's inactive, there's a stat request * in progress and poll_cb will take care of the cleanup. */ - if (uv__is_active(&ctx->timer_handle)) + if (uv_is_active((uv_handle_t*)&ctx->timer_handle)) uv_close((uv_handle_t*)&ctx->timer_handle, timer_close_cb); uv__handle_stop(handle); @@ -129,7 +139,7 @@ int uv_fs_poll_getpath(uv_fs_poll_t* handle, char* buffer, size_t* size) { struct poll_ctx* ctx; size_t required_len; - if (!uv__is_active(handle)) { + if (!uv_is_active((uv_handle_t*)handle)) { *size = 0; return UV_EINVAL; } @@ -153,6 +163,9 @@ int uv_fs_poll_getpath(uv_fs_poll_t* handle, char* buffer, size_t* size) { void uv__fs_poll_close(uv_fs_poll_t* handle) { uv_fs_poll_stop(handle); + + if (handle->poll_ctx == NULL) + uv__make_close_pending((uv_handle_t*)handle); } @@ -173,14 +186,13 @@ static void poll_cb(uv_fs_t* req) { uv_stat_t* statbuf; struct poll_ctx* ctx; uint64_t interval; + uv_fs_poll_t* handle; ctx = container_of(req, struct poll_ctx, fs_req); + handle = ctx->parent_handle; - if (ctx->parent_handle == NULL) { /* handle has been stopped or closed */ - uv_close((uv_handle_t*)&ctx->timer_handle, timer_close_cb); - uv_fs_req_cleanup(req); - return; - } + if (!uv_is_active((uv_handle_t*)handle) || uv__is_closing(handle)) + goto out; if (req->result != 0) { if (ctx->busy_polling != req->result) { @@ -205,7 +217,7 @@ static void poll_cb(uv_fs_t* req) { out: uv_fs_req_cleanup(req); - if (ctx->parent_handle == NULL) { /* handle has been stopped by callback */ + if (!uv_is_active((uv_handle_t*)handle) || uv__is_closing(handle)) { uv_close((uv_handle_t*)&ctx->timer_handle, timer_close_cb); return; } @@ -219,8 +231,27 @@ out: } -static void timer_close_cb(uv_handle_t* handle) { - uv__free(container_of(handle, struct poll_ctx, timer_handle)); +static void timer_close_cb(uv_handle_t* timer) { + struct poll_ctx* ctx; + struct poll_ctx* it; + struct poll_ctx* last; + uv_fs_poll_t* handle; + + ctx = container_of(timer, struct poll_ctx, timer_handle); + handle = ctx->parent_handle; + if (ctx == handle->poll_ctx) { + handle->poll_ctx = ctx->previous; + if (handle->poll_ctx == NULL && uv__is_closing(handle)) + uv__make_close_pending((uv_handle_t*)handle); + } else { + for (last = handle->poll_ctx, it = last->previous; + it != ctx; + last = it, it = it->previous) { + assert(last->previous != NULL); + } + last->previous = ctx->previous; + } + uv__free(ctx); } diff --git a/src/idna.c b/src/idna.c new file mode 100644 index 0000000..13ffac6 --- /dev/null +++ b/src/idna.c @@ -0,0 +1,291 @@ +/* Copyright (c) 2011, 2018 Ben Noordhuis + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* Derived from https://github.com/bnoordhuis/punycode + * but updated to support IDNA 2008. + */ + +#include "uv.h" +#include "idna.h" +#include + +static unsigned uv__utf8_decode1_slow(const char** p, + const char* pe, + unsigned a) { + unsigned b; + unsigned c; + unsigned d; + unsigned min; + + if (a > 0xF7) + return -1; + + switch (*p - pe) { + default: + if (a > 0xEF) { + min = 0x10000; + a = a & 7; + b = (unsigned char) *(*p)++; + c = (unsigned char) *(*p)++; + d = (unsigned char) *(*p)++; + break; + } + /* Fall through. */ + case 2: + if (a > 0xDF) { + min = 0x800; + b = 0x80 | (a & 15); + c = (unsigned char) *(*p)++; + d = (unsigned char) *(*p)++; + a = 0; + break; + } + /* Fall through. */ + case 1: + if (a > 0xBF) { + min = 0x80; + b = 0x80; + c = 0x80 | (a & 31); + d = (unsigned char) *(*p)++; + a = 0; + break; + } + return -1; /* Invalid continuation byte. */ + } + + if (0x80 != (0xC0 & (b ^ c ^ d))) + return -1; /* Invalid sequence. */ + + b &= 63; + c &= 63; + d &= 63; + a = (a << 18) | (b << 12) | (c << 6) | d; + + if (a < min) + return -1; /* Overlong sequence. */ + + if (a > 0x10FFFF) + return -1; /* Four-byte sequence > U+10FFFF. */ + + if (a >= 0xD800 && a <= 0xDFFF) + return -1; /* Surrogate pair. */ + + return a; +} + +unsigned uv__utf8_decode1(const char** p, const char* pe) { + unsigned a; + + a = (unsigned char) *(*p)++; + + if (a < 128) + return a; /* ASCII, common case. */ + + return uv__utf8_decode1_slow(p, pe, a); +} + +#define foreach_codepoint(c, p, pe) \ + for (; (void) (*p <= pe && (c = uv__utf8_decode1(p, pe))), *p <= pe;) + +static int uv__idna_toascii_label(const char* s, const char* se, + char** d, char* de) { + static const char alphabet[] = "abcdefghijklmnopqrstuvwxyz0123456789"; + const char* ss; + unsigned c; + unsigned h; + unsigned k; + unsigned n; + unsigned m; + unsigned q; + unsigned t; + unsigned x; + unsigned y; + unsigned bias; + unsigned delta; + unsigned todo; + int first; + + h = 0; + ss = s; + todo = 0; + + foreach_codepoint(c, &s, se) { + if (c < 128) + h++; + else if (c == (unsigned) -1) + return UV_EINVAL; + else + todo++; + } + + if (todo > 0) { + if (*d < de) *(*d)++ = 'x'; + if (*d < de) *(*d)++ = 'n'; + if (*d < de) *(*d)++ = '-'; + if (*d < de) *(*d)++ = '-'; + } + + x = 0; + s = ss; + foreach_codepoint(c, &s, se) { + if (c > 127) + continue; + + if (*d < de) + *(*d)++ = c; + + if (++x == h) + break; /* Visited all ASCII characters. */ + } + + if (todo == 0) + return h; + + /* Only write separator when we've written ASCII characters first. */ + if (h > 0) + if (*d < de) + *(*d)++ = '-'; + + n = 128; + bias = 72; + delta = 0; + first = 1; + + while (todo > 0) { + m = -1; + s = ss; + foreach_codepoint(c, &s, se) + if (c >= n) + if (c < m) + m = c; + + x = m - n; + y = h + 1; + + if (x > ~delta / y) + return UV_E2BIG; /* Overflow. */ + + delta += x * y; + n = m; + + s = ss; + foreach_codepoint(c, &s, se) { + if (c < n) + if (++delta == 0) + return UV_E2BIG; /* Overflow. */ + + if (c != n) + continue; + + for (k = 36, q = delta; /* empty */; k += 36) { + t = 1; + + if (k > bias) + t = k - bias; + + if (t > 26) + t = 26; + + if (q < t) + break; + + /* TODO(bnoordhuis) Since 1 <= t <= 26 and therefore + * 10 <= y <= 35, we can optimize the long division + * into a table-based reciprocal multiplication. + */ + x = q - t; + y = 36 - t; /* 10 <= y <= 35 since 1 <= t <= 26. */ + q = x / y; + t = t + x % y; /* 1 <= t <= 35 because of y. */ + + if (*d < de) + *(*d)++ = alphabet[t]; + } + + if (*d < de) + *(*d)++ = alphabet[q]; + + delta /= 2; + + if (first) { + delta /= 350; + first = 0; + } + + /* No overflow check is needed because |delta| was just + * divided by 2 and |delta+delta >= delta + delta/h|. + */ + h++; + delta += delta / h; + + for (bias = 0; delta > 35 * 26 / 2; bias += 36) + delta /= 35; + + bias += 36 * delta / (delta + 38); + delta = 0; + todo--; + } + + delta++; + n++; + } + + return 0; +} + +#undef foreach_codepoint + +long uv__idna_toascii(const char* s, const char* se, char* d, char* de) { + const char* si; + const char* st; + unsigned c; + char* ds; + int rc; + + ds = d; + + for (si = s; si < se; /* empty */) { + st = si; + c = uv__utf8_decode1(&si, se); + + if (c != '.') + if (c != 0x3002) /* 。 */ + if (c != 0xFF0E) /* . */ + if (c != 0xFF61) /* 。 */ + continue; + + rc = uv__idna_toascii_label(s, st, &d, de); + + if (rc < 0) + return rc; + + if (d < de) + *d++ = '.'; + + s = si; + } + + if (s < se) { + rc = uv__idna_toascii_label(s, se, &d, de); + + if (rc < 0) + return rc; + } + + if (d < de) + *d++ = '\0'; + + return d - ds; /* Number of bytes written. */ +} diff --git a/src/idna.h b/src/idna.h new file mode 100644 index 0000000..8e0c592 --- /dev/null +++ b/src/idna.h @@ -0,0 +1,31 @@ +/* Copyright (c) 2011, 2018 Ben Noordhuis + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef UV_SRC_IDNA_H_ +#define UV_SRC_IDNA_H_ + +/* Decode a single codepoint. Returns the codepoint or UINT32_MAX on error. + * |p| is updated on success _and_ error, i.e., bad multi-byte sequences are + * skipped in their entirety, not just the first bad byte. + */ +unsigned uv__utf8_decode1(const char** p, const char* pe); + +/* Convert a UTF-8 domain name to IDNA 2008 / Punycode. A return value >= 0 + * is the number of bytes written to |d|, including the trailing nul byte. + * A return value < 0 is a libuv error code. |s| and |d| can not overlap. + */ +long uv__idna_toascii(const char* s, const char* se, char* d, char* de); + +#endif /* UV_SRC_IDNA_H_ */ diff --git a/src/inet.c b/src/inet.c index 4598ca1..698ab23 100644 --- a/src/inet.c +++ b/src/inet.c @@ -59,8 +59,7 @@ static int inet_ntop4(const unsigned char *src, char *dst, size_t size) { if (l <= 0 || (size_t) l >= size) { return UV_ENOSPC; } - strncpy(dst, tmp, size); - dst[size - 1] = '\0'; + uv__strscpy(dst, tmp, size); return 0; } @@ -142,14 +141,8 @@ static int inet_ntop6(const unsigned char *src, char *dst, size_t size) { if (best.base != -1 && (best.base + best.len) == ARRAY_SIZE(words)) *tp++ = ':'; *tp++ = '\0'; - - /* - * Check for overflow, copy, and we're done. - */ - if ((size_t)(tp - tmp) > size) { + if (UV_E2BIG == uv__strscpy(dst, tmp, size)) return UV_ENOSPC; - } - strcpy(dst, tmp); return 0; } diff --git a/src/random.c b/src/random.c new file mode 100644 index 0000000..491bf70 --- /dev/null +++ b/src/random.c @@ -0,0 +1,123 @@ +/* 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. + */ + +#include "uv.h" +#include "uv-common.h" + +#ifdef _WIN32 +# include "win/internal.h" +#else +# include "unix/internal.h" +#endif + +static int uv__random(void* buf, size_t buflen) { + int rc; + +#if defined(__PASE__) + rc = uv__random_readpath("/dev/urandom", buf, buflen); +#elif defined(_AIX) + rc = uv__random_readpath("/dev/random", buf, buflen); +#elif defined(__APPLE__) || defined(__OpenBSD__) || \ + (defined(__ANDROID_API__) && __ANDROID_API__ >= 28) + rc = uv__random_getentropy(buf, buflen); + if (rc == UV_ENOSYS) + rc = uv__random_devurandom(buf, buflen); +#elif defined(__NetBSD__) + rc = uv__random_sysctl(buf, buflen); +#elif defined(__FreeBSD__) || defined(__linux__) + rc = uv__random_getrandom(buf, buflen); + if (rc == UV_ENOSYS) + rc = uv__random_devurandom(buf, buflen); +# if defined(__linux__) + switch (rc) { + case UV_EACCES: + case UV_EIO: + case UV_ELOOP: + case UV_EMFILE: + case UV_ENFILE: + case UV_ENOENT: + case UV_EPERM: + rc = uv__random_sysctl(buf, buflen); + break; + } +# endif +#elif defined(_WIN32) + uv__once_init(); + rc = uv__random_rtlgenrandom(buf, buflen); +#else + rc = uv__random_devurandom(buf, buflen); +#endif + + return rc; +} + + +static void uv__random_work(struct uv__work* w) { + uv_random_t* req; + + req = container_of(w, uv_random_t, work_req); + req->status = uv__random(req->buf, req->buflen); +} + + +static void uv__random_done(struct uv__work* w, int status) { + uv_random_t* req; + + req = container_of(w, uv_random_t, work_req); + uv__req_unregister(req->loop, req); + + if (status == 0) + status = req->status; + + req->cb(req, status, req->buf, req->buflen); +} + + +int uv_random(uv_loop_t* loop, + uv_random_t* req, + void *buf, + size_t buflen, + unsigned flags, + uv_random_cb cb) { + if (buflen > 0x7FFFFFFFu) + return UV_E2BIG; + + if (flags != 0) + return UV_EINVAL; + + if (cb == NULL) + return uv__random(buf, buflen); + + uv__req_init(loop, req, UV_RANDOM); + req->loop = loop; + req->status = 0; + req->cb = cb; + req->buf = buf; + req->buflen = buflen; + + uv__work_submit(loop, + &req->work_req, + UV__WORK_CPU, + uv__random_work, + uv__random_done); + + return 0; +} diff --git a/src/strscpy.c b/src/strscpy.c new file mode 100644 index 0000000..2a2bdce --- /dev/null +++ b/src/strscpy.c @@ -0,0 +1,17 @@ +#include "strscpy.h" +#include /* SSIZE_MAX */ + +ssize_t uv__strscpy(char* d, const char* s, size_t n) { + size_t i; + + for (i = 0; i < n; i++) + if ('\0' == (d[i] = s[i])) + return i > SSIZE_MAX ? UV_E2BIG : (ssize_t) i; + + if (i == 0) + return 0; + + d[--i] = '\0'; + + return UV_E2BIG; +} diff --git a/src/strscpy.h b/src/strscpy.h new file mode 100644 index 0000000..fbe0a39 --- /dev/null +++ b/src/strscpy.h @@ -0,0 +1,18 @@ +#ifndef UV_STRSCPY_H_ +#define UV_STRSCPY_H_ + +/* Include uv.h for its definitions of size_t and ssize_t. + * size_t can be obtained directly from but ssize_t requires + * some hoop jumping on Windows that I didn't want to duplicate here. + */ +#include "uv.h" + +/* Copies up to |n-1| bytes from |d| to |s| and always zero-terminates + * the result, except when |n==0|. Returns the number of bytes copied + * or UV_E2BIG if |d| is too small. + * + * See https://www.kernel.org/doc/htmldocs/kernel-api/API-strscpy.html + */ +ssize_t uv__strscpy(char* d, const char* s, size_t n); + +#endif /* UV_STRSCPY_H_ */ diff --git a/src/threadpool.c b/src/threadpool.c index 4875f27..0998938 100644 --- a/src/threadpool.c +++ b/src/threadpool.c @@ -27,7 +27,7 @@ #include -#define MAX_THREADPOOL_SIZE 128 +#define MAX_THREADPOOL_SIZE 1024 static uv_once_t once = UV_ONCE_INIT; static uv_cond_t cond; @@ -62,10 +62,10 @@ static void worker(void* arg) { uv_sem_post((uv_sem_t*) arg); arg = NULL; + uv_mutex_lock(&mutex); for (;;) { - uv_mutex_lock(&mutex); + /* `mutex` should always be locked at this point. */ - wait_for_work: /* Keep waiting while either no work is present or only slow I/O and we're at the threshold for that. */ while (QUEUE_EMPTY(&wq) || @@ -93,13 +93,13 @@ static void worker(void* arg) { other work in the queue is done. */ if (slow_io_work_running >= slow_work_thread_threshold()) { QUEUE_INSERT_TAIL(&wq, q); - goto wait_for_work; + continue; } /* If we encountered a request to run slow I/O work but there is none to run, that means it's cancelled => Start over. */ if (QUEUE_EMPTY(&slow_io_pending_wq)) - goto wait_for_work; + continue; is_slow_work = 1; slow_io_work_running++; @@ -122,13 +122,19 @@ static void worker(void* arg) { w->work(w); uv_mutex_lock(&w->loop->wq_mutex); - if (is_slow_work) - slow_io_work_running--; w->work = NULL; /* Signal uv_cancel() that the work req is done executing. */ QUEUE_INSERT_TAIL(&w->loop->wq, &w->wq); uv_async_send(&w->loop->wq_async); uv_mutex_unlock(&w->loop->wq_mutex); + + /* Lock `mutex` since that is expected at the start of the next + * iteration. */ + uv_mutex_lock(&mutex); + if (is_slow_work) { + /* `slow_io_work_running` is protected by `mutex`. */ + slow_io_work_running--; + } } } @@ -154,8 +160,8 @@ static void post(QUEUE* q, enum uv__work_kind kind) { } +void uv__threadpool_cleanup(void) { #ifndef _WIN32 -UV_DESTRUCTOR(static void cleanup(void)) { unsigned int i; if (nthreads == 0) @@ -175,8 +181,8 @@ UV_DESTRUCTOR(static void cleanup(void)) { threads = NULL; nthreads = 0; -} #endif +} static void init_threads(void) { @@ -366,6 +372,10 @@ int uv_cancel(uv_req_t* req) { loop = ((uv_getnameinfo_t*) req)->loop; wreq = &((uv_getnameinfo_t*) req)->work_req; break; + case UV_RANDOM: + loop = ((uv_random_t*) req)->loop; + wreq = &((uv_random_t*) req)->work_req; + break; case UV_WORK: loop = ((uv_work_t*) req)->loop; wreq = &((uv_work_t*) req)->work_req; diff --git a/src/timer.c b/src/timer.c index 2bf449a..4cf4ed4 100644 --- a/src/timer.c +++ b/src/timer.c @@ -51,12 +51,7 @@ static int timer_less_than(const struct heap_node* ha, /* Compare start_id when both have the same timeout. start_id is * allocated with loop->timer_counter in uv_timer_start(). */ - if (a->start_id < b->start_id) - return 1; - if (b->start_id < a->start_id) - return 0; - - return 0; + return a->start_id < b->start_id; } @@ -74,7 +69,7 @@ int uv_timer_start(uv_timer_t* handle, uint64_t repeat) { uint64_t clamped_timeout; - if (cb == NULL) + if (uv__is_closing(handle) || cb == NULL) return UV_EINVAL; if (uv__is_active(handle)) @@ -87,7 +82,7 @@ int uv_timer_start(uv_timer_t* handle, handle->timer_cb = cb; handle->timeout = clamped_timeout; handle->repeat = repeat; - /* start_id is the second index to be compared in uv__timer_cmp() */ + /* start_id is the second index to be compared in timer_less_than() */ handle->start_id = handle->loop->timer_counter++; heap_insert(timer_heap(handle->loop), @@ -152,7 +147,7 @@ int uv__next_timeout(const uv_loop_t* loop) { if (diff > INT_MAX) diff = INT_MAX; - return diff; + return (int) diff; } diff --git a/src/unix/aix-common.c b/src/unix/aix-common.c index e17e449..c18a529 100644 --- a/src/unix/aix-common.c +++ b/src/unix/aix-common.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -154,139 +155,3 @@ int uv_exepath(char* buffer, size_t* size) { return UV_EINVAL; } } - -void uv_free_cpu_info(uv_cpu_info_t* cpu_infos, int count) { - int i; - - for (i = 0; i < count; ++i) { - uv__free(cpu_infos[i].model); - } - - uv__free(cpu_infos); -} - - -int uv_interface_addresses(uv_interface_address_t** addresses, - int* count) { - uv_interface_address_t* address; - int sockfd, inet6, size = 1; - struct ifconf ifc; - struct ifreq *ifr, *p, flg; - struct sockaddr_dl* sa_addr; - - *count = 0; - - if (0 > (sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP))) { - return UV__ERR(errno); - } - - if (ioctl(sockfd, SIOCGSIZIFCONF, &size) == -1) { - uv__close(sockfd); - return UV__ERR(errno); - } - - ifc.ifc_req = (struct ifreq*)uv__malloc(size); - ifc.ifc_len = size; - if (ioctl(sockfd, SIOCGIFCONF, &ifc) == -1) { - uv__close(sockfd); - return UV__ERR(errno); - } - -#define ADDR_SIZE(p) MAX((p).sa_len, sizeof(p)) - - /* Count all up and running ipv4/ipv6 addresses */ - ifr = ifc.ifc_req; - while ((char*)ifr < (char*)ifc.ifc_req + ifc.ifc_len) { - p = ifr; - ifr = (struct ifreq*) - ((char*)ifr + sizeof(ifr->ifr_name) + ADDR_SIZE(ifr->ifr_addr)); - - if (!(p->ifr_addr.sa_family == AF_INET6 || - p->ifr_addr.sa_family == AF_INET)) - continue; - - memcpy(flg.ifr_name, p->ifr_name, sizeof(flg.ifr_name)); - if (ioctl(sockfd, SIOCGIFFLAGS, &flg) == -1) { - uv__close(sockfd); - return UV__ERR(errno); - } - - if (!(flg.ifr_flags & IFF_UP && flg.ifr_flags & IFF_RUNNING)) - continue; - - (*count)++; - } - - /* Alloc the return interface structs */ - *addresses = uv__malloc(*count * sizeof(uv_interface_address_t)); - if (!(*addresses)) { - uv__close(sockfd); - return UV_ENOMEM; - } - address = *addresses; - - ifr = ifc.ifc_req; - while ((char*)ifr < (char*)ifc.ifc_req + ifc.ifc_len) { - p = ifr; - ifr = (struct ifreq*) - ((char*)ifr + sizeof(ifr->ifr_name) + ADDR_SIZE(ifr->ifr_addr)); - - if (!(p->ifr_addr.sa_family == AF_INET6 || - p->ifr_addr.sa_family == AF_INET)) - continue; - - inet6 = (p->ifr_addr.sa_family == AF_INET6); - - memcpy(flg.ifr_name, p->ifr_name, sizeof(flg.ifr_name)); - if (ioctl(sockfd, SIOCGIFFLAGS, &flg) == -1) { - uv__close(sockfd); - return UV_ENOSYS; - } - - if (!(flg.ifr_flags & IFF_UP && flg.ifr_flags & IFF_RUNNING)) - continue; - - /* All conditions above must match count loop */ - - address->name = uv__strdup(p->ifr_name); - - if (inet6) - address->address.address6 = *((struct sockaddr_in6*) &p->ifr_addr); - else - address->address.address4 = *((struct sockaddr_in*) &p->ifr_addr); - - sa_addr = (struct sockaddr_dl*) &p->ifr_addr; - memcpy(address->phys_addr, LLADDR(sa_addr), sizeof(address->phys_addr)); - - if (ioctl(sockfd, SIOCGIFNETMASK, p) == -1) { - uv__close(sockfd); - return UV_ENOSYS; - } - - if (inet6) - address->netmask.netmask6 = *((struct sockaddr_in6*) &p->ifr_addr); - else - address->netmask.netmask4 = *((struct sockaddr_in*) &p->ifr_addr); - - address->is_internal = flg.ifr_flags & IFF_LOOPBACK ? 1 : 0; - - address++; - } - -#undef ADDR_SIZE - - uv__close(sockfd); - return 0; -} - - -void uv_free_interface_addresses(uv_interface_address_t* addresses, - int count) { - int i; - - for (i = 0; i < count; ++i) { - uv__free(addresses[i].name); - } - - uv__free(addresses); -} \ No newline at end of file diff --git a/src/unix/aix.c b/src/unix/aix.c index 92de814..6b4594b 100644 --- a/src/unix/aix.c +++ b/src/unix/aix.c @@ -344,6 +344,11 @@ uint64_t uv_get_total_memory(void) { } +uint64_t uv_get_constrained_memory(void) { + return 0; /* Memory constraints are unknown. */ +} + + void uv_loadavg(double avg[3]) { perfstat_cpu_total_t ps_total; int result = perfstat_cpu_total(NULL, &ps_total, sizeof(ps_total), 1); @@ -358,19 +363,15 @@ void uv_loadavg(double avg[3]) { #ifdef HAVE_SYS_AHAFS_EVPRODS_H -static char *uv__rawname(char *cp) { - static char rawbuf[FILENAME_MAX+1]; - char *dp = rindex(cp, '/'); +static char* uv__rawname(const char* cp, char (*dst)[FILENAME_MAX+1]) { + char* dp; + dp = rindex(cp, '/'); if (dp == 0) return 0; - *dp = 0; - strcpy(rawbuf, cp); - *dp = '/'; - strcat(rawbuf, "/r"); - strcat(rawbuf, dp+1); - return rawbuf; + snprintf(*dst, sizeof(*dst), "%.*s/r%s", (int) (dp - cp), cp, dp + 1); + return *dst; } @@ -399,6 +400,7 @@ static int uv__path_is_a_directory(char* filename) { * Returns 0 if AHAFS is mounted, or an error code < 0 on failure */ static int uv__is_ahafs_mounted(void){ + char rawbuf[FILENAME_MAX+1]; int rv, i = 2; struct vmount *p; int size_multiplier = 10; @@ -432,7 +434,7 @@ static int uv__is_ahafs_mounted(void){ obj = vmt2dataptr(vmt, VMT_OBJECT); /* device */ stub = vmt2dataptr(vmt, VMT_STUB); /* mount point */ - if (EQ(obj, dev) || EQ(uv__rawname(obj), dev) || EQ(stub, dev)) { + if (EQ(obj, dev) || EQ(uv__rawname(obj, &rawbuf), dev) || EQ(stub, dev)) { uv__free(p); /* Found a match */ return 0; } @@ -453,7 +455,8 @@ static int uv__makedir_p(const char *dir) { size_t len; int err; - snprintf(tmp, sizeof(tmp),"%s",dir); + /* TODO(bnoordhuis) Check uv__strscpy() return value. */ + uv__strscpy(tmp, dir, sizeof(tmp)); len = strlen(tmp); if (tmp[len - 1] == '/') tmp[len - 1] = 0; @@ -557,7 +560,7 @@ static int uv__setup_ahafs(const char* filename, int *fd) { sprintf(mon_file_write_string, "CHANGED=YES;WAIT_TYPE=WAIT_IN_SELECT;INFO_LVL=1"); rc = write(*fd, mon_file_write_string, strlen(mon_file_write_string)+1); - if (rc < 0) + if (rc < 0 && errno != EBUSY) return UV__ERR(errno); return 0; @@ -702,9 +705,9 @@ static void uv__ahafs_event(uv_loop_t* loop, uv__io_t* event_watch, unsigned int else p++; } - strncpy(fname, p, sizeof(fname) - 1); - /* Just in case */ - fname[sizeof(fname) - 1] = '\0'; + + /* TODO(bnoordhuis) Check uv__strscpy() return value. */ + uv__strscpy(fname, p, sizeof(fname)); handle->cb(handle, fname, events, 0); } @@ -730,12 +733,19 @@ int uv_fs_event_start(uv_fs_event_t* handle, char cwd[PATH_MAX]; 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] == '/') { + if (filename[0] == '\0') { + /* Missing a pathname */ + return UV_ENOENT; + } + else if (filename[0] == '/') { /* We have absolute pathname */ - snprintf(absolute_path, sizeof(absolute_path), "%s", filename); + /* 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()); @@ -769,6 +779,15 @@ int uv_fs_event_start(uv_fs_event_t* handle, uv__io_start(handle->loop, &handle->event_watcher, POLLIN); + /* AHAFS wants someone to poll for it to start mointoring. + * so kick-start it so that we don't miss an event in the + * eventuality of an event that occurs in the current loop. */ + do { + memset(&zt, 0, sizeof(zt)); + FD_ZERO(&pollfd); + FD_SET(fd, &pollfd); + rc = select(fd + 1, &pollfd, NULL, NULL, &zt); + } while (rc == -1 && errno == EINTR); return 0; #else return UV_ENOSYS; @@ -886,16 +905,20 @@ int uv_set_process_title(const char* title) { int uv_get_process_title(char* buffer, size_t size) { size_t len; - len = strlen(process_argv[0]); if (buffer == NULL || size == 0) return UV_EINVAL; - else if (size <= len) - return UV_ENOBUFS; uv_once(&process_title_mutex_once, init_process_title_mutex_once); uv_mutex_lock(&process_title_mutex); - memcpy(buffer, process_argv[0], len + 1); + len = strlen(process_argv[0]); + if (size <= len) { + uv_mutex_unlock(&process_title_mutex); + return UV_ENOBUFS; + } + + memcpy(buffer, process_argv[0], len); + buffer[len] = '\0'; uv_mutex_unlock(&process_title_mutex); @@ -903,7 +926,7 @@ int uv_get_process_title(char* buffer, size_t size) { } -UV_DESTRUCTOR(static void free_args_mem(void)) { +void uv__process_title_cleanup(void) { uv__free(args_mem); /* Keep valgrind happy. */ args_mem = NULL; } @@ -982,7 +1005,8 @@ int uv_cpu_info(uv_cpu_info_t** cpu_infos, int* count) { return UV_ENOMEM; } - strcpy(cpu_id.name, FIRST_CPU); + /* TODO(bnoordhuis) Check uv__strscpy() return value. */ + uv__strscpy(cpu_id.name, FIRST_CPU, sizeof(cpu_id.name)); result = perfstat_cpu(&cpu_id, ps_cpus, sizeof(perfstat_cpu_t), ncpus); if (result == -1) { uv__free(ps_cpus); @@ -1015,6 +1039,186 @@ int uv_cpu_info(uv_cpu_info_t** cpu_infos, int* count) { } +int uv_interface_addresses(uv_interface_address_t** addresses, int* count) { + uv_interface_address_t* address; + int sockfd, sock6fd, inet6, i, r, size = 1; + struct ifconf ifc; + struct ifreq *ifr, *p, flg; + struct in6_ifreq if6; + struct sockaddr_dl* sa_addr; + + ifc.ifc_req = NULL; + sock6fd = -1; + r = 0; + *count = 0; + *addresses = NULL; + + if (0 > (sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP))) { + r = UV__ERR(errno); + goto cleanup; + } + + if (0 > (sock6fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP))) { + r = UV__ERR(errno); + goto cleanup; + } + + if (ioctl(sockfd, SIOCGSIZIFCONF, &size) == -1) { + r = UV__ERR(errno); + goto cleanup; + } + + ifc.ifc_req = (struct ifreq*)uv__malloc(size); + if (ifc.ifc_req == NULL) { + r = UV_ENOMEM; + goto cleanup; + } + ifc.ifc_len = size; + if (ioctl(sockfd, SIOCGIFCONF, &ifc) == -1) { + r = UV__ERR(errno); + goto cleanup; + } + +#define ADDR_SIZE(p) MAX((p).sa_len, sizeof(p)) + + /* Count all up and running ipv4/ipv6 addresses */ + ifr = ifc.ifc_req; + while ((char*)ifr < (char*)ifc.ifc_req + ifc.ifc_len) { + p = ifr; + ifr = (struct ifreq*) + ((char*)ifr + sizeof(ifr->ifr_name) + ADDR_SIZE(ifr->ifr_addr)); + + if (!(p->ifr_addr.sa_family == AF_INET6 || + p->ifr_addr.sa_family == AF_INET)) + continue; + + memcpy(flg.ifr_name, p->ifr_name, sizeof(flg.ifr_name)); + if (ioctl(sockfd, SIOCGIFFLAGS, &flg) == -1) { + r = UV__ERR(errno); + goto cleanup; + } + + if (!(flg.ifr_flags & IFF_UP && flg.ifr_flags & IFF_RUNNING)) + continue; + + (*count)++; + } + + if (*count == 0) + goto cleanup; + + /* Alloc the return interface structs */ + *addresses = uv__calloc(*count, sizeof(**addresses)); + if (!(*addresses)) { + r = UV_ENOMEM; + goto cleanup; + } + address = *addresses; + + ifr = ifc.ifc_req; + while ((char*)ifr < (char*)ifc.ifc_req + ifc.ifc_len) { + p = ifr; + ifr = (struct ifreq*) + ((char*)ifr + sizeof(ifr->ifr_name) + ADDR_SIZE(ifr->ifr_addr)); + + if (!(p->ifr_addr.sa_family == AF_INET6 || + p->ifr_addr.sa_family == AF_INET)) + continue; + + inet6 = (p->ifr_addr.sa_family == AF_INET6); + + memcpy(flg.ifr_name, p->ifr_name, sizeof(flg.ifr_name)); + if (ioctl(sockfd, SIOCGIFFLAGS, &flg) == -1) + goto syserror; + + if (!(flg.ifr_flags & IFF_UP && flg.ifr_flags & IFF_RUNNING)) + continue; + + /* All conditions above must match count loop */ + + address->name = uv__strdup(p->ifr_name); + + if (inet6) + address->address.address6 = *((struct sockaddr_in6*) &p->ifr_addr); + else + address->address.address4 = *((struct sockaddr_in*) &p->ifr_addr); + + if (inet6) { + memset(&if6, 0, sizeof(if6)); + r = uv__strscpy(if6.ifr_name, p->ifr_name, sizeof(if6.ifr_name)); + if (r == UV_E2BIG) + goto cleanup; + r = 0; + memcpy(&if6.ifr_Addr, &p->ifr_addr, sizeof(if6.ifr_Addr)); + if (ioctl(sock6fd, SIOCGIFNETMASK6, &if6) == -1) + goto syserror; + address->netmask.netmask6 = *((struct sockaddr_in6*) &if6.ifr_Addr); + /* Explicitly set family as the ioctl call appears to return it as 0. */ + address->netmask.netmask6.sin6_family = AF_INET6; + } else { + if (ioctl(sockfd, SIOCGIFNETMASK, p) == -1) + goto syserror; + address->netmask.netmask4 = *((struct sockaddr_in*) &p->ifr_addr); + /* Explicitly set family as the ioctl call appears to return it as 0. */ + address->netmask.netmask4.sin_family = AF_INET; + } + + address->is_internal = flg.ifr_flags & IFF_LOOPBACK ? 1 : 0; + + address++; + } + + /* Fill in physical addresses. */ + ifr = ifc.ifc_req; + while ((char*)ifr < (char*)ifc.ifc_req + ifc.ifc_len) { + p = ifr; + ifr = (struct ifreq*) + ((char*)ifr + sizeof(ifr->ifr_name) + ADDR_SIZE(ifr->ifr_addr)); + + if (p->ifr_addr.sa_family != AF_LINK) + continue; + + address = *addresses; + for (i = 0; i < *count; i++) { + if (strcmp(address->name, p->ifr_name) == 0) { + sa_addr = (struct sockaddr_dl*) &p->ifr_addr; + memcpy(address->phys_addr, LLADDR(sa_addr), sizeof(address->phys_addr)); + } + address++; + } + } + +#undef ADDR_SIZE + goto cleanup; + +syserror: + uv_free_interface_addresses(*addresses, *count); + *addresses = NULL; + *count = 0; + r = UV_ENOSYS; + +cleanup: + if (sockfd != -1) + uv__close(sockfd); + if (sock6fd != -1) + uv__close(sock6fd); + uv__free(ifc.ifc_req); + return r; +} + + +void uv_free_interface_addresses(uv_interface_address_t* addresses, + int count) { + int i; + + for (i = 0; i < count; ++i) { + uv__free(addresses[i].name); + } + + uv__free(addresses); +} + + void uv__platform_invalidate_fd(uv_loop_t* loop, int fd) { struct pollfd* events; uintptr_t i; @@ -1022,6 +1226,7 @@ void uv__platform_invalidate_fd(uv_loop_t* loop, int fd) { struct poll_ctl pc; assert(loop->watchers != NULL); + assert(fd >= 0); events = (struct pollfd*) loop->watchers[loop->nwatchers]; nfds = (uintptr_t) loop->watchers[loop->nwatchers + 1]; diff --git a/src/unix/android-ifaddrs.c b/src/unix/android-ifaddrs.c index 99fb25a..4765cc0 100644 --- a/src/unix/android-ifaddrs.c +++ b/src/unix/android-ifaddrs.c @@ -35,6 +35,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include typedef struct NetlinkList { @@ -469,12 +470,14 @@ static int interpretAddr(struct nlmsghdr *p_hdr, struct ifaddrs **p_resultList, { case IFA_ADDRESS: case IFA_LOCAL: + l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize)); if((l_info->ifa_family == AF_INET || l_info->ifa_family == AF_INET6) && !l_addedNetmask) { /* Make room for netmask */ l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize)); l_addedNetmask = 1; } + break; case IFA_BROADCAST: l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize)); break; diff --git a/src/unix/async.c b/src/unix/async.c index 0b450ae..5f58fb8 100644 --- a/src/unix/async.c +++ b/src/unix/async.c @@ -32,10 +32,14 @@ #include #include #include +#include /* sched_yield() */ + +#ifdef __linux__ +#include +#endif static void uv__async_send(uv_loop_t* loop); static int uv__async_start(uv_loop_t* loop); -static int uv__async_eventfd(void); int uv_async_init(uv_loop_t* loop, uv_async_t* handle, uv_async_cb async_cb) { @@ -61,14 +65,55 @@ int uv_async_send(uv_async_t* handle) { if (ACCESS_ONCE(int, handle->pending) != 0) return 0; - if (cmpxchgi(&handle->pending, 0, 1) == 0) - uv__async_send(handle->loop); + /* Tell the other thread we're busy with the handle. */ + if (cmpxchgi(&handle->pending, 0, 1) != 0) + return 0; + + /* Wake up the other thread's event loop. */ + uv__async_send(handle->loop); + + /* Tell the other thread we're done. */ + if (cmpxchgi(&handle->pending, 1, 2) != 1) + abort(); return 0; } +/* Only call this from the event loop thread. */ +static int uv__async_spin(uv_async_t* handle) { + int i; + int rc; + + for (;;) { + /* 997 is not completely chosen at random. It's a prime number, acyclical + * by nature, and should therefore hopefully dampen sympathetic resonance. + */ + for (i = 0; i < 997; i++) { + /* rc=0 -- handle is not pending. + * rc=1 -- handle is pending, other thread is still working with it. + * rc=2 -- handle is pending, other thread is done. + */ + rc = cmpxchgi(&handle->pending, 2, 0); + + if (rc != 1) + return rc; + + /* Other thread is busy with this handle, spin until it's done. */ + 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_close(uv_async_t* handle) { + uv__async_spin(handle); QUEUE_REMOVE(&handle->queue); uv__handle_stop(handle); } @@ -109,8 +154,8 @@ static void uv__async_io(uv_loop_t* loop, uv__io_t* w, unsigned int events) { QUEUE_REMOVE(q); QUEUE_INSERT_TAIL(&loop->async_handles, q); - if (cmpxchgi(&h->pending, 1, 0) == 0) - continue; + if (0 == uv__async_spin(h)) + continue; /* Not pending. */ if (h->async_cb == NULL) continue; @@ -161,36 +206,18 @@ static int uv__async_start(uv_loop_t* loop) { if (loop->async_io_watcher.fd != -1) return 0; - err = uv__async_eventfd(); - if (err >= 0) { - pipefd[0] = err; - pipefd[1] = -1; - } - else if (err == UV_ENOSYS) { - err = uv__make_pipe(pipefd, UV__F_NONBLOCK); -#if defined(__linux__) - /* Save a file descriptor by opening one of the pipe descriptors as - * read/write through the procfs. That file descriptor can then - * function as both ends of the pipe. - */ - if (err == 0) { - char buf[32]; - int fd; - - snprintf(buf, sizeof(buf), "/proc/self/fd/%d", pipefd[0]); - fd = uv__open_cloexec(buf, O_RDWR); - if (fd >= 0) { - uv__close(pipefd[0]); - uv__close(pipefd[1]); - pipefd[0] = fd; - pipefd[1] = fd; - } - } -#endif - } +#ifdef __linux__ + err = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + if (err < 0) + return UV__ERR(errno); + pipefd[0] = err; + pipefd[1] = -1; +#else + err = uv__make_pipe(pipefd, UV__F_NONBLOCK); if (err < 0) return err; +#endif uv__io_init(&loop->async_io_watcher, uv__async_io, pipefd[0]); uv__io_start(loop, &loop->async_io_watcher, POLLIN); @@ -224,46 +251,3 @@ void uv__async_stop(uv_loop_t* loop) { uv__close(loop->async_io_watcher.fd); loop->async_io_watcher.fd = -1; } - - -static int uv__async_eventfd(void) { -#if defined(__linux__) - static int no_eventfd2; - static int no_eventfd; - int fd; - - if (no_eventfd2) - goto skip_eventfd2; - - fd = uv__eventfd2(0, UV__EFD_CLOEXEC | UV__EFD_NONBLOCK); - if (fd != -1) - return fd; - - if (errno != ENOSYS) - return UV__ERR(errno); - - no_eventfd2 = 1; - -skip_eventfd2: - - if (no_eventfd) - goto skip_eventfd; - - fd = uv__eventfd(0); - if (fd != -1) { - uv__cloexec(fd, 1); - uv__nonblock(fd, 1); - return fd; - } - - if (errno != ENOSYS) - return UV__ERR(errno); - - no_eventfd = 1; - -skip_eventfd: - -#endif - - return UV_ENOSYS; -} diff --git a/src/unix/atomic-ops.h b/src/unix/atomic-ops.h index 7cac1f9..347d193 100644 --- a/src/unix/atomic-ops.h +++ b/src/unix/atomic-ops.h @@ -23,7 +23,6 @@ #endif UV_UNUSED(static int cmpxchgi(int* ptr, int oldval, int newval)); -UV_UNUSED(static long cmpxchgl(long* ptr, long oldval, long newval)); UV_UNUSED(static void cpu_relax(void)); /* Prefer hand-rolled assembly over the gcc builtins because the latter also @@ -37,10 +36,6 @@ UV_UNUSED(static int cmpxchgi(int* ptr, int oldval, int newval)) { : "r" (newval), "0" (oldval) : "memory"); return out; -#elif defined(_AIX) && defined(__xlC__) - const int out = (*(volatile int*) ptr); - __compare_and_swap(ptr, &oldval, newval); - return out; #elif defined(__MVS__) unsigned int op4; if (__plo_CSST(ptr, (unsigned int*) &oldval, newval, @@ -49,43 +44,7 @@ UV_UNUSED(static int cmpxchgi(int* ptr, int oldval, int newval)) { else return op4; #elif defined(__SUNPRO_C) || defined(__SUNPRO_CC) - return atomic_cas_uint(ptr, oldval, newval); -#else - return __sync_val_compare_and_swap(ptr, oldval, newval); -#endif -} - -UV_UNUSED(static long cmpxchgl(long* ptr, long oldval, long newval)) { -#if defined(__i386__) || defined(__x86_64__) - long out; - __asm__ __volatile__ ("lock; cmpxchg %2, %1;" - : "=a" (out), "+m" (*(volatile long*) ptr) - : "r" (newval), "0" (oldval) - : "memory"); - return out; -#elif defined(_AIX) && defined(__xlC__) - const long out = (*(volatile int*) ptr); -# if defined(__64BIT__) - __compare_and_swaplp(ptr, &oldval, newval); -# else - __compare_and_swap(ptr, &oldval, newval); -# endif /* if defined(__64BIT__) */ - return out; -#elif defined (__MVS__) -#ifdef _LP64 - unsigned long long op4; - if (__plo_CSSTGR(ptr, (unsigned long long*) &oldval, newval, - (unsigned long long*) ptr, *ptr, &op4)) -#else - unsigned long op4; - if (__plo_CSST(ptr, (unsigned int*) &oldval, newval, - (unsigned int*) ptr, *ptr, &op4)) -#endif - return oldval; - else - return op4; -#elif defined(__SUNPRO_C) || defined(__SUNPRO_CC) - return atomic_cas_ulong(ptr, oldval, newval); + return atomic_cas_uint((uint_t *)ptr, (uint_t)oldval, (uint_t)newval); #else return __sync_val_compare_and_swap(ptr, oldval, newval); #endif @@ -94,6 +53,8 @@ UV_UNUSED(static long cmpxchgl(long* ptr, long oldval, long newval)) { UV_UNUSED(static void cpu_relax(void)) { #if defined(__i386__) || defined(__x86_64__) __asm__ __volatile__ ("rep; nop"); /* a.k.a. PAUSE */ +#elif (defined(__arm__) && __ARM_ARCH >= 7) || defined(__aarch64__) + __asm__ volatile("yield"); #endif } diff --git a/src/unix/bsd-ifaddrs.c b/src/unix/bsd-ifaddrs.c index 0d02154..a3385af 100644 --- a/src/unix/bsd-ifaddrs.c +++ b/src/unix/bsd-ifaddrs.c @@ -31,6 +31,10 @@ #include #endif +#if defined(__HAIKU__) +#define IFF_RUNNING IFF_LINK +#endif + static int uv__ifaddr_exclude(struct ifaddrs *ent, int exclude_type) { if (!((ent->ifa_flags & IFF_UP) && (ent->ifa_flags & IFF_RUNNING))) return 1; @@ -45,20 +49,18 @@ static int uv__ifaddr_exclude(struct ifaddrs *ent, int exclude_type) { if (exclude_type == UV__EXCLUDE_IFPHYS) return (ent->ifa_addr->sa_family != AF_LINK); #endif -#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__DragonFly__) +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__DragonFly__) || \ + defined(__HAIKU__) /* * On BSD getifaddrs returns information related to the raw underlying * devices. We're not interested in this information. */ if (ent->ifa_addr->sa_family == AF_LINK) return 1; -#elif defined(__NetBSD__) +#elif defined(__NetBSD__) || defined(__OpenBSD__) if (ent->ifa_addr->sa_family != PF_INET && ent->ifa_addr->sa_family != PF_INET6) return 1; -#elif defined(__OpenBSD__) - if (ent->ifa_addr->sa_family != PF_INET) - return 1; #endif return 0; } @@ -67,13 +69,16 @@ int uv_interface_addresses(uv_interface_address_t** addresses, int* count) { struct ifaddrs* addrs; struct ifaddrs* ent; uv_interface_address_t* address; +#if !(defined(__CYGWIN__) || defined(__MSYS__)) int i; +#endif + + *count = 0; + *addresses = NULL; if (getifaddrs(&addrs) != 0) return UV__ERR(errno); - *count = 0; - /* Count the number of interfaces */ for (ent = addrs; ent != NULL; ent = ent->ifa_next) { if (uv__ifaddr_exclude(ent, UV__EXCLUDE_IFADDR)) @@ -81,7 +86,13 @@ int uv_interface_addresses(uv_interface_address_t** addresses, int* count) { (*count)++; } - *addresses = uv__malloc(*count * sizeof(**addresses)); + if (*count == 0) { + freeifaddrs(addrs); + return 0; + } + + /* Make sure the memory is initiallized to zero using calloc() */ + *addresses = uv__calloc(*count, sizeof(**addresses)); if (*addresses == NULL) { freeifaddrs(addrs); @@ -113,6 +124,7 @@ int uv_interface_addresses(uv_interface_address_t** addresses, int* count) { address++; } +#if !(defined(__CYGWIN__) || defined(__MSYS__)) /* Fill in physical addresses for each interface */ for (ent = addrs; ent != NULL; ent = ent->ifa_next) { if (uv__ifaddr_exclude(ent, UV__EXCLUDE_IFPHYS)) @@ -122,17 +134,14 @@ int uv_interface_addresses(uv_interface_address_t** addresses, int* count) { for (i = 0; i < *count; i++) { if (strcmp(address->name, ent->ifa_name) == 0) { -#if defined(__CYGWIN__) || defined(__MSYS__) - memset(address->phys_addr, 0, sizeof(address->phys_addr)); -#else struct sockaddr_dl* sa_addr; sa_addr = (struct sockaddr_dl*)(ent->ifa_addr); memcpy(address->phys_addr, LLADDR(sa_addr), sizeof(address->phys_addr)); -#endif } address++; } } +#endif freeifaddrs(addrs); diff --git a/src/unix/bsd-proctitle.c b/src/unix/bsd-proctitle.c new file mode 100644 index 0000000..723b81c --- /dev/null +++ b/src/unix/bsd-proctitle.c @@ -0,0 +1,100 @@ +/* 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 "internal.h" + +#include +#include + + +static uv_mutex_t process_title_mutex; +static uv_once_t process_title_mutex_once = UV_ONCE_INIT; +static char* process_title; + + +static void init_process_title_mutex_once(void) { + if (uv_mutex_init(&process_title_mutex)) + abort(); +} + + +void uv__process_title_cleanup(void) { + /* TODO(bnoordhuis) uv_mutex_destroy(&process_title_mutex) + * and reset process_title_mutex_once? + */ +} + + +char** uv_setup_args(int argc, char** argv) { + process_title = argc > 0 ? uv__strdup(argv[0]) : NULL; + return argv; +} + + +int uv_set_process_title(const char* title) { + char* new_title; + + new_title = uv__strdup(title); + if (new_title == NULL) + return UV_ENOMEM; + + uv_once(&process_title_mutex_once, init_process_title_mutex_once); + uv_mutex_lock(&process_title_mutex); + + uv__free(process_title); + process_title = new_title; + setproctitle("%s", title); + + uv_mutex_unlock(&process_title_mutex); + + return 0; +} + + +int uv_get_process_title(char* buffer, size_t size) { + size_t len; + + if (buffer == NULL || size == 0) + return UV_EINVAL; + + uv_once(&process_title_mutex_once, init_process_title_mutex_once); + uv_mutex_lock(&process_title_mutex); + + if (process_title != NULL) { + len = strlen(process_title) + 1; + + if (size < len) { + uv_mutex_unlock(&process_title_mutex); + return UV_ENOBUFS; + } + + memcpy(buffer, process_title, len); + } else { + len = 0; + } + + uv_mutex_unlock(&process_title_mutex); + + buffer[len] = '\0'; + + return 0; +} diff --git a/src/unix/core.c b/src/unix/core.c index f92446f..949eefa 100644 --- a/src/unix/core.c +++ b/src/unix/core.c @@ -30,7 +30,7 @@ #include #include #include -#include +#include /* O_CLOEXEC */ #include #include #include @@ -40,43 +40,43 @@ #include /* writev */ #include /* getrusage */ #include +#include +#include #ifdef __sun -# include /* MAXHOSTNAMELEN on Solaris */ # include # include # include #endif -#ifdef __APPLE__ -# include /* _NSGetExecutablePath */ +#if defined(__APPLE__) # include -# if defined(O_CLOEXEC) -# define UV__O_CLOEXEC O_CLOEXEC -# endif -#endif +# endif /* defined(__APPLE__) */ + + +#if defined(__APPLE__) && !TARGET_OS_IPHONE +# include +# include /* _NSGetExecutablePath */ +# define environ (*_NSGetEnviron()) +#else /* defined(__APPLE__) && !TARGET_OS_IPHONE */ +extern char** environ; +#endif /* !(defined(__APPLE__) && !TARGET_OS_IPHONE) */ + #if defined(__DragonFly__) || \ defined(__FreeBSD__) || \ defined(__FreeBSD_kernel__) || \ - defined(__NetBSD__) + defined(__NetBSD__) || \ + defined(__OpenBSD__) # include # include # include -# define UV__O_CLOEXEC O_CLOEXEC -# if defined(__FreeBSD__) && __FreeBSD__ >= 10 +# if defined(__FreeBSD__) # define uv__accept4 accept4 # endif # if defined(__NetBSD__) # define uv__accept4(a, b, c, d) paccept((a), (b), (c), NULL, (d)) # endif -# if (defined(__FreeBSD__) && __FreeBSD__ >= 10) || defined(__NetBSD__) -# define UV__SOCK_NONBLOCK SOCK_NONBLOCK -# define UV__SOCK_CLOEXEC SOCK_CLOEXEC -# endif -# if !defined(F_DUP2FD_CLOEXEC) && defined(_F_DUP2FD_CLOEXEC) -# define F_DUP2FD_CLOEXEC _F_DUP2FD_CLOEXEC -# endif #endif #if defined(__ANDROID_API__) && __ANDROID_API__ < 21 @@ -87,13 +87,9 @@ #include #endif -#if !defined(__MVS__) -#include /* MAXHOSTNAMELEN on Linux and the BSDs */ -#endif - -/* Fallback for the maximum hostname length */ -#ifndef MAXHOSTNAMELEN -# define MAXHOSTNAMELEN 256 +#if defined(__linux__) +# include +# define uv__accept4 accept4 #endif static int uv__run_pending(uv_loop_t* loop); @@ -170,13 +166,13 @@ void uv_close(uv_handle_t* handle, uv_close_cb close_cb) { case UV_FS_POLL: uv__fs_poll_close((uv_fs_poll_t*)handle); - break; + /* Poll handles use file system requests, and one of them may still be + * running. The poll code will call uv__make_close_pending() for us. */ + return; case UV_SIGNAL: uv__signal_close((uv_signal_t*) handle); - /* Signal handles may not be closed immediately. The signal code will - * itself close uv__make_close_pending whenever appropriate. */ - return; + break; default: assert(0); @@ -241,6 +237,8 @@ int uv__getiovmax(void) { static void uv__finish_close(uv_handle_t* handle) { + uv_signal_t* sh; + /* Note: while the handle is in the UV_HANDLE_CLOSING state now, it's still * possible for it to be active in the sense that uv__is_active() returns * true. @@ -263,7 +261,20 @@ static void uv__finish_close(uv_handle_t* handle) { case UV_FS_EVENT: case UV_FS_POLL: case UV_POLL: + break; + case UV_SIGNAL: + /* If there are any caught signals "trapped" in the signal pipe, + * we can't call the close callback yet. Reinserting the handle + * into the closing queue makes the event loop spin but that's + * okay because we only need to deliver the pending events. + */ + sh = (uv_signal_t*) handle; + if (sh->caught_signals > sh->dispatched_signals) { + handle->flags ^= UV_HANDLE_CLOSED; + uv__make_close_pending(handle); /* Back into the queue. */ + return; + } break; case UV_NAMED_PIPE: @@ -467,52 +478,60 @@ int uv__accept(int sockfd) { int peerfd; int err; + (void) &err; assert(sockfd >= 0); - while (1) { -#if defined(__linux__) || \ - (defined(__FreeBSD__) && __FreeBSD__ >= 10) || \ - defined(__NetBSD__) - static int no_accept4; - - if (no_accept4) - goto skip; - - peerfd = uv__accept4(sockfd, - NULL, - NULL, - UV__SOCK_NONBLOCK|UV__SOCK_CLOEXEC); - if (peerfd != -1) - return peerfd; + do +#ifdef uv__accept4 + peerfd = uv__accept4(sockfd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); +#else + peerfd = accept(sockfd, NULL, NULL); +#endif + while (peerfd == -1 && errno == EINTR); - if (errno == EINTR) - continue; + if (peerfd == -1) + return UV__ERR(errno); - if (errno != ENOSYS) - return UV__ERR(errno); +#ifndef uv__accept4 + err = uv__cloexec(peerfd, 1); + if (err == 0) + err = uv__nonblock(peerfd, 1); - no_accept4 = 1; -skip: + if (err != 0) { + uv__close(peerfd); + return err; + } #endif - peerfd = accept(sockfd, NULL, NULL); - if (peerfd == -1) { - if (errno == EINTR) - continue; - return UV__ERR(errno); - } - - err = uv__cloexec(peerfd, 1); - if (err == 0) - err = uv__nonblock(peerfd, 1); + return peerfd; +} - if (err) { - uv__close(peerfd); - return err; - } - return peerfd; - } +/* close() on macos has the "interesting" quirk that it fails with EINTR + * without closing the file descriptor when a thread is in the cancel state. + * That's why libuv calls close$NOCANCEL() instead. + * + * glibc on linux has a similar issue: close() is a cancellation point and + * will unwind the thread when it's in the cancel state. Work around that + * by making the system call directly. Musl libc is unaffected. + */ +int uv__close_nocancel(int fd) { +#if defined(__APPLE__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdollar-in-identifier-extension" +#if defined(__LP64__) || TARGET_OS_IPHONE + extern int close$NOCANCEL(int); + return close$NOCANCEL(fd); +#else + extern int close$NOCANCEL$UNIX2003(int); + return close$NOCANCEL$UNIX2003(fd); +#endif +#pragma GCC diagnostic pop +#elif defined(__linux__) + return syscall(SYS_close, fd); +#else + return close(fd); +#endif } @@ -523,7 +542,7 @@ int uv__close_nocheckstdio(int fd) { assert(fd > -1); /* Catch uninitialized io_watcher.fd bugs. */ saved_errno = errno; - rc = close(fd); + rc = uv__close_nocancel(fd); if (rc == -1) { rc = UV__ERR(errno); if (rc == UV_EINTR || rc == UV__ERR(EINPROGRESS)) @@ -558,7 +577,7 @@ int uv__nonblock_ioctl(int fd, int set) { } -#if !defined(__CYGWIN__) && !defined(__MSYS__) +#if !defined(__CYGWIN__) && !defined(__MSYS__) && !defined(__HAIKU__) int uv__cloexec_ioctl(int fd, int set) { int r; @@ -636,27 +655,6 @@ int uv__cloexec_fcntl(int fd, int set) { } -/* This function is not execve-safe, there is a race window - * between the call to dup() and fcntl(FD_CLOEXEC). - */ -int uv__dup(int fd) { - int err; - - fd = dup(fd); - - if (fd == -1) - return UV__ERR(errno); - - err = uv__cloexec(fd, 1); - if (err) { - uv__close(fd); - return err; - } - - return fd; -} - - ssize_t uv__recvmsg(int fd, struct msghdr* msg, int flags) { struct cmsghdr* cmsg; ssize_t rc; @@ -696,16 +694,38 @@ ssize_t uv__recvmsg(int fd, struct msghdr* msg, int flags) { int uv_cwd(char* buffer, size_t* size) { + char scratch[1 + UV__PATH_MAX]; + if (buffer == NULL || size == NULL) return UV_EINVAL; - if (getcwd(buffer, *size) == NULL) + /* Try to read directly into the user's buffer first... */ + if (getcwd(buffer, *size) != NULL) + goto fixup; + + if (errno != ERANGE) return UV__ERR(errno); + /* ...or into scratch space if the user's buffer is too small + * so we can report how much space to provide on the next try. + */ + if (getcwd(scratch, sizeof(scratch)) == NULL) + return UV__ERR(errno); + + buffer = scratch; + +fixup: + *size = strlen(buffer); + if (*size > 1 && buffer[*size - 1] == '/') { - buffer[*size-1] = '\0'; - (*size)--; + *size -= 1; + buffer[*size] = '\0'; + } + + if (buffer == scratch) { + *size += 1; + return UV_ENOBUFS; } return 0; @@ -815,8 +835,8 @@ static void maybe_resize(uv_loop_t* loop, unsigned int len) { } nwatchers = next_power_of_two(len + 2) - 2; - watchers = uv__realloc(loop->watchers, - (nwatchers + 2) * sizeof(loop->watchers[0])); + watchers = uv__reallocf(loop->watchers, + (nwatchers + 2) * sizeof(loop->watchers[0])); if (watchers == NULL) abort(); @@ -912,7 +932,8 @@ void uv__io_close(uv_loop_t* loop, uv__io_t* w) { QUEUE_REMOVE(&w->pending_queue); /* Remove stale events for this file descriptor */ - uv__platform_invalidate_fd(loop, w->fd); + if (w->fd != -1) + uv__platform_invalidate_fd(loop, w->fd); } @@ -946,7 +967,7 @@ int uv_getrusage(uv_rusage_t* rusage) { rusage->ru_stime.tv_sec = usage.ru_stime.tv_sec; rusage->ru_stime.tv_usec = usage.ru_stime.tv_usec; -#if !defined(__MVS__) +#if !defined(__MVS__) && !defined(__HAIKU__) rusage->ru_maxrss = usage.ru_maxrss; rusage->ru_ixrss = usage.ru_ixrss; rusage->ru_idrss = usage.ru_idrss; @@ -968,24 +989,17 @@ int uv_getrusage(uv_rusage_t* rusage) { int uv__open_cloexec(const char* path, int flags) { - int err; +#if defined(O_CLOEXEC) int fd; -#if defined(UV__O_CLOEXEC) - static int no_cloexec; - - if (!no_cloexec) { - fd = open(path, flags | UV__O_CLOEXEC); - if (fd != -1) - return fd; - - if (errno != EINVAL) - return UV__ERR(errno); + fd = open(path, flags | O_CLOEXEC); + if (fd == -1) + return UV__ERR(errno); - /* O_CLOEXEC not supported. */ - no_cloexec = 1; - } -#endif + return fd; +#else /* O_CLOEXEC */ + int err; + int fd; fd = open(path, flags); if (fd == -1) @@ -998,58 +1012,35 @@ int uv__open_cloexec(const char* path, int flags) { } return fd; +#endif /* O_CLOEXEC */ } int uv__dup2_cloexec(int oldfd, int newfd) { +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__linux__) int r; -#if (defined(__FreeBSD__) && __FreeBSD__ >= 10) || defined(__NetBSD__) + r = dup3(oldfd, newfd, O_CLOEXEC); if (r == -1) return UV__ERR(errno); + return r; -#elif defined(__FreeBSD__) && defined(F_DUP2FD_CLOEXEC) - r = fcntl(oldfd, F_DUP2FD_CLOEXEC, newfd); - if (r != -1) - return r; - if (errno != EINVAL) - return UV__ERR(errno); - /* Fall through. */ -#elif defined(__linux__) - static int no_dup3; - if (!no_dup3) { - do - r = uv__dup3(oldfd, newfd, UV__O_CLOEXEC); - while (r == -1 && errno == EBUSY); - if (r != -1) - return r; - if (errno != ENOSYS) - return UV__ERR(errno); - /* Fall through. */ - no_dup3 = 1; - } -#endif - { - int err; - do - r = dup2(oldfd, newfd); -#if defined(__linux__) - while (r == -1 && errno == EBUSY); #else - while (0); /* Never retry. */ -#endif - - if (r == -1) - return UV__ERR(errno); + int err; + int r; - err = uv__cloexec(newfd, 1); - if (err) { - uv__close(newfd); - return err; - } + r = dup2(oldfd, newfd); /* Never retry. */ + if (r == -1) + return UV__ERR(errno); - return r; + err = uv__cloexec(newfd, 1); + if (err != 0) { + uv__close(newfd); + return err; } + + return r; +#endif } @@ -1256,6 +1247,62 @@ int uv_translate_sys_error(int sys_errno) { } +int uv_os_environ(uv_env_item_t** envitems, int* count) { + int i, j, cnt; + uv_env_item_t* envitem; + + *envitems = NULL; + *count = 0; + + for (i = 0; environ[i] != NULL; i++); + + *envitems = uv__calloc(i, sizeof(**envitems)); + + if (*envitems == NULL) + return UV_ENOMEM; + + for (j = 0, cnt = 0; j < i; j++) { + char* buf; + char* ptr; + + if (environ[j] == NULL) + break; + + buf = uv__strdup(environ[j]); + if (buf == NULL) + goto fail; + + ptr = strchr(buf, '='); + if (ptr == NULL) { + uv__free(buf); + continue; + } + + *ptr = '\0'; + + envitem = &(*envitems)[cnt]; + envitem->name = buf; + envitem->value = ptr + 1; + + cnt++; + } + + *count = cnt; + return 0; + +fail: + for (i = 0; i < cnt; i++) { + envitem = &(*envitems)[cnt]; + uv__free(envitem->name); + } + uv__free(*envitems); + + *envitems = NULL; + *count = 0; + return UV_ENOMEM; +} + + int uv_os_getenv(const char* name, char* buffer, size_t* size) { char* var; size_t len; @@ -1311,7 +1358,7 @@ int uv_os_gethostname(char* buffer, size_t* size) { instead by creating a large enough buffer and comparing the hostname length to the size input. */ - char buf[MAXHOSTNAMELEN + 1]; + char buf[UV_MAXHOSTNAMESIZE]; size_t len; if (buffer == NULL || size == NULL || *size == 0) @@ -1378,3 +1425,109 @@ int uv_os_setpriority(uv_pid_t pid, int priority) { return 0; } + + +int uv_os_uname(uv_utsname_t* buffer) { + struct utsname buf; + int r; + + if (buffer == NULL) + return UV_EINVAL; + + if (uname(&buf) == -1) { + r = UV__ERR(errno); + goto error; + } + + r = uv__strscpy(buffer->sysname, buf.sysname, sizeof(buffer->sysname)); + if (r == UV_E2BIG) + goto error; + +#ifdef _AIX + r = snprintf(buffer->release, + sizeof(buffer->release), + "%s.%s", + buf.version, + buf.release); + if (r >= sizeof(buffer->release)) { + r = UV_E2BIG; + goto error; + } +#else + r = uv__strscpy(buffer->release, buf.release, sizeof(buffer->release)); + if (r == UV_E2BIG) + goto error; +#endif + + r = uv__strscpy(buffer->version, buf.version, sizeof(buffer->version)); + if (r == UV_E2BIG) + goto error; + +#if defined(_AIX) || defined(__PASE__) + r = uv__strscpy(buffer->machine, "ppc64", sizeof(buffer->machine)); +#else + r = uv__strscpy(buffer->machine, buf.machine, sizeof(buffer->machine)); +#endif + + if (r == UV_E2BIG) + goto error; + + return 0; + +error: + buffer->sysname[0] = '\0'; + buffer->release[0] = '\0'; + buffer->version[0] = '\0'; + buffer->machine[0] = '\0'; + return r; +} + +int uv__getsockpeername(const uv_handle_t* handle, + uv__peersockfunc func, + struct sockaddr* name, + int* namelen) { + socklen_t socklen; + uv_os_fd_t fd; + int r; + + r = uv_fileno(handle, &fd); + if (r < 0) + return r; + + /* sizeof(socklen_t) != sizeof(int) on some systems. */ + socklen = (socklen_t) *namelen; + + if (func(fd, name, &socklen)) + return UV__ERR(errno); + + *namelen = (int) socklen; + return 0; +} + +int uv_gettimeofday(uv_timeval64_t* tv) { + struct timeval time; + + if (tv == NULL) + return UV_EINVAL; + + if (gettimeofday(&time, NULL) != 0) + return UV__ERR(errno); + + tv->tv_sec = (int64_t) time.tv_sec; + tv->tv_usec = (int32_t) time.tv_usec; + return 0; +} + +void uv_sleep(unsigned int msec) { + struct timespec timeout; + int rc; + + timeout.tv_sec = msec / 1000; + timeout.tv_nsec = (msec % 1000) * 1000 * 1000; + + do + rc = nanosleep(&timeout, &timeout); + while (rc == -1 && errno == EINTR); + + assert(rc == 0); +} diff --git a/src/unix/cygwin.c b/src/unix/cygwin.c index 9fe4093..169958d 100644 --- a/src/unix/cygwin.c +++ b/src/unix/cygwin.c @@ -38,7 +38,7 @@ int uv_uptime(double* uptime) { int uv_resident_set_memory(size_t* rss) { /* FIXME: read /proc/meminfo? */ *rss = 0; - return UV_ENOSYS; + return 0; } int uv_cpu_info(uv_cpu_info_t** cpu_infos, int* count) { @@ -48,7 +48,6 @@ int uv_cpu_info(uv_cpu_info_t** cpu_infos, int* count) { return UV_ENOSYS; } -void uv_free_cpu_info(uv_cpu_info_t* cpu_infos, int count) { - (void)cpu_infos; - (void)count; +uint64_t uv_get_constrained_memory(void) { + return 0; /* Memory constraints are unknown. */ } diff --git a/src/unix/darwin-proctitle.c b/src/unix/darwin-proctitle.c index dabde22..5288083 100644 --- a/src/unix/darwin-proctitle.c +++ b/src/unix/darwin-proctitle.c @@ -23,33 +23,25 @@ #include #include +#include #include #include #include #if !TARGET_OS_IPHONE -# include -# include +#include "darwin-stub.h" #endif static int uv__pthread_setname_np(const char* name) { - int (*dynamic_pthread_setname_np)(const char* name); char namebuf[64]; /* MAXTHREADNAMESIZE */ int err; - /* pthread_setname_np() first appeared in OS X 10.6 and iOS 3.2. */ - *(void **)(&dynamic_pthread_setname_np) = - dlsym(RTLD_DEFAULT, "pthread_setname_np"); - - if (dynamic_pthread_setname_np == NULL) - return UV_ENOSYS; - strncpy(namebuf, name, sizeof(namebuf) - 1); namebuf[sizeof(namebuf) - 1] = '\0'; - err = dynamic_pthread_setname_np(namebuf); + err = pthread_setname_np(namebuf); if (err) return UV__ERR(err); @@ -79,8 +71,6 @@ int uv__set_process_title(const char* title) { CFStringRef* display_name_key; CFDictionaryRef (*pCFBundleGetInfoDictionary)(CFBundleRef); CFBundleRef (*pCFBundleGetMainBundle)(void); - CFBundleRef hi_services_bundle; - OSStatus (*pSetApplicationIsDaemon)(int); CFDictionaryRef (*pLSApplicationCheckIn)(int, CFDictionaryRef); void (*pLSSetApplicationLaunchServicesServerConnectionStatus)(uint64_t, void*); @@ -151,30 +141,19 @@ int uv__set_process_title(const char* title) { if (pCFBundleGetInfoDictionary == NULL || pCFBundleGetMainBundle == NULL) goto out; - /* Black 10.9 magic, to remove (Not responding) mark in Activity Monitor */ - hi_services_bundle = - pCFBundleGetBundleWithIdentifier(S("com.apple.HIServices")); - err = UV_ENOENT; - if (hi_services_bundle == NULL) - goto out; - - *(void **)(&pSetApplicationIsDaemon) = pCFBundleGetFunctionPointerForName( - hi_services_bundle, - S("SetApplicationIsDaemon")); *(void **)(&pLSApplicationCheckIn) = pCFBundleGetFunctionPointerForName( launch_services_bundle, S("_LSApplicationCheckIn")); + + if (pLSApplicationCheckIn == NULL) + goto out; + *(void **)(&pLSSetApplicationLaunchServicesServerConnectionStatus) = pCFBundleGetFunctionPointerForName( launch_services_bundle, S("_LSSetApplicationLaunchServicesServerConnectionStatus")); - if (pSetApplicationIsDaemon == NULL || - pLSApplicationCheckIn == NULL || - pLSSetApplicationLaunchServicesServerConnectionStatus == NULL) { - goto out; - } - if (pSetApplicationIsDaemon(1) != noErr) + if (pLSSetApplicationLaunchServicesServerConnectionStatus == NULL) goto out; pLSSetApplicationLaunchServicesServerConnectionStatus(0, NULL); @@ -185,6 +164,10 @@ int uv__set_process_title(const char* title) { asn = pLSGetCurrentApplicationASN(); + err = UV_EBUSY; + if (asn == NULL) + goto out; + err = UV_EINVAL; if (pLSSetApplicationInformationItem(-2, /* Magic value. */ asn, diff --git a/src/unix/darwin-stub.h b/src/unix/darwin-stub.h new file mode 100644 index 0000000..b93cf67 --- /dev/null +++ b/src/unix/darwin-stub.h @@ -0,0 +1,97 @@ +/* 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. + */ + +#ifndef UV_DARWIN_STUB_H_ +#define UV_DARWIN_STUB_H_ + +#include + +struct CFArrayCallBacks; +struct CFRunLoopSourceContext; +struct FSEventStreamContext; + +typedef double CFAbsoluteTime; +typedef double CFTimeInterval; +typedef int FSEventStreamEventFlags; +typedef int OSStatus; +typedef long CFIndex; +typedef struct CFArrayCallBacks CFArrayCallBacks; +typedef struct CFRunLoopSourceContext CFRunLoopSourceContext; +typedef struct FSEventStreamContext FSEventStreamContext; +typedef uint32_t FSEventStreamCreateFlags; +typedef uint64_t FSEventStreamEventId; +typedef unsigned CFStringEncoding; +typedef void* CFAllocatorRef; +typedef void* CFArrayRef; +typedef void* CFBundleRef; +typedef void* CFDictionaryRef; +typedef void* CFRunLoopRef; +typedef void* CFRunLoopSourceRef; +typedef void* CFStringRef; +typedef void* CFTypeRef; +typedef void* FSEventStreamRef; + +typedef void (*FSEventStreamCallback)(const FSEventStreamRef, + void*, + size_t, + void*, + const FSEventStreamEventFlags*, + const FSEventStreamEventId*); + +struct CFRunLoopSourceContext { + CFIndex version; + void* info; + void* pad[7]; + void (*perform)(void*); +}; + +struct FSEventStreamContext { + CFIndex version; + void* info; + void* pad[3]; +}; + +static const CFStringEncoding kCFStringEncodingUTF8 = 0x8000100; +static const OSStatus noErr = 0; + +static const FSEventStreamEventId kFSEventStreamEventIdSinceNow = -1; + +static const int kFSEventStreamCreateFlagNoDefer = 2; +static const int kFSEventStreamCreateFlagFileEvents = 16; + +static const int kFSEventStreamEventFlagEventIdsWrapped = 8; +static const int kFSEventStreamEventFlagHistoryDone = 16; +static const int kFSEventStreamEventFlagItemChangeOwner = 0x4000; +static const int kFSEventStreamEventFlagItemCreated = 0x100; +static const int kFSEventStreamEventFlagItemFinderInfoMod = 0x2000; +static const int kFSEventStreamEventFlagItemInodeMetaMod = 0x400; +static const int kFSEventStreamEventFlagItemIsDir = 0x20000; +static const int kFSEventStreamEventFlagItemModified = 0x1000; +static const int kFSEventStreamEventFlagItemRemoved = 0x200; +static const int kFSEventStreamEventFlagItemRenamed = 0x800; +static const int kFSEventStreamEventFlagItemXattrMod = 0x8000; +static const int kFSEventStreamEventFlagKernelDropped = 4; +static const int kFSEventStreamEventFlagMount = 64; +static const int kFSEventStreamEventFlagRootChanged = 32; +static const int kFSEventStreamEventFlagUnmount = 128; +static const int kFSEventStreamEventFlagUserDropped = 2; + +#endif /* UV_DARWIN_STUB_H_ */ diff --git a/src/unix/darwin.c b/src/unix/darwin.c index 31ad8a9..654aba2 100644 --- a/src/unix/darwin.c +++ b/src/unix/darwin.c @@ -110,19 +110,24 @@ uint64_t uv_get_total_memory(void) { int which[] = {CTL_HW, HW_MEMSIZE}; size_t size = sizeof(info); - if (sysctl(which, 2, &info, &size, NULL, 0)) + if (sysctl(which, ARRAY_SIZE(which), &info, &size, NULL, 0)) return UV__ERR(errno); return (uint64_t) info; } +uint64_t uv_get_constrained_memory(void) { + return 0; /* Memory constraints are unknown. */ +} + + void uv_loadavg(double avg[3]) { struct loadavg info; size_t size = sizeof(info); int which[] = {CTL_VM, VM_LOADAVG}; - if (sysctl(which, 2, &info, &size, NULL, 0) < 0) return; + if (sysctl(which, ARRAY_SIZE(which), &info, &size, NULL, 0) < 0) return; avg[0] = (double) info.ldavg[0] / info.fscale; avg[1] = (double) info.ldavg[1] / info.fscale; @@ -157,7 +162,7 @@ int uv_uptime(double* uptime) { size_t size = sizeof(info); static int which[] = {CTL_KERN, KERN_BOOTTIME}; - if (sysctl(which, 2, &info, &size, NULL, 0)) + if (sysctl(which, ARRAY_SIZE(which), &info, &size, NULL, 0)) return UV__ERR(errno); now = time(NULL); @@ -218,14 +223,3 @@ int uv_cpu_info(uv_cpu_info_t** cpu_infos, int* count) { return 0; } - - -void uv_free_cpu_info(uv_cpu_info_t* cpu_infos, int count) { - int i; - - for (i = 0; i < count; i++) { - uv__free(cpu_infos[i].model); - } - - uv__free(cpu_infos); -} diff --git a/src/unix/freebsd.c b/src/unix/freebsd.c index 70ccb13..ef77e12 100644 --- a/src/unix/freebsd.c +++ b/src/unix/freebsd.c @@ -47,15 +47,6 @@ # define CP_INTR 4 #endif -static uv_mutex_t process_title_mutex; -static uv_once_t process_title_mutex_once = UV_ONCE_INIT; -static char *process_title; - - -static void init_process_title_mutex_once(void) { - uv_mutex_init(&process_title_mutex); -} - int uv__platform_loop_init(uv_loop_t* loop) { return uv__kqueue_init(loop); @@ -104,7 +95,7 @@ int uv_exepath(char* buffer, size_t* size) { mib[3] = -1; abspath_size = sizeof abspath; - if (sysctl(mib, 4, abspath, &abspath_size, NULL, 0)) + if (sysctl(mib, ARRAY_SIZE(mib), abspath, &abspath_size, NULL, 0)) return UV__ERR(errno); assert(abspath_size > 0); @@ -139,19 +130,24 @@ uint64_t uv_get_total_memory(void) { size_t size = sizeof(info); - if (sysctl(which, 2, &info, &size, NULL, 0)) + if (sysctl(which, ARRAY_SIZE(which), &info, &size, NULL, 0)) return UV__ERR(errno); return (uint64_t) info; } +uint64_t uv_get_constrained_memory(void) { + return 0; /* Memory constraints are unknown. */ +} + + void uv_loadavg(double avg[3]) { struct loadavg info; size_t size = sizeof(info); int which[] = {CTL_VM, VM_LOADAVG}; - if (sysctl(which, 2, &info, &size, NULL, 0) < 0) return; + if (sysctl(which, ARRAY_SIZE(which), &info, &size, NULL, 0) < 0) return; avg[0] = (double) info.ldavg[0] / info.fscale; avg[1] = (double) info.ldavg[1] / info.fscale; @@ -159,76 +155,6 @@ void uv_loadavg(double avg[3]) { } -char** uv_setup_args(int argc, char** argv) { - process_title = argc ? uv__strdup(argv[0]) : NULL; - return argv; -} - - -int uv_set_process_title(const char* title) { - int oid[4]; - char* new_title; - - new_title = uv__strdup(title); - - uv_once(&process_title_mutex_once, init_process_title_mutex_once); - uv_mutex_lock(&process_title_mutex); - - if (process_title == NULL) { - uv_mutex_unlock(&process_title_mutex); - return UV_ENOMEM; - } - - uv__free(process_title); - process_title = new_title; - - oid[0] = CTL_KERN; - oid[1] = KERN_PROC; - oid[2] = KERN_PROC_ARGS; - oid[3] = getpid(); - - sysctl(oid, - ARRAY_SIZE(oid), - NULL, - NULL, - process_title, - strlen(process_title) + 1); - - uv_mutex_unlock(&process_title_mutex); - - return 0; -} - - -int uv_get_process_title(char* buffer, size_t size) { - size_t len; - - if (buffer == NULL || size == 0) - return UV_EINVAL; - - uv_once(&process_title_mutex_once, init_process_title_mutex_once); - uv_mutex_lock(&process_title_mutex); - - if (process_title) { - len = strlen(process_title) + 1; - - if (size < len) { - uv_mutex_unlock(&process_title_mutex); - return UV_ENOBUFS; - } - - memcpy(buffer, process_title, len); - } else { - len = 0; - } - - uv_mutex_unlock(&process_title_mutex); - - buffer[len] = '\0'; - - return 0; -} - int uv_resident_set_memory(size_t* rss) { struct kinfo_proc kinfo; size_t page_size; @@ -242,7 +168,7 @@ int uv_resident_set_memory(size_t* rss) { kinfo_size = sizeof(kinfo); - if (sysctl(mib, 4, &kinfo, &kinfo_size, NULL, 0)) + if (sysctl(mib, ARRAY_SIZE(mib), &kinfo, &kinfo_size, NULL, 0)) return UV__ERR(errno); page_size = getpagesize(); @@ -364,12 +290,26 @@ int uv_cpu_info(uv_cpu_info_t** cpu_infos, int* count) { } -void uv_free_cpu_info(uv_cpu_info_t* cpu_infos, int count) { - int i; +int uv__sendmmsg(int fd, + struct uv__mmsghdr* mmsg, + unsigned int vlen, + unsigned int flags) { +#if __FreeBSD__ >= 11 + return sendmmsg(fd, mmsg, vlen, flags); +#else + return errno = ENOSYS, -1; +#endif +} - for (i = 0; i < count; i++) { - uv__free(cpu_infos[i].model); - } - uv__free(cpu_infos); +int uv__recvmmsg(int fd, + struct uv__mmsghdr* mmsg, + unsigned int vlen, + unsigned int flags, + struct timespec* timeout) { +#if __FreeBSD__ >= 11 + return recvmmsg(fd, mmsg, vlen, flags, timeout); +#else + return errno = ENOSYS, -1; +#endif } diff --git a/src/unix/fs.c b/src/unix/fs.c index c3f7951..f5b2b94 100644 --- a/src/unix/fs.c +++ b/src/unix/fs.c @@ -30,6 +30,7 @@ #include "internal.h" #include +#include #include #include #include @@ -47,7 +48,7 @@ #if defined(__DragonFly__) || \ defined(__FreeBSD__) || \ - defined(__FreeBSD_kernel_) || \ + defined(__FreeBSD_kernel__) || \ defined(__OpenBSD__) || \ defined(__NetBSD__) # define HAVE_PREADV 1 @@ -60,7 +61,7 @@ #endif #if defined(__APPLE__) -# include +# include #elif defined(__linux__) && !defined(FICLONE) # include # define FICLONE _IOW(0x94, 9, int) @@ -70,6 +71,24 @@ # include #endif +#if defined(__APPLE__) || \ + defined(__DragonFly__) || \ + defined(__FreeBSD__) || \ + defined(__FreeBSD_kernel__) || \ + defined(__OpenBSD__) || \ + defined(__NetBSD__) +# include +# include +#elif defined(__sun) || defined(__MVS__) || defined(__NetBSD__) || defined(__HAIKU__) +# include +#else +# include +#endif + +#if defined(_AIX) && _XOPEN_SOURCE <= 600 +extern char *mkdtemp(char *template); /* See issue #740 on AIX < 7 */ +#endif + #define INIT(subtype) \ do { \ if (req == NULL) \ @@ -138,19 +157,34 @@ while (0) +static int uv__fs_close(int fd) { + int rc; + + rc = uv__close_nocancel(fd); + if (rc == -1) + if (errno == EINTR || errno == EINPROGRESS) + rc = 0; /* The close is in progress, not an error. */ + + return rc; +} + + static ssize_t 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 * which do, according to recent man pages. F_FULLFSYNC is Apple's equivalent * for flushing buffered data to permanent storage. If F_FULLFSYNC is not - * supported by the file system we should fall back to fsync(). This is the - * same approach taken by sqlite. + * supported by the file system we fall back to F_BARRIERFSYNC or fsync(). + * This is the same approach taken by sqlite, except sqlite does not issue + * an F_BARRIERFSYNC call. */ int r; r = fcntl(req->file, F_FULLFSYNC); - if (r != 0 && errno == ENOTTY) + if (r != 0) + r = fcntl(req->file, 85 /* F_BARRIERFSYNC */); /* fsync + barrier */ + if (r != 0) r = fsync(req->file); return r; #else @@ -171,18 +205,35 @@ static ssize_t uv__fs_fdatasync(uv_fs_t* req) { } +UV_UNUSED(static struct timespec uv__fs_to_timespec(double time)) { + struct timespec ts; + ts.tv_sec = time; + ts.tv_nsec = (uint64_t)(time * 1000000) % 1000000 * 1000; + return ts; +} + +UV_UNUSED(static struct timeval uv__fs_to_timeval(double time)) { + struct timeval tv; + tv.tv_sec = time; + tv.tv_usec = (uint64_t)(time * 1000000) % 1000000; + return tv; +} + static ssize_t uv__fs_futime(uv_fs_t* req) { #if defined(__linux__) \ - || defined(_AIX71) + || defined(_AIX71) \ + || defined(__HAIKU__) /* utimesat() has nanosecond resolution but we stick to microseconds * for the sake of consistency with other platforms. */ struct timespec ts[2]; - ts[0].tv_sec = req->atime; - ts[0].tv_nsec = (uint64_t)(req->atime * 1000000) % 1000000 * 1000; - ts[1].tv_sec = req->mtime; - ts[1].tv_nsec = (uint64_t)(req->mtime * 1000000) % 1000000 * 1000; + ts[0] = uv__fs_to_timespec(req->atime); + ts[1] = uv__fs_to_timespec(req->mtime); +#if defined(__ANDROID_API__) && __ANDROID_API__ < 21 + return utimensat(req->file, NULL, ts, 0); +#else return futimens(req->file, ts); +#endif #elif defined(__APPLE__) \ || defined(__DragonFly__) \ || defined(__FreeBSD__) \ @@ -191,10 +242,8 @@ static ssize_t uv__fs_futime(uv_fs_t* req) { || defined(__OpenBSD__) \ || defined(__sun) struct timeval tv[2]; - tv[0].tv_sec = req->atime; - tv[0].tv_usec = (uint64_t)(req->atime * 1000000) % 1000000; - tv[1].tv_sec = req->mtime; - tv[1].tv_usec = (uint64_t)(req->mtime * 1000000) % 1000000; + tv[0] = uv__fs_to_timeval(req->atime); + tv[1] = uv__fs_to_timeval(req->mtime); # if defined(__sun) return futimesat(req->file, NULL, tv); # else @@ -220,23 +269,99 @@ static ssize_t uv__fs_mkdtemp(uv_fs_t* req) { } -static ssize_t uv__fs_open(uv_fs_t* req) { - static int no_cloexec_support; +static int (*uv__mkostemp)(char*, int); + + +static void uv__mkostemp_initonce(void) { + /* z/os doesn't have RTLD_DEFAULT but that's okay + * because it doesn't have mkostemp(O_CLOEXEC) either. + */ +#ifdef RTLD_DEFAULT + uv__mkostemp = (int (*)(char*, int)) dlsym(RTLD_DEFAULT, "mkostemp"); + + /* We don't care about errors, but we do want to clean them up. + * If there has been no error, then dlerror() will just return + * NULL. + */ + dlerror(); +#endif /* RTLD_DEFAULT */ +} + + +static int uv__fs_mkstemp(uv_fs_t* req) { + static uv_once_t once = UV_ONCE_INIT; int r; +#ifdef O_CLOEXEC + static int no_cloexec_support; +#endif + static const char pattern[] = "XXXXXX"; + static const size_t pattern_size = sizeof(pattern) - 1; + char* path; + size_t path_length; + + path = (char*) req->path; + path_length = strlen(path); + + /* EINVAL can be returned for 2 reasons: + 1. The template's last 6 characters were not XXXXXX + 2. open() didn't support O_CLOEXEC + We want to avoid going to the fallback path in case + of 1, so it's manually checked before. */ + if (path_length < pattern_size || + strcmp(path + path_length - pattern_size, pattern)) { + errno = EINVAL; + return -1; + } + + uv_once(&once, uv__mkostemp_initonce); - /* Try O_CLOEXEC before entering locks */ - if (no_cloexec_support == 0) { #ifdef O_CLOEXEC - r = open(req->path, req->flags | O_CLOEXEC, req->mode); + if (no_cloexec_support == 0 && uv__mkostemp != NULL) { + r = uv__mkostemp(path, O_CLOEXEC); + if (r >= 0) return r; + + /* If mkostemp() returns EINVAL, it means the kernel doesn't + support O_CLOEXEC, so we just fallback to mkstemp() below. */ if (errno != EINVAL) return r; + + /* We set the static variable so that next calls don't even + try to use mkostemp. */ no_cloexec_support = 1; + } #endif /* O_CLOEXEC */ + + if (req->cb != NULL) + uv_rwlock_rdlock(&req->loop->cloexec_lock); + + r = mkstemp(path); + + /* In case of failure `uv__cloexec` will leave error in `errno`, + * so it is enough to just set `r` to `-1`. + */ + if (r >= 0 && uv__cloexec(r, 1) != 0) { + r = uv__close(r); + if (r != 0) + abort(); + r = -1; } if (req->cb != NULL) + uv_rwlock_rdunlock(&req->loop->cloexec_lock); + + return r; +} + + +static ssize_t uv__fs_open(uv_fs_t* req) { +#ifdef O_CLOEXEC + return open(req->path, req->flags | O_CLOEXEC, req->mode); +#else /* O_CLOEXEC */ + int r; + + if (req->cb != NULL) uv_rwlock_rdlock(&req->loop->cloexec_lock); r = open(req->path, req->flags, req->mode); @@ -255,24 +380,71 @@ static ssize_t uv__fs_open(uv_fs_t* req) { uv_rwlock_rdunlock(&req->loop->cloexec_lock); return r; +#endif /* O_CLOEXEC */ +} + + +#if !HAVE_PREADV +static ssize_t uv__fs_preadv(uv_file fd, + uv_buf_t* bufs, + unsigned int nbufs, + off_t off) { + uv_buf_t* buf; + uv_buf_t* end; + ssize_t result; + ssize_t rc; + size_t pos; + + assert(nbufs > 0); + + result = 0; + pos = 0; + buf = bufs + 0; + end = bufs + nbufs; + + for (;;) { + do + rc = pread(fd, buf->base + pos, buf->len - pos, off + result); + while (rc == -1 && errno == EINTR); + + if (rc == 0) + break; + + if (rc == -1 && result == 0) + return UV__ERR(errno); + + if (rc == -1) + break; /* We read some data so return that, ignore the error. */ + + pos += rc; + result += rc; + + if (pos < buf->len) + continue; + + pos = 0; + buf += 1; + + if (buf == end) + break; + } + + return result; } +#endif static ssize_t uv__fs_read(uv_fs_t* req) { #if defined(__linux__) static int no_preadv; #endif + unsigned int iovmax; ssize_t result; -#if defined(_AIX) - struct stat buf; - if(fstat(req->file, &buf)) - return -1; - if(S_ISDIR(buf.st_mode)) { - errno = EISDIR; - return -1; - } -#endif /* defined(_AIX) */ + iovmax = uv__getiovmax(); + if (req->nbufs > iovmax) + req->nbufs = iovmax; + if (req->off < 0) { if (req->nbufs == 1) result = read(req->file, req->bufs[0].base, req->bufs[0].len); @@ -291,25 +463,7 @@ static ssize_t uv__fs_read(uv_fs_t* req) { if (no_preadv) retry: # endif { - off_t nread; - size_t index; - - nread = 0; - index = 0; - result = 1; - do { - if (req->bufs[index].len > 0) { - result = pread(req->file, - req->bufs[index].base, - req->bufs[index].len, - req->off + nread); - if (result > 0) - nread += result; - } - index++; - } while (index < req->nbufs && result > 0); - if (nread > 0) - result = nread; + result = uv__fs_preadv(req->file, req->bufs, req->nbufs, req->off); } # if defined(__linux__) else { @@ -327,6 +481,25 @@ static ssize_t uv__fs_read(uv_fs_t* req) { } done: + /* Early cleanup of bufs allocation, since we're done with it. */ + if (req->bufs != req->bufsml) + uv__free(req->bufs); + + req->bufs = NULL; + req->nbufs = 0; + +#ifdef __PASE__ + /* PASE returns EOPNOTSUPP when reading a directory, convert to EISDIR */ + if (result == -1 && errno == EOPNOTSUPP) { + struct stat buf; + ssize_t rc; + rc = fstat(req->file, &buf); + if (rc == 0 && S_ISDIR(buf.st_mode)) { + errno = EISDIR; + } + } +#endif + return result; } @@ -349,7 +522,7 @@ static int uv__fs_scandir_sort(UV_CONST_DIRENT** a, UV_CONST_DIRENT** b) { static ssize_t uv__fs_scandir(uv_fs_t* req) { - uv__dirent_t **dents; + uv__dirent_t** dents; int n; dents = NULL; @@ -373,29 +546,160 @@ static ssize_t uv__fs_scandir(uv_fs_t* req) { return n; } +static int uv__fs_opendir(uv_fs_t* req) { + uv_dir_t* dir; + + dir = uv__malloc(sizeof(*dir)); + if (dir == NULL) + goto error; + + dir->dir = opendir(req->path); + if (dir->dir == NULL) + goto error; + + req->ptr = dir; + return 0; + +error: + uv__free(dir); + req->ptr = NULL; + return -1; +} + +static int uv__fs_readdir(uv_fs_t* req) { + uv_dir_t* dir; + uv_dirent_t* dirent; + struct dirent* res; + unsigned int dirent_idx; + unsigned int i; + + dir = req->ptr; + dirent_idx = 0; + + while (dirent_idx < dir->nentries) { + /* readdir() returns NULL on end of directory, as well as on error. errno + is used to differentiate between the two conditions. */ + errno = 0; + res = readdir(dir->dir); + + if (res == NULL) { + if (errno != 0) + goto error; + break; + } + + if (strcmp(res->d_name, ".") == 0 || strcmp(res->d_name, "..") == 0) + continue; + + dirent = &dir->dirents[dirent_idx]; + dirent->name = uv__strdup(res->d_name); + + if (dirent->name == NULL) + goto error; + + dirent->type = uv__fs_get_dirent_type(res); + ++dirent_idx; + } + + return dirent_idx; + +error: + for (i = 0; i < dirent_idx; ++i) { + uv__free((char*) dir->dirents[i].name); + dir->dirents[i].name = NULL; + } + + return -1; +} + +static int uv__fs_closedir(uv_fs_t* req) { + uv_dir_t* dir; + + dir = req->ptr; + + if (dir->dir != NULL) { + closedir(dir->dir); + dir->dir = NULL; + } + + uv__free(req->ptr); + req->ptr = NULL; + return 0; +} + +static int uv__fs_statfs(uv_fs_t* req) { + uv_statfs_t* stat_fs; +#if defined(__sun) || defined(__MVS__) || defined(__NetBSD__) || defined(__HAIKU__) + struct statvfs buf; + + if (0 != statvfs(req->path, &buf)) +#else + struct statfs buf; + + if (0 != statfs(req->path, &buf)) +#endif /* defined(__sun) */ + return -1; + + stat_fs = uv__malloc(sizeof(*stat_fs)); + if (stat_fs == NULL) { + errno = ENOMEM; + return -1; + } + +#if defined(__sun) || defined(__MVS__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) + stat_fs->f_type = 0; /* f_type is not supported. */ +#else + stat_fs->f_type = buf.f_type; +#endif + stat_fs->f_bsize = buf.f_bsize; + stat_fs->f_blocks = buf.f_blocks; + stat_fs->f_bfree = buf.f_bfree; + stat_fs->f_bavail = buf.f_bavail; + stat_fs->f_files = buf.f_files; + stat_fs->f_ffree = buf.f_ffree; + req->ptr = stat_fs; + return 0; +} static ssize_t uv__fs_pathmax_size(const char* path) { ssize_t pathmax; pathmax = pathconf(path, _PC_PATH_MAX); - if (pathmax == -1) { -#if defined(PATH_MAX) - return PATH_MAX; -#else -#error "PATH_MAX undefined in the current platform" -#endif - } + if (pathmax == -1) + pathmax = UV__PATH_MAX; return pathmax; } static ssize_t uv__fs_readlink(uv_fs_t* req) { + ssize_t maxlen; ssize_t len; char* buf; - len = uv__fs_pathmax_size(req->path); - buf = uv__malloc(len + 1); +#if defined(_POSIX_PATH_MAX) || defined(PATH_MAX) + maxlen = uv__fs_pathmax_size(req->path); +#else + /* We may not have a real PATH_MAX. Read size of link. */ + struct stat st; + int ret; + ret = lstat(req->path, &st); + if (ret != 0) + return -1; + if (!S_ISLNK(st.st_mode)) { + errno = EINVAL; + return -1; + } + + maxlen = st.st_size; + + /* According to readlink(2) lstat can report st_size == 0 + for some symlinks, such as those in /proc or /sys. */ + if (maxlen == 0) + maxlen = uv__fs_pathmax_size(req->path); +#endif + + buf = uv__malloc(maxlen); if (buf == NULL) { errno = ENOMEM; @@ -403,17 +707,24 @@ static ssize_t uv__fs_readlink(uv_fs_t* req) { } #if defined(__MVS__) - len = os390_readlink(req->path, buf, len); + len = os390_readlink(req->path, buf, maxlen); #else - len = readlink(req->path, buf, len); + len = readlink(req->path, buf, maxlen); #endif - if (len == -1) { uv__free(buf); return -1; } + /* Uncommon case: resize to make room for the trailing nul byte. */ + if (len == maxlen) { + buf = uv__reallocf(buf, len + 1); + + if (buf == NULL) + return -1; + } + buf[len] = '\0'; req->ptr = buf; @@ -421,9 +732,15 @@ static ssize_t uv__fs_readlink(uv_fs_t* req) { } static ssize_t uv__fs_realpath(uv_fs_t* req) { - ssize_t len; char* buf; +#if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200809L + buf = realpath(req->path, NULL); + if (buf == NULL) + return -1; +#else + ssize_t len; + len = uv__fs_pathmax_size(req->path); buf = uv__malloc(len + 1); @@ -436,6 +753,7 @@ static ssize_t uv__fs_realpath(uv_fs_t* req) { uv__free(buf); return -1; } +#endif req->ptr = buf; @@ -658,15 +976,14 @@ static ssize_t uv__fs_sendfile(uv_fs_t* req) { static ssize_t uv__fs_utime(uv_fs_t* req) { #if defined(__linux__) \ || defined(_AIX71) \ - || defined(__sun) + || defined(__sun) \ + || defined(__HAIKU__) /* utimesat() has nanosecond resolution but we stick to microseconds * for the sake of consistency with other platforms. */ struct timespec ts[2]; - ts[0].tv_sec = req->atime; - ts[0].tv_nsec = (uint64_t)(req->atime * 1000000) % 1000000 * 1000; - ts[1].tv_sec = req->mtime; - ts[1].tv_nsec = (uint64_t)(req->mtime * 1000000) % 1000000 * 1000; + ts[0] = uv__fs_to_timespec(req->atime); + ts[1] = uv__fs_to_timespec(req->mtime); return utimensat(AT_FDCWD, req->path, ts, 0); #elif defined(__APPLE__) \ || defined(__DragonFly__) \ @@ -675,10 +992,8 @@ static ssize_t uv__fs_utime(uv_fs_t* req) { || defined(__NetBSD__) \ || defined(__OpenBSD__) struct timeval tv[2]; - tv[0].tv_sec = req->atime; - tv[0].tv_usec = (uint64_t)(req->atime * 1000000) % 1000000; - tv[1].tv_sec = req->mtime; - tv[1].tv_usec = (uint64_t)(req->mtime * 1000000) % 1000000; + tv[0] = uv__fs_to_timeval(req->atime); + tv[1] = uv__fs_to_timeval(req->mtime); return utimes(req->path, tv); #elif defined(_AIX) \ && !defined(_AIX71) @@ -693,7 +1008,32 @@ static ssize_t uv__fs_utime(uv_fs_t* req) { atr.att_atimechg = 1; atr.att_mtime = req->mtime; atr.att_atime = req->atime; - return __lchattr(req->path, &atr, sizeof(atr)); + return __lchattr((char*) req->path, &atr, sizeof(atr)); +#else + errno = ENOSYS; + return -1; +#endif +} + + +static ssize_t uv__fs_lutime(uv_fs_t* req) { +#if defined(__linux__) || \ + defined(_AIX71) || \ + defined(__sun) || \ + defined(__HAIKU__) + struct timespec ts[2]; + ts[0] = uv__fs_to_timespec(req->atime); + ts[1] = uv__fs_to_timespec(req->mtime); + return utimensat(AT_FDCWD, req->path, ts, AT_SYMLINK_NOFOLLOW); +#elif defined(__APPLE__) || \ + defined(__DragonFly__) || \ + defined(__FreeBSD__) || \ + defined(__FreeBSD_kernel__) || \ + defined(__NetBSD__) + struct timeval tv[2]; + tv[0] = uv__fs_to_timeval(req->atime); + tv[1] = uv__fs_to_timeval(req->mtime); + return lutimes(req->path, tv); #else errno = ENOSYS; return -1; @@ -735,25 +1075,7 @@ static ssize_t uv__fs_write(uv_fs_t* req) { if (no_pwritev) retry: # endif { - off_t written; - size_t index; - - written = 0; - index = 0; - r = 0; - do { - if (req->bufs[index].len > 0) { - r = pwrite(req->file, - req->bufs[index].base, - req->bufs[index].len, - req->off + written); - if (r > 0) - written += r; - } - index++; - } while (index < req->nbufs && r >= 0); - if (written > 0) - r = written; + r = pwrite(req->file, req->bufs[0].base, req->bufs[0].len, req->off); } # if defined(__linux__) else { @@ -780,39 +1102,18 @@ done: } static ssize_t uv__fs_copyfile(uv_fs_t* req) { -#if defined(__APPLE__) && !TARGET_OS_IPHONE - /* On macOS, use the native copyfile(3). */ - copyfile_flags_t flags; - - flags = COPYFILE_ALL; - - if (req->flags & UV_FS_COPYFILE_EXCL) - flags |= COPYFILE_EXCL; - -#ifdef COPYFILE_CLONE - if (req->flags & UV_FS_COPYFILE_FICLONE) - flags |= COPYFILE_CLONE; -#endif - - if (req->flags & UV_FS_COPYFILE_FICLONE_FORCE) { -#ifdef COPYFILE_CLONE_FORCE - flags |= COPYFILE_CLONE_FORCE; -#else - return UV_ENOSYS; -#endif - } - - return copyfile(req->path, req->new_path, NULL, flags); -#else uv_fs_t fs_req; uv_file srcfd; uv_file dstfd; - struct stat statsbuf; + struct stat src_statsbuf; + struct stat dst_statsbuf; int dst_flags; int result; int err; - size_t bytes_to_send; - int64_t in_offset; + off_t bytes_to_send; + off_t in_offset; + off_t bytes_written; + size_t bytes_chunk; dstfd = -1; err = 0; @@ -825,7 +1126,7 @@ static ssize_t uv__fs_copyfile(uv_fs_t* req) { return srcfd; /* Get the source file's mode. */ - if (fstat(srcfd, &statsbuf)) { + if (fstat(srcfd, &src_statsbuf)) { err = UV__ERR(errno); goto out; } @@ -840,7 +1141,7 @@ static ssize_t uv__fs_copyfile(uv_fs_t* req) { &fs_req, req->new_path, dst_flags, - statsbuf.st_mode, + src_statsbuf.st_mode, NULL); uv_fs_req_cleanup(&fs_req); @@ -849,26 +1150,55 @@ static ssize_t uv__fs_copyfile(uv_fs_t* req) { goto out; } - if (fchmod(dstfd, statsbuf.st_mode) == -1) { + /* Get the destination file's mode. */ + if (fstat(dstfd, &dst_statsbuf)) { err = UV__ERR(errno); goto out; } + /* Check if srcfd and dstfd refer to the same file */ + if (src_statsbuf.st_dev == dst_statsbuf.st_dev && + src_statsbuf.st_ino == dst_statsbuf.st_ino) { + goto out; + } + + if (fchmod(dstfd, src_statsbuf.st_mode) == -1) { + err = UV__ERR(errno); +#ifdef __linux__ + if (err != UV_EPERM) + goto out; + + { + struct statfs s; + + /* fchmod() on CIFS shares always fails with EPERM unless the share is + * mounted with "noperm". As fchmod() is a meaningless operation on such + * shares anyway, detect that condition and squelch the error. + */ + if (fstatfs(dstfd, &s) == -1) + goto out; + + if (s.f_type != /* CIFS */ 0xFF534D42u) + goto out; + } + + err = 0; +#else /* !__linux__ */ + goto out; +#endif /* !__linux__ */ + } + #ifdef FICLONE if (req->flags & UV_FS_COPYFILE_FICLONE || req->flags & UV_FS_COPYFILE_FICLONE_FORCE) { - if (ioctl(dstfd, FICLONE, srcfd) == -1) { - /* If an error occurred that the sendfile fallback also won't handle, or - this is a force clone then exit. Otherwise, fall through to try using - sendfile(). */ - if (errno != ENOTTY && errno != EOPNOTSUPP && errno != EXDEV) { - err = UV__ERR(errno); - goto out; - } else if (req->flags & UV_FS_COPYFILE_FICLONE_FORCE) { - err = UV_ENOTSUP; - goto out; - } - } else { + if (ioctl(dstfd, FICLONE, srcfd) == 0) { + /* ioctl() with FICLONE succeeded. */ + goto out; + } + /* If an error occurred and force was set, return the error to the caller; + * fall back to sendfile() when force was not set. */ + if (req->flags & UV_FS_COPYFILE_FICLONE_FORCE) { + err = UV__ERR(errno); goto out; } } @@ -879,21 +1209,23 @@ static ssize_t uv__fs_copyfile(uv_fs_t* req) { } #endif - bytes_to_send = statsbuf.st_size; + bytes_to_send = src_statsbuf.st_size; in_offset = 0; while (bytes_to_send != 0) { - err = uv_fs_sendfile(NULL, - &fs_req, - dstfd, - srcfd, - in_offset, - bytes_to_send, - NULL); + bytes_chunk = SSIZE_MAX; + 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; uv_fs_req_cleanup(&fs_req); - if (err < 0) + + if (bytes_written < 0) { + err = bytes_written; break; - bytes_to_send -= fs_req.result; - in_offset += fs_req.result; + } + + bytes_to_send -= bytes_written; + in_offset += bytes_written; } out: @@ -930,7 +1262,6 @@ out: errno = UV__ERR(result); return -1; -#endif } static void uv__to_stat(struct stat* src, uv_stat_t* dst) { @@ -1010,10 +1341,93 @@ static void uv__to_stat(struct stat* src, uv_stat_t* dst) { } +static int uv__fs_statx(int fd, + const char* path, + int is_fstat, + int is_lstat, + uv_stat_t* buf) { + STATIC_ASSERT(UV_ENOSYS != -1); +#ifdef __linux__ + static int no_statx; + struct uv__statx statxbuf; + int dirfd; + int flags; + int mode; + int rc; + + if (no_statx) + return UV_ENOSYS; + + dirfd = AT_FDCWD; + flags = 0; /* AT_STATX_SYNC_AS_STAT */ + mode = 0xFFF; /* STATX_BASIC_STATS + STATX_BTIME */ + + if (is_fstat) { + dirfd = fd; + flags |= 0x1000; /* AT_EMPTY_PATH */ + } + + if (is_lstat) + flags |= AT_SYMLINK_NOFOLLOW; + + rc = uv__statx(dirfd, path, flags, mode, &statxbuf); + + switch (rc) { + case 0: + break; + case -1: + /* EPERM happens when a seccomp filter rejects the system call. + * Has been observed with libseccomp < 2.3.3 and docker < 18.04. + */ + if (errno != EINVAL && errno != EPERM && errno != ENOSYS) + return -1; + /* Fall through. */ + default: + /* Normally on success, zero is returned and On error, -1 is returned. + * Observed on S390 RHEL running in a docker container with statx not + * implemented, rc might return 1 with 0 set as the error code in which + * case we return ENOSYS. + */ + no_statx = 1; + return UV_ENOSYS; + } + + buf->st_dev = 256 * statxbuf.stx_dev_major + statxbuf.stx_dev_minor; + buf->st_mode = statxbuf.stx_mode; + buf->st_nlink = statxbuf.stx_nlink; + buf->st_uid = statxbuf.stx_uid; + buf->st_gid = statxbuf.stx_gid; + buf->st_rdev = statxbuf.stx_rdev_major; + buf->st_ino = statxbuf.stx_ino; + buf->st_size = statxbuf.stx_size; + buf->st_blksize = statxbuf.stx_blksize; + buf->st_blocks = statxbuf.stx_blocks; + buf->st_atim.tv_sec = statxbuf.stx_atime.tv_sec; + buf->st_atim.tv_nsec = statxbuf.stx_atime.tv_nsec; + buf->st_mtim.tv_sec = statxbuf.stx_mtime.tv_sec; + buf->st_mtim.tv_nsec = statxbuf.stx_mtime.tv_nsec; + buf->st_ctim.tv_sec = statxbuf.stx_ctime.tv_sec; + buf->st_ctim.tv_nsec = statxbuf.stx_ctime.tv_nsec; + buf->st_birthtim.tv_sec = statxbuf.stx_btime.tv_sec; + buf->st_birthtim.tv_nsec = statxbuf.stx_btime.tv_nsec; + buf->st_flags = 0; + buf->st_gen = 0; + + return 0; +#else + return UV_ENOSYS; +#endif /* __linux__ */ +} + + static int uv__fs_stat(const char *path, uv_stat_t *buf) { struct stat pbuf; int ret; + ret = uv__fs_statx(-1, path, /* is_fstat */ 0, /* is_lstat */ 0, buf); + if (ret != UV_ENOSYS) + return ret; + ret = stat(path, &pbuf); if (ret == 0) uv__to_stat(&pbuf, buf); @@ -1026,6 +1440,10 @@ static int uv__fs_lstat(const char *path, uv_stat_t *buf) { struct stat pbuf; int ret; + ret = uv__fs_statx(-1, path, /* is_fstat */ 0, /* is_lstat */ 1, buf); + if (ret != UV_ENOSYS) + return ret; + ret = lstat(path, &pbuf); if (ret == 0) uv__to_stat(&pbuf, buf); @@ -1038,6 +1456,10 @@ static int uv__fs_fstat(int fd, uv_stat_t *buf) { struct stat pbuf; int ret; + ret = uv__fs_statx(fd, "", /* is_fstat */ 1, /* is_lstat */ 0, buf); + if (ret != UV_ENOSYS) + return ret; + ret = fstat(fd, &pbuf); if (ret == 0) uv__to_stat(&pbuf, buf); @@ -1045,9 +1467,21 @@ static int uv__fs_fstat(int fd, uv_stat_t *buf) { return ret; } +static size_t uv__fs_buf_offset(uv_buf_t* bufs, size_t size) { + size_t offset; + /* Figure out which bufs are done */ + for (offset = 0; size > 0 && bufs[offset].len <= size; ++offset) + size -= bufs[offset].len; + + /* Fix a partial read/write */ + if (size > 0) { + bufs[offset].base += size; + bufs[offset].len -= size; + } + return offset; +} -typedef ssize_t (*uv__fs_buf_iter_processor)(uv_fs_t* req); -static ssize_t uv__fs_buf_iter(uv_fs_t* req, uv__fs_buf_iter_processor process) { +static ssize_t uv__fs_write_all(uv_fs_t* req) { unsigned int iovmax; unsigned int nbufs; uv_buf_t* bufs; @@ -1064,7 +1498,10 @@ static ssize_t uv__fs_buf_iter(uv_fs_t* req, uv__fs_buf_iter_processor process) if (req->nbufs > iovmax) req->nbufs = iovmax; - result = process(req); + do + result = uv__fs_write(req); + while (result < 0 && errno == EINTR); + if (result <= 0) { if (total == 0) total = result; @@ -1074,14 +1511,12 @@ static ssize_t uv__fs_buf_iter(uv_fs_t* req, uv__fs_buf_iter_processor process) if (req->off >= 0) req->off += result; + req->nbufs = uv__fs_buf_offset(req->bufs, result); req->bufs += req->nbufs; nbufs -= req->nbufs; total += result; } - if (errno == EINTR && total == -1) - return total; - if (bufs != req->bufsml) uv__free(bufs); @@ -1098,7 +1533,8 @@ static void uv__fs_work(struct uv__work* w) { ssize_t r; req = container_of(w, uv_fs_t, work_req); - retry_on_eintr = !(req->fs_type == UV_FS_CLOSE); + retry_on_eintr = !(req->fs_type == UV_FS_CLOSE || + req->fs_type == UV_FS_READ); do { errno = 0; @@ -1112,7 +1548,7 @@ static void uv__fs_work(struct uv__work* w) { X(ACCESS, access(req->path, req->flags)); X(CHMOD, chmod(req->path, req->mode)); X(CHOWN, chown(req->path, req->uid, req->gid)); - X(CLOSE, close(req->file)); + X(CLOSE, uv__fs_close(req->file)); X(COPYFILE, uv__fs_copyfile(req)); X(FCHMOD, fchmod(req->file, req->mode)); X(FCHOWN, fchown(req->file, req->uid, req->gid)); @@ -1122,23 +1558,29 @@ static void uv__fs_work(struct uv__work* w) { X(FSYNC, uv__fs_fsync(req)); X(FTRUNCATE, ftruncate(req->file, req->off)); X(FUTIME, uv__fs_futime(req)); + X(LUTIME, uv__fs_lutime(req)); X(LSTAT, uv__fs_lstat(req->path, &req->statbuf)); X(LINK, link(req->path, req->new_path)); X(MKDIR, mkdir(req->path, req->mode)); X(MKDTEMP, uv__fs_mkdtemp(req)); + X(MKSTEMP, uv__fs_mkstemp(req)); X(OPEN, uv__fs_open(req)); - X(READ, uv__fs_buf_iter(req, uv__fs_read)); + X(READ, uv__fs_read(req)); X(SCANDIR, uv__fs_scandir(req)); + X(OPENDIR, uv__fs_opendir(req)); + X(READDIR, uv__fs_readdir(req)); + X(CLOSEDIR, uv__fs_closedir(req)); X(READLINK, uv__fs_readlink(req)); X(REALPATH, uv__fs_realpath(req)); X(RENAME, rename(req->path, req->new_path)); X(RMDIR, rmdir(req->path)); X(SENDFILE, uv__fs_sendfile(req)); X(STAT, uv__fs_stat(req->path, &req->statbuf)); + X(STATFS, uv__fs_statfs(req)); X(SYMLINK, symlink(req->path, req->new_path)); X(UNLINK, unlink(req->path)); X(UTIME, uv__fs_utime(req)); - X(WRITE, uv__fs_buf_iter(req, uv__fs_write)); + X(WRITE, uv__fs_write_all(req)); default: abort(); } #undef X @@ -1303,6 +1745,19 @@ int uv_fs_futime(uv_loop_t* loop, POST; } +int uv_fs_lutime(uv_loop_t* loop, + uv_fs_t* req, + const char* path, + double atime, + double mtime, + uv_fs_cb cb) { + INIT(LUTIME); + PATH; + req->atime = atime; + req->mtime = mtime; + POST; +} + int uv_fs_lstat(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) { INIT(LSTAT); @@ -1346,6 +1801,18 @@ int uv_fs_mkdtemp(uv_loop_t* loop, } +int uv_fs_mkstemp(uv_loop_t* loop, + uv_fs_t* req, + const char* tpl, + uv_fs_cb cb) { + INIT(MKSTEMP); + req->path = uv__strdup(tpl); + if (req->path == NULL) + return UV_ENOMEM; + POST; +} + + int uv_fs_open(uv_loop_t* loop, uv_fs_t* req, const char* path, @@ -1399,6 +1866,40 @@ int uv_fs_scandir(uv_loop_t* loop, POST; } +int uv_fs_opendir(uv_loop_t* loop, + uv_fs_t* req, + const char* path, + uv_fs_cb cb) { + INIT(OPENDIR); + PATH; + POST; +} + +int uv_fs_readdir(uv_loop_t* loop, + uv_fs_t* req, + uv_dir_t* dir, + uv_fs_cb cb) { + INIT(READDIR); + + if (dir == NULL || dir->dir == NULL || dir->dirents == NULL) + return UV_EINVAL; + + req->ptr = dir; + POST; +} + +int uv_fs_closedir(uv_loop_t* loop, + uv_fs_t* req, + uv_dir_t* dir, + uv_fs_cb cb) { + INIT(CLOSEDIR); + + if (dir == NULL) + return UV_EINVAL; + + req->ptr = dir; + POST; +} int uv_fs_readlink(uv_loop_t* loop, uv_fs_t* req, @@ -1530,15 +2031,20 @@ void uv_fs_req_cleanup(uv_fs_t* req) { /* Only necessary for asychronous requests, i.e., requests with a callback. * Synchronous ones don't copy their arguments and have req->path and - * req->new_path pointing to user-owned memory. UV_FS_MKDTEMP is the - * exception to the rule, it always allocates memory. + * req->new_path pointing to user-owned memory. UV_FS_MKDTEMP and + * UV_FS_MKSTEMP are the exception to the rule, they always allocate memory. */ - if (req->path != NULL && (req->cb != NULL || req->fs_type == UV_FS_MKDTEMP)) + if (req->path != NULL && + (req->cb != NULL || + req->fs_type == UV_FS_MKDTEMP || req->fs_type == UV_FS_MKSTEMP)) uv__free((void*) req->path); /* Memory is shared with req->new_path. */ req->path = NULL; req->new_path = NULL; + if (req->fs_type == UV_FS_READDIR && req->ptr != NULL) + uv__fs_readdir_cleanup(req); + if (req->fs_type == UV_FS_SCANDIR && req->ptr != NULL) uv__fs_scandir_cleanup(req); @@ -1546,7 +2052,7 @@ void uv_fs_req_cleanup(uv_fs_t* req) { uv__free(req->bufs); req->bufs = NULL; - if (req->ptr != &req->statbuf) + if (req->fs_type != UV_FS_OPENDIR && req->ptr != &req->statbuf) uv__free(req->ptr); req->ptr = NULL; } @@ -1570,3 +2076,17 @@ int uv_fs_copyfile(uv_loop_t* loop, req->flags = flags; POST; } + + +int uv_fs_statfs(uv_loop_t* loop, + uv_fs_t* req, + const char* path, + uv_fs_cb cb) { + INIT(STATFS); + PATH; + POST; +} + +int uv_fs_get_system_error(const uv_fs_t* req) { + return -req->result; +} diff --git a/src/unix/fsevents.c b/src/unix/fsevents.c index ee45299..a51f29b 100644 --- a/src/unix/fsevents.c +++ b/src/unix/fsevents.c @@ -21,9 +21,10 @@ #include "uv.h" #include "internal.h" -#if TARGET_OS_IPHONE +#if TARGET_OS_IPHONE || MAC_OS_X_VERSION_MAX_ALLOWED < 1070 /* iOS (currently) doesn't provide the FSEvents-API (nor CoreServices) */ +/* macOS prior to 10.7 doesn't provide the full FSEvents API so use kqueue */ int uv__fsevents_init(uv_fs_event_t* handle) { return 0; @@ -40,34 +41,33 @@ void uv__fsevents_loop_delete(uv_loop_t* loop) { #else /* TARGET_OS_IPHONE */ +#include "darwin-stub.h" + #include #include #include #include -#include -#include - -/* These are macros to avoid "initializer element is not constant" errors - * with old versions of gcc. - */ -#define kFSEventsModified (kFSEventStreamEventFlagItemFinderInfoMod | \ - kFSEventStreamEventFlagItemModified | \ - kFSEventStreamEventFlagItemInodeMetaMod | \ - kFSEventStreamEventFlagItemChangeOwner | \ - kFSEventStreamEventFlagItemXattrMod) - -#define kFSEventsRenamed (kFSEventStreamEventFlagItemCreated | \ - kFSEventStreamEventFlagItemRemoved | \ - kFSEventStreamEventFlagItemRenamed) - -#define kFSEventsSystem (kFSEventStreamEventFlagUserDropped | \ - kFSEventStreamEventFlagKernelDropped | \ - kFSEventStreamEventFlagEventIdsWrapped | \ - kFSEventStreamEventFlagHistoryDone | \ - kFSEventStreamEventFlagMount | \ - kFSEventStreamEventFlagUnmount | \ - kFSEventStreamEventFlagRootChanged) +static const int kFSEventsModified = + kFSEventStreamEventFlagItemChangeOwner | + kFSEventStreamEventFlagItemFinderInfoMod | + kFSEventStreamEventFlagItemInodeMetaMod | + kFSEventStreamEventFlagItemModified | + kFSEventStreamEventFlagItemXattrMod; + +static const int kFSEventsRenamed = + kFSEventStreamEventFlagItemCreated | + kFSEventStreamEventFlagItemRemoved | + kFSEventStreamEventFlagItemRenamed; + +static const int kFSEventsSystem = + kFSEventStreamEventFlagUserDropped | + kFSEventStreamEventFlagKernelDropped | + kFSEventStreamEventFlagEventIdsWrapped | + kFSEventStreamEventFlagHistoryDone | + kFSEventStreamEventFlagMount | + kFSEventStreamEventFlagUnmount | + kFSEventStreamEventFlagRootChanged; typedef struct uv__fsevents_event_s uv__fsevents_event_t; typedef struct uv__cf_loop_signal_s uv__cf_loop_signal_t; @@ -147,7 +147,7 @@ static void (*pFSEventStreamRelease)(FSEventStreamRef); static void (*pFSEventStreamScheduleWithRunLoop)(FSEventStreamRef, CFRunLoopRef, CFStringRef); -static Boolean (*pFSEventStreamStart)(FSEventStreamRef); +static int (*pFSEventStreamStart)(FSEventStreamRef); static void (*pFSEventStreamStop)(FSEventStreamRef); #define UV__FSEVENTS_PROCESS(handle, block) \ @@ -214,7 +214,7 @@ static void uv__fsevents_push_event(uv_fs_event_t* handle, /* Runs in CF thread, when there're events in FSEventStream */ -static void uv__fsevents_event_cb(ConstFSEventStreamRef streamRef, +static void uv__fsevents_event_cb(const FSEventStreamRef streamRef, void* info, size_t numEvents, void* eventPaths, @@ -255,42 +255,57 @@ static void uv__fsevents_event_cb(ConstFSEventStreamRef streamRef, path = paths[i]; len = strlen(path); + if (handle->realpath_len == 0) + continue; /* This should be unreachable */ + /* Filter out paths that are outside handle's request */ - if (strncmp(path, handle->realpath, handle->realpath_len) != 0) + if (len < handle->realpath_len) + continue; + + /* Make sure that realpath actually named a directory, + * (unless watching root, which alone keeps a trailing slash on the realpath) + * or that we matched the whole string */ + if (handle->realpath_len != len && + handle->realpath_len > 1 && + path[handle->realpath_len] != '/') continue; - if (handle->realpath_len > 1 || *handle->realpath != '/') { + if (memcmp(path, handle->realpath, handle->realpath_len) != 0) + continue; + + if (!(handle->realpath_len == 1 && handle->realpath[0] == '/')) { + /* Remove common prefix, unless the watched folder is "/" */ path += handle->realpath_len; len -= handle->realpath_len; - /* Skip forward slash */ - if (*path != '\0') { + /* Ignore events with path equal to directory itself */ + if (len <= 1 && (flags & kFSEventStreamEventFlagItemIsDir)) + continue; + + if (len == 0) { + /* Since we're using fsevents to watch the file itself, + * realpath == path, and we now need to get the basename of the file back + * (for commonality with other codepaths and platforms). */ + while (len < handle->realpath_len && path[-1] != '/') { + path--; + len++; + } + /* Created and Removed seem to be always set, but don't make sense */ + flags &= ~kFSEventsRenamed; + } else { + /* Skip forward slash */ path++; len--; } } -#ifdef MAC_OS_X_VERSION_10_7 - /* Ignore events with path equal to directory itself */ - if (len == 0) - continue; -#else - if (len == 0 && (flags & kFSEventStreamEventFlagItemIsDir)) - continue; -#endif /* MAC_OS_X_VERSION_10_7 */ - /* Do not emit events from subdirectories (without option set) */ - if ((handle->cf_flags & UV_FS_EVENT_RECURSIVE) == 0 && *path != 0) { + if ((handle->cf_flags & UV_FS_EVENT_RECURSIVE) == 0 && *path != '\0') { pos = strchr(path + 1, '/'); if (pos != NULL) continue; } -#ifndef MAC_OS_X_VERSION_10_7 - path = ""; - len = 0; -#endif /* MAC_OS_X_VERSION_10_7 */ - event = uv__malloc(sizeof(*event) + len); if (event == NULL) break; @@ -299,22 +314,11 @@ static void uv__fsevents_event_cb(ConstFSEventStreamRef streamRef, memcpy(event->path, path, len + 1); event->events = UV_RENAME; -#ifdef MAC_OS_X_VERSION_10_7 - if (0 != (flags & kFSEventsModified) && - 0 == (flags & kFSEventsRenamed)) { - event->events = UV_CHANGE; - } -#else - if (0 != (flags & kFSEventsModified) && - 0 != (flags & kFSEventStreamEventFlagItemIsDir) && - 0 == (flags & kFSEventStreamEventFlagItemRenamed)) { - event->events = UV_CHANGE; - } - if (0 == (flags & kFSEventStreamEventFlagItemIsDir) && - 0 == (flags & kFSEventStreamEventFlagItemRenamed)) { - event->events = UV_CHANGE; + if (0 == (flags & kFSEventsRenamed)) { + if (0 != (flags & kFSEventsModified) || + 0 == (flags & kFSEventStreamEventFlagItemIsDir)) + event->events = UV_CHANGE; } -#endif /* MAC_OS_X_VERSION_10_7 */ QUEUE_INSERT_TAIL(&head, &event->member); } @@ -335,11 +339,8 @@ static int uv__fsevents_create_stream(uv_loop_t* loop, CFArrayRef paths) { FSEventStreamCreateFlags flags; /* Initialize context */ - ctx.version = 0; + memset(&ctx, 0, sizeof(ctx)); ctx.info = loop; - ctx.retain = NULL; - ctx.release = NULL; - ctx.copyDescription = NULL; latency = 0.05; @@ -742,6 +743,8 @@ static void* uv__cf_loop_runner(void* arg) { state->signal_source, *pkCFRunLoopDefaultMode); + state->loop = NULL; + return NULL; } @@ -794,13 +797,14 @@ int uv__cf_loop_signal(uv_loop_t* loop, uv_mutex_lock(&loop->cf_mutex); QUEUE_INSERT_TAIL(&loop->cf_signals, &item->member); - uv_mutex_unlock(&loop->cf_mutex); state = loop->cf_state; assert(state != NULL); pCFRunLoopSourceSignal(state->signal_source); pCFRunLoopWakeUp(state->loop); + uv_mutex_unlock(&loop->cf_mutex); + return 0; } diff --git a/src/unix/getaddrinfo.c b/src/unix/getaddrinfo.c index 25827c1..d7ca7d1 100644 --- a/src/unix/getaddrinfo.c +++ b/src/unix/getaddrinfo.c @@ -27,6 +27,7 @@ #include "uv.h" #include "internal.h" +#include "idna.h" #include #include /* NULL */ @@ -91,7 +92,9 @@ int uv__getaddrinfo_translate_error(int sys_err) { } assert(!"unknown EAI_* error code"); abort(); +#ifndef __SUNPRO_C return 0; /* Pacify compiler. */ +#endif } @@ -141,15 +144,34 @@ int uv_getaddrinfo(uv_loop_t* loop, const char* hostname, const char* service, const struct addrinfo* hints) { + char hostname_ascii[256]; size_t hostname_len; size_t service_len; size_t hints_len; size_t len; char* buf; + long rc; if (req == NULL || (hostname == NULL && service == NULL)) return UV_EINVAL; + /* FIXME(bnoordhuis) IDNA does not seem to work z/OS, + * probably because it uses EBCDIC rather than ASCII. + */ +#ifdef __MVS__ + (void) &hostname_ascii; +#else + if (hostname != NULL) { + rc = uv__idna_toascii(hostname, + hostname + strlen(hostname), + hostname_ascii, + hostname_ascii + sizeof(hostname_ascii)); + if (rc < 0) + return rc; + hostname = hostname_ascii; + } +#endif + hostname_len = hostname ? strlen(hostname) + 1 : 0; service_len = service ? strlen(service) + 1 : 0; hints_len = hints ? sizeof(*hints) : 0; diff --git a/src/unix/haiku.c b/src/unix/haiku.c new file mode 100644 index 0000000..cf17d83 --- /dev/null +++ b/src/unix/haiku.c @@ -0,0 +1,167 @@ +/* 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 "internal.h" + +#include /* find_path() */ +#include + + +void uv_loadavg(double avg[3]) { + avg[0] = 0; + avg[1] = 0; + avg[2] = 0; +} + + +int uv_exepath(char* buffer, size_t* size) { + char abspath[B_PATH_NAME_LENGTH]; + status_t status; + ssize_t abspath_len; + + if (buffer == NULL || size == NULL || *size == 0) + return UV_EINVAL; + + status = find_path(B_APP_IMAGE_SYMBOL, B_FIND_PATH_IMAGE_PATH, NULL, abspath, + sizeof(abspath)); + if (status != B_OK) + return UV__ERR(status); + + abspath_len = uv__strscpy(buffer, abspath, *size); + *size -= 1; + if (abspath_len >= 0 && *size > (size_t)abspath_len) + *size = (size_t)abspath_len; + + return 0; +} + + +uint64_t uv_get_free_memory(void) { + status_t status; + system_info sinfo; + + status = get_system_info(&sinfo); + if (status != B_OK) + return 0; + + return (sinfo.max_pages - sinfo.used_pages) * B_PAGE_SIZE; +} + + +uint64_t uv_get_total_memory(void) { + status_t status; + system_info sinfo; + + status = get_system_info(&sinfo); + if (status != B_OK) + return 0; + + return sinfo.max_pages * B_PAGE_SIZE; +} + + +uint64_t uv_get_constrained_memory(void) { + return 0; /* Memory constraints are unknown. */ +} + + +int uv_resident_set_memory(size_t* rss) { + area_info area; + ssize_t cookie; + status_t status; + thread_info thread; + + status = get_thread_info(find_thread(NULL), &thread); + if (status != B_OK) + return UV__ERR(status); + + cookie = 0; + *rss = 0; + while (get_next_area_info(thread.team, &cookie, &area) == B_OK) + *rss += area.ram_size; + + return 0; +} + + +int uv_uptime(double* uptime) { + /* system_time() returns time since booting in microseconds */ + *uptime = (double)system_time() / 1000000; + return 0; +} + + +int uv_cpu_info(uv_cpu_info_t** cpu_infos, int* count) { + cpu_topology_node_info* topology_infos; + int i; + status_t status; + system_info system; + uint32_t topology_count; + uint64_t cpuspeed; + uv_cpu_info_t* cpu_info; + + if (cpu_infos == NULL || count == NULL) + return UV_EINVAL; + + status = get_cpu_topology_info(NULL, &topology_count); + if (status != B_OK) + return UV__ERR(status); + + topology_infos = uv__malloc(topology_count * sizeof(*topology_infos)); + if (topology_infos == NULL) + return UV_ENOMEM; + + status = get_cpu_topology_info(topology_infos, &topology_count); + if (status != B_OK) { + uv__free(topology_infos); + return UV__ERR(status); + } + + cpuspeed = 0; + for (i = 0; i < (int)topology_count; i++) { + if (topology_infos[i].type == B_TOPOLOGY_CORE) { + cpuspeed = topology_infos[i].data.core.default_frequency; + break; + } + } + + uv__free(topology_infos); + + status = get_system_info(&system); + if (status != B_OK) + return UV__ERR(status); + + *cpu_infos = uv__calloc(system.cpu_count, sizeof(**cpu_infos)); + if (*cpu_infos == NULL) + return UV_ENOMEM; + + /* CPU time and model are not exposed by Haiku. */ + cpu_info = *cpu_infos; + for (i = 0; i < (int)system.cpu_count; i++) { + cpu_info->model = uv__strdup("unknown"); + cpu_info->speed = (int)(cpuspeed / 1000000); + cpu_info++; + } + *count = system.cpu_count; + + return 0; +} diff --git a/src/unix/ibmi.c b/src/unix/ibmi.c index b1ab549..ff300ea 100644 --- a/src/unix/ibmi.c +++ b/src/unix/ibmi.c @@ -55,19 +55,211 @@ #include #include +#include +#include + + +typedef struct { + int bytes_available; + int bytes_returned; + char current_date_and_time[8]; + char system_name[8]; + char elapsed_time[6]; + char restricted_state_flag; + char reserved; + int percent_processing_unit_used; + int jobs_in_system; + int percent_permanent_addresses; + int percent_temporary_addresses; + int system_asp; + int percent_system_asp_used; + int total_auxiliary_storage; + int current_unprotected_storage_used; + int maximum_unprotected_storage_used; + int percent_db_capability; + int main_storage_size; + int number_of_partitions; + int partition_identifier; + int reserved1; + int current_processing_capacity; + char processor_sharing_attribute; + char reserved2[3]; + int number_of_processors; + int active_jobs_in_system; + int active_threads_in_system; + int maximum_jobs_in_system; + int percent_temporary_256mb_segments_used; + int percent_temporary_4gb_segments_used; + int percent_permanent_256mb_segments_used; + int percent_permanent_4gb_segments_used; + int percent_current_interactive_performance; + int percent_uncapped_cpu_capacity_used; + int percent_shared_processor_pool_used; + long main_storage_size_long; +} SSTS0200; + + +typedef struct { + char header[208]; + unsigned char loca_adapter_address[12]; +} LIND0500; + + +typedef struct { + int bytes_provided; + int bytes_available; + char msgid[7]; +} errcode_s; + + +static const unsigned char e2a[256] = { + 0, 1, 2, 3, 156, 9, 134, 127, 151, 141, 142, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 157, 133, 8, 135, 24, 25, 146, 143, 28, 29, 30, 31, + 128, 129, 130, 131, 132, 10, 23, 27, 136, 137, 138, 139, 140, 5, 6, 7, + 144, 145, 22, 147, 148, 149, 150, 4, 152, 153, 154, 155, 20, 21, 158, 26, + 32, 160, 161, 162, 163, 164, 165, 166, 167, 168, 91, 46, 60, 40, 43, 33, + 38, 169, 170, 171, 172, 173, 174, 175, 176, 177, 93, 36, 42, 41, 59, 94, + 45, 47, 178, 179, 180, 181, 182, 183, 184, 185, 124, 44, 37, 95, 62, 63, + 186, 187, 188, 189, 190, 191, 192, 193, 194, 96, 58, 35, 64, 39, 61, 34, + 195, 97, 98, 99, 100, 101, 102, 103, 104, 105, 196, 197, 198, 199, 200, 201, + 202, 106, 107, 108, 109, 110, 111, 112, 113, 114, 203, 204, 205, 206, 207, 208, + 209, 126, 115, 116, 117, 118, 119, 120, 121, 122, 210, 211, 212, 213, 214, 215, + 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, + 123, 65, 66, 67, 68, 69, 70, 71, 72, 73, 232, 233, 234, 235, 236, 237, + 125, 74, 75, 76, 77, 78, 79, 80, 81, 82, 238, 239, 240, 241, 242, 243, + 92, 159, 83, 84, 85, 86, 87, 88, 89, 90, 244, 245, 246, 247, 248, 249, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 250, 251, 252, 253, 254, 255}; + + +static const unsigned char a2e[256] = { + 0, 1, 2, 3, 55, 45, 46, 47, 22, 5, 37, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 60, 61, 50, 38, 24, 25, 63, 39, 28, 29, 30, 31, + 64, 79, 127, 123, 91, 108, 80, 125, 77, 93, 92, 78, 107, 96, 75, 97, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 122, 94, 76, 126, 110, 111, + 124, 193, 194, 195, 196, 197, 198, 199, 200, 201, 209, 210, 211, 212, 213, 214, + 215, 216, 217, 226, 227, 228, 229, 230, 231, 232, 233, 74, 224, 90, 95, 109, + 121, 129, 130, 131, 132, 133, 134, 135, 136, 137, 145, 146, 147, 148, 149, 150, + 151, 152, 153, 162, 163, 164, 165, 166, 167, 168, 169, 192, 106, 208, 161, 7, + 32, 33, 34, 35, 36, 21, 6, 23, 40, 41, 42, 43, 44, 9, 10, 27, + 48, 49, 26, 51, 52, 53, 54, 8, 56, 57, 58, 59, 4, 20, 62, 225, + 65, 66, 67, 68, 69, 70, 71, 72, 73, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 98, 99, 100, 101, 102, 103, 104, 105, 112, 113, 114, 115, 116, 117, + 118, 119, 120, 128, 138, 139, 140, 141, 142, 143, 144, 154, 155, 156, 157, 158, + 159, 160, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, + 184, 185, 186, 187, 188, 189, 190, 191, 202, 203, 204, 205, 206, 207, 218, 219, + 220, 221, 222, 223, 234, 235, 236, 237, 238, 239, 250, 251, 252, 253, 254, 255}; + + +static void iconv_e2a(unsigned char src[], unsigned char dst[], size_t length) { + size_t i; + for (i = 0; i < length; i++) + dst[i] = e2a[src[i]]; +} + + +static void iconv_a2e(const char* src, unsigned char dst[], size_t length) { + size_t srclen; + size_t i; + + srclen = strlen(src); + if (srclen > length) + abort(); + for (i = 0; i < srclen; i++) + dst[i] = a2e[src[i]]; + /* padding the remaining part with spaces */ + for (; i < length; i++) + dst[i] = a2e[' ']; +} + + +static int get_ibmi_system_status(SSTS0200* rcvr) { + /* rcvrlen is input parameter 2 to QWCRSSTS */ + unsigned int rcvrlen = sizeof(*rcvr); + unsigned char format[8], reset_status[10]; + + /* format is input parameter 3 to QWCRSSTS */ + iconv_a2e("SSTS0200", format, sizeof(format)); + /* reset_status is input parameter 4 */ + iconv_a2e("*NO", reset_status, sizeof(reset_status)); + + /* errcode is input parameter 5 to QWCRSSTS */ + errcode_s errcode; + + /* qwcrssts_pointer is the 16-byte tagged system pointer to QWCRSSTS */ + ILEpointer __attribute__((aligned(16))) qwcrssts_pointer; + + /* qwcrssts_argv is the array of argument pointers to QWCRSSTS */ + void* qwcrssts_argv[6]; + + /* Set the IBM i pointer to the QSYS/QWCRSSTS *PGM object */ + int rc = _RSLOBJ2(&qwcrssts_pointer, RSLOBJ_TS_PGM, "QWCRSSTS", "QSYS"); + + if (rc != 0) + return rc; + + /* initialize the QWCRSSTS returned info structure */ + memset(rcvr, 0, sizeof(*rcvr)); + + /* initialize the QWCRSSTS error code structure */ + memset(&errcode, 0, sizeof(errcode)); + errcode.bytes_provided = sizeof(errcode); + + /* initialize the array of argument pointers for the QWCRSSTS API */ + qwcrssts_argv[0] = rcvr; + qwcrssts_argv[1] = &rcvrlen; + qwcrssts_argv[2] = &format; + qwcrssts_argv[3] = &reset_status; + qwcrssts_argv[4] = &errcode; + qwcrssts_argv[5] = NULL; + + /* Call the IBM i QWCRSSTS API from PASE */ + rc = _PGMCALL(&qwcrssts_pointer, qwcrssts_argv, 0); + + return rc; +} + + uint64_t uv_get_free_memory(void) { - return (uint64_t) sysconf(_SC_PAGESIZE) * sysconf(_SC_AVPHYS_PAGES); + SSTS0200 rcvr; + + if (get_ibmi_system_status(&rcvr)) + return 0; + + return (uint64_t)rcvr.main_storage_size * 1024ULL; } uint64_t uv_get_total_memory(void) { - return (uint64_t) sysconf(_SC_PAGESIZE) * sysconf(_SC_PHYS_PAGES); + SSTS0200 rcvr; + + if (get_ibmi_system_status(&rcvr)) + return 0; + + return (uint64_t)rcvr.main_storage_size * 1024ULL; +} + + +uint64_t uv_get_constrained_memory(void) { + return 0; /* Memory constraints are unknown. */ } void uv_loadavg(double avg[3]) { + SSTS0200 rcvr; + + if (get_ibmi_system_status(&rcvr)) { avg[0] = avg[1] = avg[2] = 0; return; + } + + /* The average (in tenths) of the elapsed time during which the processing + * units were in use. For example, a value of 411 in binary would be 41.1%. + * This percentage could be greater than 100% for an uncapped partition. + */ + double processing_unit_used_percent = + rcvr.percent_processing_unit_used / 1000.0; + + avg[0] = avg[1] = avg[2] = processing_unit_used_percent; } @@ -110,4 +302,160 @@ int uv_cpu_info(uv_cpu_info_t** cpu_infos, int* count) { *count = numcpus; return 0; -} \ No newline at end of file +} + + +static int get_ibmi_physical_address(const char* line, char (*phys_addr)[6]) { + LIND0500 rcvr; + /* rcvrlen is input parameter 2 to QDCRLIND */ + unsigned int rcvrlen = sizeof(rcvr); + unsigned char format[8], line_name[10]; + unsigned char mac_addr[sizeof(rcvr.loca_adapter_address)]; + int c[6]; + + /* format is input parameter 3 to QDCRLIND */ + iconv_a2e("LIND0500", format, sizeof(format)); + + /* line_name is input parameter 4 to QDCRLIND */ + iconv_a2e(line, line_name, sizeof(line_name)); + + /* err is input parameter 5 to QDCRLIND */ + errcode_s err; + + /* qwcrssts_pointer is the 16-byte tagged system pointer to QDCRLIND */ + ILEpointer __attribute__((aligned(16))) qdcrlind_pointer; + + /* qwcrssts_argv is the array of argument pointers to QDCRLIND */ + void* qdcrlind_argv[6]; + + /* Set the IBM i pointer to the QSYS/QDCRLIND *PGM object */ + int rc = _RSLOBJ2(&qdcrlind_pointer, RSLOBJ_TS_PGM, "QDCRLIND", "QSYS"); + + if (rc != 0) + return rc; + + /* initialize the QDCRLIND returned info structure */ + memset(&rcvr, 0, sizeof(rcvr)); + + /* initialize the QDCRLIND error code structure */ + memset(&err, 0, sizeof(err)); + err.bytes_provided = sizeof(err); + + /* initialize the array of argument pointers for the QDCRLIND API */ + qdcrlind_argv[0] = &rcvr; + qdcrlind_argv[1] = &rcvrlen; + qdcrlind_argv[2] = &format; + qdcrlind_argv[3] = &line_name; + qdcrlind_argv[4] = &err; + qdcrlind_argv[5] = NULL; + + /* Call the IBM i QDCRLIND API from PASE */ + rc = _PGMCALL(&qdcrlind_pointer, qdcrlind_argv, 0); + if (rc != 0) + return rc; + + /* convert ebcdic loca_adapter_address to ascii first */ + iconv_e2a(rcvr.loca_adapter_address, mac_addr, + sizeof(rcvr.loca_adapter_address)); + + /* convert loca_adapter_address(char[12]) to phys_addr(char[6]) */ + int r = sscanf(mac_addr, "%02x%02x%02x%02x%02x%02x", + &c[0], &c[1], &c[2], &c[3], &c[4], &c[5]); + + if (r == ARRAY_SIZE(c)) { + (*phys_addr)[0] = c[0]; + (*phys_addr)[1] = c[1]; + (*phys_addr)[2] = c[2]; + (*phys_addr)[3] = c[3]; + (*phys_addr)[4] = c[4]; + (*phys_addr)[5] = c[5]; + } else { + memset(*phys_addr, 0, sizeof(*phys_addr)); + rc = -1; + } + return rc; +} + + +int uv_interface_addresses(uv_interface_address_t** addresses, int* count) { + uv_interface_address_t* address; + struct ifaddrs_pase *ifap = NULL, *cur; + int inet6, r = 0; + + *count = 0; + *addresses = NULL; + + if (Qp2getifaddrs(&ifap)) + return UV_ENOSYS; + + /* The first loop to get the size of the array to be allocated */ + for (cur = ifap; cur; cur = cur->ifa_next) { + if (!(cur->ifa_addr->sa_family == AF_INET6 || + cur->ifa_addr->sa_family == AF_INET)) + continue; + + if (!(cur->ifa_flags & IFF_UP && cur->ifa_flags & IFF_RUNNING)) + continue; + + (*count)++; + } + + if (*count == 0) { + Qp2freeifaddrs(ifap); + return 0; + } + + /* Alloc the return interface structs */ + *addresses = uv__calloc(*count, sizeof(**addresses)); + if (*addresses == NULL) { + Qp2freeifaddrs(ifap); + return UV_ENOMEM; + } + address = *addresses; + + /* The second loop to fill in the array */ + for (cur = ifap; cur; cur = cur->ifa_next) { + if (!(cur->ifa_addr->sa_family == AF_INET6 || + cur->ifa_addr->sa_family == AF_INET)) + continue; + + if (!(cur->ifa_flags & IFF_UP && cur->ifa_flags & IFF_RUNNING)) + continue; + + address->name = uv__strdup(cur->ifa_name); + + inet6 = (cur->ifa_addr->sa_family == AF_INET6); + + if (inet6) { + address->address.address6 = *((struct sockaddr_in6*)cur->ifa_addr); + address->netmask.netmask6 = *((struct sockaddr_in6*)cur->ifa_netmask); + address->netmask.netmask6.sin6_family = AF_INET6; + } else { + address->address.address4 = *((struct sockaddr_in*)cur->ifa_addr); + address->netmask.netmask4 = *((struct sockaddr_in*)cur->ifa_netmask); + address->netmask.netmask4.sin_family = AF_INET; + } + address->is_internal = cur->ifa_flags & IFF_LOOPBACK ? 1 : 0; + if (!address->is_internal) { + int rc = get_ibmi_physical_address(address->name, &address->phys_addr); + if (rc != 0) + r = rc; + } + + address++; + } + + Qp2freeifaddrs(ifap); + return r; +} + + +void uv_free_interface_addresses(uv_interface_address_t* addresses, int count) { + int i; + + for (i = 0; i < count; ++i) { + uv__free(addresses[i].name); + } + + uv__free(addresses); +} diff --git a/src/unix/internal.h b/src/unix/internal.h index cd79037..402ee87 100644 --- a/src/unix/internal.h +++ b/src/unix/internal.h @@ -25,11 +25,13 @@ #include "uv-common.h" #include +#include /* _POSIX_PATH_MAX, PATH_MAX */ #include /* abort */ #include /* strrchr */ -#include /* O_CLOEXEC, may be */ +#include /* O_CLOEXEC and O_NONBLOCK, if supported. */ #include #include +#include #if defined(__STRICT_ANSI__) # define inline __inline @@ -60,6 +62,14 @@ # include #endif +#if defined(_POSIX_PATH_MAX) +# define UV__PATH_MAX _POSIX_PATH_MAX +#elif defined(PATH_MAX) +# define UV__PATH_MAX PATH_MAX +#else +# define UV__PATH_MAX 8192 +#endif + #if defined(__ANDROID__) int uv__pthread_sigmask(int how, const sigset_t* set, sigset_t* oset); # ifdef pthread_sigmask @@ -95,12 +105,9 @@ int uv__pthread_sigmask(int how, const sigset_t* set, sigset_t* oset); */ #if defined(__clang__) || \ defined(__GNUC__) || \ - defined(__INTEL_COMPILER) || \ - defined(__SUNPRO_C) -# define UV_DESTRUCTOR(declaration) __attribute__((destructor)) declaration + defined(__INTEL_COMPILER) # define UV_UNUSED(declaration) __attribute__((unused)) declaration #else -# define UV_DESTRUCTOR(declaration) declaration # define UV_UNUSED(declaration) declaration #endif @@ -184,8 +191,8 @@ int uv__nonblock_ioctl(int fd, int set); int uv__nonblock_fcntl(int fd, int set); int uv__close(int fd); /* preserves errno */ int uv__close_nocheckstdio(int fd); +int uv__close_nocancel(int fd); int uv__socket(int domain, int type, int protocol); -int uv__dup(int fd); ssize_t uv__recvmsg(int fd, struct msghdr *msg, int flags); void uv__make_close_pending(uv_handle_t* handle); int uv__getiovmax(void); @@ -262,6 +269,12 @@ uv_handle_type uv__handle_type(int fd); FILE* uv__open_file(const char* path); int uv__getpwuid_r(uv_passwd_t* pwd); +/* random */ +int uv__random_devurandom(void* buf, size_t buflen); +int uv__random_getrandom(void* buf, size_t buflen); +int uv__random_getentropy(void* buf, size_t buflen); +int uv__random_readpath(const char* path, void* buf, size_t buflen); +int uv__random_sysctl(void* buf, size_t buflen); #if defined(__APPLE__) int uv___stream_fd(const uv_stream_t* handle); @@ -270,13 +283,12 @@ int uv___stream_fd(const uv_stream_t* handle); #define uv__stream_fd(handle) ((handle)->io_watcher.fd) #endif /* defined(__APPLE__) */ -#ifdef UV__O_NONBLOCK -# define UV__F_NONBLOCK UV__O_NONBLOCK +#ifdef O_NONBLOCK +# define UV__F_NONBLOCK O_NONBLOCK #else # define UV__F_NONBLOCK 1 #endif -int uv__make_socketpair(int fds[2], int flags); int uv__make_pipe(int fds[2], int flags); #if defined(__APPLE__) @@ -285,24 +297,6 @@ int uv__fsevents_init(uv_fs_event_t* handle); int uv__fsevents_close(uv_fs_event_t* handle); void uv__fsevents_loop_delete(uv_loop_t* loop); -/* OSX < 10.7 has no file events, polyfill them */ -#ifndef MAC_OS_X_VERSION_10_7 - -static const int kFSEventStreamCreateFlagFileEvents = 0x00000010; -static const int kFSEventStreamEventFlagItemCreated = 0x00000100; -static const int kFSEventStreamEventFlagItemRemoved = 0x00000200; -static const int kFSEventStreamEventFlagItemInodeMetaMod = 0x00000400; -static const int kFSEventStreamEventFlagItemRenamed = 0x00000800; -static const int kFSEventStreamEventFlagItemModified = 0x00001000; -static const int kFSEventStreamEventFlagItemFinderInfoMod = 0x00002000; -static const int kFSEventStreamEventFlagItemChangeOwner = 0x00004000; -static const int kFSEventStreamEventFlagItemXattrMod = 0x00008000; -static const int kFSEventStreamEventFlagItemIsFile = 0x00010000; -static const int kFSEventStreamEventFlagItemIsDir = 0x00020000; -static const int kFSEventStreamEventFlagItemIsSymlink = 0x00040000; - -#endif /* __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 1070 */ - #endif /* defined(__APPLE__) */ UV_UNUSED(static void uv__update_time(uv_loop_t* loop)) { @@ -325,4 +319,34 @@ UV_UNUSED(static char* uv__basename_r(const char* path)) { int uv__inotify_fork(uv_loop_t* loop, void* old_watchers); #endif +typedef int (*uv__peersockfunc)(int, struct sockaddr*, socklen_t*); + +int uv__getsockpeername(const uv_handle_t* handle, + uv__peersockfunc func, + struct sockaddr* name, + int* namelen); + +#if defined(__linux__) || \ + defined(__FreeBSD__) || \ + defined(__FreeBSD_kernel__) +#define HAVE_MMSG 1 +struct uv__mmsghdr { + struct msghdr msg_hdr; + unsigned int msg_len; +}; + +int uv__recvmmsg(int fd, + struct uv__mmsghdr* mmsg, + unsigned int vlen, + unsigned int flags, + struct timespec* timeout); +int uv__sendmmsg(int fd, + struct uv__mmsghdr* mmsg, + unsigned int vlen, + unsigned int flags); +#else +#define HAVE_MMSG 0 +#endif + + #endif /* UV_UNIX_INTERNAL_H_ */ diff --git a/src/unix/kqueue.c b/src/unix/kqueue.c index 930f2da..ad09f40 100644 --- a/src/unix/kqueue.c +++ b/src/unix/kqueue.c @@ -59,7 +59,7 @@ int uv__kqueue_init(uv_loop_t* loop) { } -#if defined(__APPLE__) +#if defined(__APPLE__) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 static int uv__has_forked_with_cfrunloop; #endif @@ -70,7 +70,7 @@ int uv__io_fork(uv_loop_t* loop) { if (err) return err; -#if defined(__APPLE__) +#if defined(__APPLE__) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 if (loop->cf_state != NULL) { /* We cannot start another CFRunloop and/or thread in the child process; CF aborts if you try or if you try to touch the thread @@ -86,7 +86,7 @@ int uv__io_fork(uv_loop_t* loop) { uv__free(loop->cf_state); loop->cf_state = NULL; } -#endif +#endif /* #if defined(__APPLE__) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 */ return err; } @@ -387,6 +387,7 @@ void uv__platform_invalidate_fd(uv_loop_t* loop, int fd) { uintptr_t nfds; assert(loop->watchers != NULL); + assert(fd >= 0); events = (struct kevent*) loop->watchers[loop->nwatchers]; nfds = (uintptr_t) loop->watchers[loop->nwatchers + 1]; @@ -452,28 +453,28 @@ int uv_fs_event_start(uv_fs_event_t* handle, uv_fs_event_cb cb, const char* path, unsigned int flags) { -#if defined(__APPLE__) - struct stat statbuf; -#endif /* defined(__APPLE__) */ int fd; +#if defined(__APPLE__) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 + struct stat statbuf; +#endif if (uv__is_active(handle)) return UV_EINVAL; - /* TODO open asynchronously - but how do we report back errors? */ - fd = open(path, O_RDONLY); - if (fd == -1) - return UV__ERR(errno); - - uv__handle_start(handle); - uv__io_init(&handle->event_watcher, uv__fs_event, fd); - handle->path = uv__strdup(path); handle->cb = cb; + handle->path = uv__strdup(path); + if (handle->path == NULL) + return UV_ENOMEM; -#if defined(__APPLE__) - if (uv__has_forked_with_cfrunloop) - goto fallback; + /* TODO open asynchronously - but how do we report back errors? */ + fd = open(handle->path, O_RDONLY); + if (fd == -1) { + uv__free(handle->path); + handle->path = NULL; + return UV__ERR(errno); + } +#if defined(__APPLE__) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 /* Nullify field to perform checks later */ handle->cf_cb = NULL; handle->realpath = NULL; @@ -486,15 +487,25 @@ int uv_fs_event_start(uv_fs_event_t* handle, if (!(statbuf.st_mode & S_IFDIR)) goto fallback; - /* The fallback fd is no longer needed */ - uv__close(fd); - handle->event_watcher.fd = -1; - - return uv__fsevents_init(handle); - + if (!uv__has_forked_with_cfrunloop) { + int r; + /* The fallback fd is no longer needed */ + uv__close_nocheckstdio(fd); + handle->event_watcher.fd = -1; + r = uv__fsevents_init(handle); + if (r == 0) { + uv__handle_start(handle); + } else { + uv__free(handle->path); + handle->path = NULL; + } + return r; + } fallback: -#endif /* defined(__APPLE__) */ +#endif /* #if defined(__APPLE__) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 */ + uv__handle_start(handle); + uv__io_init(&handle->event_watcher, uv__fs_event, fd); uv__io_start(handle->loop, &handle->event_watcher, POLLIN); return 0; @@ -502,29 +513,29 @@ fallback: int uv_fs_event_stop(uv_fs_event_t* handle) { + int r; + r = 0; + if (!uv__is_active(handle)) return 0; uv__handle_stop(handle); -#if defined(__APPLE__) - if (uv__has_forked_with_cfrunloop || uv__fsevents_close(handle)) -#endif /* defined(__APPLE__) */ - { - uv__io_close(handle->loop, &handle->event_watcher); - } - - uv__free(handle->path); - handle->path = NULL; +#if defined(__APPLE__) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 + if (!uv__has_forked_with_cfrunloop && handle->cf_cb != NULL) + r = uv__fsevents_close(handle); +#endif if (handle->event_watcher.fd != -1) { - /* When FSEvents is used, we don't use the event_watcher's fd under certain - * confitions. (see uv_fs_event_start) */ + uv__io_close(handle->loop, &handle->event_watcher); uv__close(handle->event_watcher.fd); handle->event_watcher.fd = -1; } - return 0; + uv__free(handle->path); + handle->path = NULL; + + return r; } diff --git a/src/unix/linux-core.c b/src/unix/linux-core.c index 124a0a5..99cbb1c 100644 --- a/src/unix/linux-core.c +++ b/src/unix/linux-core.c @@ -26,6 +26,7 @@ #include "uv.h" #include "internal.h" +#include #include #include #include @@ -79,16 +80,25 @@ static int read_times(FILE* statfile_fp, unsigned int numcpus, uv_cpu_info_t* ci); static void read_speeds(unsigned int numcpus, uv_cpu_info_t* ci); -static unsigned long read_cpufreq(unsigned int cpunum); +static uint64_t read_cpufreq(unsigned int cpunum); int uv__platform_loop_init(uv_loop_t* loop) { int fd; - fd = epoll_create1(EPOLL_CLOEXEC); + /* It was reported that EPOLL_CLOEXEC is not defined on Android API < 21, + * a.k.a. Lollipop. Since EPOLL_CLOEXEC is an alias for O_CLOEXEC on all + * architectures, we just use that instead. + */ +#if defined(__ANDROID_API__) && __ANDROID_API__ < 21 + fd = -1; + errno = ENOSYS; +#else + fd = epoll_create1(O_CLOEXEC); +#endif /* epoll_create1() can fail either because it's not implemented (old kernel) - * or because it doesn't understand the EPOLL_CLOEXEC flag. + * or because it doesn't understand the O_CLOEXEC flag. */ if (fd == -1 && (errno == ENOSYS || errno == EINVAL)) { fd = epoll_create(256); @@ -141,6 +151,7 @@ void uv__platform_invalidate_fd(uv_loop_t* loop, int fd) { uintptr_t nfds; assert(loop->watchers != NULL); + assert(fd >= 0); events = (struct epoll_event*) loop->watchers[loop->nwatchers]; nfds = (uintptr_t) loop->watchers[loop->nwatchers + 1]; @@ -170,6 +181,7 @@ int uv__io_check_fd(uv_loop_t* loop, int fd) { struct epoll_event e; int rc; + memset(&e, 0, sizeof(e)); e.events = POLLIN; e.data.fd = -1; @@ -196,6 +208,8 @@ void uv__io_poll(uv_loop_t* loop, int timeout) { * that being the largest value I have seen in the wild (and only once.) */ static const int max_safe_timeout = 1789569; + static int no_epoll_pwait; + static int no_epoll_wait; struct epoll_event events[1024]; struct epoll_event* pe; struct epoll_event e; @@ -203,7 +217,7 @@ void uv__io_poll(uv_loop_t* loop, int timeout) { QUEUE* q; uv__io_t* w; sigset_t sigset; - sigset_t* psigset; + uint64_t sigmask; uint64_t base; int have_signals; int nevents; @@ -218,6 +232,8 @@ void uv__io_poll(uv_loop_t* loop, int timeout) { return; } + memset(&e, 0, sizeof(e)); + while (!QUEUE_EMPTY(&loop->watcher_queue)) { q = QUEUE_HEAD(&loop->watcher_queue); QUEUE_REMOVE(q); @@ -253,11 +269,11 @@ void uv__io_poll(uv_loop_t* loop, int timeout) { w->events = w->pevents; } - psigset = NULL; + sigmask = 0; if (loop->flags & UV_LOOP_BLOCK_SIGPROF) { sigemptyset(&sigset); sigaddset(&sigset, SIGPROF); - psigset = &sigset; + sigmask |= 1 << (SIGPROF - 1); } assert(timeout >= -1); @@ -272,11 +288,35 @@ void uv__io_poll(uv_loop_t* loop, int timeout) { if (sizeof(int32_t) == sizeof(long) && timeout >= max_safe_timeout) timeout = max_safe_timeout; - nfds = epoll_pwait(loop->backend_fd, - events, - ARRAY_SIZE(events), - timeout, - psigset); + if (sigmask != 0 && no_epoll_pwait != 0) + if (pthread_sigmask(SIG_BLOCK, &sigset, NULL)) + abort(); + + if (no_epoll_wait != 0 || (sigmask != 0 && no_epoll_pwait == 0)) { +#if defined(__ANDROID_API__) && __ANDROID_API__ < 21 + nfds = -1; + errno = ENOSYS; +#else + nfds = epoll_pwait(loop->backend_fd, + events, + ARRAY_SIZE(events), + timeout, + &sigset); +#endif + if (nfds == -1 && errno == ENOSYS) + no_epoll_pwait = 1; + } else { + nfds = epoll_wait(loop->backend_fd, + events, + ARRAY_SIZE(events), + timeout); + if (nfds == -1 && errno == ENOSYS) + no_epoll_wait = 1; + } + + if (sigmask != 0 && no_epoll_pwait != 0) + if (pthread_sigmask(SIG_UNBLOCK, &sigset, NULL)) + abort(); /* Update loop->time unconditionally. It's tempting to skip the update when * timeout == 0 (i.e. non-blocking poll) but there is no guarantee that the @@ -297,6 +337,12 @@ void uv__io_poll(uv_loop_t* loop, int timeout) { } if (nfds == -1) { + if (errno == ENOSYS) { + /* epoll_wait() or epoll_pwait() failed, try the other system call. */ + assert(no_epoll_wait == 0 || no_epoll_pwait == 0); + continue; + } + if (errno != EINTR) abort(); @@ -313,9 +359,19 @@ void uv__io_poll(uv_loop_t* loop, int timeout) { have_signals = 0; nevents = 0; - assert(loop->watchers != NULL); - loop->watchers[loop->nwatchers] = (void*) events; - loop->watchers[loop->nwatchers + 1] = (void*) (uintptr_t) nfds; + { + /* Squelch a -Waddress-of-packed-member warning with gcc >= 9. */ + union { + struct epoll_event* events; + uv__io_t* watchers; + } x; + + x.events = events; + assert(loop->watchers != NULL); + loop->watchers[loop->nwatchers] = x.watchers; + loop->watchers[loop->nwatchers + 1] = (void*) (uintptr_t) nfds; + } + for (i = 0; i < nfds; i++) { pe = events + i; fd = pe->data.fd; @@ -711,21 +767,23 @@ static int read_models(unsigned int numcpus, uv_cpu_info_t* ci) { static int read_times(FILE* statfile_fp, unsigned int numcpus, uv_cpu_info_t* ci) { - unsigned long clock_ticks; struct uv_cpu_times_s ts; - unsigned long user; - unsigned long nice; - unsigned long sys; - unsigned long idle; - unsigned long dummy; - unsigned long irq; - unsigned int num; - unsigned int len; + unsigned int ticks; + unsigned int multiplier; + uint64_t user; + uint64_t nice; + uint64_t sys; + uint64_t idle; + uint64_t dummy; + uint64_t irq; + uint64_t num; + uint64_t len; char buf[1024]; - clock_ticks = sysconf(_SC_CLK_TCK); - assert(clock_ticks != (unsigned long) -1); - assert(clock_ticks != 0); + ticks = (unsigned int)sysconf(_SC_CLK_TCK); + multiplier = ((uint64_t)1000L / ticks); + assert(ticks != (unsigned int) -1); + assert(ticks != 0); rewind(statfile_fp); @@ -757,7 +815,8 @@ static int read_times(FILE* statfile_fp, * fields, they're not allowed in C89 mode. */ if (6 != sscanf(buf + len, - "%lu %lu %lu %lu %lu %lu", + "%" PRIu64 " %" PRIu64 " %" PRIu64 + "%" PRIu64 " %" PRIu64 " %" PRIu64, &user, &nice, &sys, @@ -766,11 +825,11 @@ static int read_times(FILE* statfile_fp, &irq)) abort(); - ts.user = clock_ticks * user; - ts.nice = clock_ticks * nice; - ts.sys = clock_ticks * sys; - ts.idle = clock_ticks * idle; - ts.irq = clock_ticks * irq; + ts.user = user * multiplier; + ts.nice = nice * multiplier; + ts.sys = sys * multiplier; + ts.idle = idle * multiplier; + ts.irq = irq * multiplier; ci[num++].cpu_times = ts; } assert(num == numcpus); @@ -779,8 +838,8 @@ static int read_times(FILE* statfile_fp, } -static unsigned long read_cpufreq(unsigned int cpunum) { - unsigned long val; +static uint64_t read_cpufreq(unsigned int cpunum) { + uint64_t val; char buf[1024]; FILE* fp; @@ -793,7 +852,7 @@ static unsigned long read_cpufreq(unsigned int cpunum) { if (fp == NULL) return 0; - if (fscanf(fp, "%lu", &val) != 1) + if (fscanf(fp, "%" PRIu64, &val) != 1) val = 0; fclose(fp); @@ -802,16 +861,6 @@ static unsigned long read_cpufreq(unsigned int cpunum) { } -void uv_free_cpu_info(uv_cpu_info_t* cpu_infos, int count) { - int i; - - for (i = 0; i < count; i++) { - uv__free(cpu_infos[i].model); - } - - uv__free(cpu_infos); -} - static int uv__ifaddr_exclude(struct ifaddrs *ent, int exclude_type) { if (!((ent->ifa_flags & IFF_UP) && (ent->ifa_flags & IFF_RUNNING))) return 1; @@ -826,9 +875,10 @@ static int uv__ifaddr_exclude(struct ifaddrs *ent, int exclude_type) { return !exclude_type; } -int uv_interface_addresses(uv_interface_address_t** addresses, - int* count) { +int uv_interface_addresses(uv_interface_address_t** addresses, int* count) { #ifndef HAVE_IFADDRS_H + *count = 0; + *addresses = NULL; return UV_ENOSYS; #else struct ifaddrs *addrs, *ent; @@ -836,12 +886,12 @@ int uv_interface_addresses(uv_interface_address_t** addresses, int i; struct sockaddr_ll *sll; - if (getifaddrs(&addrs)) - return UV__ERR(errno); - *count = 0; *addresses = NULL; + if (getifaddrs(&addrs)) + return UV__ERR(errno); + /* Count the number of interfaces */ for (ent = addrs; ent != NULL; ent = ent->ifa_next) { if (uv__ifaddr_exclude(ent, UV__EXCLUDE_IFADDR)) @@ -850,10 +900,13 @@ int uv_interface_addresses(uv_interface_address_t** addresses, (*count)++; } - if (*count == 0) + if (*count == 0) { + freeifaddrs(addrs); return 0; + } - *addresses = uv__malloc(*count * sizeof(**addresses)); + /* Make sure the memory is initiallized to zero using calloc() */ + *addresses = uv__calloc(*count, sizeof(**addresses)); if (!(*addresses)) { freeifaddrs(addrs); return UV_ENOMEM; @@ -892,7 +945,10 @@ int uv_interface_addresses(uv_interface_address_t** addresses, address = *addresses; for (i = 0; i < (*count); i++) { - if (strcmp(address->name, ent->ifa_name) == 0) { + size_t namelen = strlen(ent->ifa_name); + /* Alias interface share the same physical address */ + if (strncmp(address->name, ent->ifa_name, namelen) == 0 && + (address->name[namelen] == 0 || address->name[namelen] == ':')) { sll = (struct sockaddr_ll*)ent->ifa_addr; memcpy(address->phys_addr, sll->sll_addr, sizeof(address->phys_addr)); } @@ -924,3 +980,114 @@ void uv__set_process_title(const char* title) { prctl(PR_SET_NAME, title); /* Only copies first 16 characters. */ #endif } + + +static uint64_t uv__read_proc_meminfo(const char* what) { + uint64_t rc; + ssize_t n; + char* p; + int fd; + char buf[4096]; /* Large enough to hold all of /proc/meminfo. */ + + rc = 0; + fd = uv__open_cloexec("/proc/meminfo", O_RDONLY); + + if (fd < 0) + return 0; + + n = read(fd, buf, sizeof(buf) - 1); + + if (n <= 0) + goto out; + + buf[n] = '\0'; + p = strstr(buf, what); + + if (p == NULL) + goto out; + + p += strlen(what); + + if (1 != sscanf(p, "%" PRIu64 " kB", &rc)) + goto out; + + rc *= 1024; + +out: + + if (uv__close_nocheckstdio(fd)) + abort(); + + return rc; +} + + +uint64_t uv_get_free_memory(void) { + struct sysinfo info; + uint64_t rc; + + rc = uv__read_proc_meminfo("MemFree:"); + + if (rc != 0) + return rc; + + if (0 == sysinfo(&info)) + return (uint64_t) info.freeram * info.mem_unit; + + return 0; +} + + +uint64_t uv_get_total_memory(void) { + struct sysinfo info; + uint64_t rc; + + rc = uv__read_proc_meminfo("MemTotal:"); + + if (rc != 0) + return rc; + + if (0 == sysinfo(&info)) + return (uint64_t) info.totalram * info.mem_unit; + + return 0; +} + + +static uint64_t uv__read_cgroups_uint64(const char* cgroup, const char* param) { + char filename[256]; + uint64_t rc; + int fd; + ssize_t n; + char buf[32]; /* Large enough to hold an encoded uint64_t. */ + + snprintf(filename, 256, "/sys/fs/cgroup/%s/%s", cgroup, param); + + rc = 0; + fd = uv__open_cloexec(filename, O_RDONLY); + + if (fd < 0) + return 0; + + n = read(fd, buf, sizeof(buf) - 1); + + if (n > 0) { + buf[n] = '\0'; + sscanf(buf, "%" PRIu64, &rc); + } + + if (uv__close_nocheckstdio(fd)) + abort(); + + return rc; +} + + +uint64_t uv_get_constrained_memory(void) { + /* + * This might return 0 if there was a problem getting the memory limit from + * cgroups. This is OK because a return value of 0 signifies that the memory + * limit is unknown. + */ + return uv__read_cgroups_uint64("memory", "memory.limit_in_bytes"); +} diff --git a/src/unix/linux-inotify.c b/src/unix/linux-inotify.c index 7797f84..42b601a 100644 --- a/src/unix/linux-inotify.c +++ b/src/unix/linux-inotify.c @@ -29,6 +29,7 @@ #include #include +#include #include #include @@ -64,45 +65,17 @@ static void uv__inotify_read(uv_loop_t* loop, static void maybe_free_watcher_list(struct watcher_list* w, uv_loop_t* loop); -static int new_inotify_fd(void) { - int err; - int fd; - - fd = uv__inotify_init1(UV__IN_NONBLOCK | UV__IN_CLOEXEC); - if (fd != -1) - return fd; - - if (errno != ENOSYS) - return UV__ERR(errno); - - fd = uv__inotify_init(); - if (fd == -1) - return UV__ERR(errno); - - err = uv__cloexec(fd, 1); - if (err == 0) - err = uv__nonblock(fd, 1); - - if (err) { - uv__close(fd); - return err; - } - - return fd; -} - - static int init_inotify(uv_loop_t* loop) { - int err; + int fd; if (loop->inotify_fd != -1) return 0; - err = new_inotify_fd(); - if (err < 0) - return err; + fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); + if (fd < 0) + return UV__ERR(errno); - loop->inotify_fd = err; + loop->inotify_fd = fd; uv__io_init(&loop->inotify_read_watcher, uv__inotify_read, loop->inotify_fd); uv__io_start(loop, &loop->inotify_read_watcher, POLLIN); @@ -186,7 +159,7 @@ static void maybe_free_watcher_list(struct watcher_list* w, uv_loop_t* loop) { if ((!w->iterating) && QUEUE_EMPTY(&w->watchers)) { /* No watchers left for this path. Clean up. */ RB_REMOVE(watcher_root, CAST(&loop->inotify_watchers), w); - uv__inotify_rm_watch(loop->inotify_fd, w->wd); + inotify_rm_watch(loop->inotify_fd, w->wd); uv__free(w); } } @@ -194,7 +167,7 @@ static void maybe_free_watcher_list(struct watcher_list* w, uv_loop_t* loop) { static void uv__inotify_read(uv_loop_t* loop, uv__io_t* dummy, unsigned int events) { - const struct uv__inotify_event* e; + const struct inotify_event* e; struct watcher_list* w; uv_fs_event_t* h; QUEUE queue; @@ -219,12 +192,12 @@ static void uv__inotify_read(uv_loop_t* loop, /* Now we have one or more inotify_event structs. */ for (p = buf; p < buf + size; p += sizeof(*e) + e->len) { - e = (const struct uv__inotify_event*)p; + e = (const struct inotify_event*) p; events = 0; - if (e->mask & (UV__IN_ATTRIB|UV__IN_MODIFY)) + if (e->mask & (IN_ATTRIB|IN_MODIFY)) events |= UV_CHANGE; - if (e->mask & ~(UV__IN_ATTRIB|UV__IN_MODIFY)) + if (e->mask & ~(IN_ATTRIB|IN_MODIFY)) events |= UV_RENAME; w = find_watcher(loop, e->wd); @@ -278,6 +251,7 @@ int uv_fs_event_start(uv_fs_event_t* handle, const char* path, unsigned int flags) { struct watcher_list* w; + size_t len; int events; int err; int wd; @@ -289,16 +263,16 @@ int uv_fs_event_start(uv_fs_event_t* handle, if (err) return err; - events = UV__IN_ATTRIB - | UV__IN_CREATE - | UV__IN_MODIFY - | UV__IN_DELETE - | UV__IN_DELETE_SELF - | UV__IN_MOVE_SELF - | UV__IN_MOVED_FROM - | UV__IN_MOVED_TO; + events = IN_ATTRIB + | IN_CREATE + | IN_MODIFY + | IN_DELETE + | IN_DELETE_SELF + | IN_MOVE_SELF + | IN_MOVED_FROM + | IN_MOVED_TO; - wd = uv__inotify_add_watch(handle->loop->inotify_fd, path, events); + wd = inotify_add_watch(handle->loop->inotify_fd, path, events); if (wd == -1) return UV__ERR(errno); @@ -306,12 +280,13 @@ int uv_fs_event_start(uv_fs_event_t* handle, if (w) goto no_insert; - w = uv__malloc(sizeof(*w) + strlen(path) + 1); + len = strlen(path) + 1; + w = uv__malloc(sizeof(*w) + len); if (w == NULL) return UV_ENOMEM; w->wd = wd; - w->path = strcpy((char*)(w + 1), path); + w->path = memcpy(w + 1, path, len); QUEUE_INIT(&w->watchers); w->iterating = 0; RB_INSERT(watcher_root, CAST(&handle->loop->inotify_watchers), w); diff --git a/src/unix/linux-syscalls.c b/src/unix/linux-syscalls.c index bfd7544..742f26a 100644 --- a/src/unix/linux-syscalls.c +++ b/src/unix/linux-syscalls.c @@ -26,19 +26,6 @@ #include #include -#if defined(__has_feature) -# if __has_feature(memory_sanitizer) -# define MSAN_ACTIVE 1 -# include -# endif -#endif - -#if defined(__i386__) -# ifndef __NR_socketcall -# define __NR_socketcall 102 -# endif -#endif - #if defined(__arm__) # if defined(__thumb__) || defined(__ARM_EABI__) # define UV_SYSCALL_BASE 0 @@ -47,86 +34,6 @@ # endif #endif /* __arm__ */ -#ifndef __NR_accept4 -# if defined(__x86_64__) -# define __NR_accept4 288 -# elif defined(__i386__) - /* Nothing. Handled through socketcall(). */ -# elif defined(__arm__) -# define __NR_accept4 (UV_SYSCALL_BASE + 366) -# endif -#endif /* __NR_accept4 */ - -#ifndef __NR_eventfd -# if defined(__x86_64__) -# define __NR_eventfd 284 -# elif defined(__i386__) -# define __NR_eventfd 323 -# elif defined(__arm__) -# define __NR_eventfd (UV_SYSCALL_BASE + 351) -# endif -#endif /* __NR_eventfd */ - -#ifndef __NR_eventfd2 -# if defined(__x86_64__) -# define __NR_eventfd2 290 -# elif defined(__i386__) -# define __NR_eventfd2 328 -# elif defined(__arm__) -# define __NR_eventfd2 (UV_SYSCALL_BASE + 356) -# endif -#endif /* __NR_eventfd2 */ - -#ifndef __NR_inotify_init -# if defined(__x86_64__) -# define __NR_inotify_init 253 -# elif defined(__i386__) -# define __NR_inotify_init 291 -# elif defined(__arm__) -# define __NR_inotify_init (UV_SYSCALL_BASE + 316) -# endif -#endif /* __NR_inotify_init */ - -#ifndef __NR_inotify_init1 -# if defined(__x86_64__) -# define __NR_inotify_init1 294 -# elif defined(__i386__) -# define __NR_inotify_init1 332 -# elif defined(__arm__) -# define __NR_inotify_init1 (UV_SYSCALL_BASE + 360) -# endif -#endif /* __NR_inotify_init1 */ - -#ifndef __NR_inotify_add_watch -# if defined(__x86_64__) -# define __NR_inotify_add_watch 254 -# elif defined(__i386__) -# define __NR_inotify_add_watch 292 -# elif defined(__arm__) -# define __NR_inotify_add_watch (UV_SYSCALL_BASE + 317) -# endif -#endif /* __NR_inotify_add_watch */ - -#ifndef __NR_inotify_rm_watch -# if defined(__x86_64__) -# define __NR_inotify_rm_watch 255 -# elif defined(__i386__) -# define __NR_inotify_rm_watch 293 -# elif defined(__arm__) -# define __NR_inotify_rm_watch (UV_SYSCALL_BASE + 318) -# endif -#endif /* __NR_inotify_rm_watch */ - -#ifndef __NR_pipe2 -# if defined(__x86_64__) -# define __NR_pipe2 293 -# elif defined(__i386__) -# define __NR_pipe2 331 -# elif defined(__arm__) -# define __NR_pipe2 (UV_SYSCALL_BASE + 359) -# endif -#endif /* __NR_pipe2 */ - #ifndef __NR_recvmmsg # if defined(__x86_64__) # define __NR_recvmmsg 299 @@ -187,104 +94,39 @@ # endif #endif /* __NR_pwritev */ +#ifndef __NR_statx +# if defined(__x86_64__) +# define __NR_statx 332 +# elif defined(__i386__) +# define __NR_statx 383 +# elif defined(__aarch64__) +# define __NR_statx 397 +# elif defined(__arm__) +# define __NR_statx (UV_SYSCALL_BASE + 397) +# elif defined(__ppc__) +# define __NR_statx 383 +# elif defined(__s390__) +# define __NR_statx 379 +# endif +#endif /* __NR_statx */ -int uv__accept4(int fd, struct sockaddr* addr, socklen_t* addrlen, int flags) { -#if defined(__i386__) - unsigned long args[4]; - int r; - - args[0] = (unsigned long) fd; - args[1] = (unsigned long) addr; - args[2] = (unsigned long) addrlen; - args[3] = (unsigned long) flags; - - r = syscall(__NR_socketcall, 18 /* SYS_ACCEPT4 */, args); - - /* socketcall() raises EINVAL when SYS_ACCEPT4 is not supported but so does - * a bad flags argument. Try to distinguish between the two cases. - */ - if (r == -1) - if (errno == EINVAL) - if ((flags & ~(UV__SOCK_CLOEXEC|UV__SOCK_NONBLOCK)) == 0) - errno = ENOSYS; - - return r; -#elif defined(__NR_accept4) - return syscall(__NR_accept4, fd, addr, addrlen, flags); -#else - return errno = ENOSYS, -1; -#endif -} - - -int uv__eventfd(unsigned int count) { -#if defined(__NR_eventfd) - return syscall(__NR_eventfd, count); -#else - return errno = ENOSYS, -1; -#endif -} - - -int uv__eventfd2(unsigned int count, int flags) { -#if defined(__NR_eventfd2) - return syscall(__NR_eventfd2, count, flags); -#else - return errno = ENOSYS, -1; -#endif -} - - -int uv__inotify_init(void) { -#if defined(__NR_inotify_init) - return syscall(__NR_inotify_init); -#else - return errno = ENOSYS, -1; -#endif -} - - -int uv__inotify_init1(int flags) { -#if defined(__NR_inotify_init1) - return syscall(__NR_inotify_init1, flags); -#else - return errno = ENOSYS, -1; -#endif -} - - -int uv__inotify_add_watch(int fd, const char* path, uint32_t mask) { -#if defined(__NR_inotify_add_watch) - return syscall(__NR_inotify_add_watch, fd, path, mask); -#else - return errno = ENOSYS, -1; -#endif -} - - -int uv__inotify_rm_watch(int fd, int32_t wd) { -#if defined(__NR_inotify_rm_watch) - return syscall(__NR_inotify_rm_watch, fd, wd); -#else - return errno = ENOSYS, -1; -#endif -} - - -int uv__pipe2(int pipefd[2], int flags) { -#if defined(__NR_pipe2) - int result; - result = syscall(__NR_pipe2, pipefd, flags); -#if MSAN_ACTIVE - if (!result) - __msan_unpoison(pipefd, sizeof(int[2])); -#endif - return result; -#else - return errno = ENOSYS, -1; -#endif -} +#ifndef __NR_getrandom +# if defined(__x86_64__) +# define __NR_getrandom 318 +# elif defined(__i386__) +# define __NR_getrandom 355 +# elif defined(__aarch64__) +# define __NR_getrandom 384 +# elif defined(__arm__) +# define __NR_getrandom (UV_SYSCALL_BASE + 384) +# elif defined(__ppc__) +# define __NR_getrandom 359 +# elif defined(__s390__) +# define __NR_getrandom 349 +# endif +#endif /* __NR_getrandom */ +struct uv__mmsghdr; int uv__sendmmsg(int fd, struct uv__mmsghdr* mmsg, @@ -336,3 +178,28 @@ int uv__dup3(int oldfd, int newfd, int flags) { return errno = ENOSYS, -1; #endif } + + +int uv__statx(int dirfd, + const char* path, + int flags, + unsigned int mask, + struct uv__statx* statxbuf) { + /* __NR_statx make Android box killed by SIGSYS. + * That looks like a seccomp2 sandbox filter rejecting the system call. + */ +#if defined(__NR_statx) && !defined(__ANDROID__) + return syscall(__NR_statx, dirfd, path, flags, mask, statxbuf); +#else + return errno = ENOSYS, -1; +#endif +} + + +ssize_t uv__getrandom(void* buf, size_t buflen, unsigned flags) { +#if defined(__NR_getrandom) + return syscall(__NR_getrandom, buf, buflen, flags); +#else + return errno = ENOSYS, -1; +#endif +} diff --git a/src/unix/linux-syscalls.h b/src/unix/linux-syscalls.h index 3dfd329..2e8fa2a 100644 --- a/src/unix/linux-syscalls.h +++ b/src/unix/linux-syscalls.h @@ -31,87 +31,44 @@ #include #include -#if defined(__alpha__) -# define UV__O_CLOEXEC 0x200000 -#elif defined(__hppa__) -# define UV__O_CLOEXEC 0x200000 -#elif defined(__sparc__) -# define UV__O_CLOEXEC 0x400000 -#else -# define UV__O_CLOEXEC 0x80000 -#endif - -#if defined(__alpha__) -# define UV__O_NONBLOCK 0x4 -#elif defined(__hppa__) -# define UV__O_NONBLOCK O_NONBLOCK -#elif defined(__mips__) -# define UV__O_NONBLOCK 0x80 -#elif defined(__sparc__) -# define UV__O_NONBLOCK 0x4000 -#else -# define UV__O_NONBLOCK 0x800 -#endif - -#define UV__EFD_CLOEXEC UV__O_CLOEXEC -#define UV__EFD_NONBLOCK UV__O_NONBLOCK - -#define UV__IN_CLOEXEC UV__O_CLOEXEC -#define UV__IN_NONBLOCK UV__O_NONBLOCK - -#define UV__SOCK_CLOEXEC UV__O_CLOEXEC -#if defined(SOCK_NONBLOCK) -# define UV__SOCK_NONBLOCK SOCK_NONBLOCK -#else -# define UV__SOCK_NONBLOCK UV__O_NONBLOCK -#endif - -/* inotify flags */ -#define UV__IN_ACCESS 0x001 -#define UV__IN_MODIFY 0x002 -#define UV__IN_ATTRIB 0x004 -#define UV__IN_CLOSE_WRITE 0x008 -#define UV__IN_CLOSE_NOWRITE 0x010 -#define UV__IN_OPEN 0x020 -#define UV__IN_MOVED_FROM 0x040 -#define UV__IN_MOVED_TO 0x080 -#define UV__IN_CREATE 0x100 -#define UV__IN_DELETE 0x200 -#define UV__IN_DELETE_SELF 0x400 -#define UV__IN_MOVE_SELF 0x800 - -struct uv__inotify_event { - int32_t wd; - uint32_t mask; - uint32_t cookie; - uint32_t len; - /* char name[0]; */ +struct uv__statx_timestamp { + int64_t tv_sec; + uint32_t tv_nsec; + int32_t unused0; }; -struct uv__mmsghdr { - struct msghdr msg_hdr; - unsigned int msg_len; +struct uv__statx { + uint32_t stx_mask; + uint32_t stx_blksize; + uint64_t stx_attributes; + uint32_t stx_nlink; + uint32_t stx_uid; + uint32_t stx_gid; + uint16_t stx_mode; + uint16_t unused0; + uint64_t stx_ino; + uint64_t stx_size; + uint64_t stx_blocks; + uint64_t stx_attributes_mask; + struct uv__statx_timestamp stx_atime; + struct uv__statx_timestamp stx_btime; + struct uv__statx_timestamp stx_ctime; + struct uv__statx_timestamp stx_mtime; + uint32_t stx_rdev_major; + uint32_t stx_rdev_minor; + uint32_t stx_dev_major; + uint32_t stx_dev_minor; + uint64_t unused1[14]; }; -int uv__accept4(int fd, struct sockaddr* addr, socklen_t* addrlen, int flags); -int uv__eventfd(unsigned int count); -int uv__eventfd2(unsigned int count, int flags); -int uv__inotify_init(void); -int uv__inotify_init1(int flags); -int uv__inotify_add_watch(int fd, const char* path, uint32_t mask); -int uv__inotify_rm_watch(int fd, int32_t wd); -int uv__pipe2(int pipefd[2], int flags); -int uv__recvmmsg(int fd, - struct uv__mmsghdr* mmsg, - unsigned int vlen, - unsigned int flags, - struct timespec* timeout); -int uv__sendmmsg(int fd, - struct uv__mmsghdr* mmsg, - unsigned int vlen, - unsigned int flags); ssize_t uv__preadv(int fd, const struct iovec *iov, int iovcnt, int64_t offset); ssize_t uv__pwritev(int fd, const struct iovec *iov, int iovcnt, int64_t offset); int uv__dup3(int oldfd, int newfd, int flags); +int uv__statx(int dirfd, + const char* path, + int flags, + unsigned int mask, + struct uv__statx* statxbuf); +ssize_t uv__getrandom(void* buf, size_t buflen, unsigned flags); #endif /* UV_LINUX_SYSCALL_H_ */ diff --git a/src/unix/loop.c b/src/unix/loop.c index c2a03d7..e5b2889 100644 --- a/src/unix/loop.c +++ b/src/unix/loop.c @@ -106,6 +106,8 @@ fail_rwlock_init: fail_signal_init: uv__platform_loop_delete(loop); + uv__free(loop->watchers); + loop->nwatchers = 0; return err; } diff --git a/src/unix/netbsd.c b/src/unix/netbsd.c index 2605c11..c66333f 100644 --- a/src/unix/netbsd.c +++ b/src/unix/netbsd.c @@ -40,15 +40,6 @@ #include #include -static uv_mutex_t process_title_mutex; -static uv_once_t process_title_mutex_once = UV_ONCE_INIT; -static char *process_title; - - -static void init_process_title_mutex_once(void) { - uv_mutex_init(&process_title_mutex); -} - int uv__platform_loop_init(uv_loop_t* loop) { return uv__kqueue_init(loop); @@ -64,7 +55,7 @@ void uv_loadavg(double avg[3]) { size_t size = sizeof(info); int which[] = {CTL_VM, VM_LOADAVG}; - if (sysctl(which, 2, &info, &size, NULL, 0) == -1) return; + if (sysctl(which, ARRAY_SIZE(which), &info, &size, NULL, 0) == -1) return; avg[0] = (double) info.ldavg[0] / info.fscale; avg[1] = (double) info.ldavg[1] / info.fscale; @@ -96,7 +87,8 @@ int uv_exepath(char* buffer, size_t* size) { /* Copy string from the intermediate buffer to outer one with appropriate * length. */ - strlcpy(buffer, int_buf, *size); + /* TODO(bnoordhuis) Check uv__strscpy() return value. */ + uv__strscpy(buffer, int_buf, *size); /* Set new size. */ *size = strlen(buffer); @@ -110,7 +102,7 @@ uint64_t uv_get_free_memory(void) { size_t size = sizeof(info); int which[] = {CTL_VM, VM_UVMEXP}; - if (sysctl(which, 2, &info, &size, NULL, 0)) + if (sysctl(which, ARRAY_SIZE(which), &info, &size, NULL, 0)) return UV__ERR(errno); return (uint64_t) info.free * sysconf(_SC_PAGESIZE); @@ -127,69 +119,15 @@ uint64_t uv_get_total_memory(void) { #endif size_t size = sizeof(info); - if (sysctl(which, 2, &info, &size, NULL, 0)) + if (sysctl(which, ARRAY_SIZE(which), &info, &size, NULL, 0)) return UV__ERR(errno); return (uint64_t) info; } -char** uv_setup_args(int argc, char** argv) { - process_title = argc ? uv__strdup(argv[0]) : NULL; - return argv; -} - - -int uv_set_process_title(const char* title) { - char* new_title; - - new_title = uv__strdup(title); - - uv_once(&process_title_mutex_once, init_process_title_mutex_once); - uv_mutex_lock(&process_title_mutex); - - if (process_title == NULL) { - uv_mutex_unlock(&process_title_mutex); - return UV_ENOMEM; - } - - uv__free(process_title); - process_title = new_title; - setproctitle("%s", title); - - uv_mutex_unlock(&process_title_mutex); - - return 0; -} - - -int uv_get_process_title(char* buffer, size_t size) { - size_t len; - - if (buffer == NULL || size == 0) - return UV_EINVAL; - - uv_once(&process_title_mutex_once, init_process_title_mutex_once); - uv_mutex_lock(&process_title_mutex); - - if (process_title) { - len = strlen(process_title) + 1; - - if (size < len) { - uv_mutex_unlock(&process_title_mutex); - return UV_ENOBUFS; - } - - memcpy(buffer, process_title, len); - } else { - len = 0; - } - - uv_mutex_unlock(&process_title_mutex); - - buffer[len] = '\0'; - - return 0; +uint64_t uv_get_constrained_memory(void) { + return 0; /* Memory constraints are unknown. */ } @@ -229,7 +167,7 @@ int uv_uptime(double* uptime) { size_t size = sizeof(info); static int which[] = {CTL_KERN, KERN_BOOTTIME}; - if (sysctl(which, 2, &info, &size, NULL, 0)) + if (sysctl(which, ARRAY_SIZE(which), &info, &size, NULL, 0)) return UV__ERR(errno); now = time(NULL); @@ -297,13 +235,25 @@ int uv_cpu_info(uv_cpu_info_t** cpu_infos, int* count) { return 0; } +int uv__random_sysctl(void* buf, size_t len) { + static int name[] = {CTL_KERN, KERN_ARND}; + size_t count, req; + unsigned char* p; -void uv_free_cpu_info(uv_cpu_info_t* cpu_infos, int count) { - int i; + p = buf; + while (len) { + req = len < 32 ? len : 32; + count = req; + + if (sysctl(name, ARRAY_SIZE(name), p, &count, NULL, 0) == -1) + return UV__ERR(errno); - for (i = 0; i < count; i++) { - uv__free(cpu_infos[i].model); + if (count != req) + return UV_EIO; /* Can't happen. */ + + p += count; + len -= count; } - uv__free(cpu_infos); + return 0; } diff --git a/src/unix/no-proctitle.c b/src/unix/no-proctitle.c index 165740c..32aa0af 100644 --- a/src/unix/no-proctitle.c +++ b/src/unix/no-proctitle.c @@ -29,6 +29,9 @@ char** uv_setup_args(int argc, char** argv) { return argv; } +void uv__process_title_cleanup(void) { +} + int uv_set_process_title(const char* title) { return 0; } diff --git a/src/unix/openbsd.c b/src/unix/openbsd.c index ce937cd..f32a94d 100644 --- a/src/unix/openbsd.c +++ b/src/unix/openbsd.c @@ -36,16 +36,6 @@ #include -static uv_mutex_t process_title_mutex; -static uv_once_t process_title_mutex_once = UV_ONCE_INIT; -static char *process_title; - - -static void init_process_title_mutex_once(void) { - uv_mutex_init(&process_title_mutex); -} - - int uv__platform_loop_init(uv_loop_t* loop) { return uv__kqueue_init(loop); } @@ -60,7 +50,7 @@ void uv_loadavg(double avg[3]) { size_t size = sizeof(info); int which[] = {CTL_VM, VM_LOADAVG}; - if (sysctl(which, 2, &info, &size, NULL, 0) < 0) return; + if (sysctl(which, ARRAY_SIZE(which), &info, &size, NULL, 0) < 0) return; avg[0] = (double) info.ldavg[0] / info.fscale; avg[1] = (double) info.ldavg[1] / info.fscale; @@ -71,7 +61,6 @@ void uv_loadavg(double avg[3]) { int uv_exepath(char* buffer, size_t* size) { int mib[4]; char **argsbuf = NULL; - char **argsbuf_tmp; size_t argsbuf_size = 100U; size_t exepath_size; pid_t mypid; @@ -83,15 +72,14 @@ int uv_exepath(char* buffer, size_t* size) { mypid = getpid(); for (;;) { err = UV_ENOMEM; - argsbuf_tmp = uv__realloc(argsbuf, argsbuf_size); - if (argsbuf_tmp == NULL) + argsbuf = uv__reallocf(argsbuf, argsbuf_size); + if (argsbuf == NULL) goto out; - argsbuf = argsbuf_tmp; mib[0] = CTL_KERN; mib[1] = KERN_PROC_ARGS; mib[2] = mypid; mib[3] = KERN_PROC_ARGV; - if (sysctl(mib, 4, argsbuf, &argsbuf_size, NULL, 0) == 0) { + if (sysctl(mib, ARRAY_SIZE(mib), argsbuf, &argsbuf_size, NULL, 0) == 0) { break; } if (errno != ENOMEM) { @@ -127,7 +115,7 @@ uint64_t uv_get_free_memory(void) { size_t size = sizeof(info); int which[] = {CTL_VM, VM_UVMEXP}; - if (sysctl(which, 2, &info, &size, NULL, 0)) + if (sysctl(which, ARRAY_SIZE(which), &info, &size, NULL, 0)) return UV__ERR(errno); return (uint64_t) info.free * sysconf(_SC_PAGESIZE); @@ -139,69 +127,15 @@ uint64_t uv_get_total_memory(void) { int which[] = {CTL_HW, HW_PHYSMEM64}; size_t size = sizeof(info); - if (sysctl(which, 2, &info, &size, NULL, 0)) + if (sysctl(which, ARRAY_SIZE(which), &info, &size, NULL, 0)) return UV__ERR(errno); return (uint64_t) info; } -char** uv_setup_args(int argc, char** argv) { - process_title = argc ? uv__strdup(argv[0]) : NULL; - return argv; -} - - -int uv_set_process_title(const char* title) { - char* new_title; - - new_title = uv__strdup(title); - - uv_once(&process_title_mutex_once, init_process_title_mutex_once); - uv_mutex_lock(&process_title_mutex); - - if (process_title == NULL) { - uv_mutex_unlock(&process_title_mutex); - return UV_ENOMEM; - } - - uv__free(process_title); - process_title = new_title; - setproctitle("%s", title); - - uv_mutex_unlock(&process_title_mutex); - - return 0; -} - - -int uv_get_process_title(char* buffer, size_t size) { - size_t len; - - if (buffer == NULL || size == 0) - return UV_EINVAL; - - uv_once(&process_title_mutex_once, init_process_title_mutex_once); - uv_mutex_lock(&process_title_mutex); - - if (process_title) { - len = strlen(process_title) + 1; - - if (size < len) { - uv_mutex_unlock(&process_title_mutex); - return UV_ENOBUFS; - } - - memcpy(buffer, process_title, len); - } else { - len = 0; - } - - uv_mutex_unlock(&process_title_mutex); - - buffer[len] = '\0'; - - return 0; +uint64_t uv_get_constrained_memory(void) { + return 0; /* Memory constraints are unknown. */ } @@ -218,7 +152,7 @@ int uv_resident_set_memory(size_t* rss) { mib[4] = sizeof(struct kinfo_proc); mib[5] = 1; - if (sysctl(mib, 6, &kinfo, &size, NULL, 0) < 0) + if (sysctl(mib, ARRAY_SIZE(mib), &kinfo, &size, NULL, 0) < 0) return UV__ERR(errno); *rss = kinfo.p_vm_rssize * page_size; @@ -232,7 +166,7 @@ int uv_uptime(double* uptime) { size_t size = sizeof(info); static int which[] = {CTL_KERN, KERN_BOOTTIME}; - if (sysctl(which, 2, &info, &size, NULL, 0)) + if (sysctl(which, ARRAY_SIZE(which), &info, &size, NULL, 0)) return UV__ERR(errno); now = time(NULL); @@ -248,43 +182,38 @@ int uv_cpu_info(uv_cpu_info_t** cpu_infos, int* count) { uint64_t info[CPUSTATES]; char model[512]; int numcpus = 1; - int which[] = {CTL_HW,HW_MODEL,0}; + int which[] = {CTL_HW,HW_MODEL}; + int percpu[] = {CTL_KERN,KERN_CPTIME2,0}; size_t size; - int i; + int i, j; uv_cpu_info_t* cpu_info; size = sizeof(model); - if (sysctl(which, 2, &model, &size, NULL, 0)) + if (sysctl(which, ARRAY_SIZE(which), &model, &size, NULL, 0)) return UV__ERR(errno); - which[1] = HW_NCPU; + which[1] = HW_NCPUONLINE; size = sizeof(numcpus); - if (sysctl(which, 2, &numcpus, &size, NULL, 0)) + if (sysctl(which, ARRAY_SIZE(which), &numcpus, &size, NULL, 0)) return UV__ERR(errno); *cpu_infos = uv__malloc(numcpus * sizeof(**cpu_infos)); if (!(*cpu_infos)) return UV_ENOMEM; + i = 0; *count = numcpus; which[1] = HW_CPUSPEED; size = sizeof(cpuspeed); - if (sysctl(which, 2, &cpuspeed, &size, NULL, 0)) { - uv__free(*cpu_infos); - return UV__ERR(errno); - } + if (sysctl(which, ARRAY_SIZE(which), &cpuspeed, &size, NULL, 0)) + goto error; size = sizeof(info); - which[0] = CTL_KERN; - which[1] = KERN_CPTIME2; for (i = 0; i < numcpus; i++) { - which[2] = i; - size = sizeof(info); - if (sysctl(which, 3, &info, &size, NULL, 0)) { - uv__free(*cpu_infos); - return UV__ERR(errno); - } + percpu[2] = i; + if (sysctl(percpu, ARRAY_SIZE(percpu), &info, &size, NULL, 0)) + goto error; cpu_info = &(*cpu_infos)[i]; @@ -299,15 +228,13 @@ int uv_cpu_info(uv_cpu_info_t** cpu_infos, int* count) { } return 0; -} - -void uv_free_cpu_info(uv_cpu_info_t* cpu_infos, int count) { - int i; - - for (i = 0; i < count; i++) { - uv__free(cpu_infos[i].model); - } +error: + *count = 0; + for (j = 0; j < i; j++) + uv__free((*cpu_infos)[j].model); - uv__free(cpu_infos); + uv__free(*cpu_infos); + *cpu_infos = NULL; + return UV__ERR(errno); } diff --git a/src/unix/os390-syscalls.c b/src/unix/os390-syscalls.c index 1040d66..424cc48 100644 --- a/src/unix/os390-syscalls.c +++ b/src/unix/os390-syscalls.c @@ -23,11 +23,11 @@ #include "os390-syscalls.h" #include #include -#include #include #include #include +#define CW_INTRPT 1 #define CW_CONDVAR 32 #pragma linkage(BPX4CTW, OS) @@ -43,6 +43,7 @@ int scandir(const char* maindir, struct dirent*** namelist, int (*compar)(const struct dirent**, const struct dirent **)) { struct dirent** nl; + struct dirent** nl_copy; struct dirent* dirent; unsigned count; size_t allocated; @@ -62,19 +63,17 @@ int scandir(const char* maindir, struct dirent*** namelist, if (!filter || filter(dirent)) { struct dirent* copy; copy = uv__malloc(sizeof(*copy)); - if (!copy) { - while (count) { - dirent = nl[--count]; - uv__free(dirent); - } - uv__free(nl); - closedir(mdir); - errno = ENOMEM; - return -1; - } + if (!copy) + goto error; memcpy(copy, dirent, sizeof(*copy)); - nl = uv__realloc(nl, sizeof(*copy) * (count + 1)); + nl_copy = uv__realloc(nl, sizeof(*copy) * (count + 1)); + if (nl_copy == NULL) { + uv__free(copy); + goto error; + } + + nl = nl_copy; nl[count++] = copy; } } @@ -86,6 +85,16 @@ int scandir(const char* maindir, struct dirent*** namelist, *namelist = nl; return count; + +error: + while (count > 0) { + dirent = nl[--count]; + uv__free(dirent); + } + uv__free(nl); + closedir(mdir); + errno = ENOMEM; + return -1; } @@ -119,7 +128,7 @@ static void maybe_resize(uv__os390_epoll* lst, unsigned int len) { } newsize = next_power_of_two(len); - newlst = uv__realloc(lst->items, newsize * sizeof(lst->items[0])); + newlst = uv__reallocf(lst->items, newsize * sizeof(lst->items[0])); if (newlst == NULL) abort(); @@ -269,6 +278,8 @@ int epoll_ctl(uv__os390_epoll* lst, return 0; } +#define EP_MAX_PFDS (ULONG_MAX / sizeof(struct pollfd)) +#define EP_MAX_EVENTS (INT_MAX / sizeof(struct epoll_event)) int epoll_wait(uv__os390_epoll* lst, struct epoll_event* events, int maxevents, int timeout) { @@ -277,18 +288,41 @@ int epoll_wait(uv__os390_epoll* lst, struct epoll_event* events, int pollret; int reventcount; int nevents; + struct pollfd msg_fd; + int i; - _SET_FDS_MSGS(size, 1, lst->size - 1); + if (!lst || !lst->items || !events) { + errno = EFAULT; + return -1; + } + + if (lst->size > EP_MAX_PFDS) { + errno = EINVAL; + return -1; + } + + if (maxevents <= 0 || maxevents > EP_MAX_EVENTS) { + errno = EINVAL; + return -1; + } + + if (lst->size > 0) + _SET_FDS_MSGS(size, 1, lst->size - 1); + else + _SET_FDS_MSGS(size, 0, 0); pfds = lst->items; pollret = poll(pfds, size, timeout); if (pollret <= 0) return pollret; + assert(lst->size > 0); + pollret = _NFDS(pollret) + _NMSGS(pollret); reventcount = 0; nevents = 0; - for (int i = 0; + msg_fd = pfds[lst->size - 1]; + for (i = 0; i < lst->size && i < maxevents && reventcount < pollret; ++i) { struct epoll_event ev; struct pollfd* pfd; @@ -299,6 +333,7 @@ int epoll_wait(uv__os390_epoll* lst, struct epoll_event* events, ev.fd = pfd->fd; ev.events = pfd->revents; + ev.is_msg = 0; if (pfd->revents & POLLIN && pfd->revents & POLLOUT) reventcount += 2; else if (pfd->revents & (POLLIN | POLLOUT)) @@ -308,6 +343,10 @@ int epoll_wait(uv__os390_epoll* lst, struct epoll_event* events, events[nevents++] = ev; } + if (msg_fd.revents != 0 && msg_fd.fd != -1) + if (i == lst->size) + events[nevents - 1].is_msg = 1; + return nevents; } @@ -350,27 +389,36 @@ int nanosleep(const struct timespec* req, struct timespec* rem) { unsigned secrem; unsigned nanorem; int rv; - int rc; + int err; int rsn; nano = (int)req->tv_nsec; seconds = req->tv_sec; - events = CW_CONDVAR; + events = CW_CONDVAR | CW_INTRPT; + secrem = 0; + nanorem = 0; #if defined(_LP64) - BPX4CTW(&seconds, &nano, &events, &secrem, &nanorem, &rv, &rc, &rsn); + BPX4CTW(&seconds, &nano, &events, &secrem, &nanorem, &rv, &err, &rsn); #else - BPX1CTW(&seconds, &nano, &events, &secrem, &nanorem, &rv, &rc, &rsn); + BPX1CTW(&seconds, &nano, &events, &secrem, &nanorem, &rv, &err, &rsn); #endif - assert(rv == -1 && errno == EAGAIN); + /* Don't clobber errno unless BPX1CTW/BPX4CTW errored. + * Don't leak EAGAIN, that just means the timeout expired. + */ + if (rv == -1) + if (err == EAGAIN) + rv = 0; + else + errno = err; - if(rem != NULL) { + if (rem != NULL && (rv == 0 || err == EINTR)) { rem->tv_nsec = nanorem; rem->tv_sec = secrem; } - return 0; + return rv; } @@ -510,3 +558,28 @@ size_t strnlen(const char* str, size_t maxlen) { else return p - str; } + + +int sem_init(UV_PLATFORM_SEM_T* semid, int pshared, unsigned int value) { + UNREACHABLE(); +} + + +int sem_destroy(UV_PLATFORM_SEM_T* semid) { + UNREACHABLE(); +} + + +int sem_post(UV_PLATFORM_SEM_T* semid) { + UNREACHABLE(); +} + + +int sem_trywait(UV_PLATFORM_SEM_T* semid) { + UNREACHABLE(); +} + + +int sem_wait(UV_PLATFORM_SEM_T* semid) { + UNREACHABLE(); +} diff --git a/src/unix/os390-syscalls.h b/src/unix/os390-syscalls.h index ea59910..86416bb 100644 --- a/src/unix/os390-syscalls.h +++ b/src/unix/os390-syscalls.h @@ -40,6 +40,7 @@ struct epoll_event { int events; int fd; + int is_msg; }; typedef struct { @@ -64,5 +65,10 @@ int scandir(const char* maindir, struct dirent*** namelist, char *mkdtemp(char* path); ssize_t os390_readlink(const char* path, char* buf, size_t len); size_t strnlen(const char* str, size_t maxlen); +int sem_init(UV_PLATFORM_SEM_T* semid, int pshared, unsigned int value); +int sem_destroy(UV_PLATFORM_SEM_T* semid); +int sem_post(UV_PLATFORM_SEM_T* semid); +int sem_trywait(UV_PLATFORM_SEM_T* semid); +int sem_wait(UV_PLATFORM_SEM_T* semid); #endif /* UV_OS390_SYSCALL_H_ */ diff --git a/src/unix/os390.c b/src/unix/os390.c index 5e93178..dce169b 100644 --- a/src/unix/os390.c +++ b/src/unix/os390.c @@ -229,15 +229,15 @@ static int getexe(const int pid, char* buf, size_t len) { assert(((Output_buf.Output_data.offsetPath >>24) & 0xFF) == 'A'); /* Get the offset from the lowest 3 bytes */ - Output_path = (char*)(&Output_buf) + - (Output_buf.Output_data.offsetPath & 0x00FFFFFF); + Output_path = (struct Output_path_type*) ((char*) (&Output_buf) + + (Output_buf.Output_data.offsetPath & 0x00FFFFFF)); if (Output_path->len >= len) { errno = ENOBUFS; return -1; } - strncpy(buf, Output_path->path, len); + uv__strscpy(buf, Output_path->path, len); return 0; } @@ -356,14 +356,17 @@ uint64_t uv_get_total_memory(void) { } +uint64_t uv_get_constrained_memory(void) { + return 0; /* Memory constraints are unknown. */ +} + + int uv_resident_set_memory(size_t* rss) { - char* psa; char* ascb; char* rax; size_t nframes; - psa = PSA_PTR; - ascb = *(char* __ptr32 *)(psa + PSAAOLD); + ascb = *(char* __ptr32 *)(PSA_PTR + PSAAOLD); rax = *(char* __ptr32 *)(ascb + ASCBRSME); nframes = *(unsigned int*)(rax + RAXFMCT); @@ -430,13 +433,6 @@ int uv_cpu_info(uv_cpu_info_t** cpu_infos, int* count) { } -void uv_free_cpu_info(uv_cpu_info_t* cpu_infos, int count) { - for (int i = 0; i < count; ++i) - uv__free(cpu_infos[i].model); - uv__free(cpu_infos); -} - - static int uv__interface_addresses_v6(uv_interface_address_t** addresses, int* count) { uv_interface_address_t* address; @@ -512,7 +508,7 @@ static int uv__interface_addresses_v6(uv_interface_address_t** addresses, /* TODO: Retrieve netmask using SIOCGIFNETMASK ioctl */ address->is_internal = flg.__nif6e_flags & _NIF6E_FLAGS_LOOPBACK ? 1 : 0; - + memset(address->phys_addr, 0, sizeof(address->phys_addr)); address++; } @@ -531,12 +527,14 @@ int uv_interface_addresses(uv_interface_address_t** addresses, int* count) { struct ifreq* p; int count_v6; + *count = 0; + *addresses = NULL; + /* get the ipv6 addresses first */ uv_interface_address_t* addresses_v6; uv__interface_addresses_v6(&addresses_v6, &count_v6); /* now get the ipv4 addresses */ - *count = 0; /* Assume maximum buffer size allowable */ maxsize = 16384; @@ -578,6 +576,11 @@ int uv_interface_addresses(uv_interface_address_t** addresses, int* count) { (*count)++; } + if (*count == 0) { + uv__close(sockfd); + return 0; + } + /* Alloc the return interface structs */ *addresses = uv__malloc((*count + count_v6) * sizeof(uv_interface_address_t)); @@ -624,6 +627,7 @@ int uv_interface_addresses(uv_interface_address_t** addresses, int* count) { } address->is_internal = flg.ifr_flags & IFF_LOOPBACK ? 1 : 0; + memset(address->phys_addr, 0, sizeof(address->phys_addr)); address++; } @@ -651,6 +655,7 @@ void uv__platform_invalidate_fd(uv_loop_t* loop, int fd) { uintptr_t nfds; assert(loop->watchers != NULL); + assert(fd >= 0); events = (struct epoll_event*) loop->watchers[loop->nwatchers]; nfds = (uintptr_t) loop->watchers[loop->nwatchers + 1]; @@ -751,7 +756,7 @@ int uv_fs_event_stop(uv_fs_event_t* handle) { memcpy(reg_struct.__rfis_rftok, handle->rfis_rftok, sizeof(handle->rfis_rftok)); - /* + /* * This call will take "/" as the path argument in case we * don't care to supply the correct path. The system will simply * ignore it. @@ -918,7 +923,7 @@ void uv__io_poll(uv_loop_t* loop, int timeout) { continue; ep = loop->ep; - if (fd == ep->msg_queue) { + if (pe->is_msg) { os390_message_queue_handler(ep); continue; } @@ -987,7 +992,7 @@ void uv__set_process_title(const char* title) { } int uv__io_fork(uv_loop_t* loop) { - /* + /* Nullify the msg queue but don't close it because it is still being used by the parent. */ diff --git a/src/unix/pipe.c b/src/unix/pipe.c index e450a30..040d578 100644 --- a/src/unix/pipe.c +++ b/src/unix/pipe.c @@ -66,8 +66,7 @@ int uv_pipe_bind(uv_pipe_t* handle, const char* name) { sockfd = err; memset(&saddr, 0, sizeof saddr); - strncpy(saddr.sun_path, pipe_fname, sizeof(saddr.sun_path) - 1); - saddr.sun_path[sizeof(saddr.sun_path) - 1] = '\0'; + uv__strscpy(saddr.sun_path, pipe_fname, sizeof(saddr.sun_path)); saddr.sun_family = AF_UNIX; if (bind(sockfd, (struct sockaddr*)&saddr, sizeof saddr)) { @@ -96,8 +95,12 @@ int uv_pipe_listen(uv_pipe_t* handle, int backlog, uv_connection_cb cb) { if (uv__stream_fd(handle) == -1) return UV_EINVAL; -#if defined(__MVS__) + if (handle->ipc) + return UV_EINVAL; + +#if defined(__MVS__) || defined(__PASE__) /* On zOS, backlog=0 has undefined behaviour */ + /* On IBMi PASE, backlog=0 leads to "Connection refused" error */ if (backlog == 0) backlog = 1; else if (backlog < 0) @@ -186,8 +189,7 @@ void uv_pipe_connect(uv_connect_t* req, } memset(&saddr, 0, sizeof saddr); - strncpy(saddr.sun_path, name, sizeof(saddr.sun_path) - 1); - saddr.sun_path[sizeof(saddr.sun_path) - 1] = '\0'; + uv__strscpy(saddr.sun_path, name, sizeof(saddr.sun_path)); saddr.sun_family = AF_UNIX; do { @@ -217,7 +219,7 @@ void uv_pipe_connect(uv_connect_t* req, } if (err == 0) - uv__io_start(handle->loop, &handle->io_watcher, POLLIN | POLLOUT); + uv__io_start(handle->loop, &handle->io_watcher, POLLOUT); out: handle->delayed_error = err; @@ -235,9 +237,6 @@ out: } -typedef int (*uv__peersockfunc)(int, struct sockaddr*, socklen_t*); - - static int uv__pipe_getsockpeername(const uv_pipe_t* handle, uv__peersockfunc func, char* buffer, @@ -248,10 +247,13 @@ static int uv__pipe_getsockpeername(const uv_pipe_t* handle, addrlen = sizeof(sa); memset(&sa, 0, addrlen); - err = func(uv__stream_fd(handle), (struct sockaddr*) &sa, &addrlen); + err = uv__getsockpeername((const uv_handle_t*) handle, + func, + (struct sockaddr*) &sa, + (int*) &addrlen); if (err < 0) { *size = 0; - return UV__ERR(errno); + return err; } #if defined(__linux__) @@ -263,7 +265,7 @@ static int uv__pipe_getsockpeername(const uv_pipe_t* handle, addrlen = strlen(sa.sun_path); - if (addrlen >= *size) { + if ((size_t)addrlen >= *size) { *size = addrlen + 1; return UV_ENOBUFS; } diff --git a/src/unix/posix-poll.c b/src/unix/posix-poll.c index f3181f9..766e832 100644 --- a/src/unix/posix-poll.c +++ b/src/unix/posix-poll.c @@ -61,7 +61,7 @@ static void uv__pollfds_maybe_resize(uv_loop_t* loop) { return; n = loop->poll_fds_size ? loop->poll_fds_size * 2 : 64; - p = uv__realloc(loop->poll_fds, n * sizeof(*loop->poll_fds)); + p = uv__reallocf(loop->poll_fds, n * sizeof(*loop->poll_fds)); if (p == NULL) abort(); @@ -298,6 +298,8 @@ update_timeout: void uv__platform_invalidate_fd(uv_loop_t* loop, int fd) { size_t i; + assert(fd >= 0); + if (loop->poll_fds_iterating) { /* uv__io_poll is currently iterating. Just invalidate fd. */ for (i = 0; i < loop->poll_fds_used; i++) diff --git a/src/unix/process.c b/src/unix/process.c index 101c9c5..b021aae 100644 --- a/src/unix/process.c +++ b/src/unix/process.c @@ -112,72 +112,64 @@ static void uv__chld(uv_signal_t* handle, int signum) { } -int uv__make_socketpair(int fds[2], int flags) { -#if defined(__linux__) - static int no_cloexec; - - if (no_cloexec) - goto skip; - - if (socketpair(AF_UNIX, SOCK_STREAM | UV__SOCK_CLOEXEC | flags, 0, fds) == 0) - return 0; - - /* Retry on EINVAL, it means SOCK_CLOEXEC is not supported. - * Anything else is a genuine error. - */ - if (errno != EINVAL) +static int uv__make_socketpair(int fds[2]) { +#if defined(__FreeBSD__) || defined(__linux__) + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fds)) return UV__ERR(errno); - no_cloexec = 1; - -skip: -#endif + return 0; +#else + int err; if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds)) return UV__ERR(errno); - uv__cloexec(fds[0], 1); - uv__cloexec(fds[1], 1); + err = uv__cloexec(fds[0], 1); + if (err == 0) + err = uv__cloexec(fds[1], 1); - if (flags & UV__F_NONBLOCK) { - uv__nonblock(fds[0], 1); - uv__nonblock(fds[1], 1); + if (err != 0) { + uv__close(fds[0]); + uv__close(fds[1]); + return UV__ERR(errno); } return 0; +#endif } int uv__make_pipe(int fds[2], int flags) { -#if defined(__linux__) - static int no_pipe2; - - if (no_pipe2) - goto skip; - - if (uv__pipe2(fds, flags | UV__O_CLOEXEC) == 0) - return 0; - - if (errno != ENOSYS) +#if defined(__FreeBSD__) || defined(__linux__) + if (pipe2(fds, flags | O_CLOEXEC)) return UV__ERR(errno); - no_pipe2 = 1; - -skip: -#endif - + return 0; +#else if (pipe(fds)) return UV__ERR(errno); - uv__cloexec(fds[0], 1); - uv__cloexec(fds[1], 1); + if (uv__cloexec(fds[0], 1)) + goto fail; + + if (uv__cloexec(fds[1], 1)) + goto fail; if (flags & UV__F_NONBLOCK) { - uv__nonblock(fds[0], 1); - uv__nonblock(fds[1], 1); + if (uv__nonblock(fds[0], 1)) + goto fail; + + if (uv__nonblock(fds[1], 1)) + goto fail; } return 0; + +fail: + uv__close(fds[0]); + uv__close(fds[1]); + return UV__ERR(errno); +#endif } @@ -200,7 +192,7 @@ static int uv__process_init_stdio(uv_stdio_container_t* container, int fds[2]) { if (container->data.stream->type != UV_NAMED_PIPE) return UV_EINVAL; else - return uv__make_socketpair(fds, 0); + return uv__make_socketpair(fds); case UV_INHERIT_FD: case UV_INHERIT_STREAM: @@ -249,7 +241,7 @@ static int uv__process_open_stream(uv_stdio_container_t* container, static void uv__process_close_stream(uv_stdio_container_t* container) { if (!(container->flags & UV_CREATE_PIPE)) return; - uv__stream_close((uv_stream_t*)container->data.stream); + uv__stream_close(container->data.stream); } @@ -315,7 +307,7 @@ static void uv__process_child_init(const uv_process_options_t* options, use_fd = open("/dev/null", fd == 0 ? O_RDONLY : O_RDWR); close_fd = use_fd; - if (use_fd == -1) { + if (use_fd < 0) { uv__write_int(error_fd, UV__ERR(errno)); _exit(127); } @@ -385,6 +377,11 @@ static void uv__process_child_init(const uv_process_options_t* options, if (n == SIGKILL || n == SIGSTOP) continue; /* Can't be changed. */ +#if defined(__HAIKU__) + if (n == SIGKILLTHR) + continue; /* Can't be changed. */ +#endif + if (SIG_ERR != signal(n, SIG_DFL)) continue; @@ -431,6 +428,8 @@ int uv_spawn(uv_loop_t* loop, UV_PROCESS_SETGID | UV_PROCESS_SETUID | UV_PROCESS_WINDOWS_HIDE | + UV_PROCESS_WINDOWS_HIDE_CONSOLE | + UV_PROCESS_WINDOWS_HIDE_GUI | UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS))); uv__handle_init(loop, (uv_handle_t*)process, UV_PROCESS); diff --git a/src/unix/proctitle.c b/src/unix/proctitle.c index 1a8c7a7..4ee991f 100644 --- a/src/unix/proctitle.c +++ b/src/unix/proctitle.c @@ -24,17 +24,19 @@ #include #include +struct uv__process_title { + char* str; + size_t len; /* Length of the current process title. */ + size_t cap; /* Maximum capacity. Computed once in uv_setup_args(). */ +}; + extern void uv__set_process_title(const char* title); static uv_mutex_t process_title_mutex; static uv_once_t process_title_mutex_once = UV_ONCE_INIT; +static struct uv__process_title process_title; static void* args_mem; -static struct { - char* str; - size_t len; -} process_title; - static void init_process_title_mutex_once(void) { uv_mutex_init(&process_title_mutex); @@ -42,6 +44,7 @@ static void init_process_title_mutex_once(void) { char** uv_setup_args(int argc, char** argv) { + struct uv__process_title pt; char** new_argv; size_t size; char* s; @@ -50,53 +53,69 @@ char** uv_setup_args(int argc, char** argv) { if (argc <= 0) return argv; + pt.str = argv[0]; + pt.len = strlen(argv[0]); + pt.cap = pt.len + 1; + /* Calculate how much memory we need for the argv strings. */ - size = 0; - for (i = 0; i < argc; i++) + size = pt.cap; + for (i = 1; i < argc; i++) size += strlen(argv[i]) + 1; -#if defined(__MVS__) - /* argv is not adjacent. So just use argv[0] */ - process_title.str = argv[0]; - process_title.len = strlen(argv[0]); -#else - process_title.str = argv[0]; - process_title.len = argv[argc - 1] + strlen(argv[argc - 1]) - argv[0]; - assert(process_title.len + 1 == size); /* argv memory should be adjacent. */ -#endif - /* Add space for the argv pointers. */ size += (argc + 1) * sizeof(char*); new_argv = uv__malloc(size); if (new_argv == NULL) return argv; - args_mem = new_argv; /* Copy over the strings and set up the pointer table. */ + i = 0; s = (char*) &new_argv[argc + 1]; - for (i = 0; i < argc; i++) { + size = pt.cap; + goto loop; + + for (/* empty */; i < argc; i++) { size = strlen(argv[i]) + 1; + loop: memcpy(s, argv[i], size); new_argv[i] = s; s += size; } new_argv[i] = NULL; + /* argv is not adjacent on z/os, we use just argv[0] on that platform. */ +#ifndef __MVS__ + pt.cap = argv[i - 1] + size - argv[0]; +#endif + + args_mem = new_argv; + process_title = pt; + return new_argv; } int uv_set_process_title(const char* title) { + struct uv__process_title* pt; + size_t len; + + pt = &process_title; + len = strlen(title); + uv_once(&process_title_mutex_once, init_process_title_mutex_once); uv_mutex_lock(&process_title_mutex); - if (process_title.len != 0) { - /* No need to terminate, byte after is always '\0'. */ - strncpy(process_title.str, title, process_title.len); - uv__set_process_title(title); + if (len >= pt->cap) { + len = 0; + if (pt->cap > 0) + len = pt->cap - 1; } + memcpy(pt->str, title, len); + memset(pt->str + len, '\0', pt->cap - len); + pt->len = len; + uv_mutex_unlock(&process_title_mutex); return 0; @@ -126,7 +145,7 @@ int uv_get_process_title(char* buffer, size_t size) { } -UV_DESTRUCTOR(static void free_args_mem(void)) { +void uv__process_title_cleanup(void) { uv__free(args_mem); /* Keep valgrind happy. */ args_mem = NULL; } diff --git a/src/unix/random-devurandom.c b/src/unix/random-devurandom.c new file mode 100644 index 0000000..05e52a5 --- /dev/null +++ b/src/unix/random-devurandom.c @@ -0,0 +1,93 @@ +/* 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. + */ + +#include "uv.h" +#include "internal.h" + +#include +#include + +static uv_once_t once = UV_ONCE_INIT; +static int status; + + +int uv__random_readpath(const char* path, void* buf, size_t buflen) { + struct stat s; + size_t pos; + ssize_t n; + int fd; + + fd = uv__open_cloexec(path, O_RDONLY); + + if (fd < 0) + return fd; + + if (fstat(fd, &s)) { + uv__close(fd); + return UV__ERR(errno); + } + + if (!S_ISCHR(s.st_mode)) { + uv__close(fd); + return UV_EIO; + } + + for (pos = 0; pos != buflen; pos += n) { + do + n = read(fd, (char*) buf + pos, buflen - pos); + while (n == -1 && errno == EINTR); + + if (n == -1) { + uv__close(fd); + return UV__ERR(errno); + } + + if (n == 0) { + uv__close(fd); + return UV_EIO; + } + } + + uv__close(fd); + return 0; +} + + +static void uv__random_devurandom_init(void) { + char c; + + /* Linux's random(4) man page suggests applications should read at least + * once from /dev/random before switching to /dev/urandom in order to seed + * the system RNG. Reads from /dev/random can of course block indefinitely + * until entropy is available but that's the point. + */ + status = uv__random_readpath("/dev/random", &c, 1); +} + + +int uv__random_devurandom(void* buf, size_t buflen) { + uv_once(&once, uv__random_devurandom_init); + + if (status != 0) + return status; + + return uv__random_readpath("/dev/urandom", buf, buflen); +} diff --git a/src/unix/random-getentropy.c b/src/unix/random-getentropy.c new file mode 100644 index 0000000..c45d9fd --- /dev/null +++ b/src/unix/random-getentropy.c @@ -0,0 +1,57 @@ +/* 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. + */ + +#include "uv.h" +#include "internal.h" + +#include +#include + +typedef int (*uv__getentropy_cb)(void *, size_t); + +static uv__getentropy_cb uv__getentropy; +static uv_once_t once = UV_ONCE_INIT; + + +static void uv__random_getentropy_init(void) { + uv__getentropy = (uv__getentropy_cb) dlsym(RTLD_DEFAULT, "getentropy"); +} + + +int uv__random_getentropy(void* buf, size_t buflen) { + size_t pos; + size_t stride; + + uv_once(&once, uv__random_getentropy_init); + + if (uv__getentropy == NULL) + return UV_ENOSYS; + + /* getentropy() returns an error for requests > 256 bytes. */ + for (pos = 0, stride = 256; pos + stride < buflen; pos += stride) + if (uv__getentropy((char *) buf + pos, stride)) + return UV__ERR(errno); + + if (uv__getentropy((char *) buf + pos, buflen - pos)) + return UV__ERR(errno); + + return 0; +} diff --git a/src/unix/random-getrandom.c b/src/unix/random-getrandom.c new file mode 100644 index 0000000..bcc9408 --- /dev/null +++ b/src/unix/random-getrandom.c @@ -0,0 +1,88 @@ +/* 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. + */ + +#include "uv.h" +#include "internal.h" + +#ifdef __linux__ + +#include "linux-syscalls.h" + +#define uv__random_getrandom_init() 0 + +#else /* !__linux__ */ + +#include +#include + +typedef ssize_t (*uv__getrandom_cb)(void *, size_t, unsigned); + +static uv__getrandom_cb uv__getrandom; +static uv_once_t once = UV_ONCE_INIT; + +static void uv__random_getrandom_init_once(void) { + uv__getrandom = (uv__getrandom_cb) dlsym(RTLD_DEFAULT, "getrandom"); +} + +static int uv__random_getrandom_init(void) { + uv_once(&once, uv__random_getrandom_init_once); + + if (uv__getrandom == NULL) + return UV_ENOSYS; + + return 0; +} + +#endif /* !__linux__ */ + +int uv__random_getrandom(void* buf, size_t buflen) { + ssize_t n; + size_t pos; + int rc; + + rc = uv__random_getrandom_init(); + if (rc != 0) + return rc; + + for (pos = 0; pos != buflen; pos += n) { + do { + n = buflen - pos; + + /* Most getrandom() implementations promise that reads <= 256 bytes + * will always succeed and won't be interrupted by signals. + * It's therefore useful to split it up in smaller reads because + * one big read may, in theory, continuously fail with EINTR. + */ + if (n > 256) + n = 256; + + n = uv__getrandom((char *) buf + pos, n, 0); + } while (n == -1 && errno == EINTR); + + if (n == -1) + return UV__ERR(errno); + + if (n == 0) + return UV_EIO; + } + + return 0; +} diff --git a/src/unix/random-sysctl-linux.c b/src/unix/random-sysctl-linux.c new file mode 100644 index 0000000..66ba8d7 --- /dev/null +++ b/src/unix/random-sysctl-linux.c @@ -0,0 +1,99 @@ +/* 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. + */ + +#include "uv.h" +#include "internal.h" + +#include +#include + +#include +#include + + +struct uv__sysctl_args { + int* name; + int nlen; + void* oldval; + size_t* oldlenp; + void* newval; + size_t newlen; + unsigned long unused[4]; +}; + + +int uv__random_sysctl(void* buf, size_t buflen) { + static int name[] = {1 /*CTL_KERN*/, 40 /*KERN_RANDOM*/, 6 /*RANDOM_UUID*/}; + struct uv__sysctl_args args; + char uuid[16]; + char* p; + char* pe; + size_t n; + + p = buf; + pe = p + buflen; + + while (p < pe) { + memset(&args, 0, sizeof(args)); + + args.name = name; + args.nlen = ARRAY_SIZE(name); + args.oldval = uuid; + args.oldlenp = &n; + n = sizeof(uuid); + + /* Emits a deprecation warning with some kernels but that seems like + * an okay trade-off for the fallback of the fallback: this function is + * only called when neither getrandom(2) nor /dev/urandom are available. + * Fails with ENOSYS on kernels configured without CONFIG_SYSCTL_SYSCALL. + * At least arm64 never had a _sysctl system call and therefore doesn't + * have a SYS__sysctl define either. + */ +#ifdef SYS__sysctl + if (syscall(SYS__sysctl, &args) == -1) + return UV__ERR(errno); +#else + { + (void) &args; + return UV_ENOSYS; + } +#endif + + if (n != sizeof(uuid)) + return UV_EIO; /* Can't happen. */ + + /* uuid[] is now a type 4 UUID. Bytes 6 and 8 (counting from zero) contain + * 4 and 5 bits of entropy, respectively. For ease of use, we skip those + * and only use 14 of the 16 bytes. + */ + uuid[6] = uuid[14]; + uuid[8] = uuid[15]; + + n = pe - p; + if (n > 14) + n = 14; + + memcpy(p, uuid, n); + p += n; + } + + return 0; +} diff --git a/src/unix/signal.c b/src/unix/signal.c index 01aa55f..1c83e09 100644 --- a/src/unix/signal.c +++ b/src/unix/signal.c @@ -77,7 +77,7 @@ static void uv__signal_global_init(void) { } -UV_DESTRUCTOR(static void uv__signal_global_fini(void)) { +void uv__signal_cleanup(void) { /* We can only use signal-safe functions here. * That includes read/write and close, fortunately. * We do all of this directly here instead of resetting @@ -98,7 +98,7 @@ UV_DESTRUCTOR(static void uv__signal_global_fini(void)) { static void uv__signal_global_reinit(void) { - uv__signal_global_fini(); + uv__signal_cleanup(); if (uv__make_pipe(uv__signal_lock_pipefd, 0)) abort(); @@ -331,16 +331,7 @@ int uv_signal_init(uv_loop_t* loop, uv_signal_t* handle) { void uv__signal_close(uv_signal_t* handle) { - uv__signal_stop(handle); - - /* If there are any caught signals "trapped" in the signal pipe, we can't - * call the close callback yet. Otherwise, add the handle to the finish_close - * queue. - */ - if (handle->caught_signals == handle->dispatched_signals) { - uv__make_close_pending((uv_handle_t*) handle); - } } @@ -375,7 +366,7 @@ static int uv__signal_start(uv_signal_t* handle, /* Short circuit: if the signal watcher is already watching {signum} don't * go through the process of deregistering and registering the handler. - * Additionally, this avoids pending signals getting lost in the small time + * Additionally, this avoids pending signals getting lost in the small * time frame that handle->signum == 0. */ if (signum == handle->signum) { @@ -472,15 +463,6 @@ static void uv__signal_event(uv_loop_t* loop, if (handle->flags & UV_SIGNAL_ONE_SHOT) uv__signal_stop(handle); - - /* If uv_close was called while there were caught signals that were not - * yet dispatched, the uv__finish_close was deferred. Make close pending - * now if this has happened. - */ - if ((handle->flags & UV_HANDLE_CLOSING) && - (handle->caught_signals == handle->dispatched_signals)) { - uv__make_close_pending((uv_handle_t*) handle); - } } bytes -= end; @@ -563,6 +545,7 @@ static void uv__signal_stop(uv_signal_t* handle) { if (first_oneshot && !rem_oneshot) { ret = uv__signal_register_handler(handle->signum, 1); assert(ret == 0); + (void)ret; } } diff --git a/src/unix/stream.c b/src/unix/stream.c index 2e84eee..8327f9c 100644 --- a/src/unix/stream.c +++ b/src/unix/stream.c @@ -58,11 +58,19 @@ struct uv__stream_select_s { fd_set* swrite; size_t swrite_sz; }; -# define WRITE_RETRY_ON_ERROR(send_handle) \ + +/* Due to a possible kernel bug at least in OS X 10.10 "Yosemite", + * EPROTOTYPE can be returned while trying to write to a socket that is + * shutting down. If we retry the write, we should get the expected EPIPE + * instead. + */ +# define RETRY_ON_WRITE_ERROR(errno) (errno == EINTR || errno == EPROTOTYPE) +# define IS_TRANSIENT_WRITE_ERROR(errno, send_handle) \ (errno == EAGAIN || errno == EWOULDBLOCK || errno == ENOBUFS || \ - (errno == EMSGSIZE && send_handle)) + (errno == EMSGSIZE && send_handle != NULL)) #else -# define WRITE_RETRY_ON_ERROR(send_handle) \ +# define RETRY_ON_WRITE_ERROR(errno) (errno == EINTR) +# define IS_TRANSIENT_WRITE_ERROR(errno, send_handle) \ (errno == EAGAIN || errno == EWOULDBLOCK || errno == ENOBUFS) #endif /* defined(__APPLE__) */ @@ -700,6 +708,14 @@ static void uv__drain(uv_stream_t* stream) { } +static ssize_t uv__writev(int fd, struct iovec* vec, size_t n) { + if (n == 1) + return write(fd, vec->iov_base, vec->iov_len); + else + return writev(fd, vec, n); +} + + static size_t uv__write_req_size(uv_write_t* req) { size_t size; @@ -712,6 +728,37 @@ static size_t uv__write_req_size(uv_write_t* req) { } +/* Returns 1 if all write request data has been written, or 0 if there is still + * more data to write. + * + * Note: the return value only says something about the *current* request. + * There may still be other write requests sitting in the queue. + */ +static int uv__write_req_update(uv_stream_t* stream, + uv_write_t* req, + size_t n) { + uv_buf_t* buf; + size_t len; + + assert(n <= stream->write_queue_size); + stream->write_queue_size -= n; + + buf = req->bufs + req->write_index; + + do { + len = n < buf->len ? n : buf->len; + buf->base += len; + buf->len -= len; + buf += (buf->len == 0); /* Advance to next buffer if this one is empty. */ + n -= len; + } while (n > 0); + + req->write_index = buf - req->bufs; + + return req->write_index == req->nbufs; +} + + static void uv__write_req_finish(uv_write_t* req) { uv_stream_t* stream = req->handle; @@ -832,102 +879,32 @@ start: *pi = fd_to_send; } - do { + do n = sendmsg(uv__stream_fd(stream), &msg, 0); - } -#if defined(__APPLE__) - /* - * Due to a possible kernel bug at least in OS X 10.10 "Yosemite", - * EPROTOTYPE can be returned while trying to write to a socket that is - * shutting down. If we retry the write, we should get the expected EPIPE - * instead. - */ - while (n == -1 && (errno == EINTR || errno == EPROTOTYPE)); -#else - while (n == -1 && errno == EINTR); -#endif - } else { - do { - if (iovcnt == 1) { - n = write(uv__stream_fd(stream), iov[0].iov_base, iov[0].iov_len); - } else { - n = writev(uv__stream_fd(stream), iov, iovcnt); - } - } -#if defined(__APPLE__) - /* - * Due to a possible kernel bug at least in OS X 10.10 "Yosemite", - * EPROTOTYPE can be returned while trying to write to a socket that is - * shutting down. If we retry the write, we should get the expected EPIPE - * instead. - */ - while (n == -1 && (errno == EINTR || errno == EPROTOTYPE)); -#else - while (n == -1 && errno == EINTR); -#endif - } + while (n == -1 && RETRY_ON_WRITE_ERROR(errno)); - if (n < 0) { - if (!WRITE_RETRY_ON_ERROR(req->send_handle)) { - err = UV__ERR(errno); - goto error; - } else if (stream->flags & UV_HANDLE_BLOCKING_WRITES) { - /* If this is a blocking stream, try again. */ - goto start; - } + /* Ensure the handle isn't sent again in case this is a partial write. */ + if (n >= 0) + req->send_handle = NULL; } else { - /* Successful write */ - - while (n >= 0) { - uv_buf_t* buf = &(req->bufs[req->write_index]); - size_t len = buf->len; - - assert(req->write_index < req->nbufs); - - if ((size_t)n < len) { - buf->base += n; - buf->len -= n; - stream->write_queue_size -= n; - n = 0; - - /* There is more to write. */ - if (stream->flags & UV_HANDLE_BLOCKING_WRITES) { - /* - * If we're blocking then we should not be enabling the write - * watcher - instead we need to try again. - */ - goto start; - } else { - /* Break loop and ensure the watcher is pending. */ - break; - } - - } else { - /* Finished writing the buf at index req->write_index. */ - req->write_index++; - - assert((size_t)n >= len); - n -= len; - - assert(stream->write_queue_size >= len); - stream->write_queue_size -= len; + do + n = uv__writev(uv__stream_fd(stream), iov, iovcnt); + while (n == -1 && RETRY_ON_WRITE_ERROR(errno)); + } - if (req->write_index == req->nbufs) { - /* Then we're done! */ - assert(n == 0); - uv__write_req_finish(req); - /* TODO: start trying to write the next request. */ - return; - } - } - } + if (n == -1 && !IS_TRANSIENT_WRITE_ERROR(errno, req->send_handle)) { + err = UV__ERR(errno); + goto error; } - /* Either we've counted n down to zero or we've got EAGAIN. */ - assert(n == 0 || n == -1); + if (n >= 0 && uv__write_req_update(stream, req, n)) { + uv__write_req_finish(req); + return; /* TODO(bnoordhuis) Start trying to write the next request. */ + } - /* Only non-blocking streams should use the write_watcher. */ - assert(!(stream->flags & UV_HANDLE_BLOCKING_WRITES)); + /* If this is a blocking stream, try again. */ + if (stream->flags & UV_HANDLE_BLOCKING_WRITES) + goto start; /* We're not done. */ uv__io_start(stream->loop, &stream->io_watcher, POLLOUT); @@ -1023,12 +1000,12 @@ uv_handle_type uv__handle_type(int fd) { static void uv__stream_eof(uv_stream_t* stream, const uv_buf_t* buf) { stream->flags |= UV_HANDLE_READ_EOF; + stream->flags &= ~UV_HANDLE_READING; uv__io_stop(stream->loop, &stream->io_watcher, POLLIN); if (!uv__io_active(&stream->io_watcher, POLLOUT)) uv__handle_stop(stream); uv__stream_osx_interrupt_select(stream); stream->read_cb(stream, UV_EOF, buf); - stream->flags &= ~UV_HANDLE_READING; } @@ -1071,7 +1048,12 @@ static int uv__stream_queue_fd(uv_stream_t* stream, int fd) { } -#define UV__CMSG_FD_COUNT 64 +#if defined(__PASE__) +/* on IBMi PASE the control message length can not exceed 256. */ +# define UV__CMSG_FD_COUNT 60 +#else +# define UV__CMSG_FD_COUNT 64 +#endif #define UV__CMSG_FD_SIZE (UV__CMSG_FD_COUNT * sizeof(int)) @@ -1426,7 +1408,7 @@ int uv_write2(uv_write_t* req, return UV_EBADF; if (!(stream->flags & UV_HANDLE_WRITABLE)) - return -EPIPE; + return UV_EPIPE; if (send_handle) { if (stream->type != UV_NAMED_PIPE || !((uv_pipe_t*)stream)->ipc) @@ -1564,7 +1546,7 @@ int uv_try_write(uv_stream_t* stream, } if (written == 0 && req_size != 0) - return UV_EAGAIN; + return req.error < 0 ? req.error : UV_EAGAIN; else return written; } @@ -1580,7 +1562,7 @@ int uv_read_start(uv_stream_t* stream, return UV_EINVAL; if (!(stream->flags & UV_HANDLE_READABLE)) - return -ENOTCONN; + return UV_ENOTCONN; /* The UV_HANDLE_READING flag is irrelevant of the state of the tcp - it just * expresses the desired state of the user. diff --git a/src/unix/sunos.c b/src/unix/sunos.c index b6b3dfe..180cc84 100644 --- a/src/unix/sunos.c +++ b/src/unix/sunos.c @@ -117,6 +117,7 @@ void uv__platform_invalidate_fd(uv_loop_t* loop, int fd) { uintptr_t nfds; assert(loop->watchers != NULL); + assert(fd >= 0); events = (struct port_event*) loop->watchers[loop->nwatchers]; nfds = (uintptr_t) loop->watchers[loop->nwatchers + 1]; @@ -134,8 +135,10 @@ int uv__io_check_fd(uv_loop_t* loop, int fd) { if (port_associate(loop->backend_fd, PORT_SOURCE_FD, fd, POLLIN, 0)) return UV__ERR(errno); - if (port_dissociate(loop->backend_fd, PORT_SOURCE_FD, fd)) + if (port_dissociate(loop->backend_fd, PORT_SOURCE_FD, fd)) { + perror("(libuv) port_dissociate()"); abort(); + } return 0; } @@ -173,8 +176,14 @@ void uv__io_poll(uv_loop_t* loop, int timeout) { w = QUEUE_DATA(q, uv__io_t, watcher_queue); assert(w->pevents != 0); - if (port_associate(loop->backend_fd, PORT_SOURCE_FD, w->fd, w->pevents, 0)) + if (port_associate(loop->backend_fd, + PORT_SOURCE_FD, + w->fd, + w->pevents, + 0)) { + perror("(libuv) port_associate()"); abort(); + } w->events = w->pevents; } @@ -218,10 +227,12 @@ void uv__io_poll(uv_loop_t* loop, int timeout) { /* Work around another kernel bug: port_getn() may return events even * on error. */ - if (errno == EINTR || errno == ETIME) + if (errno == EINTR || errno == ETIME) { saved_errno = errno; - else + } else { + perror("(libuv) port_getn()"); abort(); + } } /* Update loop->time unconditionally. It's tempting to skip the update when @@ -369,6 +380,11 @@ uint64_t uv_get_total_memory(void) { } +uint64_t uv_get_constrained_memory(void) { + return 0; /* Memory constraints are unknown. */ +} + + void uv_loadavg(double avg[3]) { (void) getloadavg(avg, 3); } @@ -680,18 +696,10 @@ int uv_cpu_info(uv_cpu_info_t** cpu_infos, int* count) { } -void uv_free_cpu_info(uv_cpu_info_t* cpu_infos, int count) { - int i; - - for (i = 0; i < count; i++) { - uv__free(cpu_infos[i].model); - } - - uv__free(cpu_infos); -} - #ifdef SUNOS_NO_IFADDRS int uv_interface_addresses(uv_interface_address_t** addresses, int* count) { + *count = 0; + *addresses = NULL; return UV_ENOSYS; } #else /* SUNOS_NO_IFADDRS */ @@ -705,13 +713,14 @@ static int uv__set_phys_addr(uv_interface_address_t* address, struct sockaddr_dl* sa_addr; int sockfd; - int i; + size_t i; struct arpreq arpreq; /* This appears to only work as root */ sa_addr = (struct sockaddr_dl*)(ent->ifa_addr); memcpy(address->phys_addr, LLADDR(sa_addr), sizeof(address->phys_addr)); for (i = 0; i < sizeof(address->phys_addr); i++) { + /* Check that all bytes of phys_addr are zero. */ if (address->phys_addr[i] != 0) return 0; } @@ -758,11 +767,12 @@ int uv_interface_addresses(uv_interface_address_t** addresses, int* count) { struct ifaddrs* addrs; struct ifaddrs* ent; + *count = 0; + *addresses = NULL; + if (getifaddrs(&addrs)) return UV__ERR(errno); - *count = 0; - /* Count the number of interfaces */ for (ent = addrs; ent != NULL; ent = ent->ifa_next) { if (uv__ifaddr_exclude(ent)) @@ -770,6 +780,11 @@ int uv_interface_addresses(uv_interface_address_t** addresses, int* count) { (*count)++; } + if (*count == 0) { + freeifaddrs(addrs); + return 0; + } + *addresses = uv__malloc(*count * sizeof(**addresses)); if (!(*addresses)) { freeifaddrs(addrs); diff --git a/src/unix/tcp.c b/src/unix/tcp.c index 2982851..d47e943 100644 --- a/src/unix/tcp.c +++ b/src/unix/tcp.c @@ -82,7 +82,7 @@ static int maybe_new_socket(uv_tcp_t* handle, int domain, unsigned long flags) { handle->flags |= flags; return 0; } - + /* Query to see if tcp socket is bound. */ slen = sizeof(saddr); memset(&saddr, 0, sizeof(saddr)); @@ -235,12 +235,16 @@ int uv__tcp_connect(uv_connect_t* req, if (r == -1 && errno != 0) { if (errno == EINPROGRESS) ; /* not an error */ - else if (errno == ECONNREFUSED) - /* If we get a ECONNREFUSED wait until the next tick to report the - * error. Solaris wants to report immediately--other unixes want to - * wait. + else if (errno == ECONNREFUSED +#if defined(__OpenBSD__) + || errno == EINVAL +#endif + ) + /* If we get ECONNREFUSED (Solaris) or EINVAL (OpenBSD) wait until the + * next tick to report the error. Solaris and OpenBSD wants to report + * immediately -- other unixes want to wait. */ - handle->delayed_error = UV__ERR(errno); + handle->delayed_error = UV__ERR(ECONNREFUSED); else return UV__ERR(errno); } @@ -279,43 +283,44 @@ int uv_tcp_open(uv_tcp_t* handle, uv_os_sock_t sock) { int uv_tcp_getsockname(const uv_tcp_t* handle, struct sockaddr* name, int* namelen) { - socklen_t socklen; if (handle->delayed_error) return handle->delayed_error; - if (uv__stream_fd(handle) < 0) - return UV_EINVAL; /* FIXME(bnoordhuis) UV_EBADF */ - - /* sizeof(socklen_t) != sizeof(int) on some systems. */ - socklen = (socklen_t) *namelen; - - if (getsockname(uv__stream_fd(handle), name, &socklen)) - return UV__ERR(errno); - - *namelen = (int) socklen; - return 0; + return uv__getsockpeername((const uv_handle_t*) handle, + getsockname, + name, + namelen); } int uv_tcp_getpeername(const uv_tcp_t* handle, struct sockaddr* name, int* namelen) { - socklen_t socklen; if (handle->delayed_error) return handle->delayed_error; - if (uv__stream_fd(handle) < 0) - return UV_EINVAL; /* FIXME(bnoordhuis) UV_EBADF */ + return uv__getsockpeername((const uv_handle_t*) handle, + getpeername, + name, + namelen); +} + + +int uv_tcp_close_reset(uv_tcp_t* handle, uv_close_cb close_cb) { + int fd; + struct linger l = { 1, 0 }; - /* sizeof(socklen_t) != sizeof(int) on some systems. */ - socklen = (socklen_t) *namelen; + /* Disallow setting SO_LINGER to zero due to some platform inconsistencies */ + if (handle->flags & UV_HANDLE_SHUTTING) + return UV_EINVAL; - if (getpeername(uv__stream_fd(handle), name, &socklen)) + fd = uv__stream_fd(handle); + if (0 != setsockopt(fd, SOL_SOCKET, SO_LINGER, &l, sizeof(l))) return UV__ERR(errno); - *namelen = (int) socklen; + uv_close((uv_handle_t*) handle, close_cb); return 0; } @@ -374,8 +379,16 @@ int uv__tcp_keepalive(int fd, int on, unsigned int delay) { return UV__ERR(errno); #ifdef TCP_KEEPIDLE - if (on && setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &delay, sizeof(delay))) - return UV__ERR(errno); + if (on) { + int intvl = 1; /* 1 second; same as default on Win32 */ + int cnt = 10; /* 10 retries; same as hardcoded on Win32 */ + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &delay, sizeof(delay))) + return UV__ERR(errno); + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &intvl, sizeof(intvl))) + return UV__ERR(errno); + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt))) + return UV__ERR(errno); + } #endif /* Solaris/SmartOS, if you don't support keep-alive, diff --git a/src/unix/thread.c b/src/unix/thread.c index 303bc6e..c9a18d1 100644 --- a/src/unix/thread.c +++ b/src/unix/thread.c @@ -37,115 +37,128 @@ #include #endif -#ifdef __GLIBC__ +#if defined(__GLIBC__) && !defined(__UCLIBC__) #include /* gnu_get_libc_version() */ #endif #undef NANOSEC #define NANOSEC ((uint64_t) 1e9) +#if defined(PTHREAD_BARRIER_SERIAL_THREAD) +STATIC_ASSERT(sizeof(uv_barrier_t) == sizeof(pthread_barrier_t)); +#endif -#if defined(UV__PTHREAD_BARRIER_FALLBACK) -/* TODO: support barrier_attr */ -int pthread_barrier_init(pthread_barrier_t* barrier, - const void* barrier_attr, - unsigned count) { +/* Note: guard clauses should match uv_barrier_t's in include/uv/unix.h. */ +#if defined(_AIX) || \ + defined(__OpenBSD__) || \ + !defined(PTHREAD_BARRIER_SERIAL_THREAD) +int uv_barrier_init(uv_barrier_t* barrier, unsigned int count) { + struct _uv_barrier* b; int rc; - _uv_barrier* b; if (barrier == NULL || count == 0) - return EINVAL; - - if (barrier_attr != NULL) - return ENOTSUP; + return UV_EINVAL; b = uv__malloc(sizeof(*b)); if (b == NULL) - return ENOMEM; + return UV_ENOMEM; b->in = 0; b->out = 0; b->threshold = count; - if ((rc = pthread_mutex_init(&b->mutex, NULL)) != 0) + rc = uv_mutex_init(&b->mutex); + if (rc != 0) goto error2; - if ((rc = pthread_cond_init(&b->cond, NULL)) != 0) + + rc = uv_cond_init(&b->cond); + if (rc != 0) goto error; barrier->b = b; return 0; error: - pthread_mutex_destroy(&b->mutex); + uv_mutex_destroy(&b->mutex); error2: uv__free(b); return rc; } -int pthread_barrier_wait(pthread_barrier_t* barrier) { - int rc; - _uv_barrier* b; + +int uv_barrier_wait(uv_barrier_t* barrier) { + struct _uv_barrier* b; + int last; if (barrier == NULL || barrier->b == NULL) - return EINVAL; + return UV_EINVAL; b = barrier->b; - /* Lock the mutex*/ - if ((rc = pthread_mutex_lock(&b->mutex)) != 0) - return rc; + uv_mutex_lock(&b->mutex); - /* Increment the count. If this is the first thread to reach the threshold, - wake up waiters, unlock the mutex, then return - PTHREAD_BARRIER_SERIAL_THREAD. */ if (++b->in == b->threshold) { b->in = 0; - b->out = b->threshold - 1; - rc = pthread_cond_signal(&b->cond); - assert(rc == 0); - - pthread_mutex_unlock(&b->mutex); - return PTHREAD_BARRIER_SERIAL_THREAD; + b->out = b->threshold; + uv_cond_signal(&b->cond); + } else { + do + uv_cond_wait(&b->cond, &b->mutex); + while (b->in != 0); } - /* Otherwise, wait for other threads until in is set to 0, - then return 0 to indicate this is not the first thread. */ - do { - if ((rc = pthread_cond_wait(&b->cond, &b->mutex)) != 0) - break; - } while (b->in != 0); - - /* mark thread exit */ - b->out--; - pthread_cond_signal(&b->cond); - pthread_mutex_unlock(&b->mutex); - return rc; + + last = (--b->out == 0); + if (!last) + uv_cond_signal(&b->cond); /* Not needed for last thread. */ + + uv_mutex_unlock(&b->mutex); + return last; } -int pthread_barrier_destroy(pthread_barrier_t* barrier) { - int rc; - _uv_barrier* b; - if (barrier == NULL || barrier->b == NULL) - return EINVAL; +void uv_barrier_destroy(uv_barrier_t* barrier) { + struct _uv_barrier* b; b = barrier->b; + uv_mutex_lock(&b->mutex); - if ((rc = pthread_mutex_lock(&b->mutex)) != 0) - return rc; - - if (b->in > 0 || b->out > 0) - rc = EBUSY; + assert(b->in == 0); + assert(b->out == 0); - pthread_mutex_unlock(&b->mutex); + if (b->in != 0 || b->out != 0) + abort(); - if (rc) - return rc; + uv_mutex_unlock(&b->mutex); + uv_mutex_destroy(&b->mutex); + uv_cond_destroy(&b->cond); - pthread_cond_destroy(&b->cond); - pthread_mutex_destroy(&b->mutex); uv__free(barrier->b); barrier->b = NULL; - return 0; } + +#else + +int uv_barrier_init(uv_barrier_t* barrier, unsigned int count) { + return UV__ERR(pthread_barrier_init(barrier, NULL, count)); +} + + +int uv_barrier_wait(uv_barrier_t* barrier) { + int rc; + + rc = pthread_barrier_wait(barrier); + if (rc != 0) + if (rc != PTHREAD_BARRIER_SERIAL_THREAD) + abort(); + + return rc == PTHREAD_BARRIER_SERIAL_THREAD; +} + + +void uv_barrier_destroy(uv_barrier_t* barrier) { + if (pthread_barrier_destroy(barrier)) + abort(); +} + #endif @@ -159,14 +172,28 @@ static size_t thread_stack_size(void) { #if defined(__APPLE__) || defined(__linux__) struct rlimit lim; - if (getrlimit(RLIMIT_STACK, &lim)) - abort(); - - if (lim.rlim_cur != RLIM_INFINITY) { + /* getrlimit() can fail on some aarch64 systems due to a glibc bug where + * the system call wrapper invokes the wrong system call. Don't treat + * that as fatal, just use the default stack size instead. + */ + if (0 == getrlimit(RLIMIT_STACK, &lim) && lim.rlim_cur != RLIM_INFINITY) { /* pthread_attr_setstacksize() expects page-aligned values. */ lim.rlim_cur -= lim.rlim_cur % (rlim_t) getpagesize(); - if (lim.rlim_cur >= PTHREAD_STACK_MIN) - return lim.rlim_cur; + + /* Musl's PTHREAD_STACK_MIN is 2 KB on all architectures, which is + * too small to safely receive signals on. + * + * Musl's PTHREAD_STACK_MIN + MINSIGSTKSZ == 8192 on arm64 (which has + * the largest MINSIGSTKSZ of the architectures that musl supports) so + * let's use that as a lower bound. + * + * We use a hardcoded value because PTHREAD_STACK_MIN + MINSIGSTKSZ + * is between 28 and 133 KB when compiling against glibc, depending + * on the architecture. + */ + if (lim.rlim_cur >= 8192) + if (lim.rlim_cur >= PTHREAD_STACK_MIN) + return lim.rlim_cur; } #endif @@ -181,13 +208,42 @@ static size_t thread_stack_size(void) { int uv_thread_create(uv_thread_t *tid, void (*entry)(void *arg), void *arg) { + uv_thread_options_t params; + params.flags = UV_THREAD_NO_FLAGS; + return uv_thread_create_ex(tid, ¶ms, entry, arg); +} + +int uv_thread_create_ex(uv_thread_t* tid, + const uv_thread_options_t* params, + void (*entry)(void *arg), + void *arg) { int err; - size_t stack_size; pthread_attr_t* attr; pthread_attr_t attr_storage; + size_t pagesize; + size_t stack_size; + + /* Used to squelch a -Wcast-function-type warning. */ + union { + void (*in)(void*); + void* (*out)(void*); + } f; + + stack_size = + params->flags & UV_THREAD_HAS_STACK_SIZE ? params->stack_size : 0; attr = NULL; - stack_size = thread_stack_size(); + if (stack_size == 0) { + stack_size = thread_stack_size(); + } else { + pagesize = (size_t)getpagesize(); + /* Round up to the nearest page boundary. */ + stack_size = (stack_size + pagesize - 1) &~ (pagesize - 1); +#ifdef PTHREAD_STACK_MIN + if (stack_size < PTHREAD_STACK_MIN) + stack_size = PTHREAD_STACK_MIN; +#endif + } if (stack_size > 0) { attr = &attr_storage; @@ -199,7 +255,8 @@ int uv_thread_create(uv_thread_t *tid, void (*entry)(void *arg), void *arg) { abort(); } - err = pthread_create(tid, attr, (void*(*)(void*)) entry, arg); + f.in = entry; + err = pthread_create(tid, attr, f.out, arg); if (attr != NULL) pthread_attr_destroy(attr); @@ -425,7 +482,7 @@ int uv_sem_trywait(uv_sem_t* sem) { #else /* !(defined(__APPLE__) && defined(__MACH__)) */ -#ifdef __GLIBC__ +#if defined(__GLIBC__) && !defined(__UCLIBC__) /* Hack around https://sourceware.org/bugzilla/show_bug.cgi?id=12674 * by providing a custom implementation for glibc < 2.21 in terms of other @@ -461,7 +518,8 @@ typedef struct uv_semaphore_s { unsigned int value; } uv_semaphore_t; -#if defined(__GLIBC__) || platform_needs_custom_semaphore +#if (defined(__GLIBC__) && !defined(__UCLIBC__)) || \ + platform_needs_custom_semaphore STATIC_ASSERT(sizeof(uv_sem_t) >= sizeof(uv_semaphore_t*)); #endif @@ -590,7 +648,7 @@ static int uv__sem_trywait(uv_sem_t* sem) { } int uv_sem_init(uv_sem_t* sem, unsigned int value) { -#ifdef __GLIBC__ +#if defined(__GLIBC__) && !defined(__UCLIBC__) uv_once(&glibc_version_check_once, glibc_version_check); #endif @@ -767,26 +825,9 @@ int uv_cond_timedwait(uv_cond_t* cond, uv_mutex_t* mutex, uint64_t timeout) { return UV_ETIMEDOUT; abort(); +#ifndef __SUNPRO_C return UV_EINVAL; /* Satisfy the compiler. */ -} - - -int uv_barrier_init(uv_barrier_t* barrier, unsigned int count) { - return UV__ERR(pthread_barrier_init(barrier, NULL, count)); -} - - -void uv_barrier_destroy(uv_barrier_t* barrier) { - if (pthread_barrier_destroy(barrier)) - abort(); -} - - -int uv_barrier_wait(uv_barrier_t* barrier) { - int r = pthread_barrier_wait(barrier); - if (r && r != PTHREAD_BARRIER_SERIAL_THREAD) - abort(); - return r == PTHREAD_BARRIER_SERIAL_THREAD; +#endif } diff --git a/src/unix/tty.c b/src/unix/tty.c index 74d3d75..6f60aba 100644 --- a/src/unix/tty.c +++ b/src/unix/tty.c @@ -34,6 +34,34 @@ #define IMAXBEL 0 #endif +#if defined(__PASE__) +/* On IBM i PASE, for better compatibility with running interactive programs in + * a 5250 environment, isatty() will return true for the stdin/stdout/stderr + * streams created by QSH/QP2TERM. + * + * For more, see docs on PASE_STDIO_ISATTY in + * https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_74/apis/pase_environ.htm + * + * This behavior causes problems for Node as it expects that if isatty() returns + * true that TTY ioctls will be supported by that fd (which is not an + * unreasonable expectation) and when they don't it crashes with assertion + * errors. + * + * Here, we create our own version of isatty() that uses ioctl() to identify + * whether the fd is *really* a TTY or not. + */ +static int isreallyatty(int file) { + int rc; + + rc = !ioctl(file, TXISATTY + 0x81, NULL); + if (!rc && errno != EBADF) + errno = ENOTTY; + + return rc; +} +#define isatty(fd) isreallyatty(fd) +#endif + static int orig_termios_fd = -1; static struct termios orig_termios; static uv_spinlock_t termios_spinlock = UV_SPINLOCK_INITIALIZER; @@ -139,7 +167,7 @@ int uv_tty_init(uv_loop_t* loop, uv_tty_t* tty, int fd, int unused) { * slave device. */ if (uv__tty_is_slave(fd) && ttyname_r(fd, path, sizeof(path)) == 0) - r = uv__open_cloexec(path, mode); + r = uv__open_cloexec(path, mode | O_NOCTTY); else r = -1; @@ -365,3 +393,10 @@ int uv_tty_reset_mode(void) { return err; } + +void uv_tty_set_vterm_state(uv_tty_vtermstate_t state) { +} + +int uv_tty_get_vterm_state(uv_tty_vtermstate_t* state) { + return UV_ENOTSUP; +} diff --git a/src/unix/udp.c b/src/unix/udp.c index e6668a0..7cf80ef 100644 --- a/src/unix/udp.c +++ b/src/unix/udp.c @@ -30,6 +30,9 @@ #if defined(__MVS__) #include #endif +#include + +#define UV__UDP_DGRAM_MAXSIZE (64 * 1024) #if defined(IPV6_JOIN_GROUP) && !defined(IPV6_ADD_MEMBERSHIP) # define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP @@ -39,6 +42,11 @@ # define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP #endif +union uv__sockaddr { + struct sockaddr_in6 in6; + struct sockaddr_in in; + struct sockaddr addr; +}; static void uv__udp_run_completed(uv_udp_t* handle); static void uv__udp_io(uv_loop_t* loop, uv__io_t* w, unsigned int revents); @@ -48,6 +56,36 @@ static int uv__udp_maybe_deferred_bind(uv_udp_t* handle, int domain, unsigned int flags); +#if HAVE_MMSG + +#define UV__MMSG_MAXWIDTH 20 + +static int uv__udp_recvmmsg(uv_udp_t* handle, uv_buf_t* buf); +static void uv__udp_sendmmsg(uv_udp_t* handle); + +static int uv__recvmmsg_avail; +static int uv__sendmmsg_avail; +static uv_once_t once = UV_ONCE_INIT; + +static void uv__udp_mmsg_init(void) { + int ret; + int s; + s = uv__socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) + return; + ret = uv__sendmmsg(s, NULL, 0, 0); + if (ret == 0 || errno != ENOSYS) { + uv__sendmmsg_avail = 1; + uv__recvmmsg_avail = 1; + } else { + ret = uv__recvmmsg(s, NULL, 0, 0, NULL); + if (ret == 0 || errno != ENOSYS) + uv__recvmmsg_avail = 1; + } + uv__close(s); +} + +#endif void uv__udp_close(uv_udp_t* handle) { uv__io_close(handle->loop, &handle->io_watcher); @@ -147,6 +185,64 @@ static void uv__udp_io(uv_loop_t* loop, uv__io_t* w, unsigned int revents) { } } +#if HAVE_MMSG +static int uv__udp_recvmmsg(uv_udp_t* handle, uv_buf_t* buf) { + struct sockaddr_in6 peers[UV__MMSG_MAXWIDTH]; + struct iovec iov[UV__MMSG_MAXWIDTH]; + struct uv__mmsghdr msgs[UV__MMSG_MAXWIDTH]; + ssize_t nread; + uv_buf_t chunk_buf; + size_t chunks; + int flags; + size_t k; + + /* prepare structures for recvmmsg */ + chunks = buf->len / UV__UDP_DGRAM_MAXSIZE; + if (chunks > ARRAY_SIZE(iov)) + chunks = ARRAY_SIZE(iov); + for (k = 0; k < chunks; ++k) { + iov[k].iov_base = buf->base + k * UV__UDP_DGRAM_MAXSIZE; + iov[k].iov_len = UV__UDP_DGRAM_MAXSIZE; + msgs[k].msg_hdr.msg_iov = iov + k; + msgs[k].msg_hdr.msg_iovlen = 1; + msgs[k].msg_hdr.msg_name = peers + k; + msgs[k].msg_hdr.msg_namelen = sizeof(peers[0]); + msgs[k].msg_hdr.msg_control = NULL; + msgs[k].msg_hdr.msg_controllen = 0; + msgs[k].msg_hdr.msg_flags = 0; + } + + do + nread = uv__recvmmsg(handle->io_watcher.fd, msgs, chunks, 0, NULL); + while (nread == -1 && errno == EINTR); + + 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; + + chunk_buf = uv_buf_init(iov[k].iov_base, iov[k].iov_len); + handle->recv_cb(handle, + msgs[k].msg_len, + &chunk_buf, + msgs[k].msg_hdr.msg_name, + flags); + } + + /* one last callback so the original buffer is freed */ + if (handle->recv_cb != NULL) + handle->recv_cb(handle, 0, buf, NULL, 0); + } + return nread; +} +#endif static void uv__udp_recvmsg(uv_udp_t* handle) { struct sockaddr_storage peer; @@ -164,18 +260,30 @@ static void uv__udp_recvmsg(uv_udp_t* handle) { */ count = 32; - memset(&h, 0, sizeof(h)); - h.msg_name = &peer; - do { buf = uv_buf_init(NULL, 0); - handle->alloc_cb((uv_handle_t*) handle, 64 * 1024, &buf); + handle->alloc_cb((uv_handle_t*) handle, UV__UDP_DGRAM_MAXSIZE, &buf); if (buf.base == NULL || buf.len == 0) { handle->recv_cb(handle, UV_ENOBUFS, &buf, NULL, 0); return; } assert(buf.base != NULL); +#if HAVE_MMSG + if (handle->flags & UV_HANDLE_UDP_RECVMMSG) { + uv_once(&once, uv__udp_mmsg_init); + if (uv__recvmmsg_avail) { + nread = uv__udp_recvmmsg(handle, &buf); + if (nread > 0) + count -= nread; + continue; + } + } +#endif + + memset(&h, 0, sizeof(h)); + memset(&peer, 0, sizeof(peer)); + h.msg_name = &peer; h.msg_namelen = sizeof(peer); h.msg_iov = (void*) &buf; h.msg_iovlen = 1; @@ -192,33 +300,126 @@ static void uv__udp_recvmsg(uv_udp_t* handle) { handle->recv_cb(handle, UV__ERR(errno), &buf, NULL, 0); } else { - const struct sockaddr *addr; - if (h.msg_namelen == 0) - addr = NULL; - else - addr = (const struct sockaddr*) &peer; - flags = 0; if (h.msg_flags & MSG_TRUNC) flags |= UV_UDP_PARTIAL; - handle->recv_cb(handle, nread, &buf, addr, flags); + handle->recv_cb(handle, nread, &buf, (const struct sockaddr*) &peer, flags); } + count--; } /* recv_cb callback may decide to pause or close the handle */ while (nread != -1 - && count-- > 0 + && count > 0 && handle->io_watcher.fd != -1 && handle->recv_cb != NULL); } +#if HAVE_MMSG +static void uv__udp_sendmmsg(uv_udp_t* handle) { + uv_udp_send_t* req; + struct uv__mmsghdr h[UV__MMSG_MAXWIDTH]; + struct uv__mmsghdr *p; + QUEUE* q; + ssize_t npkts; + size_t pkts; + size_t i; + + if (QUEUE_EMPTY(&handle->write_queue)) + return; + +write_queue_drain: + for (pkts = 0, q = QUEUE_HEAD(&handle->write_queue); + pkts < UV__MMSG_MAXWIDTH && q != &handle->write_queue; + ++pkts, q = QUEUE_HEAD(q)) { + assert(q != NULL); + req = QUEUE_DATA(q, uv_udp_send_t, queue); + assert(req != NULL); + + p = &h[pkts]; + memset(p, 0, sizeof(*p)); + if (req->addr.ss_family == AF_UNSPEC) { + p->msg_hdr.msg_name = NULL; + p->msg_hdr.msg_namelen = 0; + } else { + p->msg_hdr.msg_name = &req->addr; + if (req->addr.ss_family == AF_INET6) + p->msg_hdr.msg_namelen = sizeof(struct sockaddr_in6); + else if (req->addr.ss_family == AF_INET) + p->msg_hdr.msg_namelen = sizeof(struct sockaddr_in); + else if (req->addr.ss_family == AF_UNIX) + p->msg_hdr.msg_namelen = sizeof(struct sockaddr_un); + else { + assert(0 && "unsupported address family"); + abort(); + } + } + h[pkts].msg_hdr.msg_iov = (struct iovec*) req->bufs; + h[pkts].msg_hdr.msg_iovlen = req->nbufs; + } + + do + npkts = uv__sendmmsg(handle->io_watcher.fd, h, pkts, 0); + while (npkts == -1 && errno == EINTR); + + if (npkts < 1) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == ENOBUFS) + return; + for (i = 0, q = QUEUE_HEAD(&handle->write_queue); + i < pkts && q != &handle->write_queue; + ++i, q = QUEUE_HEAD(q)) { + assert(q != NULL); + req = QUEUE_DATA(q, uv_udp_send_t, queue); + assert(req != NULL); + + req->status = UV__ERR(errno); + QUEUE_REMOVE(&req->queue); + QUEUE_INSERT_TAIL(&handle->write_completed_queue, &req->queue); + } + uv__io_feed(handle->loop, &handle->io_watcher); + return; + } + + for (i = 0, q = QUEUE_HEAD(&handle->write_queue); + i < pkts && q != &handle->write_queue; + ++i, q = QUEUE_HEAD(&handle->write_queue)) { + assert(q != NULL); + req = QUEUE_DATA(q, uv_udp_send_t, queue); + assert(req != NULL); + + req->status = req->bufs[0].len; + + /* Sending a datagram is an atomic operation: either all data + * is written or nothing is (and EMSGSIZE is raised). That is + * why we don't handle partial writes. Just pop the request + * off the write queue and onto the completed queue, done. + */ + QUEUE_REMOVE(&req->queue); + QUEUE_INSERT_TAIL(&handle->write_completed_queue, &req->queue); + } + + /* couldn't batch everything, continue sending (jump to avoid stack growth) */ + if (!QUEUE_EMPTY(&handle->write_queue)) + goto write_queue_drain; + uv__io_feed(handle->loop, &handle->io_watcher); + return; +} +#endif static void uv__udp_sendmsg(uv_udp_t* handle) { uv_udp_send_t* req; - QUEUE* q; struct msghdr h; + QUEUE* q; ssize_t size; +#if HAVE_MMSG + uv_once(&once, uv__udp_mmsg_init); + if (uv__sendmmsg_avail) { + uv__udp_sendmmsg(handle); + return; + } +#endif + while (!QUEUE_EMPTY(&handle->write_queue)) { q = QUEUE_HEAD(&handle->write_queue); assert(q != NULL); @@ -227,9 +428,22 @@ static void uv__udp_sendmsg(uv_udp_t* handle) { assert(req != NULL); memset(&h, 0, sizeof h); - h.msg_name = &req->addr; - h.msg_namelen = (req->addr.ss_family == AF_INET6 ? - sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)); + if (req->addr.ss_family == AF_UNSPEC) { + h.msg_name = NULL; + h.msg_namelen = 0; + } else { + h.msg_name = &req->addr; + if (req->addr.ss_family == AF_INET6) + h.msg_namelen = sizeof(struct sockaddr_in6); + else if (req->addr.ss_family == AF_INET) + h.msg_namelen = sizeof(struct sockaddr_in); + else if (req->addr.ss_family == AF_UNIX) + h.msg_namelen = sizeof(struct sockaddr_un); + else { + assert(0 && "unsupported address family"); + abort(); + } + } h.msg_iov = (struct iovec*) req->bufs; h.msg_iovlen = req->nbufs; @@ -255,7 +469,6 @@ static void uv__udp_sendmsg(uv_udp_t* handle) { } } - /* On the BSDs, SO_REUSEPORT implies SO_REUSEADDR but with some additional * refinements for programs that use multicast. * @@ -263,16 +476,30 @@ static void uv__udp_sendmsg(uv_udp_t* handle) { * are different from the BSDs: it _shares_ the port rather than steal it * from the current listener. While useful, it's not something we can emulate * on other platforms so we don't enable it. + * + * zOS does not support getsockname with SO_REUSEPORT option when using + * AF_UNIX. */ static int uv__set_reuse(int fd) { int yes; - -#if defined(SO_REUSEPORT) && !defined(__linux__) yes = 1; + +#if defined(SO_REUSEPORT) && defined(__MVS__) + struct sockaddr_in sockfd; + unsigned int sockfd_len = sizeof(sockfd); + if (getsockname(fd, (struct sockaddr*) &sockfd, &sockfd_len) == -1) + return UV__ERR(errno); + if (sockfd.sin_family == AF_UNIX) { + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes))) + return UV__ERR(errno); + } else { + if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes))) + return UV__ERR(errno); + } +#elif defined(SO_REUSEPORT) && !defined(__linux__) if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes))) return UV__ERR(errno); #else - yes = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes))) return UV__ERR(errno); #endif @@ -345,11 +572,7 @@ int uv__udp_bind(uv_udp_t* handle, static int uv__udp_maybe_deferred_bind(uv_udp_t* handle, int domain, unsigned int flags) { - union { - struct sockaddr_in6 in6; - struct sockaddr_in in; - struct sockaddr addr; - } taddr; + union uv__sockaddr taddr; socklen_t addrlen; if (handle->io_watcher.fd != -1) @@ -383,6 +606,50 @@ static int uv__udp_maybe_deferred_bind(uv_udp_t* handle, } +int uv__udp_connect(uv_udp_t* handle, + const struct sockaddr* addr, + unsigned int addrlen) { + int err; + + err = uv__udp_maybe_deferred_bind(handle, addr->sa_family, 0); + if (err) + return err; + + do { + errno = 0; + err = connect(handle->io_watcher.fd, addr, addrlen); + } while (err == -1 && errno == EINTR); + + if (err) + return UV__ERR(errno); + + handle->flags |= UV_HANDLE_UDP_CONNECTED; + + return 0; +} + + +int uv__udp_disconnect(uv_udp_t* handle) { + int r; + struct sockaddr addr; + + memset(&addr, 0, sizeof(addr)); + + addr.sa_family = AF_UNSPEC; + + do { + errno = 0; + r = connect(handle->io_watcher.fd, &addr, sizeof(addr)); + } while (r == -1 && errno == EINTR); + + if (r == -1 && errno != EAFNOSUPPORT) + return UV__ERR(errno); + + handle->flags &= ~UV_HANDLE_UDP_CONNECTED; + return 0; +} + + int uv__udp_send(uv_udp_send_t* req, uv_udp_t* handle, const uv_buf_t bufs[], @@ -395,9 +662,11 @@ int uv__udp_send(uv_udp_send_t* req, assert(nbufs > 0); - err = uv__udp_maybe_deferred_bind(handle, addr->sa_family, 0); - if (err) - return err; + if (addr) { + err = uv__udp_maybe_deferred_bind(handle, addr->sa_family, 0); + if (err) + return err; + } /* It's legal for send_queue_count > 0 even when the write_queue is empty; * it means there are error-state requests in the write_completed_queue that @@ -407,7 +676,10 @@ int uv__udp_send(uv_udp_send_t* req, uv__req_init(handle->loop, req, UV_UDP_SEND); assert(addrlen <= sizeof(req->addr)); - memcpy(&req->addr, addr, addrlen); + if (addr == NULL) + req->addr.ss_family = AF_UNSPEC; + else + memcpy(&req->addr, addr, addrlen); req->send_cb = send_cb; req->handle = handle; req->nbufs = nbufs; @@ -459,9 +731,13 @@ int uv__udp_try_send(uv_udp_t* handle, if (handle->send_queue_count != 0) return UV_EAGAIN; - err = uv__udp_maybe_deferred_bind(handle, addr->sa_family, 0); - if (err) - return err; + if (addr) { + err = uv__udp_maybe_deferred_bind(handle, addr->sa_family, 0); + if (err) + return err; + } else { + assert(handle->flags & UV_HANDLE_UDP_CONNECTED); + } memset(&h, 0, sizeof h); h.msg_name = (struct sockaddr*) addr; @@ -578,26 +854,113 @@ static int uv__udp_set_membership6(uv_udp_t* handle, } -int uv_udp_init_ex(uv_loop_t* loop, uv_udp_t* handle, unsigned int flags) { - int domain; +#if !defined(__OpenBSD__) && !defined(__NetBSD__) && !defined(__ANDROID__) +static int uv__udp_set_source_membership4(uv_udp_t* handle, + const struct sockaddr_in* multicast_addr, + const char* interface_addr, + const struct sockaddr_in* source_addr, + uv_membership membership) { + struct ip_mreq_source mreq; + int optname; int err; - int fd; - /* Use the lower 8 bits for the domain */ - domain = flags & 0xFF; - if (domain != AF_INET && domain != AF_INET6 && domain != AF_UNSPEC) - return UV_EINVAL; + err = uv__udp_maybe_deferred_bind(handle, AF_INET, UV_UDP_REUSEADDR); + if (err) + return err; - if (flags & ~0xFF) + memset(&mreq, 0, sizeof(mreq)); + + if (interface_addr != NULL) { + err = uv_inet_pton(AF_INET, interface_addr, &mreq.imr_interface.s_addr); + if (err) + return err; + } else { + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + } + + mreq.imr_multiaddr.s_addr = multicast_addr->sin_addr.s_addr; + mreq.imr_sourceaddr.s_addr = source_addr->sin_addr.s_addr; + + if (membership == UV_JOIN_GROUP) + optname = IP_ADD_SOURCE_MEMBERSHIP; + else if (membership == UV_LEAVE_GROUP) + optname = IP_DROP_SOURCE_MEMBERSHIP; + else return UV_EINVAL; - if (domain != AF_UNSPEC) { - err = uv__socket(domain, SOCK_DGRAM, 0); - if (err < 0) + if (setsockopt(handle->io_watcher.fd, + IPPROTO_IP, + optname, + &mreq, + sizeof(mreq))) { + return UV__ERR(errno); + } + + return 0; +} + + +static int uv__udp_set_source_membership6(uv_udp_t* handle, + const struct sockaddr_in6* multicast_addr, + const char* interface_addr, + const struct sockaddr_in6* source_addr, + uv_membership membership) { + struct group_source_req mreq; + struct sockaddr_in6 addr6; + int optname; + int err; + + err = uv__udp_maybe_deferred_bind(handle, AF_INET6, UV_UDP_REUSEADDR); + if (err) + return err; + + memset(&mreq, 0, sizeof(mreq)); + + if (interface_addr != NULL) { + err = uv_ip6_addr(interface_addr, 0, &addr6); + if (err) return err; - fd = err; + mreq.gsr_interface = addr6.sin6_scope_id; } else { - fd = -1; + mreq.gsr_interface = 0; + } + + STATIC_ASSERT(sizeof(mreq.gsr_group) >= sizeof(*multicast_addr)); + STATIC_ASSERT(sizeof(mreq.gsr_source) >= sizeof(*source_addr)); + memcpy(&mreq.gsr_group, multicast_addr, sizeof(*multicast_addr)); + memcpy(&mreq.gsr_source, source_addr, sizeof(*source_addr)); + + if (membership == UV_JOIN_GROUP) + optname = MCAST_JOIN_SOURCE_GROUP; + else if (membership == UV_LEAVE_GROUP) + optname = MCAST_LEAVE_SOURCE_GROUP; + else + return UV_EINVAL; + + if (setsockopt(handle->io_watcher.fd, + IPPROTO_IPV6, + optname, + &mreq, + sizeof(mreq))) { + return UV__ERR(errno); + } + + return 0; +} +#endif + + +int uv__udp_init_ex(uv_loop_t* loop, + uv_udp_t* handle, + unsigned flags, + int domain) { + int fd; + + fd = -1; + if (domain != AF_UNSPEC) { + fd = uv__socket(domain, SOCK_DGRAM, 0); + if (fd < 0) + return fd; } uv__handle_init(loop, (uv_handle_t*)handle, UV_UDP); @@ -608,12 +971,8 @@ int uv_udp_init_ex(uv_loop_t* loop, uv_udp_t* handle, unsigned int flags) { uv__io_init(&handle->io_watcher, uv__udp_io, fd); QUEUE_INIT(&handle->write_queue); QUEUE_INIT(&handle->write_completed_queue); - return 0; -} - -int uv_udp_init(uv_loop_t* loop, uv_udp_t* handle) { - return uv_udp_init_ex(loop, handle, AF_UNSPEC); + return 0; } @@ -636,6 +995,9 @@ int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock) { return err; handle->io_watcher.fd = sock; + if (uv__udp_is_connected(handle)) + handle->flags |= UV_HANDLE_UDP_CONNECTED; + return 0; } @@ -663,11 +1025,51 @@ int uv_udp_set_membership(uv_udp_t* handle, } } + +int uv_udp_set_source_membership(uv_udp_t* handle, + const char* multicast_addr, + const char* interface_addr, + const char* source_addr, + uv_membership membership) { +#if !defined(__OpenBSD__) && !defined(__NetBSD__) && !defined(__ANDROID__) + int err; + union uv__sockaddr mcast_addr; + union uv__sockaddr src_addr; + + err = uv_ip4_addr(multicast_addr, 0, &mcast_addr.in); + if (err) { + err = uv_ip6_addr(multicast_addr, 0, &mcast_addr.in6); + if (err) + return err; + err = uv_ip6_addr(source_addr, 0, &src_addr.in6); + if (err) + return err; + return uv__udp_set_source_membership6(handle, + &mcast_addr.in6, + interface_addr, + &src_addr.in6, + membership); + } + + err = uv_ip4_addr(source_addr, 0, &src_addr.in); + if (err) + return err; + return uv__udp_set_source_membership4(handle, + &mcast_addr.in, + interface_addr, + &src_addr.in, + membership); +#else + return UV_ENOSYS; +#endif +} + + static int uv__setsockopt(uv_udp_t* handle, int option4, int option6, const void* val, - size_t size) { + socklen_t size) { int r; if (handle->flags & UV_HANDLE_IPV6) @@ -743,13 +1145,17 @@ int uv_udp_set_ttl(uv_udp_t* handle, int ttl) { IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); -#endif /* defined(__sun) || defined(_AIX) || defined (__OpenBSD__) || - defined(__MVS__) */ + +#else /* !(defined(__sun) || defined(_AIX) || defined (__OpenBSD__) || + defined(__MVS__)) */ return uv__setsockopt_maybe_char(handle, IP_TTL, IPV6_UNICAST_HOPS, ttl); + +#endif /* defined(__sun) || defined(_AIX) || defined (__OpenBSD__) || + defined(__MVS__) */ } @@ -760,14 +1166,16 @@ int uv_udp_set_multicast_ttl(uv_udp_t* handle, int ttl) { * IP_MULTICAST_TTL, so hardcode the size of the option in the IPv6 case, * and use the general uv__setsockopt_maybe_char call otherwise. */ -#if defined(__sun) || defined(_AIX) || defined(__MVS__) +#if defined(__sun) || defined(_AIX) || defined(__OpenBSD__) || \ + defined(__MVS__) if (handle->flags & UV_HANDLE_IPV6) return uv__setsockopt(handle, IP_MULTICAST_TTL, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)); -#endif /* defined(__sun) || defined(_AIX) || defined(__MVS__) */ +#endif /* defined(__sun) || defined(_AIX) || defined(__OpenBSD__) || \ + defined(__MVS__) */ return uv__setsockopt_maybe_char(handle, IP_MULTICAST_TTL, @@ -783,14 +1191,16 @@ int uv_udp_set_multicast_loop(uv_udp_t* handle, int on) { * IP_MULTICAST_LOOP, so hardcode the size of the option in the IPv6 case, * and use the general uv__setsockopt_maybe_char call otherwise. */ -#if defined(__sun) || defined(_AIX) || defined(__MVS__) +#if defined(__sun) || defined(_AIX) || defined(__OpenBSD__) || \ + defined(__MVS__) if (handle->flags & UV_HANDLE_IPV6) return uv__setsockopt(handle, IP_MULTICAST_LOOP, IPV6_MULTICAST_LOOP, &on, sizeof(on)); -#endif /* defined(__sun) || defined(_AIX) || defined(__MVS__) */ +#endif /* defined(__sun) || defined(_AIX) ||defined(__OpenBSD__) || + defined(__MVS__) */ return uv__setsockopt_maybe_char(handle, IP_MULTICAST_LOOP, @@ -847,23 +1257,24 @@ int uv_udp_set_multicast_interface(uv_udp_t* handle, const char* interface_addr) return 0; } - -int uv_udp_getsockname(const uv_udp_t* handle, +int uv_udp_getpeername(const uv_udp_t* handle, struct sockaddr* name, int* namelen) { - socklen_t socklen; - if (handle->io_watcher.fd == -1) - return UV_EINVAL; /* FIXME(bnoordhuis) UV_EBADF */ - - /* sizeof(socklen_t) != sizeof(int) on some systems. */ - socklen = (socklen_t) *namelen; + return uv__getsockpeername((const uv_handle_t*) handle, + getpeername, + name, + namelen); +} - if (getsockname(handle->io_watcher.fd, name, &socklen)) - return UV__ERR(errno); +int uv_udp_getsockname(const uv_udp_t* handle, + struct sockaddr* name, + int* namelen) { - *namelen = (int) socklen; - return 0; + return uv__getsockpeername((const uv_handle_t*) handle, + getsockname, + name, + namelen); } diff --git a/src/uv-common.c b/src/uv-common.c index f0aec45..a25d6aa 100644 --- a/src/uv-common.c +++ b/src/uv-common.c @@ -34,6 +34,7 @@ # include /* malloc */ #else # include /* if_nametoindex */ +# include /* AF_UNIX, sockaddr_un */ #endif @@ -72,7 +73,9 @@ char* uv__strndup(const char* s, size_t n) { } void* uv__malloc(size_t size) { - return uv__allocator.local_malloc(size); + if (size > 0) + return uv__allocator.local_malloc(size); + return NULL; } void uv__free(void* ptr) { @@ -91,7 +94,21 @@ void* uv__calloc(size_t count, size_t size) { } void* uv__realloc(void* ptr, size_t size) { - return uv__allocator.local_realloc(ptr, size); + if (size > 0) + return uv__allocator.local_realloc(ptr, size); + uv__free(ptr); + return NULL; +} + +void* uv__reallocf(void* ptr, size_t size) { + void* newptr; + + newptr = uv__realloc(ptr, size); + if (newptr == NULL) + if (size > 0) + uv__free(ptr); + + return newptr; } int uv_replace_allocator(uv_malloc_func malloc_func, @@ -157,7 +174,7 @@ static const char* uv__unknown_err_code(int err) { #define UV_ERR_NAME_GEN_R(name, _) \ case UV_## name: \ - snprintf(buf, buflen, "%s", #name); break; + uv__strscpy(buf, #name, buflen); break; char* uv_err_name_r(int err, char* buf, size_t buflen) { switch (err) { UV_ERRNO_MAP(UV_ERR_NAME_GEN_R) @@ -205,6 +222,9 @@ int uv_ip4_addr(const char* ip, int port, struct sockaddr_in* addr) { memset(addr, 0, sizeof(*addr)); addr->sin_family = AF_INET; addr->sin_port = htons(port); +#ifdef SIN6_LEN + addr->sin_len = sizeof(*addr); +#endif return uv_inet_pton(AF_INET, ip, &(addr->sin_addr.s_addr)); } @@ -217,6 +237,9 @@ int uv_ip6_addr(const char* ip, int port, struct sockaddr_in6* addr) { memset(addr, 0, sizeof(*addr)); addr->sin6_family = AF_INET6; addr->sin6_port = htons(port); +#ifdef SIN6_LEN + addr->sin6_len = sizeof(*addr); +#endif zone_index = strchr(ip, '%'); if (zone_index != NULL) { @@ -270,6 +293,36 @@ int uv_tcp_bind(uv_tcp_t* handle, } +int uv_udp_init_ex(uv_loop_t* loop, uv_udp_t* handle, unsigned flags) { + unsigned extra_flags; + int domain; + int rc; + + /* Use the lower 8 bits for the domain. */ + domain = flags & 0xFF; + if (domain != AF_INET && domain != AF_INET6 && domain != AF_UNSPEC) + return UV_EINVAL; + + /* Use the higher bits for extra flags. */ + extra_flags = flags & ~0xFF; + if (extra_flags & ~UV_UDP_RECVMMSG) + return UV_EINVAL; + + rc = uv__udp_init_ex(loop, handle, flags, domain); + + if (rc == 0) + if (extra_flags & UV_UDP_RECVMMSG) + handle->flags |= UV_HANDLE_UDP_RECVMMSG; + + return rc; +} + + +int uv_udp_init(uv_loop_t* loop, uv_udp_t* handle) { + return uv_udp_init_ex(loop, handle, AF_UNSPEC); +} + + int uv_udp_bind(uv_udp_t* handle, const struct sockaddr* addr, unsigned int flags) { @@ -309,17 +362,20 @@ int uv_tcp_connect(uv_connect_t* req, } -int uv_udp_send(uv_udp_send_t* req, - uv_udp_t* handle, - const uv_buf_t bufs[], - unsigned int nbufs, - const struct sockaddr* addr, - uv_udp_send_cb send_cb) { +int uv_udp_connect(uv_udp_t* handle, const struct sockaddr* addr) { unsigned int addrlen; if (handle->type != UV_UDP) return UV_EINVAL; + /* Disconnect the handle */ + if (addr == NULL) { + if (!(handle->flags & UV_HANDLE_UDP_CONNECTED)) + return UV_ENOTCONN; + + return uv__udp_disconnect(handle); + } + if (addr->sa_family == AF_INET) addrlen = sizeof(struct sockaddr_in); else if (addr->sa_family == AF_INET6) @@ -327,6 +383,70 @@ int uv_udp_send(uv_udp_send_t* req, else return UV_EINVAL; + if (handle->flags & UV_HANDLE_UDP_CONNECTED) + return UV_EISCONN; + + return uv__udp_connect(handle, addr, addrlen); +} + + +int uv__udp_is_connected(uv_udp_t* handle) { + struct sockaddr_storage addr; + int addrlen; + if (handle->type != UV_UDP) + return 0; + + addrlen = sizeof(addr); + if (uv_udp_getpeername(handle, (struct sockaddr*) &addr, &addrlen) != 0) + return 0; + + return addrlen > 0; +} + + +int uv__udp_check_before_send(uv_udp_t* handle, const struct sockaddr* addr) { + unsigned int addrlen; + + if (handle->type != UV_UDP) + return UV_EINVAL; + + if (addr != NULL && (handle->flags & UV_HANDLE_UDP_CONNECTED)) + return UV_EISCONN; + + if (addr == NULL && !(handle->flags & UV_HANDLE_UDP_CONNECTED)) + return UV_EDESTADDRREQ; + + if (addr != NULL) { + if (addr->sa_family == AF_INET) + addrlen = sizeof(struct sockaddr_in); + else if (addr->sa_family == AF_INET6) + addrlen = sizeof(struct sockaddr_in6); +#if defined(AF_UNIX) && !defined(_WIN32) + else if (addr->sa_family == AF_UNIX) + addrlen = sizeof(struct sockaddr_un); +#endif + else + return UV_EINVAL; + } else { + addrlen = 0; + } + + return addrlen; +} + + +int uv_udp_send(uv_udp_send_t* req, + uv_udp_t* handle, + const uv_buf_t bufs[], + unsigned int nbufs, + const struct sockaddr* addr, + uv_udp_send_cb send_cb) { + int addrlen; + + addrlen = uv__udp_check_before_send(handle, addr); + if (addrlen < 0) + return addrlen; + return uv__udp_send(req, handle, bufs, nbufs, addr, addrlen, send_cb); } @@ -335,17 +455,11 @@ int uv_udp_try_send(uv_udp_t* handle, const uv_buf_t bufs[], unsigned int nbufs, const struct sockaddr* addr) { - unsigned int addrlen; + int addrlen; - if (handle->type != UV_UDP) - return UV_EINVAL; - - if (addr->sa_family == AF_INET) - addrlen = sizeof(struct sockaddr_in); - else if (addr->sa_family == AF_INET6) - addrlen = sizeof(struct sockaddr_in6); - else - return UV_EINVAL; + addrlen = uv__udp_check_before_send(handle, addr); + if (addrlen < 0) + return addrlen; return uv__udp_try_send(handle, bufs, nbufs, addr, addrlen); } @@ -566,37 +680,66 @@ int uv_fs_scandir_next(uv_fs_t* req, uv_dirent_t* ent) { dent = dents[(*nbufs)++]; ent->name = dent->d_name; + ent->type = uv__fs_get_dirent_type(dent); + + return 0; +} + +uv_dirent_type_t uv__fs_get_dirent_type(uv__dirent_t* dent) { + uv_dirent_type_t type; + #ifdef HAVE_DIRENT_TYPES switch (dent->d_type) { case UV__DT_DIR: - ent->type = UV_DIRENT_DIR; + type = UV_DIRENT_DIR; break; case UV__DT_FILE: - ent->type = UV_DIRENT_FILE; + type = UV_DIRENT_FILE; break; case UV__DT_LINK: - ent->type = UV_DIRENT_LINK; + type = UV_DIRENT_LINK; break; case UV__DT_FIFO: - ent->type = UV_DIRENT_FIFO; + type = UV_DIRENT_FIFO; break; case UV__DT_SOCKET: - ent->type = UV_DIRENT_SOCKET; + type = UV_DIRENT_SOCKET; break; case UV__DT_CHAR: - ent->type = UV_DIRENT_CHAR; + type = UV_DIRENT_CHAR; break; case UV__DT_BLOCK: - ent->type = UV_DIRENT_BLOCK; + type = UV_DIRENT_BLOCK; break; default: - ent->type = UV_DIRENT_UNKNOWN; + type = UV_DIRENT_UNKNOWN; } #else - ent->type = UV_DIRENT_UNKNOWN; + type = UV_DIRENT_UNKNOWN; #endif - return 0; + return type; +} + +void uv__fs_readdir_cleanup(uv_fs_t* req) { + uv_dir_t* dir; + uv_dirent_t* dirents; + int i; + + if (req->ptr == NULL) + return; + + dir = req->ptr; + dirents = dir->dirents; + req->ptr = NULL; + + if (dirents == NULL) + return; + + for (i = 0; i < req->result; ++i) { + uv__free((char*) dirents[i].name); + dirents[i].name = NULL; + } } @@ -687,3 +830,40 @@ void uv_loop_delete(uv_loop_t* loop) { if (loop != default_loop) uv__free(loop); } + + +void uv_os_free_environ(uv_env_item_t* envitems, int count) { + int i; + + for (i = 0; i < count; i++) { + uv__free(envitems[i].name); + } + + uv__free(envitems); +} + + +void uv_free_cpu_info(uv_cpu_info_t* cpu_infos, int count) { + int i; + + for (i = 0; i < count; i++) + uv__free(cpu_infos[i].model); + + uv__free(cpu_infos); +} + + +#ifdef __GNUC__ /* Also covers __clang__ and __INTEL_COMPILER. */ +__attribute__((destructor)) +#endif +void uv_library_shutdown(void) { + static int was_shutdown; + + if (was_shutdown) + return; + + uv__process_title_cleanup(); + uv__signal_cleanup(); + uv__threadpool_cleanup(); + was_shutdown = 1; +} diff --git a/src/uv-common.h b/src/uv-common.h index 5555f83..0b0f5f8 100644 --- a/src/uv-common.h +++ b/src/uv-common.h @@ -40,6 +40,7 @@ #include "uv.h" #include "uv/tree.h" #include "queue.h" +#include "strscpy.h" #if EDOM > 0 # define UV__ERR(x) (-(x)) @@ -102,6 +103,8 @@ enum { /* Only used by uv_udp_t handles. */ UV_HANDLE_UDP_PROCESSING = 0x01000000, + UV_HANDLE_UDP_CONNECTED = 0x02000000, + UV_HANDLE_UDP_RECVMMSG = 0x04000000, /* Only used by uv_pipe_t handles. */ UV_HANDLE_NON_OVERLAPPED_PIPE = 0x01000000, @@ -136,11 +139,24 @@ int uv__tcp_connect(uv_connect_t* req, unsigned int addrlen, uv_connect_cb cb); +int uv__udp_init_ex(uv_loop_t* loop, + uv_udp_t* handle, + unsigned flags, + int domain); + int uv__udp_bind(uv_udp_t* handle, const struct sockaddr* addr, unsigned int addrlen, unsigned int flags); +int uv__udp_connect(uv_udp_t* handle, + const struct sockaddr* addr, + unsigned int addrlen); + +int uv__udp_disconnect(uv_udp_t* handle); + +int uv__udp_is_connected(uv_udp_t* handle); + int uv__udp_send(uv_udp_send_t* req, uv_udp_t* handle, const uv_buf_t bufs[], @@ -183,11 +199,17 @@ size_t uv__count_bufs(const uv_buf_t bufs[], unsigned int nbufs); int uv__socket_sockopt(uv_handle_t* handle, int optname, int* value); void uv__fs_scandir_cleanup(uv_fs_t* req); +void uv__fs_readdir_cleanup(uv_fs_t* req); +uv_dirent_type_t uv__fs_get_dirent_type(uv__dirent_t* dent); int uv__next_timeout(const uv_loop_t* loop); void uv__run_timers(uv_loop_t* loop); void uv__timer_close(uv_timer_t* handle); +void uv__process_title_cleanup(void); +void uv__signal_cleanup(void); +void uv__threadpool_cleanup(void); + #define uv__has_active_reqs(loop) \ ((loop)->active_reqs.count > 0) @@ -310,5 +332,6 @@ char *uv__strndup(const char* s, size_t n); void* uv__malloc(size_t size); void uv__free(void* ptr); void* uv__realloc(void* ptr, size_t size); +void* uv__reallocf(void* ptr, size_t size); #endif /* UV_COMMON_H_ */ diff --git a/src/uv-data-getter-setters.c b/src/uv-data-getter-setters.c index 533e4a2..c302566 100644 --- a/src/uv-data-getter-setters.c +++ b/src/uv-data-getter-setters.c @@ -3,11 +3,11 @@ const char* uv_handle_type_name(uv_handle_type type) { switch (type) { #define XX(uc,lc) case UV_##uc: return #lc; - UV_HANDLE_TYPE_MAP(XX) + UV_HANDLE_TYPE_MAP(XX) #undef XX - case UV_FILE: return "file"; - case UV_HANDLE_TYPE_MAX: - case UV_UNKNOWN_HANDLE: return NULL; + case UV_FILE: return "file"; + case UV_HANDLE_TYPE_MAX: + case UV_UNKNOWN_HANDLE: return NULL; } return NULL; } @@ -31,10 +31,12 @@ void uv_handle_set_data(uv_handle_t* handle, void* data) { const char* uv_req_type_name(uv_req_type type) { switch (type) { #define XX(uc,lc) case UV_##uc: return #lc; - UV_REQ_TYPE_MAP(XX) + UV_REQ_TYPE_MAP(XX) #undef XX - case UV_REQ_TYPE_MAX: - case UV_UNKNOWN_REQ: return NULL; + case UV_REQ_TYPE_MAX: + case UV_UNKNOWN_REQ: + default: /* UV_REQ_TYPE_PRIVATE */ + break; } return NULL; } diff --git a/src/win/core.c b/src/win/core.c index bf80d77..9974a11 100644 --- a/src/win/core.c +++ b/src/win/core.c @@ -321,8 +321,13 @@ void uv__loop_close(uv_loop_t* loop) { uv__loops_remove(loop); - /* close the async handle without needing an extra loop iteration */ - assert(!loop->wq_async.async_sent); + /* 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); @@ -444,12 +449,12 @@ static void uv__poll(uv_loop_t* loop, DWORD timeout) { timeout_time = loop->time + timeout; for (repeat = 0; ; repeat++) { - success = GetQueuedCompletionStatusEx(loop->iocp, - overlappeds, - ARRAY_SIZE(overlappeds), - &count, - timeout, - FALSE); + success = pGetQueuedCompletionStatusEx(loop->iocp, + overlappeds, + ARRAY_SIZE(overlappeds), + &count, + timeout, + FALSE); if (success) { for (i = 0; i < count; i++) { @@ -623,3 +628,30 @@ int uv__socket_sockopt(uv_handle_t* handle, int optname, int* value) { return 0; } + +int uv_cpumask_size(void) { + return (int)(sizeof(DWORD_PTR) * 8); +} + +int uv__getsockpeername(const uv_handle_t* handle, + uv__peersockfunc func, + struct sockaddr* name, + int* namelen, + int delayed_error) { + + int result; + uv_os_fd_t fd; + + result = uv_fileno(handle, &fd); + if (result != 0) + return result; + + if (delayed_error) + return uv_translate_sys_error(delayed_error); + + result = func((SOCKET) fd, name, namelen); + if (result != 0) + return uv_translate_sys_error(WSAGetLastError()); + + return 0; +} diff --git a/src/win/dl.c b/src/win/dl.c index 97ac1c1..676be4d 100644 --- a/src/win/dl.c +++ b/src/win/dl.c @@ -64,7 +64,8 @@ void uv_dlclose(uv_lib_t* lib) { int uv_dlsym(uv_lib_t* lib, const char* name, void** ptr) { - *ptr = (void*) GetProcAddress(lib->handle, name); + /* Cast though integer to suppress pedantic warning about forbidden cast. */ + *ptr = (void*)(uintptr_t) GetProcAddress(lib->handle, name); return uv__dlerror(lib, "", *ptr ? 0 : GetLastError()); } @@ -75,8 +76,9 @@ const char* uv_dlerror(const uv_lib_t* lib) { static void uv__format_fallback_error(uv_lib_t* lib, int errorno){ - DWORD_PTR args[1] = { (DWORD_PTR) errorno }; - LPSTR fallback_error = "error: %1!d!"; + static const CHAR fallback_error[] = "error: %1!d!"; + DWORD_PTR args[1]; + args[0] = (DWORD_PTR) errorno; FormatMessageA(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY | @@ -107,7 +109,8 @@ static int uv__dlerror(uv_lib_t* lib, const char* filename, DWORD errorno) { MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), (LPSTR) &lib->errmsg, 0, NULL); - if (!res && GetLastError() == ERROR_MUI_FILE_NOT_FOUND) { + if (!res && (GetLastError() == ERROR_MUI_FILE_NOT_FOUND || + GetLastError() == ERROR_RESOURCE_TYPE_NOT_FOUND)) { res = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorno, diff --git a/src/win/error.c b/src/win/error.c index 24924ba..3ec984c 100644 --- a/src/win/error.c +++ b/src/win/error.c @@ -72,6 +72,7 @@ int uv_translate_sys_error(int sys_errno) { case ERROR_NOACCESS: return UV_EACCES; case WSAEACCES: return UV_EACCES; case ERROR_ELEVATION_REQUIRED: return UV_EACCES; + case ERROR_CANT_ACCESS_FILE: return UV_EACCES; case ERROR_ADDRESS_ALREADY_ASSOCIATED: return UV_EADDRINUSE; case WSAEADDRINUSE: return UV_EADDRINUSE; case WSAEADDRNOTAVAIL: return UV_EADDRNOTAVAIL; @@ -132,6 +133,7 @@ int uv_translate_sys_error(int sys_errno) { case WSAENOBUFS: return UV_ENOBUFS; case ERROR_BAD_PATHNAME: return UV_ENOENT; case ERROR_DIRECTORY: return UV_ENOENT; + case ERROR_ENVVAR_NOT_FOUND: return UV_ENOENT; case ERROR_FILE_NOT_FOUND: return UV_ENOENT; case ERROR_INVALID_NAME: return UV_ENOENT; case ERROR_INVALID_DRIVE: return UV_ENOENT; diff --git a/src/win/fs-event.c b/src/win/fs-event.c index 226e6e7..0126c5e 100644 --- a/src/win/fs-event.c +++ b/src/win/fs-event.c @@ -83,7 +83,8 @@ static void uv_relative_path(const WCHAR* filename, static int uv_split_path(const WCHAR* filename, WCHAR** dir, WCHAR** file) { size_t len, i; - + DWORD dir_len; + if (filename == NULL) { if (dir != NULL) *dir = NULL; @@ -97,12 +98,16 @@ static int uv_split_path(const WCHAR* filename, WCHAR** dir, if (i == 0) { if (dir) { - *dir = (WCHAR*)uv__malloc((MAX_PATH + 1) * sizeof(WCHAR)); + dir_len = GetCurrentDirectoryW(0, NULL); + if (dir_len == 0) { + return -1; + } + *dir = (WCHAR*)uv__malloc(dir_len * sizeof(WCHAR)); if (!*dir) { uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc"); } - if (!GetCurrentDirectoryW(MAX_PATH, *dir)) { + if (!GetCurrentDirectoryW(dir_len, *dir)) { uv__free(*dir); *dir = NULL; return -1; @@ -155,9 +160,11 @@ int uv_fs_event_start(uv_fs_event_t* handle, int name_size, is_path_dir, size; DWORD attr, last_error; WCHAR* dir = NULL, *dir_to_watch, *pathw = NULL; - WCHAR short_path_buffer[MAX_PATH]; + DWORD short_path_buffer_len; + WCHAR *short_path_buffer; WCHAR* short_path, *long_path; + short_path = NULL; if (uv__is_active(handle)) return UV_EINVAL; @@ -215,11 +222,11 @@ int uv_fs_event_start(uv_fs_event_t* handle, uv__free(long_path); long_path = NULL; } - } - if (long_path) { - uv__free(pathw); - pathw = long_path; + if (long_path) { + uv__free(pathw); + pathw = long_path; + } } dir_to_watch = pathw; @@ -230,10 +237,23 @@ int uv_fs_event_start(uv_fs_event_t* handle, */ /* Convert to short path. */ - short_path = short_path_buffer; - if (!GetShortPathNameW(pathw, short_path, ARRAY_SIZE(short_path))) { - short_path = NULL; + short_path_buffer = NULL; + short_path_buffer_len = GetShortPathNameW(pathw, NULL, 0); + if (short_path_buffer_len == 0) { + goto short_path_done; + } + short_path_buffer = uv__malloc(short_path_buffer_len * sizeof(WCHAR)); + if (short_path_buffer == NULL) { + goto short_path_done; + } + if (GetShortPathNameW(pathw, + short_path_buffer, + short_path_buffer_len) == 0) { + uv__free(short_path_buffer); + short_path_buffer = NULL; } +short_path_done: + short_path = short_path_buffer; if (uv_split_path(pathw, &dir, &handle->filew) != 0) { last_error = GetLastError(); @@ -343,6 +363,8 @@ error: if (uv__is_active(handle)) uv__handle_stop(handle); + uv__free(short_path); + return uv_translate_sys_error(last_error); } diff --git a/src/win/fs-fd-hash-inl.h b/src/win/fs-fd-hash-inl.h new file mode 100644 index 0000000..7a203d2 --- /dev/null +++ b/src/win/fs-fd-hash-inl.h @@ -0,0 +1,178 @@ +#ifndef UV_WIN_FS_FD_HASH_INL_H_ +#define UV_WIN_FS_FD_HASH_INL_H_ + +#include "uv.h" +#include "internal.h" + +/* Files are only inserted in uv__fd_hash when the UV_FS_O_FILEMAP flag is + * specified. Thus, when uv__fd_hash_get returns true, the file mapping in the + * info structure should be used for read/write operations. + * + * If the file is empty, the mapping field will be set to + * INVALID_HANDLE_VALUE. This is not an issue since the file mapping needs to + * be created anyway when the file size changes. + * + * Since file descriptors are sequential integers, the modulo operator is used + * as hashing function. For each bucket, a single linked list of arrays is + * kept to minimize allocations. A statically allocated memory buffer is kept + * for the first array in each bucket. */ + + +#define UV__FD_HASH_SIZE 256 +#define UV__FD_HASH_GROUP_SIZE 16 + +struct uv__fd_info_s { + int flags; + BOOLEAN is_directory; + HANDLE mapping; + LARGE_INTEGER size; + LARGE_INTEGER current_pos; +}; + +struct uv__fd_hash_entry_s { + uv_file fd; + struct uv__fd_info_s info; +}; + +struct uv__fd_hash_entry_group_s { + struct uv__fd_hash_entry_s entries[UV__FD_HASH_GROUP_SIZE]; + struct uv__fd_hash_entry_group_s* next; +}; + +struct uv__fd_hash_bucket_s { + size_t size; + struct uv__fd_hash_entry_group_s* data; +}; + + +static uv_mutex_t uv__fd_hash_mutex; + +static struct uv__fd_hash_entry_group_s + uv__fd_hash_entry_initial[UV__FD_HASH_SIZE * UV__FD_HASH_GROUP_SIZE]; +static struct uv__fd_hash_bucket_s uv__fd_hash[UV__FD_HASH_SIZE]; + + +INLINE static void uv__fd_hash_init(void) { + int i, err; + + err = uv_mutex_init(&uv__fd_hash_mutex); + if (err) { + uv_fatal_error(err, "uv_mutex_init"); + } + + for (i = 0; i < ARRAY_SIZE(uv__fd_hash); ++i) { + uv__fd_hash[i].size = 0; + uv__fd_hash[i].data = + uv__fd_hash_entry_initial + i * UV__FD_HASH_GROUP_SIZE; + } +} + +#define FIND_COMMON_VARIABLES \ + unsigned i; \ + unsigned bucket = fd % ARRAY_SIZE(uv__fd_hash); \ + struct uv__fd_hash_entry_s* entry_ptr = NULL; \ + struct uv__fd_hash_entry_group_s* group_ptr; \ + struct uv__fd_hash_bucket_s* bucket_ptr = &uv__fd_hash[bucket]; + +#define FIND_IN_GROUP_PTR(group_size) \ + do { \ + for (i = 0; i < group_size; ++i) { \ + if (group_ptr->entries[i].fd == fd) { \ + entry_ptr = &group_ptr->entries[i]; \ + break; \ + } \ + } \ + } while (0) + +#define FIND_IN_BUCKET_PTR() \ + do { \ + size_t first_group_size = bucket_ptr->size % UV__FD_HASH_GROUP_SIZE; \ + if (bucket_ptr->size != 0 && first_group_size == 0) \ + first_group_size = UV__FD_HASH_GROUP_SIZE; \ + group_ptr = bucket_ptr->data; \ + FIND_IN_GROUP_PTR(first_group_size); \ + for (group_ptr = group_ptr->next; \ + group_ptr != NULL && entry_ptr == NULL; \ + group_ptr = group_ptr->next) \ + FIND_IN_GROUP_PTR(UV__FD_HASH_GROUP_SIZE); \ + } while (0) + +INLINE static int uv__fd_hash_get(int fd, struct uv__fd_info_s* info) { + FIND_COMMON_VARIABLES + + uv_mutex_lock(&uv__fd_hash_mutex); + + FIND_IN_BUCKET_PTR(); + + if (entry_ptr != NULL) { + *info = entry_ptr->info; + } + + uv_mutex_unlock(&uv__fd_hash_mutex); + return entry_ptr != NULL; +} + +INLINE static void uv__fd_hash_add(int fd, struct uv__fd_info_s* info) { + FIND_COMMON_VARIABLES + + uv_mutex_lock(&uv__fd_hash_mutex); + + FIND_IN_BUCKET_PTR(); + + if (entry_ptr == NULL) { + i = bucket_ptr->size % UV__FD_HASH_GROUP_SIZE; + + if (bucket_ptr->size != 0 && i == 0) { + struct uv__fd_hash_entry_group_s* new_group_ptr = + uv__malloc(sizeof(*new_group_ptr)); + if (new_group_ptr == NULL) { + uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc"); + } + new_group_ptr->next = bucket_ptr->data; + bucket_ptr->data = new_group_ptr; + } + + bucket_ptr->size += 1; + entry_ptr = &bucket_ptr->data->entries[i]; + entry_ptr->fd = fd; + } + + entry_ptr->info = *info; + + uv_mutex_unlock(&uv__fd_hash_mutex); +} + +INLINE static int uv__fd_hash_remove(int fd, struct uv__fd_info_s* info) { + FIND_COMMON_VARIABLES + + uv_mutex_lock(&uv__fd_hash_mutex); + + FIND_IN_BUCKET_PTR(); + + if (entry_ptr != NULL) { + *info = entry_ptr->info; + + bucket_ptr->size -= 1; + + i = bucket_ptr->size % UV__FD_HASH_GROUP_SIZE; + if (entry_ptr != &bucket_ptr->data->entries[i]) { + *entry_ptr = bucket_ptr->data->entries[i]; + } + + if (bucket_ptr->size != 0 && + bucket_ptr->size % UV__FD_HASH_GROUP_SIZE == 0) { + struct uv__fd_hash_entry_group_s* old_group_ptr = bucket_ptr->data; + bucket_ptr->data = old_group_ptr->next; + uv__free(old_group_ptr); + } + } + + uv_mutex_unlock(&uv__fd_hash_mutex); + return entry_ptr != NULL; +} + +#undef FIND_COMMON_VARIABLES +#undef FIND_IN_GROUP_PTR +#undef FIND_IN_BUCKET_PTR + +#endif /* UV_WIN_FS_FD_HASH_INL_H_ */ diff --git a/src/win/fs.c b/src/win/fs.c index 0886d79..9577bc0 100644 --- a/src/win/fs.c +++ b/src/win/fs.c @@ -34,8 +34,7 @@ #include "internal.h" #include "req-inl.h" #include "handle-inl.h" - -#include +#include "fs-fd-hash-inl.h" #define UV_FS_FREE_PATHS 0x0002 @@ -96,14 +95,17 @@ return; \ } +#define MILLIONu (1000U * 1000U) +#define BILLIONu (1000U * 1000U * 1000U) + #define FILETIME_TO_UINT(filetime) \ - (*((uint64_t*) &(filetime)) - 116444736000000000ULL) + (*((uint64_t*) &(filetime)) - (uint64_t) 116444736 * BILLIONu) #define FILETIME_TO_TIME_T(filetime) \ - (FILETIME_TO_UINT(filetime) / 10000000ULL) + (FILETIME_TO_UINT(filetime) / (10u * MILLIONu)) #define FILETIME_TO_TIME_NS(filetime, secs) \ - ((FILETIME_TO_UINT(filetime) - (secs * 10000000ULL)) * 100) + ((FILETIME_TO_UINT(filetime) - (secs * (uint64_t) 10 * MILLIONu)) * 100U) #define FILETIME_TO_TIMESPEC(ts, filetime) \ do { \ @@ -113,8 +115,8 @@ #define TIME_T_TO_FILETIME(time, filetime_ptr) \ do { \ - uint64_t bigtime = ((uint64_t) ((time) * 10000000ULL)) + \ - 116444736000000000ULL; \ + uint64_t bigtime = ((uint64_t) ((time) * (uint64_t) 10 * MILLIONu)) + \ + (uint64_t) 116444736 * BILLIONu; \ (filetime_ptr)->dwLowDateTime = bigtime & 0xFFFFFFFF; \ (filetime_ptr)->dwHighDateTime = bigtime >> 32; \ } while(0) @@ -123,6 +125,8 @@ #define IS_LETTER(c) (((c) >= L'a' && (c) <= L'z') || \ ((c) >= L'A' && (c) <= L'Z')) +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) + const WCHAR JUNCTION_PREFIX[] = L"\\??\\"; const WCHAR JUNCTION_PREFIX_LEN = 4; @@ -134,8 +138,16 @@ const WCHAR UNC_PATH_PREFIX_LEN = 8; static int uv__file_symlink_usermode_flag = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; +static DWORD uv__allocation_granularity; + + void uv_fs_init(void) { - _fmode = _O_BINARY; + SYSTEM_INFO system_info; + + GetSystemInfo(&system_info); + uv__allocation_granularity = system_info.dwAllocationGranularity; + + uv__fd_hash_init(); } @@ -245,6 +257,7 @@ INLINE static void uv_fs_req_init(uv_loop_t* loop, uv_fs_t* req, req->loop = loop; req->flags = 0; req->fs_type = fs_type; + req->sys_errno_ = 0; req->result = 0; req->ptr = NULL; req->path = NULL; @@ -309,6 +322,8 @@ INLINE static int fs__readlink_handle(HANDLE handle, char** target_ptr, WCHAR* w_target; DWORD w_target_len; DWORD bytes; + size_t i; + size_t len; if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, @@ -393,6 +408,38 @@ INLINE static int fs__readlink_handle(HANDLE handle, char** target_ptr, w_target += 4; w_target_len -= 4; + } else if (reparse_data->ReparseTag == IO_REPARSE_TAG_APPEXECLINK) { + /* String #3 in the list has the target filename. */ + if (reparse_data->AppExecLinkReparseBuffer.StringCount < 3) { + SetLastError(ERROR_SYMLINK_NOT_SUPPORTED); + return -1; + } + w_target = reparse_data->AppExecLinkReparseBuffer.StringList; + /* The StringList buffer contains a list of strings separated by "\0", */ + /* with "\0\0" terminating the list. Move to the 3rd string in the list: */ + for (i = 0; i < 2; ++i) { + len = wcslen(w_target); + if (len == 0) { + SetLastError(ERROR_SYMLINK_NOT_SUPPORTED); + return -1; + } + w_target += len + 1; + } + w_target_len = wcslen(w_target); + if (w_target_len == 0) { + SetLastError(ERROR_SYMLINK_NOT_SUPPORTED); + return -1; + } + /* Make sure it is an absolute path. */ + if (!(w_target_len >= 3 && + ((w_target[0] >= L'a' && w_target[0] <= L'z') || + (w_target[0] >= L'A' && w_target[0] <= L'Z')) && + w_target[1] == L':' && + w_target[2] == L'\\')) { + SetLastError(ERROR_SYMLINK_NOT_SUPPORTED); + return -1; + } + } else { /* Reparse tag does not indicate a symlink. */ SetLastError(ERROR_SYMLINK_NOT_SUPPORTED); @@ -411,6 +458,27 @@ void fs__open(uv_fs_t* req) { HANDLE file; int fd, current_umask; int flags = req->fs.info.file_flags; + struct uv__fd_info_s fd_info; + + /* Adjust flags to be compatible with the memory file mapping. Save the + * original flags to emulate the correct behavior. */ + if (flags & UV_FS_O_FILEMAP) { + fd_info.flags = flags; + fd_info.current_pos.QuadPart = 0; + + if ((flags & (UV_FS_O_RDONLY | UV_FS_O_WRONLY | UV_FS_O_RDWR)) == + UV_FS_O_WRONLY) { + /* CreateFileMapping always needs read access */ + flags = (flags & ~UV_FS_O_WRONLY) | UV_FS_O_RDWR; + } + + if (flags & UV_FS_O_APPEND) { + /* Clear the append flag and ensure RDRW mode */ + flags &= ~UV_FS_O_APPEND; + flags &= ~(UV_FS_O_RDONLY | UV_FS_O_WRONLY | UV_FS_O_RDWR); + flags |= UV_FS_O_RDWR; + } + } /* Obtain the active umask. umask() never fails and returns the previous * umask. */ @@ -441,7 +509,8 @@ void fs__open(uv_fs_t* req) { * Here is where we deviate significantly from what CRT's _open() * does. We indiscriminately use all the sharing modes, to match * UNIX semantics. In particular, this ensures that the file can - * be deleted even whilst it's open, fixing issue #1449. + * be deleted even whilst it's open, fixing issue + * https://github.com/nodejs/node-v0.x-archive/issues/1449. * We still support exclusive sharing mode, since it is necessary * for opening raw block devices, otherwise Windows will prevent * any attempt to write past the master boot record. @@ -505,6 +574,33 @@ void fs__open(uv_fs_t* req) { } if (flags & UV_FS_O_DIRECT) { + /* + * FILE_APPEND_DATA and FILE_FLAG_NO_BUFFERING are mutually exclusive. + * Windows returns 87, ERROR_INVALID_PARAMETER if these are combined. + * + * FILE_APPEND_DATA is included in FILE_GENERIC_WRITE: + * + * FILE_GENERIC_WRITE = STANDARD_RIGHTS_WRITE | + * FILE_WRITE_DATA | + * FILE_WRITE_ATTRIBUTES | + * FILE_WRITE_EA | + * FILE_APPEND_DATA | + * SYNCHRONIZE + * + * Note: Appends are also permitted by FILE_WRITE_DATA. + * + * In order for direct writes and direct appends to succeed, we therefore + * exclude FILE_APPEND_DATA if FILE_WRITE_DATA is specified, and otherwise + * fail if the user's sole permission is a direct append, since this + * particular combination is invalid. + */ + if (access & FILE_APPEND_DATA) { + if (access & FILE_WRITE_DATA) { + access &= ~FILE_APPEND_DATA; + } else { + goto einval; + } + } attributes |= FILE_FLAG_NO_BUFFERING; } @@ -553,11 +649,55 @@ void fs__open(uv_fs_t* req) { else if (GetLastError() != ERROR_SUCCESS) SET_REQ_WIN32_ERROR(req, GetLastError()); else - SET_REQ_WIN32_ERROR(req, UV_UNKNOWN); + SET_REQ_WIN32_ERROR(req, (DWORD) UV_UNKNOWN); CloseHandle(file); return; } + if (flags & UV_FS_O_FILEMAP) { + FILE_STANDARD_INFO file_info; + if (!GetFileInformationByHandleEx(file, + FileStandardInfo, + &file_info, + sizeof file_info)) { + SET_REQ_WIN32_ERROR(req, GetLastError()); + CloseHandle(file); + return; + } + fd_info.is_directory = file_info.Directory; + + if (fd_info.is_directory) { + fd_info.size.QuadPart = 0; + fd_info.mapping = INVALID_HANDLE_VALUE; + } else { + if (!GetFileSizeEx(file, &fd_info.size)) { + SET_REQ_WIN32_ERROR(req, GetLastError()); + CloseHandle(file); + return; + } + + if (fd_info.size.QuadPart == 0) { + fd_info.mapping = INVALID_HANDLE_VALUE; + } else { + DWORD flProtect = (fd_info.flags & (UV_FS_O_RDONLY | UV_FS_O_WRONLY | + UV_FS_O_RDWR)) == UV_FS_O_RDONLY ? PAGE_READONLY : PAGE_READWRITE; + fd_info.mapping = CreateFileMapping(file, + NULL, + flProtect, + fd_info.size.HighPart, + fd_info.size.LowPart, + NULL); + if (fd_info.mapping == NULL) { + SET_REQ_WIN32_ERROR(req, GetLastError()); + CloseHandle(file); + return; + } + } + } + + uv__fd_hash_add(fd, &fd_info); + } + SET_REQ_RESULT(req, fd); return; @@ -568,9 +708,16 @@ void fs__open(uv_fs_t* req) { void fs__close(uv_fs_t* req) { int fd = req->file.fd; int result; + struct uv__fd_info_s fd_info; VERIFY_FD(fd, req); + if (uv__fd_hash_remove(fd, &fd_info)) { + if (fd_info.mapping != INVALID_HANDLE_VALUE) { + CloseHandle(fd_info.mapping); + } + } + if (fd > 2) result = _close(fd); else @@ -588,6 +735,123 @@ void fs__close(uv_fs_t* req) { } +LONG fs__filemap_ex_filter(LONG excode, PEXCEPTION_POINTERS pep, + int* perror) { + if (excode != EXCEPTION_IN_PAGE_ERROR) { + return EXCEPTION_CONTINUE_SEARCH; + } + + assert(perror != NULL); + if (pep != NULL && pep->ExceptionRecord != NULL && + pep->ExceptionRecord->NumberParameters >= 3) { + NTSTATUS status = (NTSTATUS)pep->ExceptionRecord->ExceptionInformation[3]; + *perror = pRtlNtStatusToDosError(status); + if (*perror != ERROR_SUCCESS) { + return EXCEPTION_EXECUTE_HANDLER; + } + } + *perror = UV_UNKNOWN; + return EXCEPTION_EXECUTE_HANDLER; +} + + +void fs__read_filemap(uv_fs_t* req, struct uv__fd_info_s* fd_info) { + int fd = req->file.fd; /* VERIFY_FD done in fs__read */ + int rw_flags = fd_info->flags & + (UV_FS_O_RDONLY | UV_FS_O_WRONLY | UV_FS_O_RDWR); + size_t read_size, done_read; + unsigned int index; + LARGE_INTEGER pos, end_pos; + size_t view_offset; + LARGE_INTEGER view_base; + void* view; + + if (rw_flags == UV_FS_O_WRONLY) { + SET_REQ_WIN32_ERROR(req, ERROR_ACCESS_DENIED); + return; + } + if (fd_info->is_directory) { + SET_REQ_WIN32_ERROR(req, ERROR_INVALID_FUNCTION); + return; + } + + if (req->fs.info.offset == -1) { + pos = fd_info->current_pos; + } else { + pos.QuadPart = req->fs.info.offset; + } + + /* Make sure we wont read past EOF. */ + if (pos.QuadPart >= fd_info->size.QuadPart) { + SET_REQ_RESULT(req, 0); + return; + } + + read_size = 0; + for (index = 0; index < req->fs.info.nbufs; ++index) { + read_size += req->fs.info.bufs[index].len; + } + read_size = (size_t) MIN((LONGLONG) read_size, + fd_info->size.QuadPart - pos.QuadPart); + if (read_size == 0) { + SET_REQ_RESULT(req, 0); + return; + } + + end_pos.QuadPart = pos.QuadPart + read_size; + + view_offset = pos.QuadPart % uv__allocation_granularity; + view_base.QuadPart = pos.QuadPart - view_offset; + view = MapViewOfFile(fd_info->mapping, + FILE_MAP_READ, + view_base.HighPart, + view_base.LowPart, + view_offset + read_size); + if (view == NULL) { + SET_REQ_WIN32_ERROR(req, GetLastError()); + return; + } + + done_read = 0; + for (index = 0; + index < req->fs.info.nbufs && done_read < read_size; + ++index) { + int err = 0; + size_t this_read_size = MIN(req->fs.info.bufs[index].len, + read_size - done_read); +#ifdef _MSC_VER + __try { +#endif + memcpy(req->fs.info.bufs[index].base, + (char*)view + view_offset + done_read, + this_read_size); +#ifdef _MSC_VER + } + __except (fs__filemap_ex_filter(GetExceptionCode(), + GetExceptionInformation(), &err)) { + SET_REQ_WIN32_ERROR(req, err); + UnmapViewOfFile(view); + return; + } +#endif + done_read += this_read_size; + } + assert(done_read == read_size); + + if (!UnmapViewOfFile(view)) { + SET_REQ_WIN32_ERROR(req, GetLastError()); + return; + } + + if (req->fs.info.offset == -1) { + fd_info->current_pos = end_pos; + uv__fd_hash_add(fd, fd_info); + } + + SET_REQ_RESULT(req, read_size); + return; +} + void fs__read(uv_fs_t* req) { int fd = req->file.fd; int64_t offset = req->fs.info.offset; @@ -601,9 +865,15 @@ void fs__read(uv_fs_t* req) { LARGE_INTEGER original_position; LARGE_INTEGER zero_offset; int restore_position; + struct uv__fd_info_s fd_info; VERIFY_FD(fd, req); + if (uv__fd_hash_get(fd, &fd_info)) { + fs__read_filemap(req, &fd_info); + return; + } + zero_offset.QuadPart = 0; restore_position = 0; handle = uv__get_osfhandle(fd); @@ -660,6 +930,131 @@ void fs__read(uv_fs_t* req) { } +void fs__write_filemap(uv_fs_t* req, HANDLE file, + struct uv__fd_info_s* fd_info) { + int fd = req->file.fd; /* VERIFY_FD done in fs__write */ + int force_append = fd_info->flags & UV_FS_O_APPEND; + int rw_flags = fd_info->flags & + (UV_FS_O_RDONLY | UV_FS_O_WRONLY | UV_FS_O_RDWR); + size_t write_size, done_write; + unsigned int index; + LARGE_INTEGER zero, pos, end_pos; + size_t view_offset; + LARGE_INTEGER view_base; + void* view; + FILETIME ft; + + if (rw_flags == UV_FS_O_RDONLY) { + SET_REQ_WIN32_ERROR(req, ERROR_ACCESS_DENIED); + return; + } + if (fd_info->is_directory) { + SET_REQ_WIN32_ERROR(req, ERROR_INVALID_FUNCTION); + return; + } + + write_size = 0; + for (index = 0; index < req->fs.info.nbufs; ++index) { + write_size += req->fs.info.bufs[index].len; + } + + if (write_size == 0) { + SET_REQ_RESULT(req, 0); + return; + } + + zero.QuadPart = 0; + if (force_append) { + pos = fd_info->size; + } else if (req->fs.info.offset == -1) { + pos = fd_info->current_pos; + } else { + pos.QuadPart = req->fs.info.offset; + } + + end_pos.QuadPart = pos.QuadPart + write_size; + + /* Recreate the mapping to enlarge the file if needed */ + if (end_pos.QuadPart > fd_info->size.QuadPart) { + if (fd_info->mapping != INVALID_HANDLE_VALUE) { + CloseHandle(fd_info->mapping); + } + + fd_info->mapping = CreateFileMapping(file, + NULL, + PAGE_READWRITE, + end_pos.HighPart, + end_pos.LowPart, + NULL); + if (fd_info->mapping == NULL) { + SET_REQ_WIN32_ERROR(req, GetLastError()); + CloseHandle(file); + fd_info->mapping = INVALID_HANDLE_VALUE; + fd_info->size.QuadPart = 0; + fd_info->current_pos.QuadPart = 0; + uv__fd_hash_add(fd, fd_info); + return; + } + + fd_info->size = end_pos; + uv__fd_hash_add(fd, fd_info); + } + + view_offset = pos.QuadPart % uv__allocation_granularity; + view_base.QuadPart = pos.QuadPart - view_offset; + view = MapViewOfFile(fd_info->mapping, + FILE_MAP_WRITE, + view_base.HighPart, + view_base.LowPart, + view_offset + write_size); + if (view == NULL) { + SET_REQ_WIN32_ERROR(req, GetLastError()); + return; + } + + done_write = 0; + for (index = 0; index < req->fs.info.nbufs; ++index) { + int err = 0; +#ifdef _MSC_VER + __try { +#endif + memcpy((char*)view + view_offset + done_write, + req->fs.info.bufs[index].base, + req->fs.info.bufs[index].len); +#ifdef _MSC_VER + } + __except (fs__filemap_ex_filter(GetExceptionCode(), + GetExceptionInformation(), &err)) { + SET_REQ_WIN32_ERROR(req, err); + UnmapViewOfFile(view); + return; + } +#endif + done_write += req->fs.info.bufs[index].len; + } + assert(done_write == write_size); + + if (!FlushViewOfFile(view, 0)) { + SET_REQ_WIN32_ERROR(req, GetLastError()); + UnmapViewOfFile(view); + return; + } + if (!UnmapViewOfFile(view)) { + SET_REQ_WIN32_ERROR(req, GetLastError()); + return; + } + + if (req->fs.info.offset == -1) { + fd_info->current_pos = end_pos; + uv__fd_hash_add(fd, fd_info); + } + + GetSystemTimeAsFileTime(&ft); + SetFileTime(file, NULL, NULL, &ft); + + SET_REQ_RESULT(req, done_write); +} + void fs__write(uv_fs_t* req) { int fd = req->file.fd; int64_t offset = req->fs.info.offset; @@ -672,6 +1067,7 @@ void fs__write(uv_fs_t* req) { LARGE_INTEGER original_position; LARGE_INTEGER zero_offset; int restore_position; + struct uv__fd_info_s fd_info; VERIFY_FD(fd, req); @@ -683,6 +1079,11 @@ void fs__write(uv_fs_t* req) { return; } + if (uv__fd_hash_get(fd, &fd_info)) { + fs__write_filemap(req, handle, &fd_info); + return; + } + if (offset != -1) { memset(&overlapped, 0, sizeof overlapped); overlapped_ptr = &overlapped; @@ -786,9 +1187,8 @@ void fs__unlink(uv_fs_t* req) { /* Remove read-only attribute */ FILE_BASIC_INFORMATION basic = { 0 }; - basic.FileAttributes = info.dwFileAttributes - & ~(FILE_ATTRIBUTE_READONLY) - | FILE_ATTRIBUTE_ARCHIVE; + basic.FileAttributes = (info.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY) | + FILE_ATTRIBUTE_ARCHIVE; status = pNtSetInformationFile(handle, &iosb, @@ -821,13 +1221,19 @@ void fs__unlink(uv_fs_t* req) { void fs__mkdir(uv_fs_t* req) { /* TODO: use req->mode. */ - int result = _wmkdir(req->file.pathw); - SET_REQ_RESULT(req, result); + req->result = _wmkdir(req->file.pathw); + if (req->result == -1) { + req->sys_errno_ = _doserrno; + req->result = req->sys_errno_ == ERROR_INVALID_NAME + ? UV_EINVAL + : uv_translate_sys_error(req->sys_errno_); + } } +typedef int (*uv__fs_mktemp_func)(uv_fs_t* req); /* OpenBSD original: lib/libc/stdio/mktemp.c */ -void fs__mkdtemp(uv_fs_t* req) { +void fs__mktemp(uv_fs_t* req, uv__fs_mktemp_func func) { static const WCHAR *tempchars = L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; static const size_t num_chars = 62; @@ -835,9 +1241,7 @@ void fs__mkdtemp(uv_fs_t* req) { WCHAR *cp, *ep; unsigned int tries, i; size_t len; - HCRYPTPROV h_crypt_prov; uint64_t v; - BOOL released; len = wcslen(req->file.pathw); ep = req->file.pathw + len; @@ -846,16 +1250,10 @@ void fs__mkdtemp(uv_fs_t* req) { return; } - if (!CryptAcquireContext(&h_crypt_prov, NULL, NULL, PROV_RSA_FULL, - CRYPT_VERIFYCONTEXT)) { - SET_REQ_WIN32_ERROR(req, GetLastError()); - return; - } - tries = TMP_MAX; do { - if (!CryptGenRandom(h_crypt_prov, sizeof(v), (BYTE*) &v)) { - SET_REQ_WIN32_ERROR(req, GetLastError()); + if (uv__random_rtlgenrandom((void *)&v, sizeof(v)) < 0) { + SET_REQ_UV_ERROR(req, UV_EIO, ERROR_IO_DEVICE); break; } @@ -865,25 +1263,92 @@ void fs__mkdtemp(uv_fs_t* req) { v /= num_chars; } - if (_wmkdir(req->file.pathw) == 0) { - len = strlen(req->path); - wcstombs((char*) req->path + len - num_x, ep - num_x, num_x); - SET_REQ_RESULT(req, 0); - break; - } else if (errno != EEXIST) { - SET_REQ_RESULT(req, -1); + if (func(req)) { + if (req->result >= 0) { + len = strlen(req->path); + wcstombs((char*) req->path + len - num_x, ep - num_x, num_x); + } break; } } while (--tries); - released = CryptReleaseContext(h_crypt_prov, 0); - assert(released); if (tries == 0) { SET_REQ_RESULT(req, -1); } } +static int fs__mkdtemp_func(uv_fs_t* req) { + if (_wmkdir(req->file.pathw) == 0) { + SET_REQ_RESULT(req, 0); + return 1; + } else if (errno != EEXIST) { + SET_REQ_RESULT(req, -1); + return 1; + } + + return 0; +} + + +void fs__mkdtemp(uv_fs_t* req) { + fs__mktemp(req, fs__mkdtemp_func); +} + + +static int fs__mkstemp_func(uv_fs_t* req) { + HANDLE file; + int fd; + + file = CreateFileW(req->file.pathw, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + CREATE_NEW, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (file == INVALID_HANDLE_VALUE) { + DWORD error; + error = GetLastError(); + + /* If the file exists, the main fs__mktemp() function + will retry. If it's another error, we want to stop. */ + if (error != ERROR_FILE_EXISTS) { + SET_REQ_WIN32_ERROR(req, error); + return 1; + } + + return 0; + } + + fd = _open_osfhandle((intptr_t) file, 0); + if (fd < 0) { + /* The only known failure mode for _open_osfhandle() is EMFILE, in which + * case GetLastError() will return zero. However we'll try to handle other + * errors as well, should they ever occur. + */ + if (errno == EMFILE) + SET_REQ_UV_ERROR(req, UV_EMFILE, ERROR_TOO_MANY_OPEN_FILES); + else if (GetLastError() != ERROR_SUCCESS) + SET_REQ_WIN32_ERROR(req, GetLastError()); + else + SET_REQ_WIN32_ERROR(req, UV_UNKNOWN); + CloseHandle(file); + return 1; + } + + SET_REQ_RESULT(req, fd); + + return 1; +} + + +void fs__mkstemp(uv_fs_t* req) { + fs__mktemp(req, fs__mkstemp_func); +} + + void fs__scandir(uv_fs_t* req) { static const size_t dirents_initial_size = 32; @@ -1096,6 +1561,137 @@ cleanup: uv__free(dirents); } +void fs__opendir(uv_fs_t* req) { + WCHAR* pathw; + size_t len; + const WCHAR* fmt; + WCHAR* find_path; + uv_dir_t* dir; + + pathw = req->file.pathw; + dir = NULL; + find_path = NULL; + + /* Figure out whether path is a file or a directory. */ + if (!(GetFileAttributesW(pathw) & FILE_ATTRIBUTE_DIRECTORY)) { + SET_REQ_UV_ERROR(req, UV_ENOTDIR, ERROR_DIRECTORY); + goto error; + } + + dir = uv__malloc(sizeof(*dir)); + if (dir == NULL) { + SET_REQ_UV_ERROR(req, UV_ENOMEM, ERROR_OUTOFMEMORY); + goto error; + } + + len = wcslen(pathw); + + if (len == 0) + fmt = L"./*"; + else if (IS_SLASH(pathw[len - 1])) + fmt = L"%s*"; + else + fmt = L"%s\\*"; + + find_path = uv__malloc(sizeof(WCHAR) * (len + 4)); + if (find_path == NULL) { + SET_REQ_UV_ERROR(req, UV_ENOMEM, ERROR_OUTOFMEMORY); + goto error; + } + + _snwprintf(find_path, len + 3, fmt, pathw); + dir->dir_handle = FindFirstFileW(find_path, &dir->find_data); + uv__free(find_path); + find_path = NULL; + if (dir->dir_handle == INVALID_HANDLE_VALUE && + GetLastError() != ERROR_FILE_NOT_FOUND) { + SET_REQ_WIN32_ERROR(req, GetLastError()); + goto error; + } + + dir->need_find_call = FALSE; + req->ptr = dir; + SET_REQ_RESULT(req, 0); + return; + +error: + uv__free(dir); + uv__free(find_path); + req->ptr = NULL; +} + +void fs__readdir(uv_fs_t* req) { + uv_dir_t* dir; + uv_dirent_t* dirents; + uv__dirent_t dent; + unsigned int dirent_idx; + PWIN32_FIND_DATAW find_data; + unsigned int i; + int r; + + req->flags |= UV_FS_FREE_PTR; + dir = req->ptr; + dirents = dir->dirents; + memset(dirents, 0, dir->nentries * sizeof(*dir->dirents)); + find_data = &dir->find_data; + dirent_idx = 0; + + while (dirent_idx < dir->nentries) { + if (dir->need_find_call && FindNextFileW(dir->dir_handle, find_data) == 0) { + if (GetLastError() == ERROR_NO_MORE_FILES) + break; + goto error; + } + + /* Skip "." and ".." entries. */ + if (find_data->cFileName[0] == L'.' && + (find_data->cFileName[1] == L'\0' || + (find_data->cFileName[1] == L'.' && + find_data->cFileName[2] == L'\0'))) { + dir->need_find_call = TRUE; + continue; + } + + r = uv__convert_utf16_to_utf8((const WCHAR*) &find_data->cFileName, + -1, + (char**) &dirents[dirent_idx].name); + if (r != 0) + goto error; + + /* Copy file type. */ + if ((find_data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) + dent.d_type = UV__DT_DIR; + else if ((find_data->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) + dent.d_type = UV__DT_LINK; + else if ((find_data->dwFileAttributes & FILE_ATTRIBUTE_DEVICE) != 0) + dent.d_type = UV__DT_CHAR; + else + dent.d_type = UV__DT_FILE; + + dirents[dirent_idx].type = uv__fs_get_dirent_type(&dent); + dir->need_find_call = TRUE; + ++dirent_idx; + } + + SET_REQ_RESULT(req, dirent_idx); + return; + +error: + SET_REQ_WIN32_ERROR(req, GetLastError()); + for (i = 0; i < dirent_idx; ++i) { + uv__free((char*) dirents[i].name); + dirents[i].name = NULL; + } +} + +void fs__closedir(uv_fs_t* req) { + uv_dir_t* dir; + + dir = req->ptr; + FindClose(dir->dir_handle); + uv__free(req->ptr); + SET_REQ_RESULT(req, 0); +} INLINE static int fs__stat_handle(HANDLE handle, uv_stat_t* statbuf, int do_lstat) { @@ -1199,7 +1795,7 @@ INLINE static int fs__stat_handle(HANDLE handle, uv_stat_t* statbuf, /* st_blocks contains the on-disk allocation size in 512-byte units. */ statbuf->st_blocks = - file_info.StandardInformation.AllocationSize.QuadPart >> 9ULL; + (uint64_t) file_info.StandardInformation.AllocationSize.QuadPart >> 9; statbuf->st_nlink = file_info.StandardInformation.NumberOfLinks; @@ -1249,47 +1845,57 @@ INLINE static void fs__stat_prepare_path(WCHAR* pathw) { } -INLINE static void fs__stat_impl(uv_fs_t* req, int do_lstat) { +INLINE static DWORD fs__stat_impl_from_path(WCHAR* path, + int do_lstat, + uv_stat_t* statbuf) { HANDLE handle; DWORD flags; + DWORD ret; flags = FILE_FLAG_BACKUP_SEMANTICS; - if (do_lstat) { + if (do_lstat) flags |= FILE_FLAG_OPEN_REPARSE_POINT; - } - handle = CreateFileW(req->file.pathw, + handle = CreateFileW(path, FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, flags, NULL); - if (handle == INVALID_HANDLE_VALUE) { - SET_REQ_WIN32_ERROR(req, GetLastError()); - return; - } - if (fs__stat_handle(handle, &req->statbuf, do_lstat) != 0) { - DWORD error = GetLastError(); + if (handle == INVALID_HANDLE_VALUE) + ret = GetLastError(); + else if (fs__stat_handle(handle, statbuf, do_lstat) != 0) + ret = GetLastError(); + else + ret = 0; + + CloseHandle(handle); + return ret; +} + + +INLINE static void fs__stat_impl(uv_fs_t* req, int do_lstat) { + DWORD error; + + error = fs__stat_impl_from_path(req->file.pathw, do_lstat, &req->statbuf); + if (error != 0) { if (do_lstat && (error == ERROR_SYMLINK_NOT_SUPPORTED || error == ERROR_NOT_A_REPARSE_POINT)) { /* We opened a reparse point but it was not a symlink. Try again. */ fs__stat_impl(req, 0); - } else { /* Stat failed. */ - SET_REQ_WIN32_ERROR(req, GetLastError()); + SET_REQ_WIN32_ERROR(req, error); } - CloseHandle(handle); return; } req->ptr = &req->statbuf; req->result = 0; - CloseHandle(handle); } @@ -1366,6 +1972,7 @@ static void fs__fdatasync(uv_fs_t* req) { static void fs__ftruncate(uv_fs_t* req) { int fd = req->file.fd; HANDLE handle; + struct uv__fd_info_s fd_info = { 0 }; NTSTATUS status; IO_STATUS_BLOCK io_status; FILE_END_OF_FILE_INFORMATION eof_info; @@ -1374,6 +1981,17 @@ static void fs__ftruncate(uv_fs_t* req) { handle = uv__get_osfhandle(fd); + if (uv__fd_hash_get(fd, &fd_info)) { + if (fd_info.is_directory) { + SET_REQ_WIN32_ERROR(req, ERROR_ACCESS_DENIED); + return; + } + + if (fd_info.mapping != INVALID_HANDLE_VALUE) { + CloseHandle(fd_info.mapping); + } + } + eof_info.EndOfFile.QuadPart = req->fs.info.offset; status = pNtSetInformationFile(handle, @@ -1386,6 +2004,43 @@ static void fs__ftruncate(uv_fs_t* req) { SET_REQ_RESULT(req, 0); } else { SET_REQ_WIN32_ERROR(req, pRtlNtStatusToDosError(status)); + + if (fd_info.flags) { + CloseHandle(handle); + fd_info.mapping = INVALID_HANDLE_VALUE; + fd_info.size.QuadPart = 0; + fd_info.current_pos.QuadPart = 0; + uv__fd_hash_add(fd, &fd_info); + return; + } + } + + if (fd_info.flags) { + fd_info.size = eof_info.EndOfFile; + + if (fd_info.size.QuadPart == 0) { + fd_info.mapping = INVALID_HANDLE_VALUE; + } else { + DWORD flProtect = (fd_info.flags & (UV_FS_O_RDONLY | UV_FS_O_WRONLY | + UV_FS_O_RDWR)) == UV_FS_O_RDONLY ? PAGE_READONLY : PAGE_READWRITE; + fd_info.mapping = CreateFileMapping(handle, + NULL, + flProtect, + fd_info.size.HighPart, + fd_info.size.LowPart, + NULL); + if (fd_info.mapping == NULL) { + SET_REQ_WIN32_ERROR(req, GetLastError()); + CloseHandle(handle); + fd_info.mapping = INVALID_HANDLE_VALUE; + fd_info.size.QuadPart = 0; + fd_info.current_pos.QuadPart = 0; + uv__fd_hash_add(fd, &fd_info); + return; + } + } + + uv__fd_hash_add(fd, &fd_info); } } @@ -1393,6 +2048,8 @@ static void fs__ftruncate(uv_fs_t* req) { static void fs__copyfile(uv_fs_t* req) { int flags; int overwrite; + uv_stat_t statbuf; + uv_stat_t new_statbuf; flags = req->fs.info.file_flags; @@ -1403,12 +2060,25 @@ static void fs__copyfile(uv_fs_t* req) { overwrite = flags & UV_FS_COPYFILE_EXCL; - if (CopyFileW(req->file.pathw, req->fs.info.new_pathw, overwrite) == 0) { - SET_REQ_WIN32_ERROR(req, GetLastError()); + if (CopyFileW(req->file.pathw, req->fs.info.new_pathw, overwrite) != 0) { + SET_REQ_RESULT(req, 0); return; } - SET_REQ_RESULT(req, 0); + SET_REQ_WIN32_ERROR(req, GetLastError()); + if (req->result != UV_EBUSY) + return; + + /* if error UV_EBUSY check if src and dst file are the same */ + if (fs__stat_impl_from_path(req->file.pathw, 0, &statbuf) != 0 || + fs__stat_impl_from_path(req->fs.info.new_pathw, 0, &new_statbuf) != 0) { + return; + } + + if (statbuf.st_dev == new_statbuf.st_dev && + statbuf.st_ino == new_statbuf.st_ino) { + SET_REQ_RESULT(req, 0); + } } @@ -1517,10 +2187,10 @@ static void fs__fchmod(uv_fs_t* req) { SET_REQ_WIN32_ERROR(req, pRtlNtStatusToDosError(nt_status)); goto fchmod_cleanup; } - + /* Test if the Archive attribute is cleared */ if ((file_info.FileAttributes & FILE_ATTRIBUTE_ARCHIVE) == 0) { - /* Set Archive flag, otherwise setting or clearing the read-only + /* Set Archive flag, otherwise setting or clearing the read-only flag will not work */ file_info.FileAttributes |= FILE_ATTRIBUTE_ARCHIVE; nt_status = pNtSetInformationFile(handle, @@ -1590,34 +2260,68 @@ INLINE static int fs__utime_handle(HANDLE handle, double atime, double mtime) { return 0; } - -static void fs__utime(uv_fs_t* req) { +INLINE static DWORD fs__utime_impl_from_path(WCHAR* path, + double atime, + double mtime, + int do_lutime) { HANDLE handle; + DWORD flags; + DWORD ret; - handle = CreateFileW(req->file.pathw, + flags = FILE_FLAG_BACKUP_SEMANTICS; + if (do_lutime) { + flags |= FILE_FLAG_OPEN_REPARSE_POINT; + } + + handle = CreateFileW(path, FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, + flags, NULL); if (handle == INVALID_HANDLE_VALUE) { - SET_REQ_WIN32_ERROR(req, GetLastError()); - return; + ret = GetLastError(); + } else if (fs__utime_handle(handle, atime, mtime) != 0) { + ret = GetLastError(); + } else { + ret = 0; } - if (fs__utime_handle(handle, req->fs.time.atime, req->fs.time.mtime) != 0) { - SET_REQ_WIN32_ERROR(req, GetLastError()); - CloseHandle(handle); + CloseHandle(handle); + return ret; +} + +INLINE static void fs__utime_impl(uv_fs_t* req, int do_lutime) { + DWORD error; + + error = fs__utime_impl_from_path(req->file.pathw, + req->fs.time.atime, + req->fs.time.mtime, + do_lutime); + + if (error != 0) { + if (do_lutime && + (error == ERROR_SYMLINK_NOT_SUPPORTED || + error == ERROR_NOT_A_REPARSE_POINT)) { + /* Opened file is a reparse point but not a symlink. Try again. */ + fs__utime_impl(req, 0); + } else { + /* utime failed. */ + SET_REQ_WIN32_ERROR(req, error); + } + return; } - CloseHandle(handle); - req->result = 0; } +static void fs__utime(uv_fs_t* req) { + fs__utime_impl(req, /* do_lutime */ 0); +} + static void fs__futime(uv_fs_t* req) { int fd = req->file.fd; @@ -1639,6 +2343,10 @@ static void fs__futime(uv_fs_t* req) { req->result = 0; } +static void fs__lutime(uv_fs_t* req) { + fs__utime_impl(req, /* do_lutime */ 1); +} + static void fs__link(uv_fs_t* req) { DWORD r = CreateHardLinkW(req->fs.info.new_pathw, req->file.pathw, NULL); @@ -1890,7 +2598,7 @@ static void fs__readlink(uv_fs_t* req) { } -static size_t fs__realpath_handle(HANDLE handle, char** realpath_ptr) { +static ssize_t fs__realpath_handle(HANDLE handle, char** realpath_ptr) { int r; DWORD w_realpath_len; WCHAR* w_realpath_ptr = NULL; @@ -1979,6 +2687,90 @@ static void fs__lchown(uv_fs_t* req) { req->result = 0; } + +static void fs__statfs(uv_fs_t* req) { + uv_statfs_t* stat_fs; + DWORD sectors_per_cluster; + DWORD bytes_per_sector; + DWORD free_clusters; + DWORD total_clusters; + WCHAR* pathw; + + pathw = req->file.pathw; +retry_get_disk_free_space: + if (0 == GetDiskFreeSpaceW(pathw, + §ors_per_cluster, + &bytes_per_sector, + &free_clusters, + &total_clusters)) { + DWORD err; + WCHAR* fpart; + size_t len; + DWORD ret; + BOOL is_second; + + err = GetLastError(); + is_second = pathw != req->file.pathw; + if (err != ERROR_DIRECTORY || is_second) { + if (is_second) + uv__free(pathw); + + SET_REQ_WIN32_ERROR(req, err); + return; + } + + len = MAX_PATH + 1; + pathw = uv__malloc(len * sizeof(*pathw)); + if (pathw == NULL) { + SET_REQ_UV_ERROR(req, UV_ENOMEM, ERROR_OUTOFMEMORY); + return; + } +retry_get_full_path_name: + ret = GetFullPathNameW(req->file.pathw, + len, + pathw, + &fpart); + if (ret == 0) { + uv__free(pathw); + SET_REQ_WIN32_ERROR(req, err); + return; + } else if (ret > len) { + len = ret; + pathw = uv__reallocf(pathw, len * sizeof(*pathw)); + if (pathw == NULL) { + SET_REQ_UV_ERROR(req, UV_ENOMEM, ERROR_OUTOFMEMORY); + return; + } + goto retry_get_full_path_name; + } + if (fpart != 0) + *fpart = L'\0'; + + goto retry_get_disk_free_space; + } + if (pathw != req->file.pathw) { + uv__free(pathw); + } + + stat_fs = uv__malloc(sizeof(*stat_fs)); + if (stat_fs == NULL) { + SET_REQ_UV_ERROR(req, UV_ENOMEM, ERROR_OUTOFMEMORY); + return; + } + + stat_fs->f_type = 0; + stat_fs->f_bsize = bytes_per_sector * sectors_per_cluster; + stat_fs->f_blocks = total_clusters; + stat_fs->f_bfree = free_clusters; + stat_fs->f_bavail = free_clusters; + stat_fs->f_files = 0; + stat_fs->f_ffree = 0; + req->ptr = stat_fs; + req->flags |= UV_FS_FREE_PTR; + SET_REQ_RESULT(req, 0); +} + + static void uv__fs_work(struct uv__work* w) { uv_fs_t* req; @@ -1999,6 +2791,7 @@ static void uv__fs_work(struct uv__work* w) { XX(FTRUNCATE, ftruncate) XX(UTIME, utime) XX(FUTIME, futime) + XX(LUTIME, lutime) XX(ACCESS, access) XX(CHMOD, chmod) XX(FCHMOD, fchmod) @@ -2008,15 +2801,20 @@ static void uv__fs_work(struct uv__work* w) { XX(RMDIR, rmdir) XX(MKDIR, mkdir) XX(MKDTEMP, mkdtemp) + XX(MKSTEMP, mkstemp) XX(RENAME, rename) XX(SCANDIR, scandir) + XX(READDIR, readdir) + XX(OPENDIR, opendir) + XX(CLOSEDIR, closedir) XX(LINK, link) XX(SYMLINK, symlink) XX(READLINK, readlink) XX(REALPATH, realpath) XX(CHOWN, chown) - XX(FCHOWN, fchown); - XX(LCHOWN, lchown); + XX(FCHOWN, fchown) + XX(LCHOWN, lchown) + XX(STATFS, statfs) default: assert(!"bad uv_fs_type"); } @@ -2051,6 +2849,8 @@ void uv_fs_req_cleanup(uv_fs_t* req) { if (req->flags & UV_FS_FREE_PTR) { if (req->fs_type == UV_FS_SCANDIR && req->ptr != NULL) uv__fs_scandir_cleanup(req); + else if (req->fs_type == UV_FS_READDIR) + uv__fs_readdir_cleanup(req); else uv__free(req->ptr); } @@ -2075,7 +2875,8 @@ int uv_fs_open(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags, INIT(UV_FS_OPEN); err = fs__capture_path(req, path, NULL, cb != NULL); if (err) { - return uv_translate_sys_error(err); + SET_REQ_WIN32_ERROR(req, err); + return req->result; } req->fs.info.file_flags = flags; @@ -2100,8 +2901,10 @@ int uv_fs_read(uv_loop_t* loop, uv_fs_cb cb) { INIT(UV_FS_READ); - if (bufs == NULL || nbufs == 0) + if (bufs == NULL || nbufs == 0) { + SET_REQ_UV_ERROR(req, UV_EINVAL, ERROR_INVALID_PARAMETER); return UV_EINVAL; + } req->file.fd = fd; @@ -2110,8 +2913,10 @@ int uv_fs_read(uv_loop_t* loop, if (nbufs > ARRAY_SIZE(req->fs.info.bufsml)) req->fs.info.bufs = uv__malloc(nbufs * sizeof(*bufs)); - if (req->fs.info.bufs == NULL) + if (req->fs.info.bufs == NULL) { + SET_REQ_UV_ERROR(req, UV_ENOMEM, ERROR_OUTOFMEMORY); return UV_ENOMEM; + } memcpy(req->fs.info.bufs, bufs, nbufs * sizeof(*bufs)); @@ -2129,8 +2934,10 @@ int uv_fs_write(uv_loop_t* loop, uv_fs_cb cb) { INIT(UV_FS_WRITE); - if (bufs == NULL || nbufs == 0) + if (bufs == NULL || nbufs == 0) { + SET_REQ_UV_ERROR(req, UV_EINVAL, ERROR_INVALID_PARAMETER); return UV_EINVAL; + } req->file.fd = fd; @@ -2139,8 +2946,10 @@ int uv_fs_write(uv_loop_t* loop, if (nbufs > ARRAY_SIZE(req->fs.info.bufsml)) req->fs.info.bufs = uv__malloc(nbufs * sizeof(*bufs)); - if (req->fs.info.bufs == NULL) + if (req->fs.info.bufs == NULL) { + SET_REQ_UV_ERROR(req, UV_ENOMEM, ERROR_OUTOFMEMORY); return UV_ENOMEM; + } memcpy(req->fs.info.bufs, bufs, nbufs * sizeof(*bufs)); @@ -2156,7 +2965,8 @@ int uv_fs_unlink(uv_loop_t* loop, uv_fs_t* req, const char* path, INIT(UV_FS_UNLINK); err = fs__capture_path(req, path, NULL, cb != NULL); if (err) { - return uv_translate_sys_error(err); + SET_REQ_WIN32_ERROR(req, err); + return req->result; } POST; @@ -2170,7 +2980,8 @@ int uv_fs_mkdir(uv_loop_t* loop, uv_fs_t* req, const char* path, int mode, INIT(UV_FS_MKDIR); err = fs__capture_path(req, path, NULL, cb != NULL); if (err) { - return uv_translate_sys_error(err); + SET_REQ_WIN32_ERROR(req, err); + return req->result; } req->fs.info.mode = mode; @@ -2178,14 +2989,35 @@ int uv_fs_mkdir(uv_loop_t* loop, uv_fs_t* req, const char* path, int mode, } -int uv_fs_mkdtemp(uv_loop_t* loop, uv_fs_t* req, const char* tpl, - uv_fs_cb cb) { +int uv_fs_mkdtemp(uv_loop_t* loop, + uv_fs_t* req, + const char* tpl, + uv_fs_cb cb) { int err; INIT(UV_FS_MKDTEMP); err = fs__capture_path(req, tpl, NULL, TRUE); - if (err) - return uv_translate_sys_error(err); + if (err) { + SET_REQ_WIN32_ERROR(req, err); + return req->result; + } + + POST; +} + + +int uv_fs_mkstemp(uv_loop_t* loop, + uv_fs_t* req, + const char* tpl, + uv_fs_cb cb) { + int err; + + INIT(UV_FS_MKSTEMP); + err = fs__capture_path(req, tpl, NULL, TRUE); + if (err) { + SET_REQ_WIN32_ERROR(req, err); + return req->result; + } POST; } @@ -2197,7 +3029,8 @@ int uv_fs_rmdir(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) { INIT(UV_FS_RMDIR); err = fs__capture_path(req, path, NULL, cb != NULL); if (err) { - return uv_translate_sys_error(err); + SET_REQ_WIN32_ERROR(req, err); + return req->result; } POST; @@ -2211,13 +3044,58 @@ int uv_fs_scandir(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags, INIT(UV_FS_SCANDIR); err = fs__capture_path(req, path, NULL, cb != NULL); if (err) { - return uv_translate_sys_error(err); + SET_REQ_WIN32_ERROR(req, err); + return req->result; } req->fs.info.file_flags = flags; POST; } +int uv_fs_opendir(uv_loop_t* loop, + uv_fs_t* req, + const char* path, + uv_fs_cb cb) { + int err; + + INIT(UV_FS_OPENDIR); + err = fs__capture_path(req, path, NULL, cb != NULL); + if (err) { + SET_REQ_WIN32_ERROR(req, err); + return req->result; + } + POST; +} + +int uv_fs_readdir(uv_loop_t* loop, + uv_fs_t* req, + uv_dir_t* dir, + uv_fs_cb cb) { + INIT(UV_FS_READDIR); + + if (dir == NULL || + dir->dirents == NULL || + dir->dir_handle == INVALID_HANDLE_VALUE) { + SET_REQ_UV_ERROR(req, UV_EINVAL, ERROR_INVALID_PARAMETER); + return UV_EINVAL; + } + + req->ptr = dir; + POST; +} + +int uv_fs_closedir(uv_loop_t* loop, + uv_fs_t* req, + uv_dir_t* dir, + uv_fs_cb cb) { + INIT(UV_FS_CLOSEDIR); + if (dir == NULL) { + SET_REQ_UV_ERROR(req, UV_EINVAL, ERROR_INVALID_PARAMETER); + return UV_EINVAL; + } + req->ptr = dir; + POST; +} int uv_fs_link(uv_loop_t* loop, uv_fs_t* req, const char* path, const char* new_path, uv_fs_cb cb) { @@ -2226,7 +3104,8 @@ int uv_fs_link(uv_loop_t* loop, uv_fs_t* req, const char* path, INIT(UV_FS_LINK); err = fs__capture_path(req, path, new_path, cb != NULL); if (err) { - return uv_translate_sys_error(err); + SET_REQ_WIN32_ERROR(req, err); + return req->result; } POST; @@ -2240,7 +3119,8 @@ int uv_fs_symlink(uv_loop_t* loop, uv_fs_t* req, const char* path, INIT(UV_FS_SYMLINK); err = fs__capture_path(req, path, new_path, cb != NULL); if (err) { - return uv_translate_sys_error(err); + SET_REQ_WIN32_ERROR(req, err); + return req->result; } req->fs.info.file_flags = flags; @@ -2255,7 +3135,8 @@ int uv_fs_readlink(uv_loop_t* loop, uv_fs_t* req, const char* path, INIT(UV_FS_READLINK); err = fs__capture_path(req, path, NULL, cb != NULL); if (err) { - return uv_translate_sys_error(err); + SET_REQ_WIN32_ERROR(req, err); + return req->result; } POST; @@ -2269,12 +3150,14 @@ int uv_fs_realpath(uv_loop_t* loop, uv_fs_t* req, const char* path, INIT(UV_FS_REALPATH); if (!path) { + SET_REQ_UV_ERROR(req, UV_EINVAL, ERROR_INVALID_PARAMETER); return UV_EINVAL; } err = fs__capture_path(req, path, NULL, cb != NULL); if (err) { - return uv_translate_sys_error(err); + SET_REQ_WIN32_ERROR(req, err); + return req->result; } POST; @@ -2288,7 +3171,8 @@ int uv_fs_chown(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_uid_t uid, INIT(UV_FS_CHOWN); err = fs__capture_path(req, path, NULL, cb != NULL); if (err) { - return uv_translate_sys_error(err); + SET_REQ_WIN32_ERROR(req, err); + return req->result; } POST; @@ -2309,8 +3193,10 @@ int uv_fs_lchown(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_uid_t uid, INIT(UV_FS_LCHOWN); err = fs__capture_path(req, path, NULL, cb != NULL); if (err) { - return uv_translate_sys_error(err); + SET_REQ_WIN32_ERROR(req, err); + return req->result; } + POST; } @@ -2321,7 +3207,8 @@ int uv_fs_stat(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) { INIT(UV_FS_STAT); err = fs__capture_path(req, path, NULL, cb != NULL); if (err) { - return uv_translate_sys_error(err); + SET_REQ_WIN32_ERROR(req, err); + return req->result; } POST; @@ -2334,7 +3221,8 @@ int uv_fs_lstat(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) { INIT(UV_FS_LSTAT); err = fs__capture_path(req, path, NULL, cb != NULL); if (err) { - return uv_translate_sys_error(err); + SET_REQ_WIN32_ERROR(req, err); + return req->result; } POST; @@ -2355,7 +3243,8 @@ int uv_fs_rename(uv_loop_t* loop, uv_fs_t* req, const char* path, INIT(UV_FS_RENAME); err = fs__capture_path(req, path, new_path, cb != NULL); if (err) { - return uv_translate_sys_error(err); + SET_REQ_WIN32_ERROR(req, err); + return req->result; } POST; @@ -2398,13 +3287,15 @@ int uv_fs_copyfile(uv_loop_t* loop, if (flags & ~(UV_FS_COPYFILE_EXCL | UV_FS_COPYFILE_FICLONE | UV_FS_COPYFILE_FICLONE_FORCE)) { + SET_REQ_UV_ERROR(req, UV_EINVAL, ERROR_INVALID_PARAMETER); return UV_EINVAL; } err = fs__capture_path(req, path, new_path, cb != NULL); - - if (err) - return uv_translate_sys_error(err); + if (err) { + SET_REQ_WIN32_ERROR(req, err); + return req->result; + } req->fs.info.file_flags = flags; POST; @@ -2431,8 +3322,10 @@ int uv_fs_access(uv_loop_t* loop, INIT(UV_FS_ACCESS); err = fs__capture_path(req, path, NULL, cb != NULL); - if (err) - return uv_translate_sys_error(err); + if (err) { + SET_REQ_WIN32_ERROR(req, err); + return req->result; + } req->fs.info.mode = flags; POST; @@ -2446,7 +3339,8 @@ int uv_fs_chmod(uv_loop_t* loop, uv_fs_t* req, const char* path, int mode, INIT(UV_FS_CHMOD); err = fs__capture_path(req, path, NULL, cb != NULL); if (err) { - return uv_translate_sys_error(err); + SET_REQ_WIN32_ERROR(req, err); + return req->result; } req->fs.info.mode = mode; @@ -2470,7 +3364,8 @@ int uv_fs_utime(uv_loop_t* loop, uv_fs_t* req, const char* path, double atime, INIT(UV_FS_UTIME); err = fs__capture_path(req, path, NULL, cb != NULL); if (err) { - return uv_translate_sys_error(err); + SET_REQ_WIN32_ERROR(req, err); + return req->result; } req->fs.time.atime = atime; @@ -2487,3 +3382,40 @@ int uv_fs_futime(uv_loop_t* loop, uv_fs_t* req, uv_file fd, double atime, req->fs.time.mtime = mtime; POST; } + +int uv_fs_lutime(uv_loop_t* loop, uv_fs_t* req, const char* path, double atime, + double mtime, uv_fs_cb cb) { + int err; + + INIT(UV_FS_LUTIME); + err = fs__capture_path(req, path, NULL, cb != NULL); + if (err) { + SET_REQ_WIN32_ERROR(req, err); + return req->result; + } + + req->fs.time.atime = atime; + req->fs.time.mtime = mtime; + POST; +} + + +int uv_fs_statfs(uv_loop_t* loop, + uv_fs_t* req, + const char* path, + uv_fs_cb cb) { + int err; + + INIT(UV_FS_STATFS); + err = fs__capture_path(req, path, NULL, cb != NULL); + if (err) { + SET_REQ_WIN32_ERROR(req, err); + return req->result; + } + + POST; +} + +int uv_fs_get_system_error(const uv_fs_t* req) { + return req->sys_errno_; +} diff --git a/src/win/getaddrinfo.c b/src/win/getaddrinfo.c index 614ea8e..dfab860 100644 --- a/src/win/getaddrinfo.c +++ b/src/win/getaddrinfo.c @@ -24,6 +24,7 @@ #include "uv.h" #include "internal.h" #include "req-inl.h" +#include "idna.h" /* EAI_* constants. */ #include @@ -259,11 +260,13 @@ int uv_getaddrinfo(uv_loop_t* loop, const char* node, const char* service, const struct addrinfo* hints) { + char hostname_ascii[256]; int nodesize = 0; int servicesize = 0; int hintssize = 0; char* alloc_ptr = NULL; int err; + long rc; if (req == NULL || (node == NULL && service == NULL)) { return UV_EINVAL; @@ -277,12 +280,19 @@ int uv_getaddrinfo(uv_loop_t* loop, /* calculate required memory size for all input values */ if (node != NULL) { - nodesize = ALIGNED_SIZE(MultiByteToWideChar(CP_UTF8, 0, node, -1, NULL, 0) * - sizeof(WCHAR)); + rc = uv__idna_toascii(node, + node + strlen(node), + hostname_ascii, + hostname_ascii + sizeof(hostname_ascii)); + if (rc < 0) + return rc; + nodesize = ALIGNED_SIZE(MultiByteToWideChar(CP_UTF8, 0, hostname_ascii, + -1, NULL, 0) * sizeof(WCHAR)); if (nodesize == 0) { err = GetLastError(); goto error; } + node = hostname_ascii; } if (service != NULL) { diff --git a/src/win/handle.c b/src/win/handle.c index 9d76c3f..61e4df6 100644 --- a/src/win/handle.c +++ b/src/win/handle.c @@ -139,7 +139,6 @@ void uv_close(uv_handle_t* handle, uv_close_cb cb) { case UV_FS_POLL: uv__fs_poll_close((uv_fs_poll_t*) handle); uv__handle_closing(handle); - uv_want_endgame(loop, handle); return; default: diff --git a/src/win/internal.h b/src/win/internal.h index 634b9f7..058ddb8 100644 --- a/src/win/internal.h +++ b/src/win/internal.h @@ -272,6 +272,16 @@ int uv__getpwuid_r(uv_passwd_t* pwd); int uv__convert_utf16_to_utf8(const WCHAR* utf16, int utf16len, char** utf8); int uv__convert_utf8_to_utf16(const char* utf8, int utf8len, WCHAR** utf16); +typedef int (WINAPI *uv__peersockfunc)(SOCKET, struct sockaddr*, int*); + +int uv__getsockpeername(const uv_handle_t* handle, + uv__peersockfunc func, + struct sockaddr* name, + int* namelen, + int delayed_error); + +int uv__random_rtlgenrandom(void* buf, size_t buflen); + /* * Process stdio handles. diff --git a/src/win/pipe.c b/src/win/pipe.c index 382290e..fc0112a 100644 --- a/src/win/pipe.c +++ b/src/win/pipe.c @@ -264,8 +264,9 @@ static int uv_set_pipe_handle(uv_loop_t* loop, DWORD current_mode = 0; DWORD err = 0; - if (!(handle->flags & UV_HANDLE_PIPESERVER) && - handle->handle != INVALID_HANDLE_VALUE) + if (handle->flags & UV_HANDLE_PIPESERVER) + return UV_EINVAL; + if (handle->handle != INVALID_HANDLE_VALUE) return UV_EBUSY; if (!SetNamedPipeHandleState(pipeHandle, &mode, NULL, NULL)) { @@ -312,7 +313,7 @@ static int uv_set_pipe_handle(uv_loop_t* loop, /* Overlapped pipe. Try to associate with IOCP. */ if (CreateIoCompletionPort(pipeHandle, loop->iocp, - (ULONG_PTR)handle, + (ULONG_PTR) handle, 0) == NULL) { handle->flags |= UV_HANDLE_EMULATE_IOCP; } @@ -326,6 +327,38 @@ 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) { + assert(req->pipeHandle == INVALID_HANDLE_VALUE); + + req->pipeHandle = + CreateNamedPipeW(handle->name, + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | WRITE_DAC | + (firstInstance ? FILE_FLAG_FIRST_PIPE_INSTANCE : 0), + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, + PIPE_UNLIMITED_INSTANCES, 65536, 65536, 0, NULL); + + if (req->pipeHandle == INVALID_HANDLE_VALUE) { + return 0; + } + + /* 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"); + } + + /* Stash a handle in the server object for use from places such as + * getsockname and chmod. As we transfer ownership of these to client + * objects, we'll allocate new ones here. */ + handle->handle = req->pipeHandle; + + return 1; +} + + static DWORD WINAPI pipe_shutdown_thread_proc(void* parameter) { uv_loop_t* loop; uv_pipe_t* handle; @@ -458,7 +491,7 @@ void uv_pipe_endgame(uv_loop_t* loop, uv_pipe_t* handle) { UnregisterWait(handle->read_req.wait_handle); handle->read_req.wait_handle = INVALID_HANDLE_VALUE; } - if (handle->read_req.event_handle) { + if (handle->read_req.event_handle != NULL) { CloseHandle(handle->read_req.event_handle); handle->read_req.event_handle = NULL; } @@ -540,13 +573,10 @@ int uv_pipe_bind(uv_pipe_t* handle, const char* name) { * Attempt to create the first pipe with FILE_FLAG_FIRST_PIPE_INSTANCE. * If this fails then there's already a pipe server for the given pipe name. */ - handle->pipe.serv.accept_reqs[0].pipeHandle = CreateNamedPipeW(handle->name, - PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | - FILE_FLAG_FIRST_PIPE_INSTANCE | WRITE_DAC, - PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, - PIPE_UNLIMITED_INSTANCES, 65536, 65536, 0, NULL); - - if (handle->pipe.serv.accept_reqs[0].pipeHandle == INVALID_HANDLE_VALUE) { + if (!pipe_alloc_accept(loop, + handle, + &handle->pipe.serv.accept_reqs[0], + TRUE)) { err = GetLastError(); if (err == ERROR_ACCESS_DENIED) { err = WSAEADDRINUSE; /* Translates to UV_EADDRINUSE. */ @@ -556,15 +586,6 @@ int uv_pipe_bind(uv_pipe_t* handle, const char* name) { goto error; } - if (uv_set_pipe_handle(loop, - handle, - handle->pipe.serv.accept_reqs[0].pipeHandle, - -1, - 0)) { - err = GetLastError(); - goto error; - } - handle->pipe.serv.pending_accepts = NULL; handle->flags |= UV_HANDLE_PIPESERVER; handle->flags |= UV_HANDLE_BOUND; @@ -577,11 +598,6 @@ error: handle->name = NULL; } - if (handle->pipe.serv.accept_reqs[0].pipeHandle != INVALID_HANDLE_VALUE) { - CloseHandle(handle->pipe.serv.accept_reqs[0].pipeHandle); - handle->pipe.serv.accept_reqs[0].pipeHandle = INVALID_HANDLE_VALUE; - } - return uv_translate_sys_error(err); } @@ -827,29 +843,11 @@ static void uv_pipe_queue_accept(uv_loop_t* loop, uv_pipe_t* handle, uv_pipe_accept_t* req, BOOL firstInstance) { assert(handle->flags & UV_HANDLE_LISTENING); - if (!firstInstance) { - assert(req->pipeHandle == INVALID_HANDLE_VALUE); - - req->pipeHandle = CreateNamedPipeW(handle->name, - PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | WRITE_DAC, - PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, - PIPE_UNLIMITED_INSTANCES, 65536, 65536, 0, NULL); - - if (req->pipeHandle == INVALID_HANDLE_VALUE) { - SET_REQ_ERROR(req, GetLastError()); - uv_insert_pending_req(loop, (uv_req_t*) req); - handle->reqs_pending++; - return; - } - - if (uv_set_pipe_handle(loop, handle, req->pipeHandle, -1, 0)) { - CloseHandle(req->pipeHandle); - req->pipeHandle = INVALID_HANDLE_VALUE; - SET_REQ_ERROR(req, GetLastError()); - uv_insert_pending_req(loop, (uv_req_t*) req); - handle->reqs_pending++; - return; - } + if (!firstInstance && !pipe_alloc_accept(loop, handle, req, FALSE)) { + SET_REQ_ERROR(req, GetLastError()); + uv_insert_pending_req(loop, (uv_req_t*) req); + handle->reqs_pending++; + return; } assert(req->pipeHandle != INVALID_HANDLE_VALUE); @@ -904,7 +902,7 @@ int uv_pipe_accept(uv_pipe_t* server, uv_stream_t* client) { uv__free(item); } else { - pipe_client = (uv_pipe_t*)client; + pipe_client = (uv_pipe_t*) client; /* Find a connection instance that has been connected, but not yet * accepted. */ @@ -925,6 +923,7 @@ int uv_pipe_accept(uv_pipe_t* server, uv_stream_t* client) { req->next_pending = NULL; req->pipeHandle = INVALID_HANDLE_VALUE; + server->handle = INVALID_HANDLE_VALUE; if (!(server->flags & UV_HANDLE_CLOSING)) { uv_pipe_queue_accept(loop, server, req, FALSE); } @@ -955,6 +954,10 @@ int uv_pipe_listen(uv_pipe_t* handle, int backlog, uv_connection_cb cb) { return ERROR_NOT_SUPPORTED; } + if (handle->ipc) { + return WSAEINVAL; + } + handle->flags |= UV_HANDLE_LISTENING; INCREASE_ACTIVE_COUNT(loop, handle); handle->stream.serv.connection_cb = cb; @@ -1131,6 +1134,7 @@ static void uv_pipe_queue_read(uv_loop_t* loop, uv_pipe_t* handle) { } else { memset(&req->u.io.overlapped, 0, sizeof(req->u.io.overlapped)); if (handle->flags & UV_HANDLE_EMULATE_IOCP) { + assert(req->event_handle != NULL); req->u.io.overlapped.hEvent = (HANDLE) ((uintptr_t) req->event_handle | 1); } @@ -1148,15 +1152,9 @@ static void uv_pipe_queue_read(uv_loop_t* loop, uv_pipe_t* handle) { } if (handle->flags & UV_HANDLE_EMULATE_IOCP) { - if (!req->event_handle) { - req->event_handle = CreateEvent(NULL, 0, 0, NULL); - if (!req->event_handle) { - uv_fatal_error(GetLastError(), "CreateEvent"); - } - } if (req->wait_handle == INVALID_HANDLE_VALUE) { if (!RegisterWaitForSingleObject(&req->wait_handle, - req->u.io.overlapped.hEvent, post_completion_read_wait, (void*) req, + req->event_handle, post_completion_read_wait, (void*) req, INFINITE, WT_EXECUTEINWAITTHREAD)) { SET_REQ_ERROR(req, GetLastError()); goto error; @@ -1190,8 +1188,16 @@ int uv_pipe_read_start(uv_pipe_t* handle, /* If reading was stopped and then started again, there could still be a read * request pending. */ - if (!(handle->flags & UV_HANDLE_READ_PENDING)) + if (!(handle->flags & UV_HANDLE_READ_PENDING)) { + if (handle->flags & UV_HANDLE_EMULATE_IOCP && + handle->read_req.event_handle == NULL) { + handle->read_req.event_handle = CreateEvent(NULL, 0, 0, NULL); + if (handle->read_req.event_handle == NULL) { + uv_fatal_error(GetLastError(), "CreateEvent"); + } + } uv_pipe_queue_read(loop, handle); + } return 0; } @@ -1310,7 +1316,6 @@ static int uv__pipe_write_data(uv_loop_t* loop, uv_pipe_t* handle, const uv_buf_t bufs[], size_t nbufs, - uv_stream_t* send_handle, uv_write_cb cb, int copy_always) { int err; @@ -1321,13 +1326,22 @@ static int uv__pipe_write_data(uv_loop_t* loop, UV_REQ_INIT(req, UV_WRITE); req->handle = (uv_stream_t*) handle; - req->send_handle = send_handle; + req->send_handle = NULL; req->cb = cb; /* Private fields. */ req->coalesced = 0; req->event_handle = NULL; req->wait_handle = INVALID_HANDLE_VALUE; + + /* Prepare the overlapped structure. */ memset(&req->u.io.overlapped, 0, sizeof(req->u.io.overlapped)); + if (handle->flags & (UV_HANDLE_EMULATE_IOCP | UV_HANDLE_BLOCKING_WRITES)) { + req->event_handle = CreateEvent(NULL, 0, 0, NULL); + if (req->event_handle == NULL) { + uv_fatal_error(GetLastError(), "CreateEvent"); + } + req->u.io.overlapped.hEvent = (HANDLE) ((uintptr_t) req->event_handle | 1); + } req->write_buffer = uv_null_buf_; if (nbufs == 0) { @@ -1376,11 +1390,6 @@ static int uv__pipe_write_data(uv_loop_t* loop, handle->write_queue_size += req->u.io.queued_bytes; } else if (handle->flags & UV_HANDLE_BLOCKING_WRITES) { /* Using overlapped IO, but wait for completion before returning */ - req->u.io.overlapped.hEvent = CreateEvent(NULL, 1, 0, NULL); - if (!req->u.io.overlapped.hEvent) { - uv_fatal_error(GetLastError(), "CreateEvent"); - } - result = WriteFile(handle->handle, write_buf.base, write_buf.len, @@ -1389,7 +1398,8 @@ static int uv__pipe_write_data(uv_loop_t* loop, if (!result && GetLastError() != ERROR_IO_PENDING) { err = GetLastError(); - CloseHandle(req->u.io.overlapped.hEvent); + CloseHandle(req->event_handle); + req->event_handle = NULL; return err; } @@ -1400,14 +1410,16 @@ static int uv__pipe_write_data(uv_loop_t* loop, /* Request queued by the kernel. */ req->u.io.queued_bytes = write_buf.len; handle->write_queue_size += req->u.io.queued_bytes; - if (WaitForSingleObject(req->u.io.overlapped.hEvent, INFINITE) != + if (WaitForSingleObject(req->event_handle, INFINITE) != WAIT_OBJECT_0) { err = GetLastError(); - CloseHandle(req->u.io.overlapped.hEvent); + CloseHandle(req->event_handle); + req->event_handle = NULL; return err; } } - CloseHandle(req->u.io.overlapped.hEvent); + CloseHandle(req->event_handle); + req->event_handle = NULL; REGISTER_HANDLE_REQ(loop, handle, req); handle->reqs_pending++; @@ -1434,12 +1446,8 @@ static int uv__pipe_write_data(uv_loop_t* loop, } if (handle->flags & UV_HANDLE_EMULATE_IOCP) { - req->event_handle = CreateEvent(NULL, 0, 0, NULL); - if (!req->event_handle) { - uv_fatal_error(GetLastError(), "CreateEvent"); - } if (!RegisterWaitForSingleObject(&req->wait_handle, - req->u.io.overlapped.hEvent, post_completion_write_wait, (void*) req, + req->event_handle, post_completion_write_wait, (void*) req, INFINITE, WT_EXECUTEINWAITTHREAD)) { return GetLastError(); } @@ -1541,7 +1549,7 @@ int uv__pipe_write_ipc(uv_loop_t* loop, frame_header.flags |= UV__IPC_FRAME_HAS_SOCKET_XFER; break; default: - assert(0); // Unreachable. + assert(0); /* Unreachable. */ } /* Add xfer info buffer. */ bufs[buf_index++] = uv_buf_init((char*) &xfer_info, sizeof xfer_info); @@ -1558,8 +1566,7 @@ int uv__pipe_write_ipc(uv_loop_t* loop, /* Write buffers. We set the `always_copy` flag, so it is not a problem that * some of the written data lives on the stack. */ - err = uv__pipe_write_data( - loop, req, handle, bufs, buf_count, send_handle, cb, 1); + err = uv__pipe_write_data(loop, req, handle, bufs, buf_count, cb, 1); /* If we had to heap-allocate the bufs array, free it now. */ if (bufs != stack_bufs) { @@ -1583,8 +1590,7 @@ int uv__pipe_write(uv_loop_t* loop, } else { /* Non-IPC pipe write: put data on the wire directly. */ assert(send_handle == NULL); - return uv__pipe_write_data( - loop, req, handle, bufs, nbufs, NULL, cb, 0); + return uv__pipe_write_data(loop, req, handle, bufs, nbufs, cb, 0); } } @@ -2141,7 +2147,7 @@ int uv_pipe_open(uv_pipe_t* pipe, uv_file file) { if (pipe->ipc) { assert(!(pipe->flags & UV_HANDLE_NON_OVERLAPPED_PIPE)); pipe->pipe.conn.ipc_remote_pid = uv_os_getppid(); - assert(pipe->pipe.conn.ipc_remote_pid != -1); + assert(pipe->pipe.conn.ipc_remote_pid != (DWORD) -1); } return 0; } @@ -2312,7 +2318,7 @@ uv_handle_type uv_pipe_pending_type(uv_pipe_t* handle) { } int uv_pipe_chmod(uv_pipe_t* handle, int mode) { - SID_IDENTIFIER_AUTHORITY sid_world = SECURITY_WORLD_SID_AUTHORITY; + SID_IDENTIFIER_AUTHORITY sid_world = { SECURITY_WORLD_SID_AUTHORITY }; PACL old_dacl, new_dacl; PSECURITY_DESCRIPTOR sd; EXPLICIT_ACCESS ea; @@ -2347,7 +2353,7 @@ int uv_pipe_chmod(uv_pipe_t* handle, int mode) { error = GetLastError(); goto clean_sid; } - + memset(&ea, 0, sizeof(EXPLICIT_ACCESS)); if (mode & UV_READABLE) ea.grfAccessPermissions |= GENERIC_READ | FILE_WRITE_ATTRIBUTES; diff --git a/src/win/poll.c b/src/win/poll.c index 77eb071..8785859 100644 --- a/src/win/poll.c +++ b/src/win/poll.c @@ -75,7 +75,7 @@ static AFD_POLL_INFO* uv__get_afd_poll_info_dummy(void) { static void uv__fast_poll_submit_poll_req(uv_loop_t* loop, uv_poll_t* handle) { uv_req_t* req; AFD_POLL_INFO* afd_poll_info; - DWORD result; + int result; /* Find a yet unsubmitted req to submit. */ if (handle->submitted_events_1 == 0) { @@ -134,32 +134,6 @@ static void uv__fast_poll_submit_poll_req(uv_loop_t* loop, uv_poll_t* handle) { } -static int uv__fast_poll_cancel_poll_req(uv_loop_t* loop, uv_poll_t* handle) { - AFD_POLL_INFO afd_poll_info; - DWORD result; - - afd_poll_info.Exclusive = TRUE; - afd_poll_info.NumberOfHandles = 1; - afd_poll_info.Timeout.QuadPart = INT64_MAX; - afd_poll_info.Handles[0].Handle = (HANDLE) handle->socket; - afd_poll_info.Handles[0].Status = 0; - afd_poll_info.Handles[0].Events = AFD_POLL_ALL; - - result = uv_msafd_poll(handle->socket, - &afd_poll_info, - uv__get_afd_poll_info_dummy(), - uv__get_overlapped_dummy()); - - if (result == SOCKET_ERROR) { - DWORD error = WSAGetLastError(); - if (error != WSA_IO_PENDING) - return error; - } - - return 0; -} - - static void uv__fast_poll_process_poll_req(uv_loop_t* loop, uv_poll_t* handle, uv_req_t* req) { unsigned char mask_events; @@ -226,44 +200,6 @@ static void uv__fast_poll_process_poll_req(uv_loop_t* loop, uv_poll_t* handle, } -static int uv__fast_poll_set(uv_loop_t* loop, uv_poll_t* handle, int events) { - assert(handle->type == UV_POLL); - assert(!(handle->flags & UV_HANDLE_CLOSING)); - assert((events & ~(UV_READABLE | UV_WRITABLE | UV_DISCONNECT)) == 0); - - handle->events = events; - - if (handle->events != 0) { - uv__handle_start(handle); - } else { - uv__handle_stop(handle); - } - - if ((handle->events & ~(handle->submitted_events_1 | - handle->submitted_events_2)) != 0) { - uv__fast_poll_submit_poll_req(handle->loop, handle); - } - - return 0; -} - - -static int uv__fast_poll_close(uv_loop_t* loop, uv_poll_t* handle) { - handle->events = 0; - uv__handle_closing(handle); - - if (handle->submitted_events_1 == 0 && - handle->submitted_events_2 == 0) { - uv_want_endgame(loop, (uv_handle_t*) handle); - return 0; - } else { - /* Cancel outstanding poll requests by executing another, unique poll - * request that forces the outstanding ones to return. */ - return uv__fast_poll_cancel_poll_req(loop, handle); - } -} - - static SOCKET uv__fast_poll_create_peer_socket(HANDLE iocp, WSAPROTOCOL_INFOW* protocol_info) { SOCKET sock = 0; @@ -469,41 +405,6 @@ static void uv__slow_poll_process_poll_req(uv_loop_t* loop, uv_poll_t* handle, } -static int uv__slow_poll_set(uv_loop_t* loop, uv_poll_t* handle, int events) { - assert(handle->type == UV_POLL); - assert(!(handle->flags & UV_HANDLE_CLOSING)); - assert((events & ~(UV_READABLE | UV_WRITABLE)) == 0); - - handle->events = events; - - if (handle->events != 0) { - uv__handle_start(handle); - } else { - uv__handle_stop(handle); - } - - if ((handle->events & - ~(handle->submitted_events_1 | handle->submitted_events_2)) != 0) { - uv__slow_poll_submit_poll_req(handle->loop, handle); - } - - return 0; -} - - -static int uv__slow_poll_close(uv_loop_t* loop, uv_poll_t* handle) { - handle->events = 0; - uv__handle_closing(handle); - - if (handle->submitted_events_1 == 0 && - handle->submitted_events_2 == 0) { - uv_want_endgame(loop, (uv_handle_t*) handle); - } - - return 0; -} - - int uv_poll_init(uv_loop_t* loop, uv_poll_t* handle, int fd) { return uv_poll_init_socket(loop, handle, (SOCKET) uv__get_osfhandle(fd)); } @@ -582,35 +483,43 @@ int uv_poll_init_socket(uv_loop_t* loop, uv_poll_t* handle, } -int uv_poll_start(uv_poll_t* handle, int events, uv_poll_cb cb) { - int err; +static int uv__poll_set(uv_poll_t* handle, int events, uv_poll_cb cb) { + int submitted_events; - if (!(handle->flags & UV_HANDLE_POLL_SLOW)) { - err = uv__fast_poll_set(handle->loop, handle, events); - } else { - err = uv__slow_poll_set(handle->loop, handle, events); - } + assert(handle->type == UV_POLL); + assert(!(handle->flags & UV_HANDLE_CLOSING)); + assert((events & ~(UV_READABLE | UV_WRITABLE | UV_DISCONNECT)) == 0); + + handle->events = events; + handle->poll_cb = cb; - if (err) { - return uv_translate_sys_error(err); + if (handle->events == 0) { + uv__handle_stop(handle); + return 0; } - handle->poll_cb = cb; + uv__handle_start(handle); + submitted_events = handle->submitted_events_1 | handle->submitted_events_2; + + if (handle->events & ~submitted_events) { + if (handle->flags & UV_HANDLE_POLL_SLOW) { + uv__slow_poll_submit_poll_req(handle->loop, handle); + } else { + uv__fast_poll_submit_poll_req(handle->loop, handle); + } + } return 0; } -int uv_poll_stop(uv_poll_t* handle) { - int err; +int uv_poll_start(uv_poll_t* handle, int events, uv_poll_cb cb) { + return uv__poll_set(handle, events, cb); +} - if (!(handle->flags & UV_HANDLE_POLL_SLOW)) { - err = uv__fast_poll_set(handle->loop, handle, 0); - } else { - err = uv__slow_poll_set(handle->loop, handle, 0); - } - return uv_translate_sys_error(err); +int uv_poll_stop(uv_poll_t* handle) { + return uv__poll_set(handle, 0, handle->poll_cb); } @@ -624,11 +533,43 @@ void uv_process_poll_req(uv_loop_t* loop, uv_poll_t* handle, uv_req_t* req) { int uv_poll_close(uv_loop_t* loop, uv_poll_t* handle) { - if (!(handle->flags & UV_HANDLE_POLL_SLOW)) { - return uv__fast_poll_close(loop, handle); - } else { - return uv__slow_poll_close(loop, handle); + AFD_POLL_INFO afd_poll_info; + DWORD error; + int result; + + handle->events = 0; + uv__handle_closing(handle); + + if (handle->submitted_events_1 == 0 && + handle->submitted_events_2 == 0) { + uv_want_endgame(loop, (uv_handle_t*) handle); + return 0; } + + if (handle->flags & UV_HANDLE_POLL_SLOW) + return 0; + + /* Cancel outstanding poll requests by executing another, unique poll + * request that forces the outstanding ones to return. */ + afd_poll_info.Exclusive = TRUE; + afd_poll_info.NumberOfHandles = 1; + afd_poll_info.Timeout.QuadPart = INT64_MAX; + afd_poll_info.Handles[0].Handle = (HANDLE) handle->socket; + afd_poll_info.Handles[0].Status = 0; + afd_poll_info.Handles[0].Events = AFD_POLL_ALL; + + result = uv_msafd_poll(handle->socket, + &afd_poll_info, + uv__get_afd_poll_info_dummy(), + uv__get_overlapped_dummy()); + + if (result == SOCKET_ERROR) { + error = WSAGetLastError(); + if (error != WSA_IO_PENDING) + return uv_translate_sys_error(error); + } + + return 0; } diff --git a/src/win/process.c b/src/win/process.c index b47f203..73543c6 100644 --- a/src/win/process.c +++ b/src/win/process.c @@ -58,7 +58,6 @@ static const env_var_t required_vars[] = { /* keep me sorted */ E_V("USERPROFILE"), E_V("WINDIR"), }; -static size_t n_required_vars = ARRAY_SIZE(required_vars); static HANDLE uv_global_job_handle_; @@ -692,7 +691,7 @@ int make_program_env(char* env_block[], WCHAR** dst_ptr) { WCHAR* dst_copy; WCHAR** ptr_copy; WCHAR** env_copy; - DWORD* required_vars_value_len = alloca(n_required_vars * sizeof(DWORD*)); + DWORD required_vars_value_len[ARRAY_SIZE(required_vars)]; /* first pass: determine size in UTF-16 */ for (env = env_block; *env; env++) { @@ -714,7 +713,7 @@ int make_program_env(char* env_block[], WCHAR** dst_ptr) { /* second pass: copy to UTF-16 environment block */ dst_copy = (WCHAR*)uv__malloc(env_len * sizeof(WCHAR)); - if (!dst_copy) { + if (dst_copy == NULL && env_len > 0) { return ERROR_OUTOFMEMORY; } env_copy = alloca(env_block_count * sizeof(WCHAR*)); @@ -739,13 +738,13 @@ int make_program_env(char* env_block[], WCHAR** dst_ptr) { } } *ptr_copy = NULL; - assert(env_len == ptr - dst_copy); + assert(env_len == 0 || env_len == (size_t) (ptr - dst_copy)); /* sort our (UTF-16) copy */ qsort(env_copy, env_block_count-1, sizeof(wchar_t*), qsort_wcscmp); /* third pass: check for required variables */ - for (ptr_copy = env_copy, i = 0; i < n_required_vars; ) { + for (ptr_copy = env_copy, i = 0; i < ARRAY_SIZE(required_vars); ) { int cmp; if (!*ptr_copy) { cmp = -1; @@ -778,10 +777,10 @@ int make_program_env(char* env_block[], WCHAR** dst_ptr) { } for (ptr = dst, ptr_copy = env_copy, i = 0; - *ptr_copy || i < n_required_vars; + *ptr_copy || i < ARRAY_SIZE(required_vars); ptr += len) { int cmp; - if (i >= n_required_vars) { + if (i >= ARRAY_SIZE(required_vars)) { cmp = 1; } else if (!*ptr_copy) { cmp = -1; @@ -799,7 +798,7 @@ int make_program_env(char* env_block[], WCHAR** dst_ptr) { var_size = GetEnvironmentVariableW(required_vars[i].wide, ptr, (int) (env_len - (ptr - dst))); - if (var_size != len-1) { /* race condition? */ + if (var_size != (DWORD) (len - 1)) { /* TODO: handle race condition? */ uv_fatal_error(GetLastError(), "GetEnvironmentVariableW"); } } @@ -815,7 +814,7 @@ int make_program_env(char* env_block[], WCHAR** dst_ptr) { } /* Terminate with an extra NULL. */ - assert(env_len == (ptr - dst)); + assert(env_len == (size_t) (ptr - dst)); *ptr = L'\0'; uv__free(dst_copy); @@ -964,6 +963,8 @@ int uv_spawn(uv_loop_t* loop, UV_PROCESS_SETGID | UV_PROCESS_SETUID | UV_PROCESS_WINDOWS_HIDE | + UV_PROCESS_WINDOWS_HIDE_CONSOLE | + UV_PROCESS_WINDOWS_HIDE_GUI | UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS))); err = uv_utf8_to_utf16_alloc(options->file, &application); @@ -1065,7 +1066,8 @@ int uv_spawn(uv_loop_t* loop, process_flags = CREATE_UNICODE_ENVIRONMENT; - if (options->flags & UV_PROCESS_WINDOWS_HIDE) { + if ((options->flags & UV_PROCESS_WINDOWS_HIDE_CONSOLE) || + (options->flags & UV_PROCESS_WINDOWS_HIDE)) { /* Avoid creating console window if stdio is not inherited. */ for (i = 0; i < options->stdio_count; i++) { if (options->stdio[i].flags & UV_INHERIT_FD) @@ -1073,7 +1075,9 @@ int uv_spawn(uv_loop_t* loop, if (i == options->stdio_count - 1) process_flags |= CREATE_NO_WINDOW; } - + } + 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; } else { diff --git a/src/win/signal.c b/src/win/signal.c index 3d0b8a3..3d9f92c 100644 --- a/src/win/signal.c +++ b/src/win/signal.c @@ -46,6 +46,11 @@ void uv_signals_init(void) { } +void uv__signal_cleanup(void) { + /* TODO(bnoordhuis) Undo effects of uv_signal_init()? */ +} + + static int uv__signal_compare(uv_signal_t* w1, uv_signal_t* w2) { /* Compare signums first so all watchers with the same signnum end up * adjacent. */ @@ -190,7 +195,7 @@ int uv__signal_start(uv_signal_t* handle, int signum, int oneshot) { /* Test for invalid signal values. */ - if (signum != SIGWINCH && (signum <= 0 || signum >= NSIG)) + if (signum <= 0 || signum >= NSIG) return UV_EINVAL; /* Short circuit: if the signal watcher is already watching {signum} don't go diff --git a/src/win/stream.c b/src/win/stream.c index 7656627..46a0709 100644 --- a/src/win/stream.c +++ b/src/win/stream.c @@ -198,8 +198,10 @@ int uv_try_write(uv_stream_t* stream, int uv_shutdown(uv_shutdown_t* req, uv_stream_t* handle, uv_shutdown_cb cb) { uv_loop_t* loop = handle->loop; - if (!(handle->flags & UV_HANDLE_WRITABLE)) { - return UV_EPIPE; + if (!(handle->flags & UV_HANDLE_WRITABLE) || + handle->flags & UV_HANDLE_SHUTTING || + uv__is_closing(handle)) { + return UV_ENOTCONN; } UV_REQ_INIT(req, UV_SHUTDOWN); @@ -207,6 +209,7 @@ int uv_shutdown(uv_shutdown_t* req, uv_stream_t* handle, uv_shutdown_cb cb) { req->cb = cb; handle->flags &= ~UV_HANDLE_WRITABLE; + handle->flags |= UV_HANDLE_SHUTTING; handle->stream.conn.shutdown_req = req; handle->reqs_pending++; REGISTER_HANDLE_REQ(loop, handle, req); diff --git a/src/win/tcp.c b/src/win/tcp.c index 8b6f0a5..941c801 100644 --- a/src/win/tcp.c +++ b/src/win/tcp.c @@ -251,7 +251,7 @@ void uv_tcp_endgame(uv_loop_t* loop, uv_tcp_t* handle) { UnregisterWait(req->wait_handle); req->wait_handle = INVALID_HANDLE_VALUE; } - if (req->event_handle) { + if (req->event_handle != NULL) { CloseHandle(req->event_handle); req->event_handle = NULL; } @@ -268,7 +268,7 @@ void uv_tcp_endgame(uv_loop_t* loop, uv_tcp_t* handle) { UnregisterWait(handle->read_req.wait_handle); handle->read_req.wait_handle = INVALID_HANDLE_VALUE; } - if (handle->read_req.event_handle) { + if (handle->read_req.event_handle != NULL) { CloseHandle(handle->read_req.event_handle); handle->read_req.event_handle = NULL; } @@ -428,6 +428,7 @@ static void uv_tcp_queue_accept(uv_tcp_t* handle, uv_tcp_accept_t* req) { /* Prepare the overlapped structure. */ memset(&(req->u.io.overlapped), 0, sizeof(req->u.io.overlapped)); if (handle->flags & UV_HANDLE_EMULATE_IOCP) { + assert(req->event_handle != NULL); req->u.io.overlapped.hEvent = (HANDLE) ((ULONG_PTR) req->event_handle | 1); } @@ -466,7 +467,7 @@ static void uv_tcp_queue_accept(uv_tcp_t* handle, uv_tcp_accept_t* req) { closesocket(accept_socket); /* Destroy the event handle */ if (handle->flags & UV_HANDLE_EMULATE_IOCP) { - CloseHandle(req->u.io.overlapped.hEvent); + CloseHandle(req->event_handle); req->event_handle = NULL; } } @@ -509,7 +510,7 @@ static void uv_tcp_queue_read(uv_loop_t* loop, uv_tcp_t* handle) { /* Prepare the overlapped structure. */ memset(&(req->u.io.overlapped), 0, sizeof(req->u.io.overlapped)); if (handle->flags & UV_HANDLE_EMULATE_IOCP) { - assert(req->event_handle); + assert(req->event_handle != NULL); req->u.io.overlapped.hEvent = (HANDLE) ((ULONG_PTR) req->event_handle | 1); } @@ -549,6 +550,21 @@ static void uv_tcp_queue_read(uv_loop_t* loop, uv_tcp_t* handle) { } +int uv_tcp_close_reset(uv_tcp_t* handle, uv_close_cb close_cb) { + struct linger l = { 1, 0 }; + + /* Disallow setting SO_LINGER to zero due to some platform inconsistencies */ + if (handle->flags & UV_HANDLE_SHUTTING) + return UV_EINVAL; + + if (0 != setsockopt(handle->socket, SOL_SOCKET, SO_LINGER, (const char*)&l, sizeof(l))) + return uv_translate_sys_error(WSAGetLastError()); + + uv_close((uv_handle_t*) handle, close_cb); + return 0; +} + + int uv_tcp_listen(uv_tcp_t* handle, int backlog, uv_connection_cb cb) { unsigned int i, simultaneous_accepts; uv_tcp_accept_t* req; @@ -597,8 +613,8 @@ int uv_tcp_listen(uv_tcp_t* handle, int backlog, uv_connection_cb cb) { simultaneous_accepts = handle->flags & UV_HANDLE_TCP_SINGLE_ACCEPT ? 1 : uv_simultaneous_server_accepts; - if(!handle->tcp.serv.accept_reqs) { - handle->tcp.serv.accept_reqs = (uv_tcp_accept_t*) + if (handle->tcp.serv.accept_reqs == NULL) { + handle->tcp.serv.accept_reqs = uv__malloc(uv_simultaneous_server_accepts * sizeof(uv_tcp_accept_t)); if (!handle->tcp.serv.accept_reqs) { uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc"); @@ -613,7 +629,7 @@ int uv_tcp_listen(uv_tcp_t* handle, int backlog, uv_connection_cb cb) { req->wait_handle = INVALID_HANDLE_VALUE; if (handle->flags & UV_HANDLE_EMULATE_IOCP) { req->event_handle = CreateEvent(NULL, 0, 0, NULL); - if (!req->event_handle) { + if (req->event_handle == NULL) { uv_fatal_error(GetLastError(), "CreateEvent"); } } else { @@ -722,9 +738,9 @@ int uv_tcp_read_start(uv_tcp_t* handle, uv_alloc_cb alloc_cb, * request pending. */ if (!(handle->flags & UV_HANDLE_READ_PENDING)) { if (handle->flags & UV_HANDLE_EMULATE_IOCP && - !handle->read_req.event_handle) { + handle->read_req.event_handle == NULL) { handle->read_req.event_handle = CreateEvent(NULL, 0, 0, NULL); - if (!handle->read_req.event_handle) { + if (handle->read_req.event_handle == NULL) { uv_fatal_error(GetLastError(), "CreateEvent"); } } @@ -809,44 +825,24 @@ static int uv_tcp_try_connect(uv_connect_t* req, int uv_tcp_getsockname(const uv_tcp_t* handle, struct sockaddr* name, int* namelen) { - int result; - - if (handle->socket == INVALID_SOCKET) { - return UV_EINVAL; - } - - if (handle->delayed_error) { - return uv_translate_sys_error(handle->delayed_error); - } - result = getsockname(handle->socket, name, namelen); - if (result != 0) { - return uv_translate_sys_error(WSAGetLastError()); - } - - return 0; + return uv__getsockpeername((const uv_handle_t*) handle, + getsockname, + name, + namelen, + handle->delayed_error); } int uv_tcp_getpeername(const uv_tcp_t* handle, struct sockaddr* name, int* namelen) { - int result; - if (handle->socket == INVALID_SOCKET) { - return UV_EINVAL; - } - - if (handle->delayed_error) { - return uv_translate_sys_error(handle->delayed_error); - } - - result = getpeername(handle->socket, name, namelen); - if (result != 0) { - return uv_translate_sys_error(WSAGetLastError()); - } - - return 0; + return uv__getsockpeername((const uv_handle_t*) handle, + getpeername, + name, + namelen, + handle->delayed_error); } @@ -867,7 +863,7 @@ int uv_tcp_write(uv_loop_t* loop, memset(&(req->u.io.overlapped), 0, sizeof(req->u.io.overlapped)); if (handle->flags & UV_HANDLE_EMULATE_IOCP) { req->event_handle = CreateEvent(NULL, 0, 0, NULL); - if (!req->event_handle) { + if (req->event_handle == NULL) { uv_fatal_error(GetLastError(), "CreateEvent"); } req->u.io.overlapped.hEvent = (HANDLE) ((ULONG_PTR) req->event_handle | 1); @@ -945,6 +941,7 @@ void uv_process_tcp_read_req(uv_loop_t* loop, uv_tcp_t* handle, uv_req_t* req) { DWORD bytes, flags, err; uv_buf_t buf; + int count; assert(handle->type == UV_TCP); @@ -999,7 +996,8 @@ void uv_process_tcp_read_req(uv_loop_t* loop, uv_tcp_t* handle, } /* Do nonblocking reads until the buffer is empty */ - while (handle->flags & UV_HANDLE_READING) { + count = 32; + while ((handle->flags & UV_HANDLE_READING) && (count-- > 0)) { buf = uv_buf_init(NULL, 0); handle->alloc_cb((uv_handle_t*) handle, 65536, &buf); if (buf.base == NULL || buf.len == 0) { @@ -1083,7 +1081,7 @@ void uv_process_tcp_write_req(uv_loop_t* loop, uv_tcp_t* handle, UnregisterWait(req->wait_handle); req->wait_handle = INVALID_HANDLE_VALUE; } - if (req->event_handle) { + if (req->event_handle != NULL) { CloseHandle(req->event_handle); req->event_handle = NULL; } diff --git a/src/win/thread.c b/src/win/thread.c index 6e41dcd..89c53ad 100644 --- a/src/win/thread.c +++ b/src/win/thread.c @@ -23,6 +23,12 @@ #include #include +#if defined(__MINGW64_VERSION_MAJOR) +/* MemoryBarrier expands to __mm_mfence in some cases (x86+sse2), which may + * require this header in some versions of mingw64. */ +#include +#endif + #include "uv.h" #include "internal.h" @@ -106,9 +112,34 @@ static UINT __stdcall uv__thread_start(void* arg) { int uv_thread_create(uv_thread_t *tid, void (*entry)(void *arg), void *arg) { + uv_thread_options_t params; + params.flags = UV_THREAD_NO_FLAGS; + return uv_thread_create_ex(tid, ¶ms, entry, arg); +} + +int uv_thread_create_ex(uv_thread_t* tid, + const uv_thread_options_t* params, + void (*entry)(void *arg), + void *arg) { struct thread_ctx* ctx; int err; HANDLE thread; + SYSTEM_INFO sysinfo; + size_t stack_size; + size_t pagesize; + + stack_size = + params->flags & UV_THREAD_HAS_STACK_SIZE ? params->stack_size : 0; + + if (stack_size != 0) { + GetNativeSystemInfo(&sysinfo); + pagesize = (size_t)sysinfo.dwPageSize; + /* Round up to the nearest page boundary. */ + stack_size = (stack_size + pagesize - 1) &~ (pagesize - 1); + + if ((unsigned)stack_size != stack_size) + return UV_EINVAL; + } ctx = uv__malloc(sizeof(*ctx)); if (ctx == NULL) @@ -118,9 +149,9 @@ int uv_thread_create(uv_thread_t *tid, void (*entry)(void *arg), void *arg) { ctx->arg = arg; /* Create the thread in suspended state so we have a chance to pass - * its own creation handle to it */ + * its own creation handle to it */ thread = (HANDLE) _beginthreadex(NULL, - 0, + (unsigned)stack_size, uv__thread_start, ctx, CREATE_SUSPENDED, diff --git a/src/win/tty.c b/src/win/tty.c index 7045b11..488d9b2 100644 --- a/src/win/tty.c +++ b/src/win/tty.c @@ -46,14 +46,16 @@ #define UNICODE_REPLACEMENT_CHARACTER (0xfffd) -#define ANSI_NORMAL 0x00 -#define ANSI_ESCAPE_SEEN 0x02 -#define ANSI_CSI 0x04 -#define ANSI_ST_CONTROL 0x08 -#define ANSI_IGNORE 0x10 -#define ANSI_IN_ARG 0x20 -#define ANSI_IN_STRING 0x40 -#define ANSI_BACKSLASH_SEEN 0x80 +#define ANSI_NORMAL 0x0000 +#define ANSI_ESCAPE_SEEN 0x0002 +#define ANSI_CSI 0x0004 +#define ANSI_ST_CONTROL 0x0008 +#define ANSI_IGNORE 0x0010 +#define ANSI_IN_ARG 0x0020 +#define ANSI_IN_STRING 0x0040 +#define ANSI_BACKSLASH_SEEN 0x0080 +#define ANSI_EXTENSION 0x0100 +#define ANSI_DECSCUSR 0x0200 #define MAX_INPUT_BUFFER_LENGTH 8192 #define MAX_CONSOLE_CHAR 8192 @@ -62,7 +64,12 @@ #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 #endif -static void uv_tty_capture_initial_style(CONSOLE_SCREEN_BUFFER_INFO* info); +#define CURSOR_SIZE_SMALL 25 +#define CURSOR_SIZE_LARGE 100 + +static void uv_tty_capture_initial_style( + CONSOLE_SCREEN_BUFFER_INFO* screen_buffer_info, + CONSOLE_CURSOR_INFO* cursor_info); static void uv_tty_update_virtual_window(CONSOLE_SCREEN_BUFFER_INFO* info); static int uv__cancel_read_console(uv_tty_t* handle); @@ -120,6 +127,8 @@ static int uv_tty_virtual_width = -1; static HANDLE uv__tty_console_handle = INVALID_HANDLE_VALUE; static int uv__tty_console_height = -1; static int uv__tty_console_width = -1; +static HANDLE uv__tty_console_resized = INVALID_HANDLE_VALUE; +static uv_mutex_t uv__tty_console_resize_mutex; static DWORD WINAPI uv__tty_console_resize_message_loop_thread(void* param); static void CALLBACK uv__tty_console_resize_event(HWINEVENTHOOK hWinEventHook, @@ -129,6 +138,8 @@ static void CALLBACK uv__tty_console_resize_event(HWINEVENTHOOK hWinEventHook, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime); +static DWORD WINAPI uv__tty_console_resize_watcher_thread(void* param); +static void uv__tty_console_signal_resize(void); /* We use a semaphore rather than a mutex or critical section because in some cases (uv__cancel_read_console) we need take the lock in the main thread and @@ -145,13 +156,11 @@ static char uv_tty_default_fg_bright = 0; static char uv_tty_default_bg_bright = 0; static char uv_tty_default_inverse = 0; -typedef enum { - UV_SUPPORTED, - UV_UNCHECKED, - UV_UNSUPPORTED -} uv_vtermstate_t; +static CONSOLE_CURSOR_INFO uv_tty_default_cursor_info; + /* Determine whether or not ANSI support is enabled. */ -static uv_vtermstate_t uv__vterm_state = UV_UNCHECKED; +static BOOL uv__need_check_vterm_state = TRUE; +static uv_tty_vtermstate_t uv__vterm_state = UV_TTY_UNSUPPORTED; static void uv__determine_vterm_state(HANDLE handle); void uv_console_init(void) { @@ -164,10 +173,16 @@ void uv_console_init(void) { OPEN_EXISTING, 0, 0); - if (uv__tty_console_handle != NULL) { + if (uv__tty_console_handle != INVALID_HANDLE_VALUE) { + CONSOLE_SCREEN_BUFFER_INFO sb_info; QueueUserWorkItem(uv__tty_console_resize_message_loop_thread, NULL, WT_EXECUTELONGFUNCTION); + uv_mutex_init(&uv__tty_console_resize_mutex); + if (GetConsoleScreenBufferInfo(uv__tty_console_handle, &sb_info)) { + uv__tty_console_width = sb_info.dwSize.X; + uv__tty_console_height = sb_info.srWindow.Bottom - sb_info.srWindow.Top + 1; + } } } @@ -177,6 +192,7 @@ int uv_tty_init(uv_loop_t* loop, uv_tty_t* tty, uv_file fd, int unused) { DWORD NumberOfEvents; HANDLE handle; CONSOLE_SCREEN_BUFFER_INFO screen_buffer_info; + CONSOLE_CURSOR_INFO cursor_info; (void)unused; uv__once_init(); @@ -209,15 +225,20 @@ int uv_tty_init(uv_loop_t* loop, uv_tty_t* tty, uv_file fd, int unused) { return uv_translate_sys_error(GetLastError()); } + /* Obtain the cursor info with the output handle. */ + if (!GetConsoleCursorInfo(handle, &cursor_info)) { + return uv_translate_sys_error(GetLastError()); + } + /* Obtain the tty_output_lock because the virtual window state is shared * between all uv_tty_t handles. */ uv_sem_wait(&uv_tty_output_lock); - if (uv__vterm_state == UV_UNCHECKED) + if (uv__need_check_vterm_state) uv__determine_vterm_state(handle); - /* Remember the original console text attributes. */ - uv_tty_capture_initial_style(&screen_buffer_info); + /* Remember the original console text attributes and cursor info. */ + uv_tty_capture_initial_style(&screen_buffer_info, &cursor_info); uv_tty_update_virtual_window(&screen_buffer_info); @@ -268,7 +289,9 @@ int uv_tty_init(uv_loop_t* loop, uv_tty_t* tty, uv_file fd, int unused) { /* Set the default console text attributes based on how the console was * configured when libuv started. */ -static void uv_tty_capture_initial_style(CONSOLE_SCREEN_BUFFER_INFO* info) { +static void uv_tty_capture_initial_style( + CONSOLE_SCREEN_BUFFER_INFO* screen_buffer_info, + CONSOLE_CURSOR_INFO* cursor_info) { static int style_captured = 0; /* Only do this once. @@ -277,7 +300,7 @@ static void uv_tty_capture_initial_style(CONSOLE_SCREEN_BUFFER_INFO* info) { return; /* Save raw win32 attributes. */ - uv_tty_default_text_attributes = info->wAttributes; + uv_tty_default_text_attributes = screen_buffer_info->wAttributes; /* Convert black text on black background to use white text. */ if (uv_tty_default_text_attributes == 0) @@ -317,6 +340,9 @@ static void uv_tty_capture_initial_style(CONSOLE_SCREEN_BUFFER_INFO* info) { if (uv_tty_default_text_attributes & COMMON_LVB_REVERSE_VIDEO) uv_tty_default_inverse = 1; + /* Save the cursor size and the cursor state. */ + uv_tty_default_cursor_info = *cursor_info; + style_captured = 1; } @@ -360,6 +386,8 @@ int uv_tty_set_mode(uv_tty_t* tty, uv_tty_mode_t mode) { } } else { was_reading = 0; + alloc_cb = NULL; + read_cb = NULL; } uv_sem_wait(&uv_tty_output_lock); @@ -726,6 +754,12 @@ void uv_process_tty_read_raw_req(uv_loop_t* loop, uv_tty_t* handle, } records_left--; + /* We might be not subscribed to EVENT_CONSOLE_LAYOUT or we might be + * running under some TTY emulator that does not send those events. */ + if (handle->tty.rd.last_input_record.EventType == WINDOW_BUFFER_SIZE_EVENT) { + uv__tty_console_signal_resize(); + } + /* Ignore other events that are not key events. */ if (handle->tty.rd.last_input_record.EventType != KEY_EVENT) { continue; @@ -733,8 +767,9 @@ void uv_process_tty_read_raw_req(uv_loop_t* loop, uv_tty_t* handle, /* Ignore keyup events, unless the left alt key was held and a valid * unicode character was emitted. */ - if (!KEV.bKeyDown && !(((KEV.dwControlKeyState & LEFT_ALT_PRESSED) || - KEV.wVirtualKeyCode==VK_MENU) && KEV.uChar.UnicodeChar != 0)) { + if (!KEV.bKeyDown && + (KEV.wVirtualKeyCode != VK_MENU || + KEV.uChar.UnicodeChar == 0)) { continue; } @@ -791,8 +826,9 @@ void uv_process_tty_read_raw_req(uv_loop_t* loop, uv_tty_t* handle, if (KEV.uChar.UnicodeChar >= 0xDC00 && KEV.uChar.UnicodeChar < 0xE000) { /* UTF-16 surrogate pair */ - WCHAR utf16_buffer[2] = { handle->tty.rd.last_utf16_high_surrogate, - KEV.uChar.UnicodeChar}; + WCHAR utf16_buffer[2]; + utf16_buffer[0] = handle->tty.rd.last_utf16_high_surrogate; + utf16_buffer[1] = KEV.uChar.UnicodeChar; char_len = WideCharToMultiByte(CP_UTF8, 0, utf16_buffer, @@ -941,20 +977,15 @@ void uv_process_tty_read_line_req(uv_loop_t* loop, uv_tty_t* handle, handle->read_cb((uv_stream_t*) handle, uv_translate_sys_error(GET_REQ_ERROR(req)), &buf); - } else { - /* The read was cancelled, or whatever we don't care */ - handle->read_cb((uv_stream_t*) handle, 0, &buf); } - } else { - if (!(handle->flags & UV_HANDLE_CANCELLATION_PENDING)) { + if (!(handle->flags & UV_HANDLE_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); - } else { - handle->flags &= ~UV_HANDLE_CANCELLATION_PENDING; - handle->read_cb((uv_stream_t*) handle, 0, &buf); } + handle->flags &= ~UV_HANDLE_CANCELLATION_PENDING; } /* Wait for more input events. */ @@ -1033,6 +1064,7 @@ int uv_tty_read_stop(uv_tty_t* handle) { /* Cancel raw read. Write some bullshit event to force the console wait to * return. */ memset(&record, 0, sizeof record); + record.EventType = FOCUS_EVENT; if (!WriteConsoleInputW(handle->handle, &record, 1, &written)) { return GetLastError(); } @@ -1218,7 +1250,7 @@ static int uv_tty_move_caret(uv_tty_t* handle, int x, unsigned char x_relative, static int uv_tty_reset(uv_tty_t* handle, DWORD* error) { const COORD origin = {0, 0}; const WORD char_attrs = uv_tty_default_text_attributes; - CONSOLE_SCREEN_BUFFER_INFO info; + CONSOLE_SCREEN_BUFFER_INFO screen_buffer_info; DWORD count, written; if (*error != ERROR_SUCCESS) { @@ -1239,12 +1271,12 @@ static int uv_tty_reset(uv_tty_t* handle, DWORD* error) { /* Clear the screen buffer. */ retry: - if (!GetConsoleScreenBufferInfo(handle->handle, &info)) { - *error = GetLastError(); - return -1; + if (!GetConsoleScreenBufferInfo(handle->handle, &screen_buffer_info)) { + *error = GetLastError(); + return -1; } - count = info.dwSize.X * info.dwSize.Y; + count = screen_buffer_info.dwSize.X * screen_buffer_info.dwSize.Y; if (!(FillConsoleOutputCharacterW(handle->handle, L'\x20', @@ -1267,7 +1299,13 @@ static int uv_tty_reset(uv_tty_t* handle, DWORD* error) { /* Move the virtual window up to the top. */ uv_tty_virtual_offset = 0; - uv_tty_update_virtual_window(&info); + uv_tty_update_virtual_window(&screen_buffer_info); + + /* Reset the cursor size and the cursor state. */ + if (!SetConsoleCursorInfo(handle->handle, &uv_tty_default_cursor_info)) { + *error = GetLastError(); + return -1; + } return 0; } @@ -1606,6 +1644,31 @@ static int uv_tty_set_cursor_visibility(uv_tty_t* handle, return 0; } +static int uv_tty_set_cursor_shape(uv_tty_t* handle, int style, DWORD* error) { + CONSOLE_CURSOR_INFO cursor_info; + + if (!GetConsoleCursorInfo(handle->handle, &cursor_info)) { + *error = GetLastError(); + return -1; + } + + if (style == 0) { + cursor_info.dwSize = uv_tty_default_cursor_info.dwSize; + } else if (style <= 2) { + cursor_info.dwSize = CURSOR_SIZE_LARGE; + } else { + cursor_info.dwSize = CURSOR_SIZE_SMALL; + } + + if (!SetConsoleCursorInfo(handle->handle, &cursor_info)) { + *error = GetLastError(); + return -1; + } + + return 0; +} + + static int uv_tty_write_bufs(uv_tty_t* handle, const uv_buf_t bufs[], unsigned int nbufs, @@ -1613,28 +1676,16 @@ static int uv_tty_write_bufs(uv_tty_t* handle, /* We can only write 8k characters at a time. Windows can't handle much more * characters in a single console write anyway. */ WCHAR utf16_buf[MAX_CONSOLE_CHAR]; - WCHAR* utf16_buffer; DWORD utf16_buf_used = 0; - unsigned int i, len, max_len, pos; - int allocate = 0; - -#define FLUSH_TEXT() \ - do { \ - pos = 0; \ - do { \ - len = utf16_buf_used - pos; \ - if (len > MAX_CONSOLE_CHAR) \ - len = MAX_CONSOLE_CHAR; \ - uv_tty_emit_text(handle, &utf16_buffer[pos], len, error); \ - pos += len; \ - } while (pos < utf16_buf_used); \ - if (allocate) { \ - uv__free(utf16_buffer); \ - allocate = 0; \ - utf16_buffer = utf16_buf; \ - } \ - utf16_buf_used = 0; \ - } while (0) + unsigned int i; + +#define FLUSH_TEXT() \ + do { \ + if (utf16_buf_used > 0) { \ + uv_tty_emit_text(handle, utf16_buf, utf16_buf_used, error); \ + utf16_buf_used = 0; \ + } \ + } while (0) #define ENSURE_BUFFER_SPACE(wchars_needed) \ if (wchars_needed > ARRAY_SIZE(utf16_buf) - utf16_buf_used) { \ @@ -1645,54 +1696,18 @@ static int uv_tty_write_bufs(uv_tty_t* handle, unsigned char utf8_bytes_left = handle->tty.wr.utf8_bytes_left; unsigned int utf8_codepoint = handle->tty.wr.utf8_codepoint; unsigned char previous_eol = handle->tty.wr.previous_eol; - unsigned char ansi_parser_state = handle->tty.wr.ansi_parser_state; + unsigned short ansi_parser_state = handle->tty.wr.ansi_parser_state; /* Store the error here. If we encounter an error, stop trying to do i/o but * keep parsing the buffer so we leave the parser in a consistent state. */ *error = ERROR_SUCCESS; - utf16_buffer = utf16_buf; - uv_sem_wait(&uv_tty_output_lock); for (i = 0; i < nbufs; i++) { uv_buf_t buf = bufs[i]; unsigned int j; - if (uv__vterm_state == UV_SUPPORTED && buf.len > 0) { - utf16_buf_used = MultiByteToWideChar(CP_UTF8, - 0, - buf.base, - buf.len, - NULL, - 0); - - if (utf16_buf_used == 0) { - *error = GetLastError(); - break; - } - - max_len = (utf16_buf_used + 1) * sizeof(WCHAR); - allocate = max_len > MAX_CONSOLE_CHAR; - if (allocate) - utf16_buffer = uv__malloc(max_len); - if (!MultiByteToWideChar(CP_UTF8, - 0, - buf.base, - buf.len, - utf16_buffer, - utf16_buf_used)) { - if (allocate) - uv__free(utf16_buffer); - *error = GetLastError(); - break; - } - - FLUSH_TEXT(); - - continue; - } - for (j = 0; j < buf.len; j++) { unsigned char c = buf.base[j]; @@ -1749,7 +1764,9 @@ static int uv_tty_write_bufs(uv_tty_t* handle, } /* Parse vt100/ansi escape codes */ - if (ansi_parser_state == ANSI_NORMAL) { + if (uv__vterm_state == UV_TTY_SUPPORTED) { + /* Pass through escape codes if conhost supports them. */ + } else if (ansi_parser_state == ANSI_NORMAL) { switch (utf8_codepoint) { case '\033': ansi_parser_state = ANSI_ESCAPE_SEEN; @@ -1795,7 +1812,7 @@ static int uv_tty_write_bufs(uv_tty_t* handle, ansi_parser_state = ANSI_NORMAL; continue; - case '8': + case '8': /* Restore the cursor position and text attributes */ FLUSH_TEXT(); uv_tty_restore_state(handle, 1, error); @@ -1813,121 +1830,193 @@ static int uv_tty_write_bufs(uv_tty_t* handle, } } + } else if (ansi_parser_state == ANSI_IGNORE) { + /* We're ignoring this command. Stop only on command character. */ + if (utf8_codepoint >= '@' && utf8_codepoint <= '~') { + ansi_parser_state = ANSI_NORMAL; + } + continue; + + } else if (ansi_parser_state == ANSI_DECSCUSR) { + /* So far we've the sequence `ESC [ arg space`, and we're waiting for + * the final command byte. */ + if (utf8_codepoint >= '@' && utf8_codepoint <= '~') { + /* Command byte */ + if (utf8_codepoint == 'q') { + /* Change the cursor shape */ + int style = handle->tty.wr.ansi_csi_argc + ? handle->tty.wr.ansi_csi_argv[0] : 1; + if (style >= 0 && style <= 6) { + FLUSH_TEXT(); + uv_tty_set_cursor_shape(handle, style, error); + } + } + + /* Sequence ended - go back to normal state. */ + ansi_parser_state = ANSI_NORMAL; + continue; + } + /* Unexpected character, but sequence hasn't ended yet. Ignore the rest + * of the sequence. */ + ansi_parser_state = ANSI_IGNORE; + } else if (ansi_parser_state & ANSI_CSI) { - if (!(ansi_parser_state & ANSI_IGNORE)) { - if (utf8_codepoint >= '0' && utf8_codepoint <= '9') { - /* Parsing a numerical argument */ - - if (!(ansi_parser_state & ANSI_IN_ARG)) { - /* We were not currently parsing a number */ - - /* Check for too many arguments */ - if (handle->tty.wr.ansi_csi_argc >= ARRAY_SIZE(handle->tty.wr.ansi_csi_argv)) { - ansi_parser_state |= ANSI_IGNORE; - continue; - } - - ansi_parser_state |= ANSI_IN_ARG; - handle->tty.wr.ansi_csi_argc++; - handle->tty.wr.ansi_csi_argv[handle->tty.wr.ansi_csi_argc - 1] = - (unsigned short) utf8_codepoint - '0'; + /* So far we've seen `ESC [`, and we may or may not have already parsed + * some of the arguments that follow. */ + + if (utf8_codepoint >= '0' && utf8_codepoint <= '9') { + /* Parse a numerical argument. */ + if (!(ansi_parser_state & ANSI_IN_ARG)) { + /* We were not currently parsing a number, add a new one. */ + /* Check for that there are too many arguments. */ + if (handle->tty.wr.ansi_csi_argc >= + ARRAY_SIZE(handle->tty.wr.ansi_csi_argv)) { + ansi_parser_state = ANSI_IGNORE; continue; - } else { - /* We were already parsing a number. Parse next digit. */ - uint32_t value = 10 * - handle->tty.wr.ansi_csi_argv[handle->tty.wr.ansi_csi_argc - 1]; - - /* Check for overflow. */ - if (value > UINT16_MAX) { - ansi_parser_state |= ANSI_IGNORE; - continue; - } - - handle->tty.wr.ansi_csi_argv[handle->tty.wr.ansi_csi_argc - 1] = - (unsigned short) value + (utf8_codepoint - '0'); - continue; } + ansi_parser_state |= ANSI_IN_ARG; + handle->tty.wr.ansi_csi_argc++; + handle->tty.wr.ansi_csi_argv[handle->tty.wr.ansi_csi_argc - 1] = + (unsigned short) utf8_codepoint - '0'; + continue; + + } else { + /* We were already parsing a number. Parse next digit. */ + uint32_t value = 10 * + handle->tty.wr.ansi_csi_argv[handle->tty.wr.ansi_csi_argc - 1]; - } else if (utf8_codepoint == ';') { - /* Denotes the end of an argument. */ - if (ansi_parser_state & ANSI_IN_ARG) { - ansi_parser_state &= ~ANSI_IN_ARG; + /* Check for overflow. */ + if (value > UINT16_MAX) { + ansi_parser_state = ANSI_IGNORE; continue; + } - } else { - /* If ANSI_IN_ARG is not set, add another argument and default it - * to 0. */ + handle->tty.wr.ansi_csi_argv[handle->tty.wr.ansi_csi_argc - 1] = + (unsigned short) value + (utf8_codepoint - '0'); + continue; + } - /* Check for too many arguments */ - if (handle->tty.wr.ansi_csi_argc >= ARRAY_SIZE(handle->tty.wr.ansi_csi_argv)) { - ansi_parser_state |= ANSI_IGNORE; - continue; - } + } else if (utf8_codepoint == ';') { + /* Denotes the end of an argument. */ + if (ansi_parser_state & ANSI_IN_ARG) { + ansi_parser_state &= ~ANSI_IN_ARG; + continue; - handle->tty.wr.ansi_csi_argc++; - handle->tty.wr.ansi_csi_argv[handle->tty.wr.ansi_csi_argc - 1] = 0; + } else { + /* If ANSI_IN_ARG is not set, add another argument and default + * it to 0. */ + + /* Check for too many arguments */ + if (handle->tty.wr.ansi_csi_argc >= + + ARRAY_SIZE(handle->tty.wr.ansi_csi_argv)) { + ansi_parser_state = ANSI_IGNORE; continue; } - } else if (utf8_codepoint == '?' && !(ansi_parser_state & ANSI_IN_ARG) && - handle->tty.wr.ansi_csi_argc == 0) { - /* Ignores '?' if it is the first character after CSI[. This is an - * extension character from the VT100 codeset that is supported and - * used by most ANSI terminals today. */ + handle->tty.wr.ansi_csi_argc++; + handle->tty.wr.ansi_csi_argv[handle->tty.wr.ansi_csi_argc - 1] = 0; continue; + } - } else if (utf8_codepoint >= '@' && utf8_codepoint <= '~' && - (handle->tty.wr.ansi_csi_argc > 0 || utf8_codepoint != '[')) { - int x, y, d; + } else if (utf8_codepoint == '?' && + !(ansi_parser_state & ANSI_IN_ARG) && + !(ansi_parser_state & ANSI_EXTENSION) && + handle->tty.wr.ansi_csi_argc == 0) { + /* Pass through '?' if it is the first character after CSI */ + /* This is an extension character from the VT100 codeset */ + /* that is supported and used by most ANSI terminals today. */ + ansi_parser_state |= ANSI_EXTENSION; + continue; + + } else if (utf8_codepoint == ' ' && + !(ansi_parser_state & ANSI_EXTENSION)) { + /* We expect a command byte to follow after this space. The only + * command that we current support is 'set cursor style'. */ + ansi_parser_state = ANSI_DECSCUSR; + continue; + + } else if (utf8_codepoint >= '@' && utf8_codepoint <= '~') { + /* Command byte */ + if (ansi_parser_state & ANSI_EXTENSION) { + /* Sequence is `ESC [ ? args command`. */ + switch (utf8_codepoint) { + case 'l': + /* Hide the cursor */ + if (handle->tty.wr.ansi_csi_argc == 1 && + handle->tty.wr.ansi_csi_argv[0] == 25) { + FLUSH_TEXT(); + uv_tty_set_cursor_visibility(handle, 0, error); + } + break; + + case 'h': + /* Show the cursor */ + if (handle->tty.wr.ansi_csi_argc == 1 && + handle->tty.wr.ansi_csi_argv[0] == 25) { + FLUSH_TEXT(); + uv_tty_set_cursor_visibility(handle, 1, error); + } + break; + } - /* Command byte */ + } else { + /* Sequence is `ESC [ args command`. */ + int x, y, d; switch (utf8_codepoint) { case 'A': /* cursor up */ FLUSH_TEXT(); - y = -(handle->tty.wr.ansi_csi_argc ? handle->tty.wr.ansi_csi_argv[0] : 1); + y = -(handle->tty.wr.ansi_csi_argc + ? handle->tty.wr.ansi_csi_argv[0] : 1); uv_tty_move_caret(handle, 0, 1, y, 1, error); break; case 'B': /* cursor down */ FLUSH_TEXT(); - y = handle->tty.wr.ansi_csi_argc ? handle->tty.wr.ansi_csi_argv[0] : 1; + y = handle->tty.wr.ansi_csi_argc + ? handle->tty.wr.ansi_csi_argv[0] : 1; uv_tty_move_caret(handle, 0, 1, y, 1, error); break; case 'C': /* cursor forward */ FLUSH_TEXT(); - x = handle->tty.wr.ansi_csi_argc ? handle->tty.wr.ansi_csi_argv[0] : 1; + x = handle->tty.wr.ansi_csi_argc + ? handle->tty.wr.ansi_csi_argv[0] : 1; uv_tty_move_caret(handle, x, 1, 0, 1, error); break; case 'D': /* cursor back */ FLUSH_TEXT(); - x = -(handle->tty.wr.ansi_csi_argc ? handle->tty.wr.ansi_csi_argv[0] : 1); + x = -(handle->tty.wr.ansi_csi_argc + ? handle->tty.wr.ansi_csi_argv[0] : 1); uv_tty_move_caret(handle, x, 1, 0, 1, error); break; case 'E': /* cursor next line */ FLUSH_TEXT(); - y = handle->tty.wr.ansi_csi_argc ? handle->tty.wr.ansi_csi_argv[0] : 1; + y = handle->tty.wr.ansi_csi_argc + ? handle->tty.wr.ansi_csi_argv[0] : 1; uv_tty_move_caret(handle, 0, 0, y, 1, error); break; case 'F': /* cursor previous line */ FLUSH_TEXT(); - y = -(handle->tty.wr.ansi_csi_argc ? handle->tty.wr.ansi_csi_argv[0] : 1); + y = -(handle->tty.wr.ansi_csi_argc + ? handle->tty.wr.ansi_csi_argv[0] : 1); uv_tty_move_caret(handle, 0, 0, y, 1, error); break; case 'G': /* cursor horizontal move absolute */ FLUSH_TEXT(); - x = (handle->tty.wr.ansi_csi_argc >= 1 && handle->tty.wr.ansi_csi_argv[0]) + x = (handle->tty.wr.ansi_csi_argc >= 1 && + handle->tty.wr.ansi_csi_argv[0]) ? handle->tty.wr.ansi_csi_argv[0] - 1 : 0; uv_tty_move_caret(handle, x, 0, 0, 1, error); break; @@ -1936,9 +2025,11 @@ static int uv_tty_write_bufs(uv_tty_t* handle, case 'f': /* cursor move absolute */ FLUSH_TEXT(); - y = (handle->tty.wr.ansi_csi_argc >= 1 && handle->tty.wr.ansi_csi_argv[0]) + y = (handle->tty.wr.ansi_csi_argc >= 1 && + handle->tty.wr.ansi_csi_argv[0]) ? handle->tty.wr.ansi_csi_argv[0] - 1 : 0; - x = (handle->tty.wr.ansi_csi_argc >= 2 && handle->tty.wr.ansi_csi_argv[1]) + x = (handle->tty.wr.ansi_csi_argc >= 2 && + handle->tty.wr.ansi_csi_argv[1]) ? handle->tty.wr.ansi_csi_argv[1] - 1 : 0; uv_tty_move_caret(handle, x, 0, y, 0, error); break; @@ -1946,7 +2037,8 @@ static int uv_tty_write_bufs(uv_tty_t* handle, case 'J': /* Erase screen */ FLUSH_TEXT(); - d = handle->tty.wr.ansi_csi_argc ? handle->tty.wr.ansi_csi_argv[0] : 0; + d = handle->tty.wr.ansi_csi_argc + ? handle->tty.wr.ansi_csi_argv[0] : 0; if (d >= 0 && d <= 2) { uv_tty_clear(handle, d, 1, error); } @@ -1955,7 +2047,8 @@ static int uv_tty_write_bufs(uv_tty_t* handle, case 'K': /* Erase line */ FLUSH_TEXT(); - d = handle->tty.wr.ansi_csi_argc ? handle->tty.wr.ansi_csi_argv[0] : 0; + d = handle->tty.wr.ansi_csi_argc + ? handle->tty.wr.ansi_csi_argv[0] : 0; if (d >= 0 && d <= 2) { uv_tty_clear(handle, d, 0, error); } @@ -1978,41 +2071,17 @@ static int uv_tty_write_bufs(uv_tty_t* handle, FLUSH_TEXT(); uv_tty_restore_state(handle, 0, error); break; - - case 'l': - /* Hide the cursor */ - if (handle->tty.wr.ansi_csi_argc == 1 && - handle->tty.wr.ansi_csi_argv[0] == 25) { - FLUSH_TEXT(); - uv_tty_set_cursor_visibility(handle, 0, error); - } - break; - - case 'h': - /* Show the cursor */ - if (handle->tty.wr.ansi_csi_argc == 1 && - handle->tty.wr.ansi_csi_argv[0] == 25) { - FLUSH_TEXT(); - uv_tty_set_cursor_visibility(handle, 1, error); - } - break; } + } - /* Sequence ended - go back to normal state. */ - ansi_parser_state = ANSI_NORMAL; - continue; + /* Sequence ended - go back to normal state. */ + ansi_parser_state = ANSI_NORMAL; + continue; - } else { - /* We don't support commands that use private mode characters or - * intermediaries. Ignore the rest of the sequence. */ - ansi_parser_state |= ANSI_IGNORE; - continue; - } } else { - /* We're ignoring this command. Stop only on command character. */ - if (utf8_codepoint >= '@' && utf8_codepoint <= '~') { - ansi_parser_state = ANSI_NORMAL; - } + /* We don't support commands that use private mode characters or + * intermediaries. Ignore the rest of the sequence. */ + ansi_parser_state = ANSI_IGNORE; continue; } @@ -2179,14 +2248,14 @@ void uv_process_tty_write_req(uv_loop_t* loop, uv_tty_t* handle, void uv_tty_close(uv_tty_t* handle) { assert(handle->u.fd == -1 || handle->u.fd > 2); + if (handle->flags & UV_HANDLE_READING) + uv_tty_read_stop(handle); + if (handle->u.fd == -1) CloseHandle(handle->handle); else close(handle->u.fd); - if (handle->flags & UV_HANDLE_READING) - uv_tty_read_stop(handle); - handle->u.fd = -1; handle->handle = INVALID_HANDLE_VALUE; handle->flags &= ~(UV_HANDLE_READABLE | UV_HANDLE_WRITABLE); @@ -2264,38 +2333,56 @@ int uv_tty_reset_mode(void) { static void uv__determine_vterm_state(HANDLE handle) { DWORD dwMode = 0; + uv__need_check_vterm_state = FALSE; if (!GetConsoleMode(handle, &dwMode)) { - uv__vterm_state = UV_UNSUPPORTED; return; } dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; if (!SetConsoleMode(handle, dwMode)) { - uv__vterm_state = UV_UNSUPPORTED; return; } - uv__vterm_state = UV_SUPPORTED; + uv__vterm_state = UV_TTY_SUPPORTED; } static DWORD WINAPI uv__tty_console_resize_message_loop_thread(void* param) { - CONSOLE_SCREEN_BUFFER_INFO sb_info; + NTSTATUS status; + ULONG_PTR conhost_pid; MSG msg; - if (!GetConsoleScreenBufferInfo(uv__tty_console_handle, &sb_info)) + if (pSetWinEventHook == NULL || pNtQueryInformationProcess == NULL) return 0; - uv__tty_console_width = sb_info.dwSize.X; - uv__tty_console_height = sb_info.srWindow.Bottom - sb_info.srWindow.Top + 1; + status = pNtQueryInformationProcess(GetCurrentProcess(), + ProcessConsoleHostProcess, + &conhost_pid, + sizeof(conhost_pid), + NULL); - if (pSetWinEventHook == NULL) + if (!NT_SUCCESS(status)) { + /* We couldn't retrieve our console host process, probably because this + * is a 32-bit process running on 64-bit Windows. Fall back to receiving + * console events from the input stream only. */ + return 0; + } + + /* Ensure the PID is a multiple of 4, which is required by SetWinEventHook */ + conhost_pid &= ~(ULONG_PTR)0x3; + + uv__tty_console_resized = CreateEvent(NULL, TRUE, FALSE, NULL); + if (uv__tty_console_resized == NULL) + return 0; + if (QueueUserWorkItem(uv__tty_console_resize_watcher_thread, + NULL, + WT_EXECUTELONGFUNCTION) == 0) return 0; if (!pSetWinEventHook(EVENT_CONSOLE_LAYOUT, EVENT_CONSOLE_LAYOUT, NULL, uv__tty_console_resize_event, - 0, + (DWORD)conhost_pid, 0, WINEVENT_OUTOFCONTEXT)) return 0; @@ -2314,6 +2401,20 @@ static void CALLBACK uv__tty_console_resize_event(HWINEVENTHOOK hWinEventHook, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime) { + SetEvent(uv__tty_console_resized); +} + +static DWORD WINAPI uv__tty_console_resize_watcher_thread(void* param) { + for (;;) { + /* Make sure to not overwhelm the system with resize events */ + Sleep(33); + WaitForSingleObject(uv__tty_console_resized, INFINITE); + uv__tty_console_signal_resize(); + ResetEvent(uv__tty_console_resized); + } +} + +static void uv__tty_console_signal_resize(void) { CONSOLE_SCREEN_BUFFER_INFO sb_info; int width, height; @@ -2323,9 +2424,28 @@ static void CALLBACK uv__tty_console_resize_event(HWINEVENTHOOK hWinEventHook, width = sb_info.dwSize.X; height = sb_info.srWindow.Bottom - sb_info.srWindow.Top + 1; + uv_mutex_lock(&uv__tty_console_resize_mutex); + assert(uv__tty_console_width != -1 && uv__tty_console_height != -1); if (width != uv__tty_console_width || height != uv__tty_console_height) { uv__tty_console_width = width; uv__tty_console_height = height; + uv_mutex_unlock(&uv__tty_console_resize_mutex); uv__signal_dispatch(SIGWINCH); + } else { + uv_mutex_unlock(&uv__tty_console_resize_mutex); } } + +void uv_tty_set_vterm_state(uv_tty_vtermstate_t state) { + uv_sem_wait(&uv_tty_output_lock); + uv__need_check_vterm_state = FALSE; + uv__vterm_state = state; + uv_sem_post(&uv_tty_output_lock); +} + +int uv_tty_get_vterm_state(uv_tty_vtermstate_t* state) { + uv_sem_wait(&uv_tty_output_lock); + *state = uv__vterm_state; + uv_sem_post(&uv_tty_output_lock); + return 0; +} diff --git a/src/win/udp.c b/src/win/udp.c index 37df849..1c4977a 100644 --- a/src/win/udp.c +++ b/src/win/udp.c @@ -36,22 +36,27 @@ const unsigned int uv_active_udp_streams_threshold = 0; /* A zero-size buffer for use by uv_udp_read */ static char uv_zero_[] = ""; - -int uv_udp_getsockname(const uv_udp_t* handle, +int uv_udp_getpeername(const uv_udp_t* handle, struct sockaddr* name, int* namelen) { - int result; - if (handle->socket == INVALID_SOCKET) { - return UV_EINVAL; - } + return uv__getsockpeername((const uv_handle_t*) handle, + getpeername, + name, + namelen, + 0); +} - result = getsockname(handle->socket, name, namelen); - if (result != 0) { - return uv_translate_sys_error(WSAGetLastError()); - } - return 0; +int uv_udp_getsockname(const uv_udp_t* handle, + struct sockaddr* name, + int* namelen) { + + return uv__getsockpeername((const uv_handle_t*) handle, + getsockname, + name, + namelen, + 0); } @@ -120,17 +125,10 @@ static int uv_udp_set_socket(uv_loop_t* loop, uv_udp_t* handle, SOCKET socket, } -int uv_udp_init_ex(uv_loop_t* loop, uv_udp_t* handle, unsigned int flags) { - int domain; - - /* Use the lower 8 bits for the domain */ - domain = flags & 0xFF; - if (domain != AF_INET && domain != AF_INET6 && domain != AF_UNSPEC) - return UV_EINVAL; - - if (flags & ~0xFF) - return UV_EINVAL; - +int uv__udp_init_ex(uv_loop_t* loop, + uv_udp_t* handle, + unsigned flags, + int domain) { uv__handle_init(loop, (uv_handle_t*) handle, UV_UDP); handle->socket = INVALID_SOCKET; handle->reqs_pending = 0; @@ -169,11 +167,6 @@ int uv_udp_init_ex(uv_loop_t* loop, uv_udp_t* handle, unsigned int flags) { } -int uv_udp_init(uv_loop_t* loop, uv_udp_t* handle) { - return uv_udp_init_ex(loop, handle, AF_UNSPEC); -} - - void uv_udp_close(uv_loop_t* loop, uv_udp_t* handle) { uv_udp_recv_stop(handle); closesocket(handle->socket); @@ -697,6 +690,114 @@ int uv__udp_set_membership6(uv_udp_t* handle, } +static int uv__udp_set_source_membership4(uv_udp_t* handle, + const struct sockaddr_in* multicast_addr, + const char* interface_addr, + const struct sockaddr_in* source_addr, + uv_membership membership) { + struct ip_mreq_source mreq; + int optname; + int err; + + if (handle->flags & UV_HANDLE_IPV6) + return UV_EINVAL; + + /* If the socket is unbound, bind to inaddr_any. */ + err = uv_udp_maybe_bind(handle, + (const struct sockaddr*) &uv_addr_ip4_any_, + sizeof(uv_addr_ip4_any_), + UV_UDP_REUSEADDR); + if (err) + return uv_translate_sys_error(err); + + memset(&mreq, 0, sizeof(mreq)); + + if (interface_addr != NULL) { + err = uv_inet_pton(AF_INET, interface_addr, &mreq.imr_interface.s_addr); + if (err) + return err; + } else { + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + } + + mreq.imr_multiaddr.s_addr = multicast_addr->sin_addr.s_addr; + mreq.imr_sourceaddr.s_addr = source_addr->sin_addr.s_addr; + + if (membership == UV_JOIN_GROUP) + optname = IP_ADD_SOURCE_MEMBERSHIP; + else if (membership == UV_LEAVE_GROUP) + optname = IP_DROP_SOURCE_MEMBERSHIP; + else + return UV_EINVAL; + + if (setsockopt(handle->socket, + IPPROTO_IP, + optname, + (char*) &mreq, + sizeof(mreq)) == SOCKET_ERROR) { + return uv_translate_sys_error(WSAGetLastError()); + } + + return 0; +} + + +int uv__udp_set_source_membership6(uv_udp_t* handle, + const struct sockaddr_in6* multicast_addr, + const char* interface_addr, + const struct sockaddr_in6* source_addr, + uv_membership membership) { + struct group_source_req mreq; + struct sockaddr_in6 addr6; + int optname; + int err; + + if ((handle->flags & UV_HANDLE_BOUND) && !(handle->flags & UV_HANDLE_IPV6)) + return UV_EINVAL; + + err = uv_udp_maybe_bind(handle, + (const struct sockaddr*) &uv_addr_ip6_any_, + sizeof(uv_addr_ip6_any_), + UV_UDP_REUSEADDR); + + if (err) + return uv_translate_sys_error(err); + + memset(&mreq, 0, sizeof(mreq)); + + if (interface_addr != NULL) { + err = uv_ip6_addr(interface_addr, 0, &addr6); + if (err) + return err; + mreq.gsr_interface = addr6.sin6_scope_id; + } else { + mreq.gsr_interface = 0; + } + + STATIC_ASSERT(sizeof(mreq.gsr_group) >= sizeof(*multicast_addr)); + STATIC_ASSERT(sizeof(mreq.gsr_source) >= sizeof(*source_addr)); + memcpy(&mreq.gsr_group, multicast_addr, sizeof(*multicast_addr)); + memcpy(&mreq.gsr_source, source_addr, sizeof(*source_addr)); + + if (membership == UV_JOIN_GROUP) + optname = MCAST_JOIN_SOURCE_GROUP; + else if (membership == UV_LEAVE_GROUP) + optname = MCAST_LEAVE_SOURCE_GROUP; + else + return UV_EINVAL; + + if (setsockopt(handle->socket, + IPPROTO_IPV6, + optname, + (char*) &mreq, + sizeof(mreq)) == SOCKET_ERROR) { + return uv_translate_sys_error(WSAGetLastError()); + } + + return 0; +} + + int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, const char* interface_addr, @@ -713,6 +814,50 @@ int uv_udp_set_membership(uv_udp_t* handle, } +int uv_udp_set_source_membership(uv_udp_t* handle, + const char* multicast_addr, + const char* interface_addr, + const char* source_addr, + uv_membership membership) { + int err; + struct sockaddr_storage mcast_addr; + struct sockaddr_in* mcast_addr4; + struct sockaddr_in6* mcast_addr6; + struct sockaddr_storage src_addr; + struct sockaddr_in* src_addr4; + struct sockaddr_in6* src_addr6; + + mcast_addr4 = (struct sockaddr_in*)&mcast_addr; + mcast_addr6 = (struct sockaddr_in6*)&mcast_addr; + src_addr4 = (struct sockaddr_in*)&src_addr; + src_addr6 = (struct sockaddr_in6*)&src_addr; + + err = uv_ip4_addr(multicast_addr, 0, mcast_addr4); + if (err) { + err = uv_ip6_addr(multicast_addr, 0, mcast_addr6); + if (err) + return err; + err = uv_ip6_addr(source_addr, 0, src_addr6); + if (err) + return err; + return uv__udp_set_source_membership6(handle, + mcast_addr6, + interface_addr, + src_addr6, + membership); + } + + err = uv_ip4_addr(source_addr, 0, src_addr4); + if (err) + return err; + return uv__udp_set_source_membership4(handle, + mcast_addr4, + interface_addr, + src_addr4, + membership); +} + + int uv_udp_set_multicast_interface(uv_udp_t* handle, const char* interface_addr) { struct sockaddr_storage addr_st; struct sockaddr_in* addr4; @@ -784,6 +929,18 @@ int uv_udp_set_broadcast(uv_udp_t* handle, int value) { } +int uv__udp_is_bound(uv_udp_t* handle) { + struct sockaddr_storage addr; + int addrlen; + + addrlen = sizeof(addr); + if (uv_udp_getsockname(handle, (struct sockaddr*) &addr, &addrlen) != 0) + return 0; + + return addrlen > 0; +} + + int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock) { WSAPROTOCOL_INFOW protocol_info; int opt_len; @@ -803,7 +960,16 @@ int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock) { handle, sock, protocol_info.iAddressFamily); - return uv_translate_sys_error(err); + if (err) + return uv_translate_sys_error(err); + + if (uv__udp_is_bound(handle)) + handle->flags |= UV_HANDLE_BOUND; + + if (uv__udp_is_connected(handle)) + handle->flags |= UV_HANDLE_UDP_CONNECTED; + + return 0; } @@ -880,6 +1046,50 @@ int uv__udp_bind(uv_udp_t* handle, } +int uv__udp_connect(uv_udp_t* handle, + const struct sockaddr* addr, + unsigned int addrlen) { + const struct sockaddr* bind_addr; + int err; + + if (!(handle->flags & UV_HANDLE_BOUND)) { + if (addrlen == sizeof(uv_addr_ip4_any_)) + bind_addr = (const struct sockaddr*) &uv_addr_ip4_any_; + else if (addrlen == sizeof(uv_addr_ip6_any_)) + bind_addr = (const struct sockaddr*) &uv_addr_ip6_any_; + else + return UV_EINVAL; + + err = uv_udp_maybe_bind(handle, bind_addr, addrlen, 0); + if (err) + return uv_translate_sys_error(err); + } + + err = connect(handle->socket, addr, addrlen); + if (err) + return uv_translate_sys_error(err); + + handle->flags |= UV_HANDLE_UDP_CONNECTED; + + return 0; +} + + +int uv__udp_disconnect(uv_udp_t* handle) { + int err; + struct sockaddr addr; + + memset(&addr, 0, sizeof(addr)); + + err = connect(handle->socket, &addr, sizeof(addr)); + if (err) + return uv_translate_sys_error(err); + + handle->flags &= ~UV_HANDLE_UDP_CONNECTED; + return 0; +} + + /* This function is an egress point, i.e. it returns libuv errors rather than * system errors. */ @@ -900,6 +1110,7 @@ int uv__udp_send(uv_udp_send_t* req, bind_addr = (const struct sockaddr*) &uv_addr_ip6_any_; else return UV_EINVAL; + err = uv_udp_maybe_bind(handle, bind_addr, addrlen, 0); if (err) return uv_translate_sys_error(err); @@ -925,9 +1136,11 @@ int uv__udp_try_send(uv_udp_t* handle, assert(nbufs > 0); - err = uv__convert_to_localhost_if_unspecified(addr, &converted); - if (err) - return err; + if (addr != NULL) { + err = uv__convert_to_localhost_if_unspecified(addr, &converted); + if (err) + return err; + } /* Already sending a message.*/ if (handle->send_queue_count != 0) diff --git a/src/win/util.c b/src/win/util.c index c994984..9e1e7f7 100644 --- a/src/win/util.c +++ b/src/win/util.c @@ -59,16 +59,9 @@ # define UNLEN 256 #endif -/* - Max hostname length. The Windows gethostname() documentation states that 256 - bytes will always be large enough to hold the null-terminated hostname. -*/ -#ifndef MAXHOSTNAMELEN -# define MAXHOSTNAMELEN 256 -#endif -/* Maximum environment variable size, including the terminating null */ -#define MAX_ENV_VAR_LENGTH 32767 +/* A RtlGenRandom() by any other name... */ +extern BOOLEAN NTAPI SystemFunction036(PVOID Buffer, ULONG BufferLength); /* Cached copy of the process title, plus a mutex guarding it. */ static char *process_title; @@ -158,20 +151,26 @@ int uv_exepath(char* buffer, size_t* size_ptr) { int uv_cwd(char* buffer, size_t* size) { DWORD utf16_len; - WCHAR utf16_buffer[MAX_PATH]; + WCHAR *utf16_buffer; int r; if (buffer == NULL || size == NULL) { return UV_EINVAL; } - utf16_len = GetCurrentDirectoryW(MAX_PATH, utf16_buffer); + utf16_len = GetCurrentDirectoryW(0, NULL); if (utf16_len == 0) { return uv_translate_sys_error(GetLastError()); - } else if (utf16_len > MAX_PATH) { - /* This should be impossible; however the CRT has a code path to deal with - * this scenario, so I added a check anyway. */ - return UV_EIO; + } + utf16_buffer = uv__malloc(utf16_len * sizeof(WCHAR)); + if (utf16_buffer == NULL) { + return UV_ENOMEM; + } + + utf16_len = GetCurrentDirectoryW(utf16_len, utf16_buffer); + if (utf16_len == 0) { + uv__free(utf16_buffer); + return uv_translate_sys_error(GetLastError()); } /* utf16_len contains the length, *not* including the terminating null. */ @@ -195,8 +194,10 @@ int uv_cwd(char* buffer, size_t* size) { NULL, NULL); if (r == 0) { + uv__free(utf16_buffer); return uv_translate_sys_error(GetLastError()); } else if (r > (int) *size) { + uv__free(utf16_buffer); *size = r; return UV_ENOBUFS; } @@ -210,6 +211,8 @@ int uv_cwd(char* buffer, size_t* size) { *size > INT_MAX ? INT_MAX : (int) *size, NULL, NULL); + uv__free(utf16_buffer); + if (r == 0) { return uv_translate_sys_error(GetLastError()); } @@ -220,43 +223,61 @@ int uv_cwd(char* buffer, size_t* size) { int uv_chdir(const char* dir) { - WCHAR utf16_buffer[MAX_PATH]; - size_t utf16_len; + WCHAR *utf16_buffer; + size_t utf16_len, new_utf16_len; WCHAR drive_letter, env_var[4]; if (dir == NULL) { return UV_EINVAL; } + utf16_len = MultiByteToWideChar(CP_UTF8, + 0, + dir, + -1, + NULL, + 0); + if (utf16_len == 0) { + return uv_translate_sys_error(GetLastError()); + } + utf16_buffer = uv__malloc(utf16_len * sizeof(WCHAR)); + if (utf16_buffer == NULL) { + return UV_ENOMEM; + } + if (MultiByteToWideChar(CP_UTF8, 0, dir, -1, utf16_buffer, - MAX_PATH) == 0) { - DWORD error = GetLastError(); - /* The maximum length of the current working directory is 260 chars, - * including terminating null. If it doesn't fit, the path name must be too - * long. */ - if (error == ERROR_INSUFFICIENT_BUFFER) { - return UV_ENAMETOOLONG; - } else { - return uv_translate_sys_error(error); - } + utf16_len) == 0) { + uv__free(utf16_buffer); + return uv_translate_sys_error(GetLastError()); } if (!SetCurrentDirectoryW(utf16_buffer)) { + uv__free(utf16_buffer); return uv_translate_sys_error(GetLastError()); } /* Windows stores the drive-local path in an "hidden" environment variable, * which has the form "=C:=C:\Windows". SetCurrentDirectory does not update * this, so we'll have to do it. */ - utf16_len = GetCurrentDirectoryW(MAX_PATH, utf16_buffer); + new_utf16_len = GetCurrentDirectoryW(utf16_len, utf16_buffer); + if (new_utf16_len > utf16_len ) { + uv__free(utf16_buffer); + utf16_buffer = uv__malloc(new_utf16_len * sizeof(WCHAR)); + if (utf16_buffer == NULL) { + /* When updating the environment variable fails, return UV_OK anyway. + * We did successfully change current working directory, only updating + * hidden env variable failed. */ + return 0; + } + new_utf16_len = GetCurrentDirectoryW(new_utf16_len, utf16_buffer); + } if (utf16_len == 0) { - return uv_translate_sys_error(GetLastError()); - } else if (utf16_len > MAX_PATH) { - return UV_EIO; + uv__free(utf16_buffer); + return 0; } /* The returned directory should not have a trailing slash, unless it points @@ -288,11 +309,10 @@ int uv_chdir(const char* dir) { env_var[2] = L':'; env_var[3] = L'\0'; - if (!SetEnvironmentVariableW(env_var, utf16_buffer)) { - return uv_translate_sys_error(GetLastError()); - } + SetEnvironmentVariableW(env_var, utf16_buffer); } + uv__free(utf16_buffer); return 0; } @@ -327,6 +347,11 @@ uint64_t uv_get_total_memory(void) { } +uint64_t uv_get_constrained_memory(void) { + return 0; /* Memory constraints are unknown. */ +} + + uv_pid_t uv_os_getpid(void) { return GetCurrentProcessId(); } @@ -360,6 +385,10 @@ char** uv_setup_args(int argc, char** argv) { } +void uv__process_title_cleanup(void) { +} + + int uv_set_process_title(const char* title) { int err; int length; @@ -684,12 +713,9 @@ int uv_cpu_info(uv_cpu_info_t** cpu_infos_ptr, int* cpu_count_ptr) { NULL, (BYTE*)&cpu_brand, &cpu_brand_size); - if (err != ERROR_SUCCESS) { - RegCloseKey(processor_key); - goto error; - } - RegCloseKey(processor_key); + if (err != ERROR_SUCCESS) + goto error; cpu_info = &cpu_infos[i]; cpu_info->speed = cpu_speed; @@ -713,9 +739,11 @@ int uv_cpu_info(uv_cpu_info_t** cpu_infos_ptr, int* cpu_count_ptr) { return 0; error: - /* 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); + 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_infos); uv__free(sppi); @@ -724,17 +752,6 @@ int uv_cpu_info(uv_cpu_info_t** cpu_infos_ptr, int* cpu_count_ptr) { } -void uv_free_cpu_info(uv_cpu_info_t* cpu_infos, int count) { - int i; - - for (i = 0; i < count; i++) { - uv__free(cpu_infos[i].model); - } - - uv__free(cpu_infos); -} - - static int is_windows_version_or_greater(DWORD os_major, DWORD os_minor, WORD service_pack_major, @@ -816,6 +833,9 @@ int uv_interface_addresses(uv_interface_address_t** addresses_ptr, int is_vista_or_greater; ULONG flags; + *addresses_ptr = NULL; + *count_ptr = 0; + is_vista_or_greater = is_windows_version_or_greater(6, 0, 0, 0); if (is_vista_or_greater) { flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | @@ -1171,20 +1191,29 @@ int uv_os_homedir(char* buffer, size_t* size) { int uv_os_tmpdir(char* buffer, size_t* size) { - wchar_t path[MAX_PATH + 1]; + wchar_t *path; DWORD bufsize; size_t len; if (buffer == NULL || size == NULL || *size == 0) return UV_EINVAL; - len = GetTempPathW(MAX_PATH + 1, path); + len = 0; + len = GetTempPathW(0, NULL); + if (len == 0) { + return uv_translate_sys_error(GetLastError()); + } + /* Include space for terminating null char. */ + len += 1; + path = uv__malloc(len * sizeof(wchar_t)); + if (path == NULL) { + return UV_ENOMEM; + } + len = GetTempPathW(len, path); if (len == 0) { + uv__free(path); return uv_translate_sys_error(GetLastError()); - } else if (len > MAX_PATH + 1) { - /* This should not be possible */ - return UV_EIO; } /* The returned directory should not have a trailing slash, unless it points @@ -1199,8 +1228,10 @@ int uv_os_tmpdir(char* buffer, size_t* size) { bufsize = WideCharToMultiByte(CP_UTF8, 0, path, -1, NULL, 0, NULL, NULL); if (bufsize == 0) { + uv__free(path); return uv_translate_sys_error(GetLastError()); } else if (bufsize > *size) { + uv__free(path); *size = bufsize; return UV_ENOBUFS; } @@ -1214,6 +1245,7 @@ int uv_os_tmpdir(char* buffer, size_t* size) { *size, NULL, NULL); + uv__free(path); if (bufsize == 0) return uv_translate_sys_error(GetLastError()); @@ -1325,7 +1357,7 @@ int uv__convert_utf8_to_utf16(const char* utf8, int utf8len, WCHAR** utf16) { return uv_translate_sys_error(GetLastError()); } - (*utf16)[bufsize] = '\0'; + (*utf16)[bufsize] = L'\0'; return 0; } @@ -1333,7 +1365,7 @@ int uv__convert_utf8_to_utf16(const char* utf8, int utf8len, WCHAR** utf16) { int uv__getpwuid_r(uv_passwd_t* pwd) { HANDLE token; wchar_t username[UNLEN + 1]; - wchar_t path[MAX_PATH]; + wchar_t *path; DWORD bufsize; int r; @@ -1344,15 +1376,24 @@ int uv__getpwuid_r(uv_passwd_t* pwd) { if (OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &token) == 0) return uv_translate_sys_error(GetLastError()); - bufsize = ARRAY_SIZE(path); - if (!GetUserProfileDirectoryW(token, path, &bufsize)) { + bufsize = 0; + GetUserProfileDirectoryW(token, NULL, &bufsize); + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { r = GetLastError(); CloseHandle(token); + return uv_translate_sys_error(r); + } - /* This should not be possible */ - if (r == ERROR_INSUFFICIENT_BUFFER) - return UV_ENOMEM; + path = uv__malloc(bufsize * sizeof(wchar_t)); + if (path == NULL) { + CloseHandle(token); + return UV_ENOMEM; + } + if (!GetUserProfileDirectoryW(token, path, &bufsize)) { + r = GetLastError(); + CloseHandle(token); + uv__free(path); return uv_translate_sys_error(r); } @@ -1362,6 +1403,7 @@ int uv__getpwuid_r(uv_passwd_t* pwd) { bufsize = ARRAY_SIZE(username); if (!GetUserNameW(username, &bufsize)) { r = GetLastError(); + uv__free(path); /* This should not be possible */ if (r == ERROR_INSUFFICIENT_BUFFER) @@ -1372,6 +1414,7 @@ int uv__getpwuid_r(uv_passwd_t* pwd) { pwd->homedir = NULL; r = uv__convert_utf16_to_utf8(path, -1, &pwd->homedir); + uv__free(path); if (r != 0) return r; @@ -1397,8 +1440,81 @@ int uv_os_get_passwd(uv_passwd_t* pwd) { } +int uv_os_environ(uv_env_item_t** envitems, int* count) { + wchar_t* env; + wchar_t* penv; + int i, cnt; + uv_env_item_t* envitem; + + *envitems = NULL; + *count = 0; + + env = GetEnvironmentStringsW(); + if (env == NULL) + return 0; + + for (penv = env, i = 0; *penv != L'\0'; penv += wcslen(penv) + 1, i++); + + *envitems = uv__calloc(i, sizeof(**envitems)); + if (*envitems == NULL) { + FreeEnvironmentStringsW(env); + return UV_ENOMEM; + } + + penv = env; + cnt = 0; + + while (*penv != L'\0' && cnt < i) { + char* buf; + char* ptr; + + if (uv__convert_utf16_to_utf8(penv, -1, &buf) != 0) + goto fail; + + /* Using buf + 1 here because we know that `buf` has length at least 1, + * and some special environment variables on Windows start with a = sign. */ + ptr = strchr(buf + 1, '='); + if (ptr == NULL) { + uv__free(buf); + goto do_continue; + } + + *ptr = '\0'; + + envitem = &(*envitems)[cnt]; + envitem->name = buf; + envitem->value = ptr + 1; + + cnt++; + + do_continue: + penv += wcslen(penv) + 1; + } + + FreeEnvironmentStringsW(env); + + *count = cnt; + return 0; + +fail: + FreeEnvironmentStringsW(env); + + for (i = 0; i < cnt; i++) { + envitem = &(*envitems)[cnt]; + uv__free(envitem->name); + } + uv__free(*envitems); + + *envitems = NULL; + *count = 0; + return UV_ENOMEM; +} + + int uv_os_getenv(const char* name, char* buffer, size_t* size) { - wchar_t var[MAX_ENV_VAR_LENGTH]; + wchar_t fastvar[512]; + wchar_t* var; + DWORD varlen; wchar_t* name_w; DWORD bufsize; size_t len; @@ -1412,27 +1528,52 @@ int uv_os_getenv(const char* name, char* buffer, size_t* size) { if (r != 0) return r; - len = GetEnvironmentVariableW(name_w, var, MAX_ENV_VAR_LENGTH); + var = fastvar; + varlen = ARRAY_SIZE(fastvar); + + for (;;) { + SetLastError(ERROR_SUCCESS); + len = GetEnvironmentVariableW(name_w, var, varlen); + + if (len < varlen) + break; + + /* Try repeatedly because we might have been preempted by another thread + * modifying the environment variable just as we're trying to read it. + */ + if (var != fastvar) + uv__free(var); + + varlen = 1 + len; + var = uv__malloc(varlen * sizeof(*var)); + + if (var == NULL) { + r = UV_ENOMEM; + goto fail; + } + } + uv__free(name_w); - assert(len < MAX_ENV_VAR_LENGTH); /* len does not include the null */ + name_w = NULL; if (len == 0) { r = GetLastError(); - - if (r == ERROR_ENVVAR_NOT_FOUND) - return UV_ENOENT; - - return uv_translate_sys_error(r); + if (r != ERROR_SUCCESS) { + r = uv_translate_sys_error(r); + goto fail; + } } /* Check how much space we need */ bufsize = WideCharToMultiByte(CP_UTF8, 0, var, -1, NULL, 0, NULL, NULL); if (bufsize == 0) { - return uv_translate_sys_error(GetLastError()); + r = uv_translate_sys_error(GetLastError()); + goto fail; } else if (bufsize > *size) { *size = bufsize; - return UV_ENOBUFS; + r = UV_ENOBUFS; + goto fail; } /* Convert to UTF-8 */ @@ -1445,11 +1586,23 @@ int uv_os_getenv(const char* name, char* buffer, size_t* size) { NULL, NULL); - if (bufsize == 0) - return uv_translate_sys_error(GetLastError()); + if (bufsize == 0) { + r = uv_translate_sys_error(GetLastError()); + goto fail; + } *size = bufsize - 1; - return 0; + r = 0; + +fail: + + if (name_w != NULL) + uv__free(name_w); + + if (var != fastvar) + uv__free(var); + + return r; } @@ -1507,7 +1660,7 @@ int uv_os_unsetenv(const char* name) { int uv_os_gethostname(char* buffer, size_t* size) { - char buf[MAXHOSTNAMELEN + 1]; + char buf[UV_MAXHOSTNAMESIZE]; size_t len; if (buffer == NULL || size == NULL || *size == 0) @@ -1624,3 +1777,195 @@ int uv_os_setpriority(uv_pid_t pid, int priority) { CloseHandle(handle); return r; } + + +int uv_os_uname(uv_utsname_t* buffer) { + /* Implementation loosely based on + https://github.com/gagern/gnulib/blob/master/lib/uname.c */ + OSVERSIONINFOW os_info; + SYSTEM_INFO system_info; + HKEY registry_key; + WCHAR product_name_w[256]; + DWORD product_name_w_size; + int version_size; + int processor_level; + int r; + + if (buffer == NULL) + return UV_EINVAL; + + uv__once_init(); + os_info.dwOSVersionInfoSize = sizeof(os_info); + os_info.szCSDVersion[0] = L'\0'; + + /* Try calling RtlGetVersion(), and fall back to the deprecated GetVersionEx() + if RtlGetVersion() is not available. */ + if (pRtlGetVersion) { + pRtlGetVersion(&os_info); + } else { + /* Silence GetVersionEx() deprecation warning. */ + #pragma warning(suppress : 4996) + if (GetVersionExW(&os_info) == 0) { + r = uv_translate_sys_error(GetLastError()); + goto error; + } + } + + /* Populate the version field. */ + version_size = 0; + r = RegOpenKeyExW(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + 0, + KEY_QUERY_VALUE, + ®istry_key); + + if (r == ERROR_SUCCESS) { + product_name_w_size = sizeof(product_name_w); + r = RegGetValueW(registry_key, + NULL, + L"ProductName", + RRF_RT_REG_SZ, + NULL, + (PVOID) product_name_w, + &product_name_w_size); + RegCloseKey(registry_key); + + if (r == ERROR_SUCCESS) { + version_size = WideCharToMultiByte(CP_UTF8, + 0, + product_name_w, + -1, + buffer->version, + sizeof(buffer->version), + NULL, + NULL); + if (version_size == 0) { + r = uv_translate_sys_error(GetLastError()); + goto error; + } + } + } + + /* Append service pack information to the version if present. */ + if (os_info.szCSDVersion[0] != L'\0') { + if (version_size > 0) + buffer->version[version_size - 1] = ' '; + + if (WideCharToMultiByte(CP_UTF8, + 0, + os_info.szCSDVersion, + -1, + buffer->version + version_size, + sizeof(buffer->version) - version_size, + NULL, + NULL) == 0) { + r = uv_translate_sys_error(GetLastError()); + goto error; + } + } + + /* Populate the sysname field. */ +#ifdef __MINGW32__ + r = snprintf(buffer->sysname, + sizeof(buffer->sysname), + "MINGW32_NT-%u.%u", + (unsigned int) os_info.dwMajorVersion, + (unsigned int) os_info.dwMinorVersion); + assert(r < sizeof(buffer->sysname)); +#else + uv__strscpy(buffer->sysname, "Windows_NT", sizeof(buffer->sysname)); +#endif + + /* Populate the release field. */ + r = snprintf(buffer->release, + sizeof(buffer->release), + "%d.%d.%d", + (unsigned int) os_info.dwMajorVersion, + (unsigned int) os_info.dwMinorVersion, + (unsigned int) os_info.dwBuildNumber); + assert(r < sizeof(buffer->release)); + + /* Populate the machine field. */ + GetSystemInfo(&system_info); + + switch (system_info.wProcessorArchitecture) { + case PROCESSOR_ARCHITECTURE_AMD64: + uv__strscpy(buffer->machine, "x86_64", sizeof(buffer->machine)); + break; + case PROCESSOR_ARCHITECTURE_IA64: + uv__strscpy(buffer->machine, "ia64", sizeof(buffer->machine)); + break; + case PROCESSOR_ARCHITECTURE_INTEL: + uv__strscpy(buffer->machine, "i386", sizeof(buffer->machine)); + + if (system_info.wProcessorLevel > 3) { + processor_level = system_info.wProcessorLevel < 6 ? + system_info.wProcessorLevel : 6; + buffer->machine[1] = '0' + processor_level; + } + + break; + case PROCESSOR_ARCHITECTURE_IA32_ON_WIN64: + uv__strscpy(buffer->machine, "i686", sizeof(buffer->machine)); + break; + case PROCESSOR_ARCHITECTURE_MIPS: + uv__strscpy(buffer->machine, "mips", sizeof(buffer->machine)); + break; + case PROCESSOR_ARCHITECTURE_ALPHA: + case PROCESSOR_ARCHITECTURE_ALPHA64: + uv__strscpy(buffer->machine, "alpha", sizeof(buffer->machine)); + break; + case PROCESSOR_ARCHITECTURE_PPC: + uv__strscpy(buffer->machine, "powerpc", sizeof(buffer->machine)); + break; + case PROCESSOR_ARCHITECTURE_SHX: + uv__strscpy(buffer->machine, "sh", sizeof(buffer->machine)); + break; + case PROCESSOR_ARCHITECTURE_ARM: + uv__strscpy(buffer->machine, "arm", sizeof(buffer->machine)); + break; + default: + uv__strscpy(buffer->machine, "unknown", sizeof(buffer->machine)); + break; + } + + return 0; + +error: + buffer->sysname[0] = '\0'; + buffer->release[0] = '\0'; + buffer->version[0] = '\0'; + buffer->machine[0] = '\0'; + return r; +} + +int uv_gettimeofday(uv_timeval64_t* tv) { + /* Based on https://doxygen.postgresql.org/gettimeofday_8c_source.html */ + const uint64_t epoch = (uint64_t) 116444736000000000ULL; + FILETIME file_time; + ULARGE_INTEGER ularge; + + if (tv == NULL) + return UV_EINVAL; + + GetSystemTimeAsFileTime(&file_time); + ularge.LowPart = file_time.dwLowDateTime; + ularge.HighPart = file_time.dwHighDateTime; + tv->tv_sec = (int64_t) ((ularge.QuadPart - epoch) / 10000000L); + tv->tv_usec = (int32_t) (((ularge.QuadPart - epoch) % 10000000L) / 10); + return 0; +} + +int uv__random_rtlgenrandom(void* buf, size_t buflen) { + if (buflen == 0) + return 0; + + if (SystemFunction036(buf, buflen) == FALSE) + return UV_EIO; + + return 0; +} + +void uv_sleep(unsigned int msec) { + Sleep(msec); +} diff --git a/src/win/winapi.c b/src/win/winapi.c index 2c09b44..bb86ec8 100644 --- a/src/win/winapi.c +++ b/src/win/winapi.c @@ -26,6 +26,7 @@ /* Ntdll function pointers */ +sRtlGetVersion pRtlGetVersion; sRtlNtStatusToDosError pRtlNtStatusToDosError; sNtDeviceIoControlFile pNtDeviceIoControlFile; sNtQueryInformationFile pNtQueryInformationFile; @@ -33,6 +34,7 @@ sNtSetInformationFile pNtSetInformationFile; sNtQueryVolumeInformationFile pNtQueryVolumeInformationFile; sNtQueryDirectoryFile pNtQueryDirectoryFile; sNtQuerySystemInformation pNtQuerySystemInformation; +sNtQueryInformationProcess pNtQueryInformationProcess; /* Kernel32 function pointers */ sGetQueuedCompletionStatusEx pGetQueuedCompletionStatusEx; @@ -55,6 +57,9 @@ void uv_winapi_init(void) { uv_fatal_error(GetLastError(), "GetModuleHandleA"); } + pRtlGetVersion = (sRtlGetVersion) GetProcAddress(ntdll_module, + "RtlGetVersion"); + pRtlNtStatusToDosError = (sRtlNtStatusToDosError) GetProcAddress( ntdll_module, "RtlNtStatusToDosError"); @@ -102,6 +107,13 @@ void uv_winapi_init(void) { uv_fatal_error(GetLastError(), "GetProcAddress"); } + pNtQueryInformationProcess = (sNtQueryInformationProcess) GetProcAddress( + ntdll_module, + "NtQueryInformationProcess"); + if (pNtQueryInformationProcess == NULL) { + uv_fatal_error(GetLastError(), "GetProcAddress"); + } + kernel32_module = GetModuleHandleA("kernel32.dll"); if (kernel32_module == NULL) { uv_fatal_error(GetLastError(), "GetModuleHandleA"); @@ -122,5 +134,4 @@ void uv_winapi_init(void) { pSetWinEventHook = (sSetWinEventHook) GetProcAddress(user32_module, "SetWinEventHook"); } - } diff --git a/src/win/winapi.h b/src/win/winapi.h index cfbac52..cbe1437 100644 --- a/src/win/winapi.h +++ b/src/win/winapi.h @@ -4109,6 +4109,9 @@ #endif /* from winternl.h */ +#if !defined(__UNICODE_STRING_DEFINED) && defined(__MINGW32__) +#define __UNICODE_STRING_DEFINED +#endif typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; @@ -4149,6 +4152,10 @@ typedef const UNICODE_STRING *PCUNICODE_STRING; struct { UCHAR DataBuffer[1]; } GenericReparseBuffer; + struct { + ULONG StringCount; + WCHAR StringList[1]; + } AppExecLinkReparseBuffer; }; } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; #endif @@ -4433,6 +4440,10 @@ typedef struct _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION { # define SystemProcessorPerformanceInformation 8 #endif +#ifndef ProcessConsoleHostProcess +# define ProcessConsoleHostProcess 49 +#endif + #ifndef FILE_DEVICE_FILE_SYSTEM # define FILE_DEVICE_FILE_SYSTEM 0x00000009 #endif @@ -4510,12 +4521,18 @@ typedef struct _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION { #ifndef IO_REPARSE_TAG_SYMLINK # define IO_REPARSE_TAG_SYMLINK (0xA000000CL) #endif +#ifndef IO_REPARSE_TAG_APPEXECLINK +# define IO_REPARSE_TAG_APPEXECLINK (0x8000001BL) +#endif typedef VOID (NTAPI *PIO_APC_ROUTINE) (PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, ULONG Reserved); +typedef NTSTATUS (NTAPI *sRtlGetVersion) + (PRTL_OSVERSIONINFOW lpVersionInformation); + typedef ULONG (NTAPI *sRtlNtStatusToDosError) (NTSTATUS Status); @@ -4572,6 +4589,13 @@ typedef NTSTATUS (NTAPI *sNtQueryDirectoryFile) BOOLEAN RestartScan ); +typedef NTSTATUS (NTAPI *sNtQueryInformationProcess) + (HANDLE ProcessHandle, + UINT ProcessInformationClass, + PVOID ProcessInformation, + ULONG Length, + PULONG ReturnLength); + /* * Kernel32 headers */ @@ -4704,6 +4728,7 @@ typedef HWINEVENTHOOK (WINAPI *sSetWinEventHook) /* Ntdll function pointers */ +extern sRtlGetVersion pRtlGetVersion; extern sRtlNtStatusToDosError pRtlNtStatusToDosError; extern sNtDeviceIoControlFile pNtDeviceIoControlFile; extern sNtQueryInformationFile pNtQueryInformationFile; @@ -4711,6 +4736,7 @@ extern sNtSetInformationFile pNtSetInformationFile; extern sNtQueryVolumeInformationFile pNtQueryVolumeInformationFile; extern sNtQueryDirectoryFile pNtQueryDirectoryFile; extern sNtQuerySystemInformation pNtQuerySystemInformation; +extern sNtQueryInformationProcess pNtQueryInformationProcess; /* Kernel32 function pointers */ extern sGetQueuedCompletionStatusEx pGetQueuedCompletionStatusEx; diff --git a/src/win/winsock.c b/src/win/winsock.c index 5e7da2a..4cf6e6b 100644 --- a/src/win/winsock.c +++ b/src/win/winsock.c @@ -74,11 +74,6 @@ BOOL uv_get_connectex_function(SOCKET socket, LPFN_CONNECTEX* target) { } -static int error_means_no_support(DWORD error) { - return error == WSAEPROTONOSUPPORT || error == WSAESOCKTNOSUPPORT || - error == WSAEPFNOSUPPORT || error == WSAEAFNOSUPPORT; -} - void uv_winsock_init(void) { WSADATA wsa_data; @@ -87,12 +82,6 @@ void uv_winsock_init(void) { WSAPROTOCOL_INFOW protocol_info; int opt_len; - /* Initialize winsock */ - errorno = WSAStartup(MAKEWORD(2, 2), &wsa_data); - if (errorno != 0) { - uv_fatal_error(errorno, "WSAStartup"); - } - /* Set implicit binding address used by connectEx */ if (uv_ip4_addr("0.0.0.0", 0, &uv_addr_ip4_any_)) { abort(); @@ -102,50 +91,45 @@ void uv_winsock_init(void) { abort(); } - /* Detect non-IFS LSPs */ - dummy = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + /* Skip initialization in safe mode without network support */ + if (1 == GetSystemMetrics(SM_CLEANBOOT)) return; + /* Initialize winsock */ + errorno = WSAStartup(MAKEWORD(2, 2), &wsa_data); + if (errorno != 0) { + uv_fatal_error(errorno, "WSAStartup"); + } + + /* Try to detect non-IFS LSPs */ + uv_tcp_non_ifs_lsp_ipv4 = 1; + dummy = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); if (dummy != INVALID_SOCKET) { opt_len = (int) sizeof protocol_info; if (getsockopt(dummy, SOL_SOCKET, SO_PROTOCOL_INFOW, (char*) &protocol_info, - &opt_len) == SOCKET_ERROR) - uv_fatal_error(WSAGetLastError(), "getsockopt"); - - if (!(protocol_info.dwServiceFlags1 & XP1_IFS_HANDLES)) - uv_tcp_non_ifs_lsp_ipv4 = 1; - - if (closesocket(dummy) == SOCKET_ERROR) - uv_fatal_error(WSAGetLastError(), "closesocket"); - - } else if (!error_means_no_support(WSAGetLastError())) { - /* Any error other than "socket type not supported" is fatal. */ - uv_fatal_error(WSAGetLastError(), "socket"); + &opt_len) == 0) { + if (protocol_info.dwServiceFlags1 & XP1_IFS_HANDLES) + uv_tcp_non_ifs_lsp_ipv4 = 0; + } + closesocket(dummy); } - /* Detect IPV6 support and non-IFS LSPs */ + /* Try to detect IPV6 support and non-IFS LSPs */ + uv_tcp_non_ifs_lsp_ipv6 = 1; dummy = socket(AF_INET6, SOCK_STREAM, IPPROTO_IP); - if (dummy != INVALID_SOCKET) { opt_len = (int) sizeof protocol_info; if (getsockopt(dummy, SOL_SOCKET, SO_PROTOCOL_INFOW, (char*) &protocol_info, - &opt_len) == SOCKET_ERROR) - uv_fatal_error(WSAGetLastError(), "getsockopt"); - - if (!(protocol_info.dwServiceFlags1 & XP1_IFS_HANDLES)) - uv_tcp_non_ifs_lsp_ipv6 = 1; - - if (closesocket(dummy) == SOCKET_ERROR) - uv_fatal_error(WSAGetLastError(), "closesocket"); - - } else if (!error_means_no_support(WSAGetLastError())) { - /* Any error other than "socket type not supported" is fatal. */ - uv_fatal_error(WSAGetLastError(), "socket"); + &opt_len) == 0) { + if (protocol_info.dwServiceFlags1 & XP1_IFS_HANDLES) + uv_tcp_non_ifs_lsp_ipv6 = 0; + } + closesocket(dummy); } } diff --git a/src/win/winsock.h b/src/win/winsock.h index 7ecb755..2af9588 100644 --- a/src/win/winsock.h +++ b/src/win/winsock.h @@ -54,6 +54,14 @@ # define SIO_BASE_HANDLE 0x48000022 #endif +#ifndef MCAST_JOIN_SOURCE_GROUP +# define MCAST_JOIN_SOURCE_GROUP 45 +#endif + +#ifndef MCAST_LEAVE_SOURCE_GROUP +# define MCAST_LEAVE_SOURCE_GROUP 46 +#endif + /* * TDI defines that are only in the DDK. * We only need receive flags so far. diff --git a/test/benchmark-list.h b/test/benchmark-list.h index 1e84307..29e44c3 100644 --- a/test/benchmark-list.h +++ b/test/benchmark-list.h @@ -23,6 +23,7 @@ BENCHMARK_DECLARE (sizes) BENCHMARK_DECLARE (loop_count) BENCHMARK_DECLARE (loop_count_timed) BENCHMARK_DECLARE (ping_pongs) +BENCHMARK_DECLARE (ping_udp) BENCHMARK_DECLARE (tcp_write_batch) BENCHMARK_DECLARE (tcp4_pound_100) BENCHMARK_DECLARE (tcp4_pound_1000) diff --git a/test/benchmark-multi-accept.c b/test/benchmark-multi-accept.c index 2f32c0c..5a18623 100644 --- a/test/benchmark-multi-accept.c +++ b/test/benchmark-multi-accept.c @@ -218,8 +218,12 @@ static void send_listen_handles(uv_handle_type type, } else ASSERT(0); - - ASSERT(0 == uv_pipe_init(loop, &ctx.ipc_pipe, 1)); + /* We need to initialize this pipe with ipc=0 - this is not a uv_pipe we'll + * be sending handles over, it's just for listening for new connections. + * If we accept a connection then the connected pipe must be initialized + * with ipc=1. + */ + ASSERT(0 == uv_pipe_init(loop, &ctx.ipc_pipe, 0)); ASSERT(0 == uv_pipe_bind(&ctx.ipc_pipe, IPC_PIPE_NAME)); ASSERT(0 == uv_listen((uv_stream_t*) &ctx.ipc_pipe, 128, ipc_connection_cb)); diff --git a/test/benchmark-ping-udp.c b/test/benchmark-ping-udp.c new file mode 100644 index 0000000..a29502a --- /dev/null +++ b/test/benchmark-ping-udp.c @@ -0,0 +1,163 @@ +/* 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 +#include + +/* Run the benchmark for this many ms */ +#define TIME 5000 + +typedef struct { + int pongs; + int state; + uv_udp_t udp; + struct sockaddr_in server_addr; +} pinger_t; + +typedef struct buf_s { + uv_buf_t uv_buf_t; + struct buf_s* next; +} buf_t; + +static char PING[] = "PING\n"; + +static uv_loop_t* loop; + +static int completed_pingers; +static unsigned long completed_pings; +static int64_t start_time; + + +static void buf_alloc(uv_handle_t* tcp, size_t size, uv_buf_t* buf) { + static char slab[64 * 1024]; + buf->base = slab; + buf->len = sizeof(slab); +} + + +static void buf_free(const uv_buf_t* buf) { +} + + +static void pinger_close_cb(uv_handle_t* handle) { + pinger_t* pinger; + + pinger = (pinger_t*)handle->data; +#if DEBUG + fprintf(stderr, "ping_pongs: %d roundtrips/s\n", + pinger->pongs / (TIME / 1000)); +#endif + + completed_pings += pinger->pongs; + completed_pingers++; + free(pinger); +} + +static void pinger_write_ping(pinger_t* pinger) { + uv_buf_t buf; + int r; + + buf = uv_buf_init(PING, sizeof(PING) - 1); + r = uv_udp_try_send(&pinger->udp, &buf, 1, + (const struct sockaddr*) &pinger->server_addr); + if (r < 0) + FATAL("uv_udp_send failed"); +} + +static void pinger_read_cb(uv_udp_t* udp, + ssize_t nread, + const uv_buf_t* buf, + const struct sockaddr* addr, + unsigned flags) { + ssize_t i; + pinger_t* pinger; + pinger = (pinger_t*)udp->data; + + /* Now we count the pings */ + for (i = 0; i < nread; i++) { + ASSERT(buf->base[i] == PING[pinger->state]); + pinger->state = (pinger->state + 1) % (sizeof(PING) - 1); + if (pinger->state == 0) { + pinger->pongs++; + if (uv_now(loop) - start_time > TIME) { + uv_close((uv_handle_t*)udp, pinger_close_cb); + break; + } + pinger_write_ping(pinger); + } + } + + buf_free(buf); +} + +static void udp_pinger_new(void) { + pinger_t* pinger = malloc(sizeof(*pinger)); + int r; + + ASSERT(0 == uv_ip4_addr("127.0.0.1", TEST_PORT, &pinger->server_addr)); + pinger->state = 0; + pinger->pongs = 0; + + /* Try to do NUM_PINGS ping-pongs (connection-less). */ + r = uv_udp_init(loop, &pinger->udp); + ASSERT(r == 0); + + pinger->udp.data = pinger; + + /* Start pinging */ + if (0 != uv_udp_recv_start(&pinger->udp, buf_alloc, pinger_read_cb)) { + FATAL("uv_udp_read_start failed"); + } + pinger_write_ping(pinger); +} + +static int ping_udp(unsigned pingers) { + unsigned i; + + loop = uv_default_loop(); + start_time = uv_now(loop); + + for (i = 0; i < pingers; ++i) { + udp_pinger_new(); + } + uv_run(loop, UV_RUN_DEFAULT); + ASSERT(completed_pingers >= 1); + + fprintf(stderr, "ping_pongs: %d pingers, ~ %lu roundtrips/s\n", + completed_pingers, completed_pings / (TIME/1000)); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +#define X(PINGERS) \ +BENCHMARK_IMPL(ping_udp##PINGERS) {\ + return ping_udp(PINGERS); \ +} + +X(1) +X(10) +X(100) + +#undef X diff --git a/test/echo-server.c b/test/echo-server.c index bfed676..c65142f 100644 --- a/test/echo-server.c +++ b/test/echo-server.c @@ -37,6 +37,7 @@ static uv_tcp_t tcpServer; static uv_udp_t udpServer; static uv_pipe_t pipeServer; static uv_handle_t* server; +static uv_udp_send_t* send_freelist; static void after_write(uv_write_t* req, int status); static void after_read(uv_stream_t*, ssize_t nread, const uv_buf_t* buf); @@ -133,6 +134,14 @@ static void echo_alloc(uv_handle_t* handle, buf->len = suggested_size; } +static void slab_alloc(uv_handle_t* handle, + size_t suggested_size, + uv_buf_t* buf) { + /* up to 16 datagrams at once */ + static char slab[16 * 64 * 1024]; + buf->base = slab; + buf->len = sizeof(slab); +} static void on_connection(uv_stream_t* server, int status) { uv_stream_t* stream; @@ -178,35 +187,43 @@ static void on_server_close(uv_handle_t* handle) { ASSERT(handle == server); } +static uv_udp_send_t* send_alloc(void) { + uv_udp_send_t* req = send_freelist; + if (req != NULL) + send_freelist = req->data; + else + req = malloc(sizeof(*req)); + return req; +} -static void on_send(uv_udp_send_t* req, int status); - +static void on_send(uv_udp_send_t* req, int status) { + ASSERT(req != NULL); + ASSERT(status == 0); + req->data = send_freelist; + send_freelist = req; +} static void on_recv(uv_udp_t* handle, ssize_t nread, const uv_buf_t* rcvbuf, const struct sockaddr* addr, unsigned flags) { - uv_udp_send_t* req; uv_buf_t sndbuf; + if (nread == 0) { + /* Everything OK, but nothing read. */ + return; + } + ASSERT(nread > 0); ASSERT(addr->sa_family == AF_INET); - req = malloc(sizeof(*req)); + uv_udp_send_t* req = send_alloc(); ASSERT(req != NULL); - - sndbuf = *rcvbuf; - ASSERT(0 == uv_udp_send(req, handle, &sndbuf, 1, addr, on_send)); -} - - -static void on_send(uv_udp_send_t* req, int status) { - ASSERT(status == 0); - free(req); + sndbuf = uv_buf_init(rcvbuf->base, nread); + ASSERT(0 <= uv_udp_send(req, handle, &sndbuf, 1, addr, on_send)); } - static int tcp4_echo_start(int port) { struct sockaddr_in addr; int r; @@ -277,8 +294,10 @@ static int tcp6_echo_start(int port) { static int udp4_echo_start(int port) { + struct sockaddr_in addr; int r; + ASSERT(0 == uv_ip4_addr("127.0.0.1", port, &addr)); server = (uv_handle_t*)&udpServer; serverType = UDP; @@ -288,7 +307,13 @@ static int udp4_echo_start(int port) { return 1; } - r = uv_udp_recv_start(&udpServer, echo_alloc, on_recv); + r = uv_udp_bind(&udpServer, (const struct sockaddr*) &addr, 0); + if (r) { + fprintf(stderr, "uv_udp_bind: %s\n", uv_strerror(r)); + return 1; + } + + r = uv_udp_recv_start(&udpServer, slab_alloc, on_recv); if (r) { fprintf(stderr, "uv_udp_recv_start: %s\n", uv_strerror(r)); return 1; @@ -340,6 +365,7 @@ HELPER_IMPL(tcp4_echo_server) { if (tcp4_echo_start(TEST_PORT)) return 1; + notify_parent_process(); uv_run(loop, UV_RUN_DEFAULT); return 0; } @@ -351,6 +377,7 @@ HELPER_IMPL(tcp6_echo_server) { if (tcp6_echo_start(TEST_PORT)) return 1; + notify_parent_process(); uv_run(loop, UV_RUN_DEFAULT); return 0; } @@ -362,6 +389,7 @@ HELPER_IMPL(pipe_echo_server) { if (pipe_echo_start(TEST_PIPENAME)) return 1; + notify_parent_process(); uv_run(loop, UV_RUN_DEFAULT); return 0; } @@ -373,6 +401,7 @@ HELPER_IMPL(udp4_echo_server) { if (udp4_echo_start(TEST_PORT)) return 1; + notify_parent_process(); uv_run(loop, UV_RUN_DEFAULT); return 0; } diff --git a/test/fixtures/lorem_ipsum.txt b/test/fixtures/lorem_ipsum.txt new file mode 100644 index 0000000..1b37687 --- /dev/null +++ b/test/fixtures/lorem_ipsum.txt @@ -0,0 +1 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. diff --git a/test/run-benchmarks.c b/test/run-benchmarks.c index 6e42623..980c9be 100644 --- a/test/run-benchmarks.c +++ b/test/run-benchmarks.c @@ -33,8 +33,7 @@ static int maybe_run_test(int argc, char **argv); int main(int argc, char **argv) { - if (platform_init(argc, argv)) - return EXIT_FAILURE; + platform_init(argc, argv); switch (argc) { case 1: return run_tests(1); diff --git a/test/run-tests.c b/test/run-tests.c index 9b8af04..e5e75e1 100644 --- a/test/run-tests.c +++ b/test/run-tests.c @@ -42,17 +42,24 @@ int ipc_helper_tcp_connection(void); int ipc_helper_closed_handle(void); int ipc_send_recv_helper(void); int ipc_helper_bind_twice(void); +int ipc_helper_send_zero(void); int stdio_over_pipes_helper(void); -int spawn_stdin_stdout(void); +void spawn_stdin_stdout(void); +void process_title_big_argv(void); int spawn_tcp_server_helper(void); static int maybe_run_test(int argc, char **argv); int main(int argc, char **argv) { - if (platform_init(argc, argv)) +#ifndef _WIN32 + if (0 == geteuid() && NULL == getenv("UV_RUN_AS_ROOT")) { + fprintf(stderr, "The libuv test suite cannot be run as root.\n"); return EXIT_FAILURE; + } +#endif + platform_init(argc, argv); argv = uv_setup_args(argc, argv); switch (argc) { @@ -66,7 +73,9 @@ int main(int argc, char **argv) { return EXIT_FAILURE; } +#ifndef __SUNPRO_C return EXIT_SUCCESS; +#endif } @@ -104,25 +113,33 @@ static int maybe_run_test(int argc, char **argv) { return ipc_helper_bind_twice(); } + if (strcmp(argv[1], "ipc_helper_send_zero") == 0) { + return ipc_helper_send_zero(); + } + if (strcmp(argv[1], "stdio_over_pipes_helper") == 0) { return stdio_over_pipes_helper(); } if (strcmp(argv[1], "spawn_helper1") == 0) { + notify_parent_process(); return 1; } if (strcmp(argv[1], "spawn_helper2") == 0) { + notify_parent_process(); printf("hello world\n"); return 1; } if (strcmp(argv[1], "spawn_tcp_server_helper") == 0) { + notify_parent_process(); return spawn_tcp_server_helper(); } if (strcmp(argv[1], "spawn_helper3") == 0) { char buffer[256]; + notify_parent_process(); ASSERT(buffer == fgets(buffer, sizeof(buffer) - 1, stdin)); buffer[sizeof(buffer) - 1] = '\0'; fputs(buffer, stdout); @@ -130,17 +147,19 @@ static int maybe_run_test(int argc, char **argv) { } if (strcmp(argv[1], "spawn_helper4") == 0) { + notify_parent_process(); /* Never surrender, never return! */ while (1) uv_sleep(10000); } if (strcmp(argv[1], "spawn_helper5") == 0) { const char out[] = "fourth stdio!\n"; + notify_parent_process(); + { #ifdef _WIN32 - DWORD bytes; - WriteFile((HANDLE) _get_osfhandle(3), out, sizeof(out) - 1, &bytes, NULL); + DWORD bytes; + WriteFile((HANDLE) _get_osfhandle(3), out, sizeof(out) - 1, &bytes, NULL); #else - { ssize_t r; do @@ -148,14 +167,16 @@ static int maybe_run_test(int argc, char **argv) { while (r == -1 && errno == EINTR); fsync(3); - } #endif + } return 1; } if (strcmp(argv[1], "spawn_helper6") == 0) { int r; + notify_parent_process(); + r = fprintf(stdout, "hello world\n"); ASSERT(r > 0); @@ -168,6 +189,9 @@ static int maybe_run_test(int argc, char **argv) { if (strcmp(argv[1], "spawn_helper7") == 0) { int r; char *test; + + notify_parent_process(); + /* Test if the test value from the parent is still set */ test = getenv("ENV_TEST"); ASSERT(test != NULL); @@ -181,16 +205,24 @@ static int maybe_run_test(int argc, char **argv) { #ifndef _WIN32 if (strcmp(argv[1], "spawn_helper8") == 0) { int fd; + + notify_parent_process(); ASSERT(sizeof(fd) == read(0, &fd, sizeof(fd))); ASSERT(fd > 2); +# if defined(__PASE__) /* On IBMi PASE, write() returns 1 */ + ASSERT(1 == write(fd, "x", 1)); +# else ASSERT(-1 == write(fd, "x", 1)); +# endif /* !__PASE__ */ return 1; } #endif /* !_WIN32 */ if (strcmp(argv[1], "spawn_helper9") == 0) { - return spawn_stdin_stdout(); + notify_parent_process(); + spawn_stdin_stdout(); + return 1; } #ifndef _WIN32 @@ -200,10 +232,17 @@ static int maybe_run_test(int argc, char **argv) { ASSERT(uid == getuid()); ASSERT(gid == getgid()); + notify_parent_process(); return 1; } #endif /* !_WIN32 */ + if (strcmp(argv[1], "process_title_big_argv_helper") == 0) { + notify_parent_process(); + process_title_big_argv(); + return 0; + } + return run_test(argv[1], 0, 1); } diff --git a/test/runner-unix.c b/test/runner-unix.c index de0db0c..b7ca1f8 100644 --- a/test/runner-unix.c +++ b/test/runner-unix.c @@ -26,7 +26,7 @@ #include /* uintptr_t */ #include -#include /* readlink, usleep */ +#include /* usleep */ #include /* strdup */ #include #include @@ -40,20 +40,39 @@ #include #include +extern char** environ; + +static void closefd(int fd) { + if (close(fd) == 0 || errno == EINTR || errno == EINPROGRESS) + return; + + perror("close"); + abort(); +} + + +void notify_parent_process(void) { + char* arg; + int fd; + + arg = getenv("UV_TEST_RUNNER_FD"); + if (arg == NULL) + return; + + fd = atoi(arg); + assert(fd > STDERR_FILENO); + unsetenv("UV_TEST_RUNNER_FD"); + closefd(fd); +} + /* Do platform-specific initialization. */ -int platform_init(int argc, char **argv) { +void platform_init(int argc, char **argv) { /* Disable stdio output buffering. */ setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); signal(SIGPIPE, SIG_IGN); - - if (realpath(argv[0], executable_path) == NULL) { - perror("realpath"); - return -1; - } - - return 0; + snprintf(executable_path, sizeof(executable_path), "%s", argv[0]); } @@ -64,9 +83,31 @@ int process_start(char* name, char* part, process_info_t* p, int is_helper) { int stdout_fd; const char* arg; char* args[16]; + int pipefd[2]; + char fdstr[8]; + ssize_t rc; int n; pid_t pid; + arg = getenv("UV_USE_VALGRIND"); + n = 0; + + /* Disable valgrind for helpers, it complains about helpers leaking memory. + * They're killed after the test and as such never get a chance to clean up. + */ + if (is_helper == 0 && arg != NULL && atoi(arg) != 0) { + args[n++] = "valgrind"; + args[n++] = "--quiet"; + args[n++] = "--leak-check=full"; + args[n++] = "--show-reachable=yes"; + args[n++] = "--error-exitcode=125"; + } + + args[n++] = executable_path; + args[n++] = name; + args[n++] = part; + args[n++] = NULL; + stdout_file = tmpfile(); stdout_fd = fileno(stdout_file); if (!stdout_file) { @@ -74,6 +115,19 @@ int process_start(char* name, char* part, process_info_t* p, int is_helper) { return -1; } + if (is_helper) { + if (pipe(pipefd)) { + perror("pipe"); + return -1; + } + + snprintf(fdstr, sizeof(fdstr), "%d", pipefd[1]); + if (setenv("UV_TEST_RUNNER_FD", fdstr, /* overwrite */ 1)) { + perror("setenv"); + return -1; + } + } + p->terminated = 0; p->status = 0; @@ -86,29 +140,12 @@ int process_start(char* name, char* part, process_info_t* p, int is_helper) { if (pid == 0) { /* child */ - arg = getenv("UV_USE_VALGRIND"); - n = 0; - - /* Disable valgrind for helpers, it complains about helpers leaking memory. - * They're killed after the test and as such never get a chance to clean up. - */ - if (is_helper == 0 && arg != NULL && atoi(arg) != 0) { - args[n++] = "valgrind"; - args[n++] = "--quiet"; - args[n++] = "--leak-check=full"; - args[n++] = "--show-reachable=yes"; - args[n++] = "--error-exitcode=125"; - } - - args[n++] = executable_path; - args[n++] = name; - args[n++] = part; - args[n++] = NULL; - + if (is_helper) + closefd(pipefd[0]); dup2(stdout_fd, STDOUT_FILENO); dup2(stdout_fd, STDERR_FILENO); - execvp(args[0], args); - perror("execvp()"); + execve(args[0], args, environ); + perror("execve()"); _exit(127); } @@ -117,6 +154,28 @@ int process_start(char* name, char* part, process_info_t* p, int is_helper) { p->name = strdup(name); p->stdout_file = stdout_file; + if (!is_helper) + return 0; + + closefd(pipefd[1]); + unsetenv("UV_TEST_RUNNER_FD"); + + do + rc = read(pipefd[0], &n, 1); + while (rc == -1 && errno == EINTR); + + closefd(pipefd[0]); + + if (rc == -1) { + perror("read"); + return -1; + } + + if (rc > 0) { + fprintf(stderr, "EOF expected but got data.\n"); + return -1; + } + return 0; } @@ -306,8 +365,8 @@ int process_copy_output(process_info_t* p, FILE* stream) { } /* TODO: what if the line is longer than buf */ - while (fgets(buf, sizeof(buf), p->stdout_file) != NULL) - print_lines(buf, strlen(buf), stream); + while ((r = fread(buf, 1, sizeof(buf), p->stdout_file)) != 0) + print_lines(buf, r, stream); if (ferror(p->stdout_file)) { perror("read"); @@ -333,7 +392,8 @@ int process_read_last_line(process_info_t *p, buffer[0] = '\0'; while (fgets(buffer, buffer_len, p->stdout_file) != NULL) { - for (ptr = buffer; *ptr && *ptr != '\r' && *ptr != '\n'; ptr++); + for (ptr = buffer; *ptr && *ptr != '\r' && *ptr != '\n'; ptr++) + ; *ptr = '\0'; } @@ -383,17 +443,3 @@ void rewind_cursor(void) { fprintf(stderr, "\033[2K\r"); #endif } - - -/* Pause the calling thread for a number of milliseconds. */ -void uv_sleep(int msec) { - int sec; - int usec; - - sec = msec / 1000; - usec = (msec % 1000) * 1000; - if (sec > 0) - sleep(sec); - if (usec > 0) - usleep(usec); -} diff --git a/test/runner-win.c b/test/runner-win.c index ce97270..8c2a00b 100644 --- a/test/runner-win.c +++ b/test/runner-win.c @@ -43,7 +43,7 @@ /* Do platform-specific initialization. */ -int platform_init(int argc, char **argv) { +void platform_init(int argc, char **argv) { /* Disable the "application crashed" popup. */ SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); @@ -56,13 +56,17 @@ int platform_init(int argc, char **argv) { _setmode(1, _O_BINARY); _setmode(2, _O_BINARY); +#ifdef _MSC_VER + _set_fmode(_O_BINARY); +#else + _fmode = _O_BINARY; +#endif + /* Disable stdio output buffering. */ setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); strcpy(executable_path, argv[0]); - - return 0; } @@ -76,6 +80,11 @@ int process_start(char *name, char *part, process_info_t *p, int is_helper) { PROCESS_INFORMATION pi; DWORD result; + if (!is_helper) { + /* Give the helpers time to settle. Race-y, fix this. */ + uv_sleep(250); + } + if (GetTempPathW(sizeof(path) / sizeof(WCHAR), (WCHAR*)&path) == 0) goto error; if (GetTempFileNameW((WCHAR*)&path, L"uv", 0, (WCHAR*)&filename) == 0) @@ -189,7 +198,7 @@ int process_wait(process_info_t *vec, int n, int timeout) { result = WaitForMultipleObjects(n, handles, TRUE, timeout_api); - if (result >= WAIT_OBJECT_0 && result < WAIT_OBJECT_0 + n) { + if (result < WAIT_OBJECT_0 + n) { /* All processes are terminated. */ return 0; } @@ -211,28 +220,19 @@ long int process_output_size(process_info_t *p) { int process_copy_output(process_info_t* p, FILE* stream) { char buf[1024]; int fd, r; - FILE* f; fd = _open_osfhandle((intptr_t)p->stdio_out, _O_RDONLY | _O_TEXT); if (fd == -1) return -1; - f = _fdopen(fd, "rt"); - if (f == NULL) { - _close(fd); - return -1; - } - r = fseek(f, 0, SEEK_SET); + r = _lseek(fd, 0, SEEK_SET); if (r < 0) return -1; - while (fgets(buf, sizeof(buf), f) != NULL) - print_lines(buf, strlen(buf), stream); - - if (ferror(f)) - return -1; + while ((r = _read(fd, buf, sizeof(buf))) != 0) + print_lines(buf, r, stream); - fclose(f); + _close(fd); return 0; } @@ -263,7 +263,8 @@ int process_read_last_line(process_info_t *p, if (!ReadFile(p->stdio_out, buffer, buffer_len - 1, &read, &overlapped)) return -1; - for (start = read - 1; start >= 0; start--) { + start = read; + while (start-- > 0) { if (buffer[start] == '\n' || buffer[start] == '\r') break; } @@ -303,7 +304,7 @@ void process_cleanup(process_info_t *p) { } -static int clear_line() { +static int clear_line(void) { HANDLE handle; CONSOLE_SCREEN_BUFFER_INFO info; COORD coord; @@ -343,9 +344,3 @@ void rewind_cursor() { fprintf(stderr, "\n"); } } - - -/* Pause the calling thread for a number of milliseconds. */ -void uv_sleep(int msec) { - Sleep(msec); -} diff --git a/test/runner-win.h b/test/runner-win.h index 8cc4c16..975eed7 100644 --- a/test/runner-win.h +++ b/test/runner-win.h @@ -20,7 +20,9 @@ */ /* Don't complain about write(), fileno() etc. being deprecated. */ +#ifdef _MSC_VER #pragma warning(disable : 4996) +#endif #include diff --git a/test/runner.c b/test/runner.c index f017902..bb50b43 100644 --- a/test/runner.c +++ b/test/runner.c @@ -20,6 +20,7 @@ */ #include +#include #include #include "runner.h" @@ -167,6 +168,7 @@ int run_test(const char* test, process_info_t processes[1024]; process_info_t *main_proc; task_entry_t* task; + int timeout_multiplier; int process_count; int result; int status; @@ -215,9 +217,6 @@ int run_test(const char* test, process_count++; } - /* Give the helpers time to settle. Race-y, fix this. */ - uv_sleep(250); - /* Now start the test itself. */ for (task = TASKS; task->main; task++) { if (strcmp(test, task->task_name) != 0) { @@ -252,7 +251,22 @@ int run_test(const char* test, goto out; } - result = process_wait(main_proc, 1, task->timeout); + timeout_multiplier = 1; +#ifndef _WIN32 + do { + const char* var; + + var = getenv("UV_TEST_TIMEOUT_MULTIPLIER"); + if (var == NULL) + break; + + timeout_multiplier = atoi(var); + if (timeout_multiplier <= 0) + timeout_multiplier = 1; + } while (0); +#endif + + result = process_wait(main_proc, 1, task->timeout * timeout_multiplier); if (result == -1) { FATAL("process_wait failed"); } else if (result == -2) { @@ -422,13 +436,18 @@ void print_lines(const char* buffer, size_t size, FILE* stream) { start = buffer; while ((end = memchr(start, '\n', &buffer[size] - start))) { - fprintf(stream, "# %.*s\n", (int) (end - start), start); + fputs("# ", stream); + fwrite(start, 1, (int)(end - start), stream); + fputs("\n", stream); fflush(stream); start = end + 1; } - if (start < &buffer[size]) { - fprintf(stream, "# %s\n", start); + end = &buffer[size]; + if (start < end) { + fputs("# ", stream); + fwrite(start, 1, (int)(end - start), stream); + fputs("\n", stream); fflush(stream); } } diff --git a/test/runner.h b/test/runner.h index 1a33950..6801564 100644 --- a/test/runner.h +++ b/test/runner.h @@ -84,11 +84,7 @@ typedef struct { #define TEST_HELPER HELPER_ENTRY #define BENCHMARK_HELPER HELPER_ENTRY -#ifdef PATH_MAX -extern char executable_path[PATH_MAX]; -#else extern char executable_path[4096]; -#endif /* * Include platform-dependent definitions @@ -136,7 +132,7 @@ void print_lines(const char* buffer, size_t size, FILE* stream); */ /* Do platform-specific initialization. */ -int platform_init(int argc, char** argv); +void platform_init(int argc, char** argv); /* Invoke "argv[0] test-name [test-part]". Store process info in *p. Make sure * that all stdio output of the processes is buffered up. */ diff --git a/test/task.h b/test/task.h index 92a90a5..e95e3bd 100644 --- a/test/task.h +++ b/test/task.h @@ -27,6 +27,8 @@ #include #include #include +#include +#include #if defined(_MSC_VER) && _MSC_VER < 1600 # include "uv/stdint-msvc2008.h" @@ -44,6 +46,10 @@ # pragma clang diagnostic ignored "-Wc99-extensions" #endif +#ifdef __GNUC__ +# pragma GCC diagnostic ignored "-Wvariadic-macros" +#endif + #define TEST_PORT 9123 #define TEST_PORT_2 9124 @@ -105,6 +111,118 @@ typedef enum { } \ } while (0) +#define ASSERT_BASE(expr, a, operator, b, type, conv) \ + do { \ + if (!(expr)) { \ + fprintf(stderr, \ + "Assertion failed in %s on line %d: `%s %s %s` " \ + "(%"conv" %s %"conv")\n", \ + __FILE__, \ + __LINE__, \ + #a, \ + #operator, \ + #b, \ + (type)a, \ + #operator, \ + (type)b); \ + abort(); \ + } \ + } while (0) + +#define ASSERT_BASE_LEN(expr, a, operator, b, conv, len) \ + do { \ + if (!(expr)) { \ + fprintf(stderr, \ + "Assertion failed in %s on line %d: `%s %s %s` " \ + "(%.*"#conv" %s %.*"#conv")\n", \ + __FILE__, \ + __LINE__, \ + #a, \ + #operator, \ + #b, \ + (int)len, \ + a, \ + #operator, \ + (int)len, \ + b); \ + abort(); \ + } \ + } while (0) + +#define ASSERT_BASE_HEX(expr, a, operator, b, size) \ + do { \ + if (!(expr)) { \ + int i; \ + unsigned char* a_ = (unsigned char*)a; \ + unsigned char* b_ = (unsigned char*)b; \ + fprintf(stderr, \ + "Assertion failed in %s on line %d: `%s %s %s` (", \ + __FILE__, \ + __LINE__, \ + #a, \ + #operator, \ + #b); \ + for (i = 0; i < size; ++i) { \ + if (i > 0) fprintf(stderr, ":"); \ + fprintf(stderr, "%02X", a_[i]); \ + } \ + fprintf(stderr, " %s ", #operator); \ + for (i = 0; i < size; ++i) { \ + if (i > 0) fprintf(stderr, ":"); \ + fprintf(stderr, "%02X", b_[i]); \ + } \ + fprintf(stderr, ")\n"); \ + abort(); \ + } \ + } while (0) + +#define ASSERT_INT_BASE(a, operator, b, type, conv) \ + ASSERT_BASE(a operator b, a, operator, b, type, conv) + +#define ASSERT_EQ(a, b) ASSERT_INT_BASE(a, ==, b, int64_t, PRId64) +#define ASSERT_GE(a, b) ASSERT_INT_BASE(a, >=, b, int64_t, PRId64) +#define ASSERT_GT(a, b) ASSERT_INT_BASE(a, >, b, int64_t, PRId64) +#define ASSERT_LE(a, b) ASSERT_INT_BASE(a, <=, b, int64_t, PRId64) +#define ASSERT_LT(a, b) ASSERT_INT_BASE(a, <, b, int64_t, PRId64) +#define ASSERT_NE(a, b) ASSERT_INT_BASE(a, !=, b, int64_t, PRId64) + +#define ASSERT_UINT64_EQ(a, b) ASSERT_INT_BASE(a, ==, b, uint64_t, PRIu64) +#define ASSERT_UINT64_GE(a, b) ASSERT_INT_BASE(a, >=, b, uint64_t, PRIu64) +#define ASSERT_UINT64_GT(a, b) ASSERT_INT_BASE(a, >, b, uint64_t, PRIu64) +#define ASSERT_UINT64_LE(a, b) ASSERT_INT_BASE(a, <=, b, uint64_t, PRIu64) +#define ASSERT_UINT64_LT(a, b) ASSERT_INT_BASE(a, <, b, uint64_t, PRIu64) +#define ASSERT_UINT64_NE(a, b) ASSERT_INT_BASE(a, !=, b, uint64_t, PRIu64) + +#define ASSERT_STR_EQ(a, b) \ + ASSERT_BASE(strcmp(a, b) == 0, a, ==, b, char*, "s") + +#define ASSERT_STR_NE(a, b) \ + ASSERT_BASE(strcmp(a, b) != 0, a, !=, b, char*, "s") + +#define ASSERT_MEM_EQ(a, b, size) \ + ASSERT_BASE_LEN(memcmp(a, b, size) == 0, a, ==, b, s, size) + +#define ASSERT_MEM_NE(a, b, size) \ + ASSERT_BASE_LEN(memcmp(a, b, size) != 0, a, !=, b, s, size) + +#define ASSERT_MEM_HEX_EQ(a, b, size) \ + ASSERT_BASE_HEX(memcmp(a, b, size) == 0, a, ==, b, size) + +#define ASSERT_MEM_HEX_NE(a, b, size) \ + ASSERT_BASE_HEX(memcmp(a, b, size) != 0, a, !=, b, size) + +#define ASSERT_NULL(a) \ + ASSERT_BASE(a == NULL, a, ==, NULL, void*, "p") + +#define ASSERT_NOT_NULL(a) \ + ASSERT_BASE(a != NULL, a, !=, NULL, void*, "p") + +#define ASSERT_PTR_EQ(a, b) \ + ASSERT_BASE((void*)a == (void*)b, a, ==, b, void*, "p") + +#define ASSERT_PTR_NE(a, b) \ + ASSERT_BASE((void*)a != (void*)b, a, !=, b, void*, "p") + /* This macro cleans up the main loop. This is used to avoid valgrind * warnings about memory being "leaked" by the main event loop. */ @@ -112,6 +230,7 @@ typedef enum { do { \ close_loop(uv_default_loop()); \ ASSERT(0 == uv_loop_close(uv_default_loop())); \ + uv_library_shutdown(); \ } while (0) /* Just sugar for wrapping the main() for a task or helper. */ @@ -127,9 +246,6 @@ typedef enum { int run_helper_##name(void); \ int run_helper_##name(void) -/* Pause the calling thread for a number of milliseconds. */ -void uv_sleep(int msec); - /* Format big numbers nicely. WARNING: leaks memory. */ const char* fmt(double d); @@ -174,13 +290,18 @@ extern int snprintf(char*, size_t, const char*, ...); #if defined(__clang__) || \ defined(__GNUC__) || \ - defined(__INTEL_COMPILER) || \ - defined(__SUNPRO_C) + 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) { if (!uv_is_closing(handle)) @@ -209,7 +330,7 @@ UNUSED static int can_ipv6(void) { return supported; } -#if defined(__CYGWIN__) || defined(__MSYS__) +#if defined(__CYGWIN__) || defined(__MSYS__) || defined(__PASE__) # define NO_FS_EVENTS "Filesystem watching not supported on this platform." #endif diff --git a/test/test-barrier.c b/test/test-barrier.c index dfd2dbd..89858db 100644 --- a/test/test-barrier.c +++ b/test/test-barrier.c @@ -104,3 +104,45 @@ TEST_IMPL(barrier_3) { return 0; } + +static void serial_worker(void* data) { + uv_barrier_t* barrier; + + barrier = data; + if (uv_barrier_wait(barrier) > 0) + uv_barrier_destroy(barrier); + + uv_sleep(100); /* Wait a bit before terminating. */ +} + +/* Ensure that uv_barrier_wait returns positive only after all threads have + * exited the barrier. If this value is returned too early and the barrier is + * destroyed prematurely, then this test may see a crash. */ +TEST_IMPL(barrier_serial_thread) { + uv_thread_t threads[4]; + uv_barrier_t barrier; + unsigned i; + + ASSERT(0 == uv_barrier_init(&barrier, ARRAY_SIZE(threads) + 1)); + + for (i = 0; i < ARRAY_SIZE(threads); ++i) + ASSERT(0 == uv_thread_create(&threads[i], serial_worker, &barrier)); + + if (uv_barrier_wait(&barrier) > 0) + uv_barrier_destroy(&barrier); + + for (i = 0; i < ARRAY_SIZE(threads); ++i) + ASSERT(0 == uv_thread_join(&threads[i])); + + return 0; +} + +/* Single thread uv_barrier_wait should return correct return value. */ +TEST_IMPL(barrier_serial_thread_single) { + uv_barrier_t barrier; + + ASSERT(0 == uv_barrier_init(&barrier, 1)); + ASSERT(0 < uv_barrier_wait(&barrier)); + uv_barrier_destroy(&barrier); + return 0; +} diff --git a/test/test-close-fd.c b/test/test-close-fd.c index 93a7bd7..2ed9a10 100644 --- a/test/test-close-fd.c +++ b/test/test-close-fd.c @@ -73,4 +73,8 @@ TEST_IMPL(close_fd) { return 0; } -#endif /* !defined(_WIN32) */ +#else + +typedef int file_has_no_tests; /* ISO C forbids an empty translation unit. */ + +#endif /* !_WIN32 */ diff --git a/test/test-condvar.c b/test/test-condvar.c index 50f3c04..32abccc 100644 --- a/test/test-condvar.c +++ b/test/test-condvar.c @@ -235,7 +235,7 @@ TEST_IMPL(condvar_5) { uint64_t elapsed; uint64_t timeout; - timeout = 100 * 1e6; /* 100 ms in ns */ + timeout = 100 * 1000 * 1000; /* 100 ms in ns */ /* Mostly irrelevant. We need cond and mutex initialized. */ worker_config_init(&wc, 0, NULL, NULL); diff --git a/test/test-connection-fail.c b/test/test-connection-fail.c index 8338cac..5904810 100644 --- a/test/test-connection-fail.c +++ b/test/test-connection-fail.c @@ -120,6 +120,11 @@ static void connection_fail(uv_connect_cb connect_cb) { * expect an error. */ TEST_IMPL(connection_fail) { +/* TODO(gengjiawen): Fix test on QEMU. */ +#if defined(__QEMU__) + RETURN_SKIP("Test does not currently work in QEMU"); +#endif + connection_fail(on_connect_with_close); ASSERT(timer_close_cb_calls == 0); @@ -136,6 +141,11 @@ TEST_IMPL(connection_fail) { * attempt. */ TEST_IMPL(connection_fail_doesnt_auto_close) { +/* TODO(gengjiawen): Fix test on QEMU. */ +#if defined(__QEMU__) + RETURN_SKIP("Test does not currently work in QEMU"); +#endif + int r; r = uv_timer_init(uv_default_loop(), &timer); diff --git a/test/test-cwd-and-chdir.c b/test/test-cwd-and-chdir.c index 1e95043..faeed02 100644 --- a/test/test-cwd-and-chdir.c +++ b/test/test-cwd-and-chdir.c @@ -23,8 +23,7 @@ #include "task.h" #include -#define PATHMAX 1024 -extern char executable_path[]; +#define PATHMAX 4096 TEST_IMPL(cwd_and_chdir) { char buffer_orig[PATHMAX]; @@ -33,9 +32,16 @@ TEST_IMPL(cwd_and_chdir) { size_t size2; int err; + size1 = 1; + err = uv_cwd(buffer_orig, &size1); + ASSERT(err == UV_ENOBUFS); + ASSERT(size1 > 1); + size1 = sizeof buffer_orig; err = uv_cwd(buffer_orig, &size1); ASSERT(err == 0); + ASSERT(size1 > 0); + ASSERT(buffer_orig[size1] != '/'); err = uv_chdir(buffer_orig); ASSERT(err == 0); diff --git a/test/test-dlerror.c b/test/test-dlerror.c index 8f7697b..70cc9bf 100644 --- a/test/test-dlerror.c +++ b/test/test-dlerror.c @@ -42,13 +42,17 @@ TEST_IMPL(dlerror) { msg = uv_dlerror(&lib); ASSERT(msg != NULL); +#ifndef __OpenBSD__ ASSERT(strstr(msg, path) != NULL); +#endif ASSERT(strstr(msg, dlerror_no_error) == NULL); /* Should return the same error twice in a row. */ msg = uv_dlerror(&lib); ASSERT(msg != NULL); +#ifndef __OpenBSD__ ASSERT(strstr(msg, path) != NULL); +#endif ASSERT(strstr(msg, dlerror_no_error) == NULL); uv_dlclose(&lib); diff --git a/test/test-emfile.c b/test/test-emfile.c index 8e44ac5..bc1fce5 100644 --- a/test/test-emfile.c +++ b/test/test-emfile.c @@ -38,6 +38,11 @@ static uv_tcp_t client_handle; TEST_IMPL(emfile) { + struct sockaddr_in addr; + struct rlimit limits; + uv_connect_t connect_req; + uv_loop_t* loop; + int first_fd; #if defined(_AIX) || defined(__MVS__) /* On AIX, if a 'accept' call fails ECONNRESET is set on the socket * which causes uv__emfile_trick to not work as intended and this test @@ -45,11 +50,6 @@ TEST_IMPL(emfile) { */ RETURN_SKIP("uv__emfile_trick does not work on this OS"); #endif - struct sockaddr_in addr; - struct rlimit limits; - uv_connect_t connect_req; - uv_loop_t* loop; - int first_fd; /* Lower the file descriptor limit and use up all fds save one. */ limits.rlim_cur = limits.rlim_max = maxfd + 1; @@ -114,4 +114,8 @@ static void connect_cb(uv_connect_t* req, int status) { uv_close((uv_handle_t*) &client_handle, NULL); } -#endif /* !defined(_WIN32) */ +#else + +typedef int file_has_no_tests; /* ISO C forbids an empty translation unit. */ + +#endif /* !_WIN32 */ diff --git a/test/test-env-vars.c b/test/test-env-vars.c index 641050e..ecaba33 100644 --- a/test/test-env-vars.c +++ b/test/test-env-vars.c @@ -27,9 +27,11 @@ TEST_IMPL(env_vars) { const char* name = "UV_TEST_FOO"; + const char* name2 = "UV_TEST_FOO2"; char buf[BUF_SIZE]; size_t size; - int r; + int i, r, envcount, found, found_win_special; + uv_env_item_t* envitems; /* Reject invalid inputs when setting an environment variable */ r = uv_os_setenv(NULL, "foo"); @@ -86,5 +88,83 @@ TEST_IMPL(env_vars) { r = uv_os_unsetenv(name); ASSERT(r == 0); + /* Setting an environment variable to the empty string does not delete it. */ + r = uv_os_setenv(name, ""); + ASSERT(r == 0); + size = BUF_SIZE; + r = uv_os_getenv(name, buf, &size); + ASSERT(r == 0); + ASSERT(size == 0); + ASSERT(strlen(buf) == 0); + + /* Check getting all env variables. */ + r = uv_os_setenv(name, "123456789"); + ASSERT(r == 0); + r = uv_os_setenv(name2, ""); + ASSERT(r == 0); +#ifdef _WIN32 + /* Create a special environment variable on Windows in case there are no + naturally occurring ones. */ + r = uv_os_setenv("=Z:", "\\"); + ASSERT(r == 0); +#endif + + r = uv_os_environ(&envitems, &envcount); + ASSERT(r == 0); + ASSERT(envcount > 0); + + found = 0; + found_win_special = 0; + + for (i = 0; i < envcount; i++) { + /* printf("Env: %s = %s\n", envitems[i].name, envitems[i].value); */ + if (strcmp(envitems[i].name, name) == 0) { + found++; + ASSERT(strcmp(envitems[i].value, "123456789") == 0); + } else if (strcmp(envitems[i].name, name2) == 0) { + found++; + ASSERT(strlen(envitems[i].value) == 0); + } else if (envitems[i].name[0] == '=') { + found_win_special++; + } + } + + ASSERT(found == 2); +#ifdef _WIN32 + ASSERT(found_win_special > 0); +#endif + + uv_os_free_environ(envitems, envcount); + + r = uv_os_unsetenv(name); + ASSERT(r == 0); + + r = uv_os_unsetenv(name2); + ASSERT(r == 0); + + for (i = 1; i <= 4; i++) { + size_t n; + char* p; + + n = i * 32768; + size = n + 1; + + p = malloc(size); + ASSERT_NOT_NULL(p); + + memset(p, 'x', n); + p[n] = '\0'; + + ASSERT_EQ(0, uv_os_setenv(name, p)); + ASSERT_EQ(0, uv_os_getenv(name, p, &size)); + ASSERT_EQ(n, size); + + for (n = 0; n < size; n++) + ASSERT_EQ('x', p[n]); + + ASSERT_EQ(0, uv_os_unsetenv(name)); + free(p); + } + return 0; } diff --git a/test/test-fork.c b/test/test-fork.c index f47ae3e..9e4684f 100644 --- a/test/test-fork.c +++ b/test/test-fork.c @@ -676,5 +676,8 @@ TEST_IMPL(fork_threadpool_queue_work_simple) { } #endif /* !__MVS__ */ +#else + +typedef int file_has_no_tests; /* ISO C forbids an empty translation unit. */ #endif /* !_WIN32 */ diff --git a/test/test-fs-copyfile.c b/test/test-fs-copyfile.c index eadff54..3335c88 100644 --- a/test/test-fs-copyfile.c +++ b/test/test-fs-copyfile.c @@ -23,7 +23,9 @@ #include "task.h" #if defined(__unix__) || defined(__POSIX__) || \ - defined(__APPLE__) || defined(_AIX) || defined(__MVS__) + defined(__APPLE__) || defined(__sun) || \ + defined(_AIX) || defined(__MVS__) || \ + defined(__HAIKU__) #include /* unlink, etc. */ #else # include @@ -118,6 +120,13 @@ TEST_IMPL(fs_copyfile) { ASSERT(r != 0); uv_fs_req_cleanup(&req); + /* Succeeds if src and dst files are identical. */ + touch_file(src, 12); + r = uv_fs_copyfile(NULL, &req, src, src, 0, NULL); + ASSERT(r == 0); + uv_fs_req_cleanup(&req); + unlink(src); + /* Copies file synchronously. Creates new file. */ unlink(dst); r = uv_fs_copyfile(NULL, &req, fixture, dst, 0, NULL); @@ -179,11 +188,25 @@ TEST_IMPL(fs_copyfile) { unlink(dst); r = uv_fs_copyfile(NULL, &req, fixture, dst, UV_FS_COPYFILE_FICLONE_FORCE, NULL); - ASSERT(r == 0 || r == UV_ENOSYS || r == UV_ENOTSUP); + ASSERT(r <= 0); if (r == 0) handle_result(&req); +#ifndef _WIN32 + /* Copying respects permissions/mode. */ + unlink(dst); + touch_file(dst, 0); + chmod(dst, S_IRUSR|S_IRGRP|S_IROTH); /* Sets file mode to 444 (read-only). */ + r = uv_fs_copyfile(NULL, &req, fixture, dst, 0, NULL); + /* On IBMi PASE, qsecofr users can overwrite read-only files */ +# ifndef __PASE__ + ASSERT(req.result == UV_EACCES); + ASSERT(r == UV_EACCES); +# endif + uv_fs_req_cleanup(&req); +#endif + unlink(dst); /* Cleanup */ return 0; } diff --git a/test/test-fs-event.c b/test/test-fs-event.c index 5ddccff..28a6a1e 100644 --- a/test/test-fs-event.c +++ b/test/test-fs-event.c @@ -25,6 +25,10 @@ #include #include +#if defined(__APPLE__) && !TARGET_OS_IPHONE +# include +#endif + #ifndef HAVE_KQUEUE # if defined(__APPLE__) || \ defined(__DragonFly__) || \ @@ -47,6 +51,7 @@ static const char file_prefix[] = "fsevent-"; static const int fs_event_file_count = 16; #if defined(__APPLE__) || defined(_WIN32) static const char file_prefix_in_subdir[] = "subdir"; +static int fs_multievent_cb_called; #endif static uv_timer_t timer; static int timer_cb_called; @@ -280,7 +285,13 @@ static void fs_event_cb_dir_multi_file_in_subdir(uv_fs_event_t* handle, if (filename && strcmp(filename, file_prefix_in_subdir) == 0) return; #endif - fs_event_cb_called++; + /* It may happen that the "subdir" creation event is captured even though + * we started watching after its actual creation. + */ + if (strcmp(filename, "subdir") == 0) + return; + + fs_multievent_cb_called++; ASSERT(handle == &fs_event); ASSERT(status == 0); ASSERT(events == UV_CHANGE || events == UV_RENAME); @@ -295,11 +306,13 @@ static void fs_event_cb_dir_multi_file_in_subdir(uv_fs_event_t* handle, sizeof(file_prefix_in_subdir) - 1) == 0); #endif - if (fs_event_created + fs_event_removed == fs_event_file_count) { + if (fs_event_created == fs_event_file_count && + fs_multievent_cb_called == fs_event_created) { /* Once we've processed all create events, delete all files */ ASSERT(0 == uv_timer_start(&timer, fs_event_unlink_files_in_subdir, 1, 0)); - } else if (fs_event_cb_called == 2 * fs_event_file_count) { + } else if (fs_multievent_cb_called == 2 * fs_event_file_count) { /* Once we've processed all create and delete events, stop watching */ + ASSERT(fs_event_removed == fs_event_file_count); uv_close((uv_handle_t*) &timer, close_cb); uv_close((uv_handle_t*) handle, close_cb); } @@ -393,6 +406,21 @@ static void timer_cb_watch_twice(uv_timer_t* handle) { uv_close((uv_handle_t*) handle, NULL); } +static void fs_event_cb_close(uv_fs_event_t* handle, + const char* filename, + int events, + int status) { + ASSERT(status == 0); + + ASSERT(fs_event_cb_called < 3); + ++fs_event_cb_called; + + if (fs_event_cb_called == 3) { + uv_close((uv_handle_t*) handle, close_cb); + } +} + + TEST_IMPL(fs_event_watch_dir) { #if defined(NO_FS_EVENTS) RETURN_SKIP(NO_FS_EVENTS); @@ -434,10 +462,12 @@ TEST_IMPL(fs_event_watch_dir) { return 0; } + TEST_IMPL(fs_event_watch_dir_recursive) { #if defined(__APPLE__) || defined(_WIN32) uv_loop_t* loop; int r; + uv_fs_event_t fs_event_root; /* Setup */ loop = uv_default_loop(); @@ -451,17 +481,37 @@ TEST_IMPL(fs_event_watch_dir_recursive) { r = uv_fs_event_init(loop, &fs_event); ASSERT(r == 0); - r = uv_fs_event_start(&fs_event, fs_event_cb_dir_multi_file_in_subdir, "watch_dir", UV_FS_EVENT_RECURSIVE); + r = uv_fs_event_start(&fs_event, + fs_event_cb_dir_multi_file_in_subdir, + "watch_dir", + UV_FS_EVENT_RECURSIVE); ASSERT(r == 0); r = uv_timer_init(loop, &timer); ASSERT(r == 0); r = uv_timer_start(&timer, fs_event_create_files_in_subdir, 100, 0); ASSERT(r == 0); +#ifndef _WIN32 + /* Also try to watch the root directory. + * This will be noisier, so we're just checking for any couple events to happen. */ + r = uv_fs_event_init(loop, &fs_event_root); + ASSERT(r == 0); + r = uv_fs_event_start(&fs_event_root, + fs_event_cb_close, + "/", + UV_FS_EVENT_RECURSIVE); + ASSERT(r == 0); +#else + fs_event_cb_called += 3; + close_cb_called += 1; + (void)fs_event_root; +#endif + uv_run(loop, UV_RUN_DEFAULT); - ASSERT(fs_event_cb_called == fs_event_created + fs_event_removed); - ASSERT(close_cb_called == 2); + ASSERT(fs_multievent_cb_called == fs_event_created + fs_event_removed); + ASSERT(fs_event_cb_called == 3); + ASSERT(close_cb_called == 3); /* Cleanup */ fs_event_unlink_files_in_subdir(NULL); @@ -480,6 +530,8 @@ TEST_IMPL(fs_event_watch_dir_recursive) { #ifdef _WIN32 TEST_IMPL(fs_event_watch_dir_short_path) { uv_loop_t* loop; + uv_fs_t req; + int has_shortnames; int r; /* Setup */ @@ -489,26 +541,37 @@ TEST_IMPL(fs_event_watch_dir_short_path) { create_dir("watch_dir"); create_file("watch_dir/file1"); - r = uv_fs_event_init(loop, &fs_event); - ASSERT(r == 0); - r = uv_fs_event_start(&fs_event, fs_event_cb_dir, "watch_~1", 0); - ASSERT(r == 0); - r = uv_timer_init(loop, &timer); - ASSERT(r == 0); - r = uv_timer_start(&timer, timer_cb_file, 100, 0); - ASSERT(r == 0); + /* Newer version of Windows ship with + HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\NtfsDisable8dot3NameCreation + not equal to 0. So we verify the files we created are addressable by a 8.3 + short name */ + has_shortnames = uv_fs_stat(NULL, &req, "watch_~1", NULL) != UV_ENOENT; + if (has_shortnames) { + r = uv_fs_event_init(loop, &fs_event); + ASSERT(r == 0); + r = uv_fs_event_start(&fs_event, fs_event_cb_dir, "watch_~1", 0); + ASSERT(r == 0); + r = uv_timer_init(loop, &timer); + ASSERT(r == 0); + r = uv_timer_start(&timer, timer_cb_file, 100, 0); + ASSERT(r == 0); - uv_run(loop, UV_RUN_DEFAULT); + uv_run(loop, UV_RUN_DEFAULT); - ASSERT(fs_event_cb_called == 1); - ASSERT(timer_cb_called == 1); - ASSERT(close_cb_called == 1); + ASSERT(fs_event_cb_called == 1); + ASSERT(timer_cb_called == 1); + ASSERT(close_cb_called == 1); + } /* Cleanup */ remove("watch_dir/file1"); remove("watch_dir/"); MAKE_VALGRIND_HAPPY(); + + if (!has_shortnames) + RETURN_SKIP("Was not able to address files with 8.3 short name."); + return 0; } #endif @@ -576,6 +639,15 @@ TEST_IMPL(fs_event_watch_file_exact_path) { create_dir("watch_dir"); create_file("watch_dir/file.js"); create_file("watch_dir/file.jsx"); +#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_12) + /* Empirically, FSEvents seems to (reliably) report the preceeding + * create_file events prior to macOS 10.11.6 in the subsequent fs_watch + * creation, but that behavior hasn't been observed to occur on newer + * versions. Give a long delay here to let the system settle before running + * the test. */ + uv_sleep(1100); + uv_update_time(loop); +#endif r = uv_fs_event_init(loop, &fs_event); ASSERT(r == 0); @@ -635,6 +707,13 @@ TEST_IMPL(fs_event_watch_file_current_dir) { /* Setup */ remove("watch_file"); create_file("watch_file"); +#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_12) + /* Empirically, kevent seems to (sometimes) report the preceeding + * create_file events prior to macOS 10.11.6 in the subsequent fs_event_start + * So let the system settle before running the test. */ + uv_sleep(1100); + uv_update_time(loop); +#endif r = uv_fs_event_init(loop, &fs_event); ASSERT(r == 0); @@ -648,7 +727,7 @@ TEST_IMPL(fs_event_watch_file_current_dir) { r = uv_timer_init(loop, &timer); ASSERT(r == 0); - r = uv_timer_start(&timer, timer_cb_touch, 100, 0); + r = uv_timer_start(&timer, timer_cb_touch, 1100, 0); ASSERT(r == 0); ASSERT(timer_cb_touch_called == 0); @@ -843,18 +922,6 @@ TEST_IMPL(fs_event_close_with_pending_event) { return 0; } -static void fs_event_cb_close(uv_fs_event_t* handle, const char* filename, - int events, int status) { - ASSERT(status == 0); - - ASSERT(fs_event_cb_called < 3); - ++fs_event_cb_called; - - if (fs_event_cb_called == 3) { - uv_close((uv_handle_t*) handle, close_cb); - } -} - TEST_IMPL(fs_event_close_in_callback) { #if defined(NO_FS_EVENTS) RETURN_SKIP(NO_FS_EVENTS); @@ -936,32 +1003,48 @@ TEST_IMPL(fs_event_getpath) { RETURN_SKIP(NO_FS_EVENTS); #endif uv_loop_t* loop = uv_default_loop(); + unsigned i; int r; char buf[1024]; size_t len; + const char* const watch_dir[] = { + "watch_dir", + "watch_dir/", + "watch_dir///", + "watch_dir/subfolder/..", + "watch_dir//subfolder//..//", + }; create_dir("watch_dir"); + create_dir("watch_dir/subfolder"); - r = uv_fs_event_init(loop, &fs_event); - ASSERT(r == 0); - len = sizeof buf; - r = uv_fs_event_getpath(&fs_event, buf, &len); - ASSERT(r == UV_EINVAL); - r = uv_fs_event_start(&fs_event, fail_cb, "watch_dir", 0); - ASSERT(r == 0); - len = sizeof buf; - r = uv_fs_event_getpath(&fs_event, buf, &len); - ASSERT(r == 0); - ASSERT(buf[len - 1] != 0); - ASSERT(buf[len] == '\0'); - ASSERT(memcmp(buf, "watch_dir", len) == 0); - r = uv_fs_event_stop(&fs_event); - ASSERT(r == 0); - uv_close((uv_handle_t*) &fs_event, close_cb); - uv_run(loop, UV_RUN_DEFAULT); + for (i = 0; i < ARRAY_SIZE(watch_dir); i++) { + r = uv_fs_event_init(loop, &fs_event); + ASSERT(r == 0); + len = sizeof buf; + r = uv_fs_event_getpath(&fs_event, buf, &len); + ASSERT(r == UV_EINVAL); + r = uv_fs_event_start(&fs_event, fail_cb, watch_dir[i], 0); + ASSERT(r == 0); + len = 0; + r = uv_fs_event_getpath(&fs_event, buf, &len); + ASSERT(r == UV_ENOBUFS); + ASSERT(len < sizeof buf); /* sanity check */ + ASSERT(len == strlen(watch_dir[i]) + 1); + r = uv_fs_event_getpath(&fs_event, buf, &len); + ASSERT(r == 0); + ASSERT(len == strlen(watch_dir[i])); + ASSERT(strcmp(buf, watch_dir[i]) == 0); + r = uv_fs_event_stop(&fs_event); + ASSERT(r == 0); + uv_close((uv_handle_t*) &fs_event, close_cb); - ASSERT(close_cb_called == 1); + uv_run(loop, UV_RUN_DEFAULT); + + ASSERT(close_cb_called == 1); + close_cb_called = 0; + } remove("watch_dir/"); MAKE_VALGRIND_HAPPY(); @@ -1082,6 +1165,9 @@ TEST_IMPL(fs_event_watch_invalid_path) { r = uv_fs_event_start(&fs_event, fs_event_cb_file, "<:;", 0); ASSERT(r != 0); ASSERT(uv_is_active((uv_handle_t*) &fs_event) == 0); + r = uv_fs_event_start(&fs_event, fs_event_cb_file, "", 0); + ASSERT(r != 0); + ASSERT(uv_is_active((uv_handle_t*) &fs_event) == 0); MAKE_VALGRIND_HAPPY(); return 0; } diff --git a/test/test-fs-fd-hash.c b/test/test-fs-fd-hash.c new file mode 100644 index 0000000..8b4bc03 --- /dev/null +++ b/test/test-fs-fd-hash.c @@ -0,0 +1,133 @@ +/* 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. + */ + +#if defined(_WIN32) && !defined(USING_UV_SHARED) + +#include "uv.h" +#include "task.h" + +#include "../src/win/fs-fd-hash-inl.h" + + +#define HASH_MAX 1000000000 +#define HASH_INC (1000 * UV__FD_HASH_SIZE + 2) +#define BUCKET_MAX (UV__FD_HASH_SIZE * UV__FD_HASH_GROUP_SIZE * 10) +#define BUCKET_INC UV__FD_HASH_SIZE +#define FD_DIFF 9 + + +void assert_nonexistent(int fd) { + struct uv__fd_info_s info = { 0 }; + ASSERT(!uv__fd_hash_get(fd, &info)); + ASSERT(!uv__fd_hash_remove(fd, &info)); +} + +void assert_existent(int fd) { + struct uv__fd_info_s info = { 0 }; + ASSERT(uv__fd_hash_get(fd, &info)); + ASSERT(info.flags == fd + FD_DIFF); +} + +void assert_insertion(int fd) { + struct uv__fd_info_s info = { 0 }; + assert_nonexistent(fd); + info.flags = fd + FD_DIFF; + uv__fd_hash_add(fd, &info); + assert_existent(fd); +} + +void assert_removal(int fd) { + struct uv__fd_info_s info = { 0 }; + assert_existent(fd); + uv__fd_hash_remove(fd, &info); + ASSERT(info.flags == fd + FD_DIFF); + assert_nonexistent(fd); +} + + +/* Run a function for a set of values up to a very high number */ +#define RUN_HASH(function) \ + do { \ + for (fd = 0; fd < HASH_MAX; fd += HASH_INC) { \ + function(fd); \ + } \ + } while (0) + +/* Run a function for a set of values that will cause many collisions */ +#define RUN_COLLISIONS(function) \ + do { \ + for (fd = 1; fd < BUCKET_MAX; fd += BUCKET_INC) { \ + function(fd); \ + } \ + } while (0) + + +TEST_IMPL(fs_fd_hash) { + int fd; + + uv__fd_hash_init(); + + /* Empty table */ + RUN_HASH(assert_nonexistent); + RUN_COLLISIONS(assert_nonexistent); + + /* Fill up */ + RUN_HASH(assert_insertion); + RUN_COLLISIONS(assert_insertion); + + /* Full */ + RUN_HASH(assert_existent); + RUN_COLLISIONS(assert_existent); + + /* Update */ + { + struct uv__fd_info_s info = { 0 }; + info.flags = FD_DIFF + FD_DIFF; + uv__fd_hash_add(0, &info); + } + { + struct uv__fd_info_s info = { 0 }; + ASSERT(uv__fd_hash_get(0, &info)); + ASSERT(info.flags == FD_DIFF + FD_DIFF); + } + { + /* Leave as it was, will be again tested below */ + struct uv__fd_info_s info = { 0 }; + info.flags = FD_DIFF; + uv__fd_hash_add(0, &info); + } + + /* Remove all */ + RUN_HASH(assert_removal); + RUN_COLLISIONS(assert_removal); + + /* Empty table */ + RUN_HASH(assert_nonexistent); + RUN_COLLISIONS(assert_nonexistent); + + return 0; +} + +#else + +typedef int file_has_no_tests; /* ISO C forbids an empty translation unit. */ + +#endif /* ifndef _WIN32 */ diff --git a/test/test-fs-open-flags.c b/test/test-fs-open-flags.c new file mode 100644 index 0000000..fcef2fb --- /dev/null +++ b/test/test-fs-open-flags.c @@ -0,0 +1,435 @@ +/* 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. + */ + +#ifdef _WIN32 + +#include "uv.h" +#include "task.h" + +#if defined(__unix__) || defined(__POSIX__) || \ + defined(__APPLE__) || defined(__sun) || \ + defined(_AIX) || defined(__MVS__) || \ + defined(__HAIKU__) +# include /* unlink, rmdir */ +#else +# include +# define rmdir _rmdir +# define unlink _unlink +#endif + +static int flags; + +static uv_fs_t close_req; +static uv_fs_t mkdir_req; +static uv_fs_t open_req; +static uv_fs_t read_req; +static uv_fs_t rmdir_req; +static uv_fs_t unlink_req; +static uv_fs_t write_req; + +static char buf[32]; +static uv_buf_t iov; + +/* Opening the same file multiple times quickly can cause uv_fs_open to fail + * with EBUSY, so append an identifier to the file name for each operation */ +static int sid = 0; + +#define FILE_NAME_SIZE 128 +static char absent_file[FILE_NAME_SIZE]; +static char empty_file[FILE_NAME_SIZE]; +static char dummy_file[FILE_NAME_SIZE]; +static char empty_dir[] = "empty_dir"; + +static void setup() { + int r; + + /* empty_dir */ + r = uv_fs_rmdir(NULL, &rmdir_req, empty_dir, NULL); + ASSERT(r == 0 || r == UV_ENOENT); + ASSERT(rmdir_req.result == 0 || rmdir_req.result == UV_ENOENT); + uv_fs_req_cleanup(&rmdir_req); + + r = uv_fs_mkdir(NULL, &mkdir_req, empty_dir, 0755, NULL); + ASSERT(r == 0); + ASSERT(mkdir_req.result == 0); + uv_fs_req_cleanup(&mkdir_req); +} + +static void refresh() { + int r; + + /* absent_file */ + sprintf(absent_file, "test_file_%d", sid++); + + r = uv_fs_unlink(NULL, &unlink_req, absent_file, NULL); + ASSERT(r == 0 || r == UV_ENOENT); + ASSERT(unlink_req.result == 0 || unlink_req.result == UV_ENOENT); + uv_fs_req_cleanup(&unlink_req); + + /* empty_file */ + sprintf(empty_file, "test_file_%d", sid++); + + r = uv_fs_open(NULL, &open_req, empty_file, + UV_FS_O_TRUNC | UV_FS_O_CREAT | UV_FS_O_WRONLY, S_IWUSR | S_IRUSR, NULL); + ASSERT(r >= 0); + ASSERT(open_req.result >= 0); + uv_fs_req_cleanup(&open_req); + + r = uv_fs_close(NULL, &close_req, open_req.result, NULL); + ASSERT(r == 0); + ASSERT(close_req.result == 0); + uv_fs_req_cleanup(&close_req); + + /* dummy_file */ + sprintf(dummy_file, "test_file_%d", sid++); + + r = uv_fs_open(NULL, &open_req, dummy_file, + UV_FS_O_TRUNC | UV_FS_O_CREAT | UV_FS_O_WRONLY, S_IWUSR | S_IRUSR, NULL); + ASSERT(r >= 0); + ASSERT(open_req.result >= 0); + uv_fs_req_cleanup(&open_req); + + iov = uv_buf_init("a", 1); + r = uv_fs_write(NULL, &write_req, open_req.result, &iov, 1, -1, NULL); + ASSERT(r == 1); + ASSERT(write_req.result == 1); + uv_fs_req_cleanup(&write_req); + + r = uv_fs_close(NULL, &close_req, open_req.result, NULL); + ASSERT(r == 0); + ASSERT(close_req.result == 0); + uv_fs_req_cleanup(&close_req); +} + +static void cleanup() { + unlink(absent_file); + unlink(empty_file); + unlink(dummy_file); +} + +static void openFail(char *file, int error) { + int r; + + refresh(); + + r = uv_fs_open(NULL, &open_req, file, flags, S_IWUSR | S_IRUSR, NULL); + ASSERT(r == error); + ASSERT(open_req.result == error); + uv_fs_req_cleanup(&open_req); + + /* Ensure the first call does not create the file */ + r = uv_fs_open(NULL, &open_req, file, flags, S_IWUSR | S_IRUSR, NULL); + ASSERT(r == error); + ASSERT(open_req.result == error); + uv_fs_req_cleanup(&open_req); + + cleanup(); +} + +static void refreshOpen(char *file) { + int r; + + refresh(); + + r = uv_fs_open(NULL, &open_req, file, flags, S_IWUSR | S_IRUSR, NULL); + ASSERT(r >= 0); + ASSERT(open_req.result >= 0); + uv_fs_req_cleanup(&open_req); +} + +static void writeExpect(char *file, char *expected, int size) { + int r; + + refreshOpen(file); + + iov = uv_buf_init("b", 1); + r = uv_fs_write(NULL, &write_req, open_req.result, &iov, 1, -1, NULL); + ASSERT(r == 1); + ASSERT(write_req.result == 1); + uv_fs_req_cleanup(&write_req); + + iov = uv_buf_init("c", 1); + r = uv_fs_write(NULL, &write_req, open_req.result, &iov, 1, -1, NULL); + ASSERT(r == 1); + ASSERT(write_req.result == 1); + uv_fs_req_cleanup(&write_req); + + r = uv_fs_close(NULL, &close_req, open_req.result, NULL); + ASSERT(r == 0); + ASSERT(close_req.result == 0); + uv_fs_req_cleanup(&close_req); + + /* Check contents */ + r = uv_fs_open(NULL, &open_req, file, UV_FS_O_RDONLY, S_IWUSR | S_IRUSR, NULL); + ASSERT(r >= 0); + ASSERT(open_req.result >= 0); + uv_fs_req_cleanup(&open_req); + + iov = uv_buf_init(buf, sizeof(buf)); + r = uv_fs_read(NULL, &read_req, open_req.result, &iov, 1, -1, NULL); + ASSERT(r == size); + ASSERT(read_req.result == size); + ASSERT(strncmp(buf, expected, size) == 0); + uv_fs_req_cleanup(&read_req); + + r = uv_fs_close(NULL, &close_req, open_req.result, NULL); + ASSERT(r == 0); + ASSERT(close_req.result == 0); + uv_fs_req_cleanup(&close_req); + + cleanup(); +} + +static void writeFail(char *file, int error) { + int r; + + refreshOpen(file); + + iov = uv_buf_init("z", 1); + r = uv_fs_write(NULL, &write_req, open_req.result, &iov, 1, -1, NULL); + ASSERT(r == error); + ASSERT(write_req.result == error); + uv_fs_req_cleanup(&write_req); + + iov = uv_buf_init("z", 1); + r = uv_fs_write(NULL, &write_req, open_req.result, &iov, 1, -1, NULL); + ASSERT(r == error); + ASSERT(write_req.result == error); + uv_fs_req_cleanup(&write_req); + + r = uv_fs_close(NULL, &close_req, open_req.result, NULL); + ASSERT(r == 0); + ASSERT(close_req.result == 0); + uv_fs_req_cleanup(&close_req); + + cleanup(); +} + +static void readExpect(char *file, char *expected, int size) { + int r; + + refreshOpen(file); + + iov = uv_buf_init(buf, sizeof(buf)); + r = uv_fs_read(NULL, &read_req, open_req.result, &iov, 1, -1, NULL); + ASSERT(r == size); + ASSERT(read_req.result == size); + ASSERT(strncmp(buf, expected, size) == 0); + uv_fs_req_cleanup(&read_req); + + r = uv_fs_close(NULL, &close_req, open_req.result, NULL); + ASSERT(r == 0); + ASSERT(close_req.result == 0); + uv_fs_req_cleanup(&close_req); + + cleanup(); +} + +static void readFail(char *file, int error) { + int r; + + refreshOpen(file); + + iov = uv_buf_init(buf, sizeof(buf)); + r = uv_fs_read(NULL, &read_req, open_req.result, &iov, 1, -1, NULL); + ASSERT(r == error); + ASSERT(read_req.result == error); + uv_fs_req_cleanup(&read_req); + + iov = uv_buf_init(buf, sizeof(buf)); + r = uv_fs_read(NULL, &read_req, open_req.result, &iov, 1, -1, NULL); + ASSERT(r == error); + ASSERT(read_req.result == error); + uv_fs_req_cleanup(&read_req); + + r = uv_fs_close(NULL, &close_req, open_req.result, NULL); + ASSERT(r == 0); + ASSERT(close_req.result == 0); + uv_fs_req_cleanup(&close_req); + + cleanup(); +} + +static void fs_open_flags(int add_flags) { + /* Follow the order from + * https://github.com/nodejs/node/blob/1a96abe849/lib/internal/fs/utils.js#L329-L354 + */ + + /* r */ + flags = add_flags | UV_FS_O_RDONLY; + openFail(absent_file, UV_ENOENT); + writeFail(empty_file, UV_EPERM); + readExpect(empty_file, "", 0); + writeFail(dummy_file, UV_EPERM); + readExpect(dummy_file, "a", 1); + writeFail(empty_dir, UV_EPERM); + readFail(empty_dir, UV_EISDIR); + + /* rs */ + flags = add_flags | UV_FS_O_RDONLY | UV_FS_O_SYNC; + openFail(absent_file, UV_ENOENT); + writeFail(empty_file, UV_EPERM); + readExpect(empty_file, "", 0); + writeFail(dummy_file, UV_EPERM); + readExpect(dummy_file, "a", 1); + writeFail(empty_dir, UV_EPERM); + readFail(empty_dir, UV_EISDIR); + + /* r+ */ + flags = add_flags | UV_FS_O_RDWR; + openFail(absent_file, UV_ENOENT); + writeExpect(empty_file, "bc", 2); + readExpect(empty_file, "", 0); + writeExpect(dummy_file, "bc", 2); + readExpect(dummy_file, "a", 1); + writeFail(empty_dir, UV_EISDIR); + readFail(empty_dir, UV_EISDIR); + + /* rs+ */ + flags = add_flags | UV_FS_O_RDWR | UV_FS_O_SYNC; + openFail(absent_file, UV_ENOENT); + writeExpect(empty_file, "bc", 2); + readExpect(empty_file, "", 0); + writeExpect(dummy_file, "bc", 2); + readExpect(dummy_file, "a", 1); + writeFail(empty_dir, UV_EISDIR); + readFail(empty_dir, UV_EISDIR); + + /* w */ + flags = add_flags | UV_FS_O_TRUNC | UV_FS_O_CREAT | UV_FS_O_WRONLY; + writeExpect(absent_file, "bc", 2); + readFail(absent_file, UV_EPERM); + writeExpect(empty_file, "bc", 2); + readFail(empty_file, UV_EPERM); + writeExpect(dummy_file, "bc", 2); + readFail(dummy_file, UV_EPERM); + openFail(empty_dir, UV_EISDIR); + + /* wx */ + flags = add_flags | UV_FS_O_TRUNC | UV_FS_O_CREAT | UV_FS_O_WRONLY | + UV_FS_O_EXCL; + writeExpect(absent_file, "bc", 2); + readFail(absent_file, UV_EPERM); + openFail(empty_file, UV_EEXIST); + openFail(dummy_file, UV_EEXIST); + openFail(empty_dir, UV_EEXIST); + + /* w+ */ + flags = add_flags | UV_FS_O_TRUNC | UV_FS_O_CREAT | UV_FS_O_RDWR; + writeExpect(absent_file, "bc", 2); + readExpect(absent_file, "", 0); + writeExpect(empty_file, "bc", 2); + readExpect(empty_file, "", 0); + writeExpect(dummy_file, "bc", 2); + readExpect(dummy_file, "", 0); + openFail(empty_dir, UV_EISDIR); + + /* wx+ */ + flags = add_flags | UV_FS_O_TRUNC | UV_FS_O_CREAT | UV_FS_O_RDWR | + UV_FS_O_EXCL; + writeExpect(absent_file, "bc", 2); + readExpect(absent_file, "", 0); + openFail(empty_file, UV_EEXIST); + openFail(dummy_file, UV_EEXIST); + openFail(empty_dir, UV_EEXIST); + + /* a */ + flags = add_flags | UV_FS_O_APPEND | UV_FS_O_CREAT | UV_FS_O_WRONLY; + writeExpect(absent_file, "bc", 2); + readFail(absent_file, UV_EPERM); + writeExpect(empty_file, "bc", 2); + readFail(empty_file, UV_EPERM); + writeExpect(dummy_file, "abc", 3); + readFail(dummy_file, UV_EPERM); + writeFail(empty_dir, UV_EISDIR); + readFail(empty_dir, UV_EPERM); + + /* ax */ + flags = add_flags | UV_FS_O_APPEND | UV_FS_O_CREAT | UV_FS_O_WRONLY | + UV_FS_O_EXCL; + writeExpect(absent_file, "bc", 2); + readFail(absent_file, UV_EPERM); + openFail(empty_file, UV_EEXIST); + openFail(dummy_file, UV_EEXIST); + openFail(empty_dir, UV_EEXIST); + + /* as */ + flags = add_flags | UV_FS_O_APPEND | UV_FS_O_CREAT | UV_FS_O_WRONLY | + UV_FS_O_SYNC; + writeExpect(absent_file, "bc", 2); + readFail(absent_file, UV_EPERM); + writeExpect(empty_file, "bc", 2); + readFail(empty_file, UV_EPERM); + writeExpect(dummy_file, "abc", 3); + readFail(dummy_file, UV_EPERM); + writeFail(empty_dir, UV_EISDIR); + readFail(empty_dir, UV_EPERM); + + /* a+ */ + flags = add_flags | UV_FS_O_APPEND | UV_FS_O_CREAT | UV_FS_O_RDWR; + writeExpect(absent_file, "bc", 2); + readExpect(absent_file, "", 0); + writeExpect(empty_file, "bc", 2); + readExpect(empty_file, "", 0); + writeExpect(dummy_file, "abc", 3); + readExpect(dummy_file, "a", 1); + writeFail(empty_dir, UV_EISDIR); + readFail(empty_dir, UV_EISDIR); + + /* ax+ */ + flags = add_flags | UV_FS_O_APPEND | UV_FS_O_CREAT | UV_FS_O_RDWR | + UV_FS_O_EXCL; + writeExpect(absent_file, "bc", 2); + readExpect(absent_file, "", 0); + openFail(empty_file, UV_EEXIST); + openFail(dummy_file, UV_EEXIST); + openFail(empty_dir, UV_EEXIST); + + /* as+ */ + flags = add_flags | UV_FS_O_APPEND | UV_FS_O_CREAT | UV_FS_O_RDWR | + UV_FS_O_SYNC; + writeExpect(absent_file, "bc", 2); + readExpect(absent_file, "", 0); + writeExpect(empty_file, "bc", 2); + readExpect(empty_file, "", 0); + writeExpect(dummy_file, "abc", 3); + readExpect(dummy_file, "a", 1); + writeFail(empty_dir, UV_EISDIR); + readFail(empty_dir, UV_EISDIR); +} +TEST_IMPL(fs_open_flags) { + setup(); + + fs_open_flags(0); + fs_open_flags(UV_FS_O_FILEMAP); + + /* Cleanup. */ + rmdir(empty_dir); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +#else + +typedef int file_has_no_tests; /* ISO C forbids an empty translation unit. */ + +#endif /* ifndef _WIN32 */ diff --git a/test/test-fs-poll.c b/test/test-fs-poll.c index 737d50d..9dfd5fd 100644 --- a/test/test-fs-poll.c +++ b/test/test-fs-poll.c @@ -37,6 +37,10 @@ static void poll_cb_fail(uv_fs_poll_t* handle, int status, const uv_stat_t* prev, const uv_stat_t* curr); +static void poll_cb_noop(uv_fs_poll_t* handle, + int status, + const uv_stat_t* prev, + const uv_stat_t* curr); static uv_fs_poll_t poll_handle; static uv_timer_t timer_handle; @@ -84,6 +88,12 @@ static void poll_cb_fail(uv_fs_poll_t* handle, ASSERT(0 && "fail_cb called"); } +static void poll_cb_noop(uv_fs_poll_t* handle, + int status, + const uv_stat_t* prev, + const uv_stat_t* curr) { +} + static void poll_cb(uv_fs_poll_t* handle, int status, @@ -185,3 +195,106 @@ TEST_IMPL(fs_poll_getpath) { MAKE_VALGRIND_HAPPY(); return 0; } + + +TEST_IMPL(fs_poll_close_request) { + uv_loop_t loop; + uv_fs_poll_t poll_handle; + + remove(FIXTURE); + + ASSERT(0 == uv_loop_init(&loop)); + + ASSERT(0 == uv_fs_poll_init(&loop, &poll_handle)); + ASSERT(0 == uv_fs_poll_start(&poll_handle, poll_cb_fail, FIXTURE, 100)); + uv_close((uv_handle_t*) &poll_handle, close_cb); + while (close_cb_called == 0) + uv_run(&loop, UV_RUN_ONCE); + ASSERT(close_cb_called == 1); + + ASSERT(0 == uv_loop_close(&loop)); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +TEST_IMPL(fs_poll_close_request_multi_start_stop) { + uv_loop_t loop; + uv_fs_poll_t poll_handle; + int i; + + remove(FIXTURE); + + ASSERT(0 == uv_loop_init(&loop)); + + ASSERT(0 == uv_fs_poll_init(&loop, &poll_handle)); + + for (i = 0; i < 10; ++i) { + ASSERT(0 == uv_fs_poll_start(&poll_handle, poll_cb_fail, FIXTURE, 100)); + ASSERT(0 == uv_fs_poll_stop(&poll_handle)); + } + uv_close((uv_handle_t*) &poll_handle, close_cb); + while (close_cb_called == 0) + uv_run(&loop, UV_RUN_ONCE); + ASSERT(close_cb_called == 1); + + ASSERT(0 == uv_loop_close(&loop)); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +TEST_IMPL(fs_poll_close_request_multi_stop_start) { + uv_loop_t loop; + uv_fs_poll_t poll_handle; + int i; + + remove(FIXTURE); + + ASSERT(0 == uv_loop_init(&loop)); + + ASSERT(0 == uv_fs_poll_init(&loop, &poll_handle)); + + for (i = 0; i < 10; ++i) { + ASSERT(0 == uv_fs_poll_stop(&poll_handle)); + ASSERT(0 == uv_fs_poll_start(&poll_handle, poll_cb_fail, FIXTURE, 100)); + } + uv_close((uv_handle_t*) &poll_handle, close_cb); + while (close_cb_called == 0) + uv_run(&loop, UV_RUN_ONCE); + ASSERT(close_cb_called == 1); + + ASSERT(0 == uv_loop_close(&loop)); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +TEST_IMPL(fs_poll_close_request_stop_when_active) { + /* Regression test for https://github.com/libuv/libuv/issues/2287. */ + uv_loop_t loop; + uv_fs_poll_t poll_handle; + + remove(FIXTURE); + + ASSERT(0 == uv_loop_init(&loop)); + + /* Set up all handles. */ + ASSERT(0 == uv_fs_poll_init(&loop, &poll_handle)); + ASSERT(0 == uv_fs_poll_start(&poll_handle, poll_cb_noop, FIXTURE, 100)); + uv_run(&loop, UV_RUN_ONCE); + + /* Close the timer handle, and do not crash. */ + ASSERT(0 == uv_fs_poll_stop(&poll_handle)); + uv_run(&loop, UV_RUN_ONCE); + + /* Clean up after the test. */ + uv_close((uv_handle_t*) &poll_handle, close_cb); + uv_run(&loop, UV_RUN_ONCE); + ASSERT(close_cb_called == 1); + + ASSERT(0 == uv_loop_close(&loop)); + + MAKE_VALGRIND_HAPPY(); + return 0; +} diff --git a/test/test-fs-readdir.c b/test/test-fs-readdir.c new file mode 100644 index 0000000..5efc853 --- /dev/null +++ b/test/test-fs-readdir.c @@ -0,0 +1,462 @@ +/* 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 +#include + +static uv_fs_t opendir_req; +static uv_fs_t readdir_req; +static uv_fs_t closedir_req; + +static uv_dirent_t dirents[1]; + +static int empty_opendir_cb_count; +static int empty_closedir_cb_count; + +static void cleanup_test_files(void) { + uv_fs_t req; + + uv_fs_unlink(NULL, &req, "test_dir/file1", NULL); + uv_fs_req_cleanup(&req); + uv_fs_unlink(NULL, &req, "test_dir/file2", NULL); + uv_fs_req_cleanup(&req); + uv_fs_rmdir(NULL, &req, "test_dir/test_subdir", NULL); + uv_fs_req_cleanup(&req); + uv_fs_rmdir(NULL, &req, "test_dir", NULL); + uv_fs_req_cleanup(&req); +} + +static void empty_closedir_cb(uv_fs_t* req) { + ASSERT(req == &closedir_req); + ASSERT(req->fs_type == UV_FS_CLOSEDIR); + ASSERT(req->result == 0); + ++empty_closedir_cb_count; + uv_fs_req_cleanup(req); +} + +static void empty_readdir_cb(uv_fs_t* req) { + uv_dir_t* dir; + int r; + + ASSERT(req == &readdir_req); + ASSERT(req->fs_type == UV_FS_READDIR); + ASSERT(req->result == 0); + dir = req->ptr; + uv_fs_req_cleanup(req); + r = uv_fs_closedir(uv_default_loop(), + &closedir_req, + dir, + empty_closedir_cb); + ASSERT(r == 0); +} + +static void empty_opendir_cb(uv_fs_t* req) { + uv_dir_t* dir; + int r; + + ASSERT(req == &opendir_req); + ASSERT(req->fs_type == UV_FS_OPENDIR); + ASSERT(req->result == 0); + ASSERT(req->ptr != NULL); + dir = req->ptr; + dir->dirents = dirents; + dir->nentries = ARRAY_SIZE(dirents); + r = uv_fs_readdir(uv_default_loop(), + &readdir_req, + dir, + empty_readdir_cb); + ASSERT(r == 0); + uv_fs_req_cleanup(req); + ++empty_opendir_cb_count; +} + +/* + * This test makes sure that both synchronous and asynchronous flavors + * of the uv_fs_opendir() -> uv_fs_readdir() -> uv_fs_closedir() sequence work + * as expected when processing an empty directory. + */ +TEST_IMPL(fs_readdir_empty_dir) { + const char* path; + uv_fs_t mkdir_req; + uv_fs_t rmdir_req; + int r; + int nb_entries_read; + uv_dir_t* dir; + + path = "./empty_dir/"; + uv_fs_mkdir(uv_default_loop(), &mkdir_req, path, 0777, NULL); + uv_fs_req_cleanup(&mkdir_req); + + /* Fill the req to ensure that required fields are cleaned up. */ + memset(&opendir_req, 0xdb, sizeof(opendir_req)); + + /* Testing the synchronous flavor. */ + r = uv_fs_opendir(uv_default_loop(), + &opendir_req, + path, + NULL); + ASSERT(r == 0); + ASSERT(opendir_req.fs_type == UV_FS_OPENDIR); + ASSERT(opendir_req.result == 0); + ASSERT(opendir_req.ptr != NULL); + dir = opendir_req.ptr; + uv_fs_req_cleanup(&opendir_req); + + /* Fill the req to ensure that required fields are cleaned up. */ + memset(&readdir_req, 0xdb, sizeof(readdir_req)); + dir->dirents = dirents; + dir->nentries = ARRAY_SIZE(dirents); + nb_entries_read = uv_fs_readdir(uv_default_loop(), + &readdir_req, + dir, + NULL); + ASSERT(nb_entries_read == 0); + uv_fs_req_cleanup(&readdir_req); + + /* Fill the req to ensure that required fields are cleaned up. */ + memset(&closedir_req, 0xdb, sizeof(closedir_req)); + uv_fs_closedir(uv_default_loop(), &closedir_req, dir, NULL); + ASSERT(closedir_req.result == 0); + uv_fs_req_cleanup(&closedir_req); + + /* Testing the asynchronous flavor. */ + + /* Fill the req to ensure that required fields are cleaned up. */ + memset(&opendir_req, 0xdb, sizeof(opendir_req)); + memset(&readdir_req, 0xdb, sizeof(readdir_req)); + memset(&closedir_req, 0xdb, sizeof(closedir_req)); + + r = uv_fs_opendir(uv_default_loop(), &opendir_req, path, empty_opendir_cb); + ASSERT(r == 0); + ASSERT(empty_opendir_cb_count == 0); + ASSERT(empty_closedir_cb_count == 0); + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + ASSERT(r == 0); + ASSERT(empty_opendir_cb_count == 1); + ASSERT(empty_closedir_cb_count == 1); + uv_fs_rmdir(uv_default_loop(), &rmdir_req, path, NULL); + uv_fs_req_cleanup(&rmdir_req); + MAKE_VALGRIND_HAPPY(); + return 0; +} + +/* + * This test makes sure that reading a non-existing directory with + * uv_fs_{open,read}_dir() returns proper error codes. + */ + +static int non_existing_opendir_cb_count; + +static void non_existing_opendir_cb(uv_fs_t* req) { + ASSERT(req == &opendir_req); + ASSERT(req->fs_type == UV_FS_OPENDIR); + ASSERT(req->result == UV_ENOENT); + ASSERT(req->ptr == NULL); + + uv_fs_req_cleanup(req); + ++non_existing_opendir_cb_count; +} + +TEST_IMPL(fs_readdir_non_existing_dir) { + const char* path; + int r; + + path = "./non-existing-dir/"; + + /* Fill the req to ensure that required fields are cleaned up. */ + memset(&opendir_req, 0xdb, sizeof(opendir_req)); + + /* Testing the synchronous flavor. */ + r = uv_fs_opendir(uv_default_loop(), &opendir_req, path, NULL); + ASSERT(r == UV_ENOENT); + ASSERT(opendir_req.fs_type == UV_FS_OPENDIR); + ASSERT(opendir_req.result == UV_ENOENT); + ASSERT(opendir_req.ptr == NULL); + uv_fs_req_cleanup(&opendir_req); + + /* Fill the req to ensure that required fields are cleaned up. */ + memset(&opendir_req, 0xdb, sizeof(opendir_req)); + + /* Testing the async flavor. */ + r = uv_fs_opendir(uv_default_loop(), + &opendir_req, + path, + non_existing_opendir_cb); + ASSERT(r == 0); + ASSERT(non_existing_opendir_cb_count == 0); + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + ASSERT(r == 0); + ASSERT(non_existing_opendir_cb_count == 1); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +/* + * This test makes sure that reading a file as a directory reports correct + * error codes. + */ + +static int file_opendir_cb_count; + +static void file_opendir_cb(uv_fs_t* req) { + ASSERT(req == &opendir_req); + ASSERT(req->fs_type == UV_FS_OPENDIR); + ASSERT(req->result == UV_ENOTDIR); + ASSERT(req->ptr == NULL); + + uv_fs_req_cleanup(req); + ++file_opendir_cb_count; +} + +TEST_IMPL(fs_readdir_file) { + const char* path; + int r; + + path = "test/fixtures/empty_file"; + + /* Fill the req to ensure that required fields are cleaned up. */ + memset(&opendir_req, 0xdb, sizeof(opendir_req)); + + /* Testing the synchronous flavor. */ + r = uv_fs_opendir(uv_default_loop(), &opendir_req, path, NULL); + + ASSERT(r == UV_ENOTDIR); + ASSERT(opendir_req.fs_type == UV_FS_OPENDIR); + ASSERT(opendir_req.result == UV_ENOTDIR); + ASSERT(opendir_req.ptr == NULL); + + uv_fs_req_cleanup(&opendir_req); + + /* Fill the req to ensure that required fields are cleaned up. */ + memset(&opendir_req, 0xdb, sizeof(opendir_req)); + + /* Testing the async flavor. */ + r = uv_fs_opendir(uv_default_loop(), &opendir_req, path, file_opendir_cb); + ASSERT(r == 0); + ASSERT(file_opendir_cb_count == 0); + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + ASSERT(r == 0); + ASSERT(file_opendir_cb_count == 1); + MAKE_VALGRIND_HAPPY(); + return 0; +} + +/* + * This test makes sure that reading a non-empty directory with + * uv_fs_{open,read}_dir() returns proper directory entries, including the + * correct entry types. + */ + +static int non_empty_opendir_cb_count; +static int non_empty_readdir_cb_count; +static int non_empty_closedir_cb_count; + +static void non_empty_closedir_cb(uv_fs_t* req) { + ASSERT(req == &closedir_req); + ASSERT(req->result == 0); + uv_fs_req_cleanup(req); + ++non_empty_closedir_cb_count; +} + +static void non_empty_readdir_cb(uv_fs_t* req) { + uv_dir_t* dir; + + ASSERT(req == &readdir_req); + ASSERT(req->fs_type == UV_FS_READDIR); + dir = req->ptr; + + if (req->result == 0) { + uv_fs_req_cleanup(req); + ASSERT(non_empty_readdir_cb_count == 3); + uv_fs_closedir(uv_default_loop(), + &closedir_req, + dir, + non_empty_closedir_cb); + } else { + ASSERT(req->result == 1); + ASSERT(dir->dirents == dirents); + ASSERT(strcmp(dirents[0].name, "file1") == 0 || + strcmp(dirents[0].name, "file2") == 0 || + strcmp(dirents[0].name, "test_subdir") == 0); +#ifdef HAVE_DIRENT_TYPES + if (!strcmp(dirents[0].name, "test_subdir")) + ASSERT(dirents[0].type == UV_DIRENT_DIR); + else + ASSERT(dirents[0].type == UV_DIRENT_FILE); +#else + ASSERT(dirents[0].type == UV_DIRENT_UNKNOWN); +#endif /* HAVE_DIRENT_TYPES */ + + ++non_empty_readdir_cb_count; + uv_fs_req_cleanup(req); + dir->dirents = dirents; + dir->nentries = ARRAY_SIZE(dirents); + uv_fs_readdir(uv_default_loop(), + &readdir_req, + dir, + non_empty_readdir_cb); + } +} + +static void non_empty_opendir_cb(uv_fs_t* req) { + uv_dir_t* dir; + int r; + + ASSERT(req == &opendir_req); + ASSERT(req->fs_type == UV_FS_OPENDIR); + ASSERT(req->result == 0); + ASSERT(req->ptr != NULL); + + dir = req->ptr; + dir->dirents = dirents; + dir->nentries = ARRAY_SIZE(dirents); + + r = uv_fs_readdir(uv_default_loop(), + &readdir_req, + dir, + non_empty_readdir_cb); + ASSERT(r == 0); + uv_fs_req_cleanup(req); + ++non_empty_opendir_cb_count; +} + +TEST_IMPL(fs_readdir_non_empty_dir) { + size_t entries_count; + uv_fs_t mkdir_req; + uv_fs_t rmdir_req; + uv_fs_t create_req; + uv_fs_t close_req; + uv_dir_t* dir; + int r; + + cleanup_test_files(); + + r = uv_fs_mkdir(uv_default_loop(), &mkdir_req, "test_dir", 0755, NULL); + ASSERT(r == 0); + + /* Create two files synchronously. */ + r = uv_fs_open(uv_default_loop(), + &create_req, + "test_dir/file1", + O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR, + NULL); + ASSERT(r >= 0); + uv_fs_req_cleanup(&create_req); + r = uv_fs_close(uv_default_loop(), + &close_req, + create_req.result, + NULL); + ASSERT(r == 0); + uv_fs_req_cleanup(&close_req); + + r = uv_fs_open(uv_default_loop(), + &create_req, + "test_dir/file2", + O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR, + NULL); + ASSERT(r >= 0); + uv_fs_req_cleanup(&create_req); + r = uv_fs_close(uv_default_loop(), + &close_req, + create_req.result, + NULL); + ASSERT(r == 0); + uv_fs_req_cleanup(&close_req); + + r = uv_fs_mkdir(uv_default_loop(), + &mkdir_req, + "test_dir/test_subdir", + 0755, + NULL); + ASSERT(r == 0); + uv_fs_req_cleanup(&mkdir_req); + + /* Fill the req to ensure that required fields are cleaned up. */ + memset(&opendir_req, 0xdb, sizeof(opendir_req)); + + /* Testing the synchronous flavor. */ + r = uv_fs_opendir(uv_default_loop(), &opendir_req, "test_dir", NULL); + ASSERT(r == 0); + ASSERT(opendir_req.fs_type == UV_FS_OPENDIR); + ASSERT(opendir_req.result == 0); + ASSERT(opendir_req.ptr != NULL); + + entries_count = 0; + dir = opendir_req.ptr; + dir->dirents = dirents; + dir->nentries = ARRAY_SIZE(dirents); + uv_fs_req_cleanup(&opendir_req); + + while (uv_fs_readdir(uv_default_loop(), + &readdir_req, + dir, + NULL) != 0) { + ASSERT(strcmp(dirents[0].name, "file1") == 0 || + strcmp(dirents[0].name, "file2") == 0 || + strcmp(dirents[0].name, "test_subdir") == 0); +#ifdef HAVE_DIRENT_TYPES + if (!strcmp(dirents[0].name, "test_subdir")) + ASSERT(dirents[0].type == UV_DIRENT_DIR); + else + ASSERT(dirents[0].type == UV_DIRENT_FILE); +#else + ASSERT(dirents[0].type == UV_DIRENT_UNKNOWN); +#endif /* HAVE_DIRENT_TYPES */ + uv_fs_req_cleanup(&readdir_req); + ++entries_count; + } + + ASSERT(entries_count == 3); + uv_fs_req_cleanup(&readdir_req); + + /* Fill the req to ensure that required fields are cleaned up. */ + memset(&closedir_req, 0xdb, sizeof(closedir_req)); + uv_fs_closedir(uv_default_loop(), &closedir_req, dir, NULL); + ASSERT(closedir_req.result == 0); + uv_fs_req_cleanup(&closedir_req); + + /* Testing the asynchronous flavor. */ + + /* Fill the req to ensure that required fields are cleaned up. */ + memset(&opendir_req, 0xdb, sizeof(opendir_req)); + + r = uv_fs_opendir(uv_default_loop(), + &opendir_req, + "test_dir", + non_empty_opendir_cb); + ASSERT(r == 0); + ASSERT(non_empty_opendir_cb_count == 0); + ASSERT(non_empty_closedir_cb_count == 0); + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + ASSERT(r == 0); + ASSERT(non_empty_opendir_cb_count == 1); + ASSERT(non_empty_closedir_cb_count == 1); + + uv_fs_rmdir(uv_default_loop(), &rmdir_req, "test_subdir", NULL); + uv_fs_req_cleanup(&rmdir_req); + + cleanup_test_files(); + MAKE_VALGRIND_HAPPY(); + return 0; + } diff --git a/test/test-fs.c b/test/test-fs.c index 9c1e8be..ae9923a 100644 --- a/test/test-fs.c +++ b/test/test-fs.c @@ -26,10 +26,13 @@ #include /* memset */ #include #include +#include /* INT_MAX, PATH_MAX, IOV_MAX */ /* FIXME we shouldn't need to branch in this file */ #if defined(__unix__) || defined(__POSIX__) || \ - defined(__APPLE__) || defined(_AIX) || defined(__MVS__) + defined(__APPLE__) || defined(__sun) || \ + defined(_AIX) || defined(__MVS__) || \ + defined(__HAIKU__) #include /* unlink, rmdir, etc. */ #else # include @@ -52,7 +55,7 @@ #endif #define TOO_LONG_NAME_LENGTH 65536 -#define PATHMAX 1024 +#define PATHMAX 4096 typedef struct { const char* path; @@ -70,6 +73,7 @@ static int write_cb_count; static int unlink_cb_count; static int mkdir_cb_count; static int mkdtemp_cb_count; +static int mkstemp_cb_count; static int rmdir_cb_count; static int scandir_cb_count; static int stat_cb_count; @@ -91,6 +95,8 @@ static int readlink_cb_count; static int realpath_cb_count; static int utime_cb_count; static int futime_cb_count; +static int lutime_cb_count; +static int statfs_cb_count; static uv_loop_t* loop; @@ -103,6 +109,9 @@ static uv_fs_t close_req; static uv_fs_t mkdir_req; static uv_fs_t mkdtemp_req1; static uv_fs_t mkdtemp_req2; +static uv_fs_t mkstemp_req1; +static uv_fs_t mkstemp_req2; +static uv_fs_t mkstemp_req3; static uv_fs_t rmdir_req; static uv_fs_t scandir_req; static uv_fs_t stat_req; @@ -121,6 +130,31 @@ static char test_buf2[] = "second-buffer\n"; static uv_buf_t iov; #ifdef _WIN32 +int uv_test_getiovmax(void) { + return INT32_MAX; /* Emulated by libuv, so no real limit. */ +} +#else +int uv_test_getiovmax(void) { +#if defined(IOV_MAX) + return IOV_MAX; +#elif defined(_SC_IOV_MAX) + static int iovmax = -1; + if (iovmax == -1) { + iovmax = sysconf(_SC_IOV_MAX); + /* On some embedded devices (arm-linux-uclibc based ip camera), + * sysconf(_SC_IOV_MAX) can not get the correct value. The return + * value is -1 and the errno is EINPROGRESS. Degrade the value to 1. + */ + if (iovmax == -1) iovmax = 1; + } + return iovmax; +#else + return 1024; +#endif +} +#endif + +#ifdef _WIN32 /* * This tag and guid have no special meaning, and don't conflict with * reserved ids. @@ -128,7 +162,7 @@ static uv_buf_t iov; static unsigned REPARSE_TAG = 0x9913; static GUID REPARSE_GUID = { 0x1bf6205f, 0x46ae, 0x4527, - 0xb1, 0x0c, 0xc5, 0x09, 0xb7, 0x55, 0x22, 0x80 }; + { 0xb1, 0x0c, 0xc5, 0x09, 0xb7, 0x55, 0x22, 0x80 }}; #endif static void check_permission(const char* filename, unsigned int mode) { @@ -276,6 +310,12 @@ static void chown_root_cb(uv_fs_t* req) { # if defined(__CYGWIN__) /* On Cygwin, uid 0 is invalid (no root). */ ASSERT(req->result == UV_EINVAL); +# elif defined(__PASE__) + /* On IBMi PASE, there is no root user. uid 0 is user qsecofr. + * User may grant qsecofr's privileges, including changing + * the file's ownership to uid 0. + */ + ASSERT(req->result == 0 || req->result == UV_EPERM); # else ASSERT(req->result == UV_EPERM); # endif @@ -302,6 +342,39 @@ static void fstat_cb(uv_fs_t* req) { } +static void statfs_cb(uv_fs_t* req) { + uv_statfs_t* stats; + + ASSERT(req->fs_type == UV_FS_STATFS); + ASSERT(req->result == 0); + ASSERT(req->ptr != NULL); + stats = req->ptr; + +#if defined(_WIN32) || defined(__sun) || defined(_AIX) || defined(__MVS__) || \ + defined(__OpenBSD__) || defined(__NetBSD__) + ASSERT(stats->f_type == 0); +#else + ASSERT(stats->f_type > 0); +#endif + + ASSERT(stats->f_bsize > 0); + ASSERT(stats->f_blocks > 0); + ASSERT(stats->f_bfree <= stats->f_blocks); + ASSERT(stats->f_bavail <= stats->f_bfree); + +#ifdef _WIN32 + ASSERT(stats->f_files == 0); + ASSERT(stats->f_ffree == 0); +#else + /* There is no assertion for stats->f_files that makes sense, so ignore it. */ + ASSERT(stats->f_ffree <= stats->f_files); +#endif + uv_fs_req_cleanup(req); + ASSERT(req->ptr == NULL); + statfs_cb_count++; +} + + static void close_cb(uv_fs_t* req) { int r; ASSERT(req == &close_req); @@ -477,6 +550,32 @@ static void mkdtemp_cb(uv_fs_t* req) { } +static void check_mkstemp_result(uv_fs_t* req) { + int r; + + ASSERT(req->fs_type == UV_FS_MKSTEMP); + ASSERT(req->result >= 0); + ASSERT(req->path); + ASSERT(strlen(req->path) == 16); + ASSERT(memcmp(req->path, "test_file_", 10) == 0); + ASSERT(memcmp(req->path + 10, "XXXXXX", 6) != 0); + check_permission(req->path, 0600); + + /* Check if req->path is actually a file */ + r = uv_fs_stat(NULL, &stat_req, req->path, NULL); + ASSERT(r == 0); + ASSERT(stat_req.statbuf.st_mode & S_IFREG); + uv_fs_req_cleanup(&stat_req); +} + + +static void mkstemp_cb(uv_fs_t* req) { + ASSERT(req == &mkstemp_req1); + check_mkstemp_result(req); + mkstemp_cb_count++; +} + + static void rmdir_cb(uv_fs_t* req) { ASSERT(req == &rmdir_req); ASSERT(req->fs_type == UV_FS_RMDIR); @@ -584,6 +683,15 @@ static void sendfile_cb(uv_fs_t* req) { } +static void sendfile_nodata_cb(uv_fs_t* req) { + ASSERT(req == &sendfile_req); + ASSERT(req->fs_type == UV_FS_SENDFILE); + ASSERT(req->result == 0); + sendfile_cb_count++; + uv_fs_req_cleanup(req); +} + + static void open_noent_cb(uv_fs_t* req) { ASSERT(req->fs_type == UV_FS_OPEN); ASSERT(req->result == UV_ENOENT); @@ -699,12 +807,19 @@ TEST_IMPL(fs_file_loop) { return 0; } -static void check_utime(const char* path, double atime, double mtime) { +static void check_utime(const char* path, + double atime, + double mtime, + int test_lutime) { uv_stat_t* s; uv_fs_t req; int r; - r = uv_fs_stat(loop, &req, path, NULL); + if (test_lutime) + r = uv_fs_lstat(loop, &req, path, NULL); + else + r = uv_fs_stat(loop, &req, path, NULL); + ASSERT(r == 0); ASSERT(req.result == 0); @@ -725,7 +840,7 @@ static void utime_cb(uv_fs_t* req) { ASSERT(req->fs_type == UV_FS_UTIME); c = req->data; - check_utime(c->path, c->atime, c->mtime); + check_utime(c->path, c->atime, c->mtime, /* test_lutime */ 0); uv_fs_req_cleanup(req); utime_cb_count++; @@ -740,13 +855,27 @@ static void futime_cb(uv_fs_t* req) { ASSERT(req->fs_type == UV_FS_FUTIME); c = req->data; - check_utime(c->path, c->atime, c->mtime); + check_utime(c->path, c->atime, c->mtime, /* test_lutime */ 0); uv_fs_req_cleanup(req); futime_cb_count++; } +static void lutime_cb(uv_fs_t* req) { + utime_check_t* c; + + ASSERT(req->result == 0); + ASSERT(req->fs_type == UV_FS_LUTIME); + + c = req->data; + check_utime(c->path, c->atime, c->mtime, /* test_lutime */ 1); + + uv_fs_req_cleanup(req); + lutime_cb_count++; +} + + TEST_IMPL(fs_file_async) { int r; @@ -810,7 +939,7 @@ TEST_IMPL(fs_file_async) { } -TEST_IMPL(fs_file_sync) { +static void fs_file_sync(int add_flags) { int r; /* Setup. */ @@ -819,8 +948,8 @@ TEST_IMPL(fs_file_sync) { loop = uv_default_loop(); - r = uv_fs_open(loop, &open_req1, "test_file", O_WRONLY | O_CREAT, - S_IWUSR | S_IRUSR, NULL); + r = uv_fs_open(loop, &open_req1, "test_file", + O_WRONLY | O_CREAT | add_flags, S_IWUSR | S_IRUSR, NULL); ASSERT(r >= 0); ASSERT(open_req1.result >= 0); uv_fs_req_cleanup(&open_req1); @@ -836,7 +965,7 @@ TEST_IMPL(fs_file_sync) { ASSERT(close_req.result == 0); uv_fs_req_cleanup(&close_req); - r = uv_fs_open(NULL, &open_req1, "test_file", O_RDWR, 0, NULL); + r = uv_fs_open(NULL, &open_req1, "test_file", O_RDWR | add_flags, 0, NULL); ASSERT(r >= 0); ASSERT(open_req1.result >= 0); uv_fs_req_cleanup(&open_req1); @@ -863,7 +992,8 @@ TEST_IMPL(fs_file_sync) { ASSERT(rename_req.result == 0); uv_fs_req_cleanup(&rename_req); - r = uv_fs_open(NULL, &open_req1, "test_file2", O_RDONLY, 0, NULL); + r = uv_fs_open(NULL, &open_req1, "test_file2", O_RDONLY | add_flags, 0, + NULL); ASSERT(r >= 0); ASSERT(open_req1.result >= 0); uv_fs_req_cleanup(&open_req1); @@ -889,13 +1019,17 @@ TEST_IMPL(fs_file_sync) { /* Cleanup */ unlink("test_file"); unlink("test_file2"); +} +TEST_IMPL(fs_file_sync) { + fs_file_sync(0); + fs_file_sync(UV_FS_O_FILEMAP); MAKE_VALGRIND_HAPPY(); return 0; } -TEST_IMPL(fs_file_write_null_buffer) { +static void fs_file_write_null_buffer(int add_flags) { int r; /* Setup. */ @@ -903,8 +1037,8 @@ TEST_IMPL(fs_file_write_null_buffer) { loop = uv_default_loop(); - r = uv_fs_open(NULL, &open_req1, "test_file", O_WRONLY | O_CREAT, - S_IWUSR | S_IRUSR, NULL); + r = uv_fs_open(NULL, &open_req1, "test_file", + O_WRONLY | O_CREAT | add_flags, S_IWUSR | S_IRUSR, NULL); ASSERT(r >= 0); ASSERT(open_req1.result >= 0); uv_fs_req_cleanup(&open_req1); @@ -921,6 +1055,10 @@ TEST_IMPL(fs_file_write_null_buffer) { uv_fs_req_cleanup(&close_req); unlink("test_file"); +} +TEST_IMPL(fs_file_write_null_buffer) { + fs_file_write_null_buffer(0); + fs_file_write_null_buffer(UV_FS_O_FILEMAP); MAKE_VALGRIND_HAPPY(); return 0; @@ -1022,7 +1160,7 @@ TEST_IMPL(fs_async_dir) { } -TEST_IMPL(fs_async_sendfile) { +static int test_sendfile(void (*setup)(int), uv_fs_cb cb, off_t expected_size) { int f, r; struct stat s1, s2; @@ -1035,14 +1173,8 @@ TEST_IMPL(fs_async_sendfile) { f = open("test_file", O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR); ASSERT(f != -1); - r = write(f, "begin\n", 6); - ASSERT(r == 6); - - r = lseek(f, 65536, SEEK_CUR); - ASSERT(r == 65542); - - r = write(f, "end\n", 4); - ASSERT(r != -1); + if (setup != NULL) + setup(f); r = close(f); ASSERT(r == 0); @@ -1060,7 +1192,7 @@ TEST_IMPL(fs_async_sendfile) { uv_fs_req_cleanup(&open_req2); r = uv_fs_sendfile(loop, &sendfile_req, open_req2.result, open_req1.result, - 0, 131072, sendfile_cb); + 0, 131072, cb); ASSERT(r == 0); uv_run(loop, UV_RUN_DEFAULT); @@ -1073,9 +1205,10 @@ TEST_IMPL(fs_async_sendfile) { ASSERT(r == 0); uv_fs_req_cleanup(&close_req); - stat("test_file", &s1); - stat("test_file2", &s2); - ASSERT(65546 == s2.st_size && s1.st_size == s2.st_size); + ASSERT(0 == stat("test_file", &s1)); + ASSERT(0 == stat("test_file2", &s2)); + ASSERT(s1.st_size == s2.st_size); + ASSERT(s2.st_size == expected_size); /* Cleanup. */ unlink("test_file"); @@ -1086,6 +1219,23 @@ TEST_IMPL(fs_async_sendfile) { } +static void sendfile_setup(int f) { + ASSERT(6 == write(f, "begin\n", 6)); + ASSERT(65542 == lseek(f, 65536, SEEK_CUR)); + ASSERT(4 == write(f, "end\n", 4)); +} + + +TEST_IMPL(fs_async_sendfile) { + return test_sendfile(sendfile_setup, sendfile_cb, 65546); +} + + +TEST_IMPL(fs_async_sendfile_nodata) { + return test_sendfile(NULL, sendfile_nodata_cb, 0); +} + + TEST_IMPL(fs_mkdtemp) { int r; const char* path_template = "test_dir_XXXXXX"; @@ -1117,6 +1267,69 @@ TEST_IMPL(fs_mkdtemp) { } +TEST_IMPL(fs_mkstemp) { + int r; + int fd; + const char path_template[] = "test_file_XXXXXX"; + uv_fs_t req; + + loop = uv_default_loop(); + + r = uv_fs_mkstemp(loop, &mkstemp_req1, path_template, mkstemp_cb); + ASSERT(r == 0); + + uv_run(loop, UV_RUN_DEFAULT); + ASSERT(mkstemp_cb_count == 1); + + /* sync mkstemp */ + r = uv_fs_mkstemp(NULL, &mkstemp_req2, path_template, NULL); + ASSERT(r >= 0); + check_mkstemp_result(&mkstemp_req2); + + /* mkstemp return different values on subsequent calls */ + ASSERT(strcmp(mkstemp_req1.path, mkstemp_req2.path) != 0); + + /* invalid template returns EINVAL */ + ASSERT(uv_fs_mkstemp(NULL, &mkstemp_req3, "test_file", NULL) == UV_EINVAL); + + /* We can write to the opened file */ + iov = uv_buf_init(test_buf, sizeof(test_buf)); + r = uv_fs_write(NULL, &req, mkstemp_req1.result, &iov, 1, -1, NULL); + ASSERT(r == sizeof(test_buf)); + ASSERT(req.result == sizeof(test_buf)); + uv_fs_req_cleanup(&req); + + /* Cleanup */ + uv_fs_close(NULL, &req, mkstemp_req1.result, NULL); + uv_fs_req_cleanup(&req); + uv_fs_close(NULL, &req, mkstemp_req2.result, NULL); + uv_fs_req_cleanup(&req); + + fd = uv_fs_open(NULL, &req, mkstemp_req1.path , O_RDONLY, 0, NULL); + ASSERT(fd >= 0); + uv_fs_req_cleanup(&req); + + memset(buf, 0, sizeof(buf)); + iov = uv_buf_init(buf, sizeof(buf)); + r = uv_fs_read(NULL, &req, fd, &iov, 1, -1, NULL); + ASSERT(r >= 0); + ASSERT(req.result >= 0); + ASSERT(strcmp(buf, test_buf) == 0); + uv_fs_req_cleanup(&req); + + uv_fs_close(NULL, &req, fd, NULL); + uv_fs_req_cleanup(&req); + + unlink(mkstemp_req1.path); + unlink(mkstemp_req2.path); + uv_fs_req_cleanup(&mkstemp_req1); + uv_fs_req_cleanup(&mkstemp_req2); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + TEST_IMPL(fs_fstat) { int r; uv_fs_t req; @@ -1138,12 +1351,32 @@ TEST_IMPL(fs_fstat) { file = req.result; uv_fs_req_cleanup(&req); +#ifndef _WIN32 + ASSERT(0 == fstat(file, &t)); + ASSERT(0 == uv_fs_fstat(NULL, &req, file, NULL)); + ASSERT(req.result == 0); + s = req.ptr; +# if defined(__APPLE__) + ASSERT(s->st_birthtim.tv_sec == t.st_birthtimespec.tv_sec); + ASSERT(s->st_birthtim.tv_nsec == t.st_birthtimespec.tv_nsec); +# elif defined(__linux__) + /* If statx() is supported, the birth time should be equal to the change time + * because we just created the file. On older kernels, it's set to zero. + */ + ASSERT(s->st_birthtim.tv_sec == 0 || + s->st_birthtim.tv_sec == t.st_ctim.tv_sec); + ASSERT(s->st_birthtim.tv_nsec == 0 || + s->st_birthtim.tv_nsec == t.st_ctim.tv_nsec); +# endif +#endif + iov = uv_buf_init(test_buf, sizeof(test_buf)); r = uv_fs_write(NULL, &req, file, &iov, 1, -1, NULL); ASSERT(r == sizeof(test_buf)); ASSERT(req.result == sizeof(test_buf)); uv_fs_req_cleanup(&req); + memset(&req.statbuf, 0xaa, sizeof(req.statbuf)); r = uv_fs_fstat(NULL, &req, file, NULL); ASSERT(r == 0); ASSERT(req.result == 0); @@ -1171,10 +1404,6 @@ TEST_IMPL(fs_fstat) { ASSERT(s->st_mtim.tv_nsec == t.st_mtimespec.tv_nsec); ASSERT(s->st_ctim.tv_sec == t.st_ctimespec.tv_sec); ASSERT(s->st_ctim.tv_nsec == t.st_ctimespec.tv_nsec); - ASSERT(s->st_birthtim.tv_sec == t.st_birthtimespec.tv_sec); - ASSERT(s->st_birthtim.tv_nsec == t.st_birthtimespec.tv_nsec); - ASSERT(s->st_flags == t.st_flags); - ASSERT(s->st_gen == t.st_gen); #elif defined(_AIX) ASSERT(s->st_atim.tv_sec == t.st_atime); ASSERT(s->st_atim.tv_nsec == 0); @@ -1209,8 +1438,6 @@ TEST_IMPL(fs_fstat) { defined(__NetBSD__) ASSERT(s->st_birthtim.tv_sec == t.st_birthtim.tv_sec); ASSERT(s->st_birthtim.tv_nsec == t.st_birthtim.tv_nsec); - ASSERT(s->st_flags == t.st_flags); - ASSERT(s->st_gen == t.st_gen); # endif #else ASSERT(s->st_atim.tv_sec == t.st_atime); @@ -1222,6 +1449,14 @@ TEST_IMPL(fs_fstat) { #endif #endif +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) + ASSERT(s->st_flags == t.st_flags); + ASSERT(s->st_gen == t.st_gen); +#else + ASSERT(s->st_flags == 0); + ASSERT(s->st_gen == 0); +#endif + uv_fs_req_cleanup(&req); /* Now do the uv_fs_fstat call asynchronously */ @@ -1408,7 +1643,7 @@ TEST_IMPL(fs_chmod) { uv_run(loop, UV_RUN_DEFAULT); ASSERT(fchmod_cb_count == 1); - close(file); + uv_fs_close(loop, &req, file, NULL); /* * Run the loop just to check we don't have make any extraneous uv_ref() @@ -1451,7 +1686,7 @@ TEST_IMPL(fs_unlink_readonly) { ASSERT(req.result == sizeof(test_buf)); uv_fs_req_cleanup(&req); - close(file); + uv_fs_close(loop, &req, file, NULL); /* Make the file read-only */ r = uv_fs_chmod(NULL, &req, "test_file", 0400, NULL); @@ -1510,7 +1745,7 @@ TEST_IMPL(fs_unlink_archive_readonly) { ASSERT(req.result == sizeof(test_buf)); uv_fs_req_cleanup(&req); - close(file); + uv_fs_close(loop, &req, file, NULL); /* Make the file read-only and clear archive flag */ r = SetFileAttributes("test_file", FILE_ATTRIBUTE_READONLY); @@ -1592,6 +1827,8 @@ TEST_IMPL(fs_chown) { uv_run(loop, UV_RUN_DEFAULT); ASSERT(fchown_cb_count == 1); +#ifndef __HAIKU__ + /* Haiku doesn't support hardlink */ /* sync link */ r = uv_fs_link(NULL, &req, "test_file", "test_file_link", NULL); ASSERT(r == 0); @@ -1609,6 +1846,7 @@ TEST_IMPL(fs_chown) { ASSERT(r == 0); uv_run(loop, UV_RUN_DEFAULT); ASSERT(lchown_cb_count == 1); +#endif /* Close file */ r = uv_fs_close(NULL, &req, file, NULL); @@ -1657,7 +1895,7 @@ TEST_IMPL(fs_link) { ASSERT(req.result == sizeof(test_buf)); uv_fs_req_cleanup(&req); - close(file); + uv_fs_close(loop, &req, file, NULL); /* sync link */ r = uv_fs_link(NULL, &req, "test_file", "test_file_link", NULL); @@ -1699,7 +1937,7 @@ TEST_IMPL(fs_link) { ASSERT(req.result >= 0); ASSERT(strcmp(buf, test_buf) == 0); - close(link); + uv_fs_close(loop, &req, link, NULL); /* * Run the loop just to check we don't have make any extraneous uv_ref() @@ -1806,7 +2044,7 @@ TEST_IMPL(fs_symlink) { ASSERT(req.result == sizeof(test_buf)); uv_fs_req_cleanup(&req); - close(file); + uv_fs_close(loop, &req, file, NULL); /* sync symlink */ r = uv_fs_symlink(NULL, &req, "test_file", "test_file_symlink", 0, NULL); @@ -1844,7 +2082,7 @@ TEST_IMPL(fs_symlink) { ASSERT(req.result >= 0); ASSERT(strcmp(buf, test_buf) == 0); - close(link); + uv_fs_close(loop, &req, link, NULL); r = uv_fs_symlink(NULL, &req, @@ -1906,7 +2144,7 @@ TEST_IMPL(fs_symlink) { ASSERT(req.result >= 0); ASSERT(strcmp(buf, test_buf) == 0); - close(link); + uv_fs_close(loop, &req, link, NULL); r = uv_fs_symlink(NULL, &req, @@ -2013,7 +2251,12 @@ int test_symlink_dir_impl(int type) { #ifdef _WIN32 ASSERT(((uv_stat_t*)req.ptr)->st_size == strlen(test_dir + 4)); #else +# ifdef __PASE__ + /* On IBMi PASE, st_size returns the length of the symlink itself. */ + ASSERT(((uv_stat_t*)req.ptr)->st_size == strlen("test_dir_symlink")); +# else ASSERT(((uv_stat_t*)req.ptr)->st_size == strlen(test_dir)); +# endif #endif uv_fs_req_cleanup(&req); @@ -2210,6 +2453,57 @@ TEST_IMPL(fs_non_symlink_reparse_point) { MAKE_VALGRIND_HAPPY(); return 0; } + +TEST_IMPL(fs_lstat_windows_store_apps) { + uv_loop_t* loop; + char localappdata[MAX_PATH]; + char windowsapps_path[MAX_PATH]; + char file_path[MAX_PATH]; + size_t len; + int r; + uv_fs_t req; + uv_fs_t stat_req; + uv_dirent_t dirent; + + loop = uv_default_loop(); + ASSERT_NOT_NULL(loop); + len = sizeof(localappdata); + r = uv_os_getenv("LOCALAPPDATA", localappdata, &len); + if (r == UV_ENOENT) { + MAKE_VALGRIND_HAPPY(); + return TEST_SKIP; + } + ASSERT_EQ(r, 0); + r = snprintf(windowsapps_path, + sizeof(localappdata), + "%s\\Microsoft\\WindowsApps", + localappdata); + ASSERT_GT(r, 0); + if (uv_fs_opendir(loop, &req, windowsapps_path, NULL) != 0) { + /* If we cannot read the directory, skip the test. */ + MAKE_VALGRIND_HAPPY(); + return TEST_SKIP; + } + if (uv_fs_scandir(loop, &req, windowsapps_path, 0, NULL) <= 0) { + MAKE_VALGRIND_HAPPY(); + return TEST_SKIP; + } + while (uv_fs_scandir_next(&req, &dirent) != UV_EOF) { + if (dirent.type != UV_DIRENT_LINK) { + continue; + } + if (snprintf(file_path, + sizeof(file_path), + "%s\\%s", + windowsapps_path, + dirent.name) < 0) { + continue; + } + ASSERT_EQ(uv_fs_lstat(loop, &stat_req, file_path, NULL), 0); + } + MAKE_VALGRIND_HAPPY(); + return 0; +} #endif @@ -2228,7 +2522,7 @@ TEST_IMPL(fs_utime) { ASSERT(r >= 0); ASSERT(req.result >= 0); uv_fs_req_cleanup(&req); - close(r); + uv_fs_close(loop, &req, r, NULL); atime = mtime = 400497753; /* 1982-09-10 11:22:33 */ @@ -2249,7 +2543,7 @@ TEST_IMPL(fs_utime) { r = uv_fs_stat(NULL, &req, path, NULL); ASSERT(r == 0); ASSERT(req.result == 0); - check_utime(path, atime, mtime); + check_utime(path, atime, mtime, /* test_lutime */ 0); uv_fs_req_cleanup(&req); atime = mtime = 1291404900; /* 2010-12-03 20:35:00 - mees <3 */ @@ -2305,9 +2599,6 @@ TEST_IMPL(fs_stat_root) { TEST_IMPL(fs_futime) { -#if defined(_AIX) && !defined(_AIX71) - RETURN_SKIP("futime is not implemented for AIX versions below 7.1"); -#else utime_check_t checkme; const char* path = "test_file"; double atime; @@ -2315,6 +2606,9 @@ TEST_IMPL(fs_futime) { uv_file file; uv_fs_t req; int r; +#if defined(_AIX) && !defined(_AIX71) + RETURN_SKIP("futime is not implemented for AIX versions below 7.1"); +#endif /* Setup. */ loop = uv_default_loop(); @@ -2323,7 +2617,7 @@ TEST_IMPL(fs_futime) { ASSERT(r >= 0); ASSERT(req.result >= 0); uv_fs_req_cleanup(&req); - close(r); + uv_fs_close(loop, &req, r, NULL); atime = mtime = 400497753; /* 1982-09-10 11:22:33 */ @@ -2355,7 +2649,7 @@ TEST_IMPL(fs_futime) { r = uv_fs_stat(NULL, &req, path, NULL); ASSERT(r == 0); ASSERT(req.result == 0); - check_utime(path, atime, mtime); + check_utime(path, atime, mtime, /* test_lutime */ 0); uv_fs_req_cleanup(&req); atime = mtime = 1291404900; /* 2010-12-03 20:35:00 - mees <3 */ @@ -2376,7 +2670,84 @@ TEST_IMPL(fs_futime) { MAKE_VALGRIND_HAPPY(); return 0; +} + + +TEST_IMPL(fs_lutime) { + utime_check_t checkme; + const char* path = "test_file"; + const char* symlink_path = "test_file_symlink"; + double atime; + double mtime; + uv_fs_t req; + int r, s; + + + /* Setup */ + loop = uv_default_loop(); + unlink(path); + r = uv_fs_open(NULL, &req, path, O_RDWR | O_CREAT, S_IWUSR | S_IRUSR, NULL); + ASSERT(r >= 0); + ASSERT(req.result >= 0); + uv_fs_req_cleanup(&req); + uv_fs_close(loop, &req, r, NULL); + + unlink(symlink_path); + s = uv_fs_symlink(NULL, &req, path, symlink_path, 0, NULL); +#ifdef _WIN32 + if (s == UV_EPERM) { + /* + * Creating a symlink before Windows 10 Creators Update was only allowed + * when running elevated console (with admin rights) + */ + RETURN_SKIP( + "Symlink creation requires elevated console (with admin rights)"); + } +#endif + ASSERT(s == 0); + ASSERT(req.result == 0); + uv_fs_req_cleanup(&req); + + /* Test the synchronous version. */ + atime = mtime = 400497753; /* 1982-09-10 11:22:33 */ + +#ifdef _WIN32 + mtime += 0.444; /* 1982-09-10 11:22:33.444 */ +#endif + + checkme.atime = atime; + checkme.mtime = mtime; + checkme.path = symlink_path; + req.data = &checkme; + + r = uv_fs_lutime(NULL, &req, symlink_path, atime, mtime, NULL); +#if (defined(_AIX) && !defined(_AIX71)) || \ + defined(__MVS__) + ASSERT(r == UV_ENOSYS); + RETURN_SKIP("lutime is not implemented for z/OS and AIX versions below 7.1"); #endif + ASSERT(r == 0); + lutime_cb(&req); + ASSERT(lutime_cb_count == 1); + + /* Test the asynchronous version. */ + atime = mtime = 1291404900; /* 2010-12-03 20:35:00 */ + + checkme.atime = atime; + checkme.mtime = mtime; + checkme.path = symlink_path; + + r = uv_fs_lutime(loop, &req, symlink_path, atime, mtime, lutime_cb); + ASSERT(r == 0); + uv_run(loop, UV_RUN_DEFAULT); + ASSERT(lutime_cb_count == 2); + + /* Cleanup. */ + unlink(path); + unlink(symlink_path); + + MAKE_VALGRIND_HAPPY(); + return 0; } @@ -2519,7 +2890,7 @@ TEST_IMPL(fs_open_dir) { } -TEST_IMPL(fs_file_open_append) { +static void fs_file_open_append(int add_flags) { int r; /* Setup. */ @@ -2527,8 +2898,8 @@ TEST_IMPL(fs_file_open_append) { loop = uv_default_loop(); - r = uv_fs_open(NULL, &open_req1, "test_file", O_WRONLY | O_CREAT, - S_IWUSR | S_IRUSR, NULL); + r = uv_fs_open(NULL, &open_req1, "test_file", + O_WRONLY | O_CREAT | add_flags, S_IWUSR | S_IRUSR, NULL); ASSERT(r >= 0); ASSERT(open_req1.result >= 0); uv_fs_req_cleanup(&open_req1); @@ -2544,7 +2915,8 @@ TEST_IMPL(fs_file_open_append) { ASSERT(close_req.result == 0); uv_fs_req_cleanup(&close_req); - r = uv_fs_open(NULL, &open_req1, "test_file", O_RDWR | O_APPEND, 0, NULL); + r = uv_fs_open(NULL, &open_req1, "test_file", + O_RDWR | O_APPEND | add_flags, 0, NULL); ASSERT(r >= 0); ASSERT(open_req1.result >= 0); uv_fs_req_cleanup(&open_req1); @@ -2560,7 +2932,8 @@ TEST_IMPL(fs_file_open_append) { ASSERT(close_req.result == 0); uv_fs_req_cleanup(&close_req); - r = uv_fs_open(NULL, &open_req1, "test_file", O_RDONLY, S_IRUSR, NULL); + r = uv_fs_open(NULL, &open_req1, "test_file", O_RDONLY | add_flags, + S_IRUSR, NULL); ASSERT(r >= 0); ASSERT(open_req1.result >= 0); uv_fs_req_cleanup(&open_req1); @@ -2582,6 +2955,10 @@ TEST_IMPL(fs_file_open_append) { /* Cleanup */ unlink("test_file"); +} +TEST_IMPL(fs_file_open_append) { + fs_file_open_append(0); + fs_file_open_append(UV_FS_O_FILEMAP); MAKE_VALGRIND_HAPPY(); return 0; @@ -2657,7 +3034,65 @@ TEST_IMPL(fs_rename_to_existing_file) { } -TEST_IMPL(fs_read_file_eof) { +static void fs_read_bufs(int add_flags) { + char scratch[768]; + uv_buf_t bufs[4]; + + ASSERT(0 <= uv_fs_open(NULL, &open_req1, + "test/fixtures/lorem_ipsum.txt", + O_RDONLY | add_flags, 0, NULL)); + ASSERT(open_req1.result >= 0); + uv_fs_req_cleanup(&open_req1); + + ASSERT(UV_EINVAL == uv_fs_read(NULL, &read_req, open_req1.result, + NULL, 0, 0, NULL)); + ASSERT(UV_EINVAL == uv_fs_read(NULL, &read_req, open_req1.result, + NULL, 1, 0, NULL)); + ASSERT(UV_EINVAL == uv_fs_read(NULL, &read_req, open_req1.result, + bufs, 0, 0, NULL)); + + bufs[0] = uv_buf_init(scratch + 0, 256); + bufs[1] = uv_buf_init(scratch + 256, 256); + bufs[2] = uv_buf_init(scratch + 512, 128); + bufs[3] = uv_buf_init(scratch + 640, 128); + + ASSERT(446 == uv_fs_read(NULL, + &read_req, + open_req1.result, + bufs + 0, + 2, /* 2x 256 bytes. */ + 0, /* Positional read. */ + NULL)); + ASSERT(read_req.result == 446); + uv_fs_req_cleanup(&read_req); + + ASSERT(190 == uv_fs_read(NULL, + &read_req, + open_req1.result, + bufs + 2, + 2, /* 2x 128 bytes. */ + 256, /* Positional read. */ + NULL)); + ASSERT(read_req.result == /* 446 - 256 */ 190); + uv_fs_req_cleanup(&read_req); + + ASSERT(0 == memcmp(bufs[1].base + 0, bufs[2].base, 128)); + ASSERT(0 == memcmp(bufs[1].base + 128, bufs[3].base, 190 - 128)); + + ASSERT(0 == uv_fs_close(NULL, &close_req, open_req1.result, NULL)); + ASSERT(close_req.result == 0); + uv_fs_req_cleanup(&close_req); +} +TEST_IMPL(fs_read_bufs) { + fs_read_bufs(0); + fs_read_bufs(UV_FS_O_FILEMAP); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + +static void fs_read_file_eof(int add_flags) { #if defined(__CYGWIN__) || defined(__MSYS__) RETURN_SKIP("Cygwin pread at EOF may (incorrectly) return data!"); #endif @@ -2668,8 +3103,8 @@ TEST_IMPL(fs_read_file_eof) { loop = uv_default_loop(); - r = uv_fs_open(NULL, &open_req1, "test_file", O_WRONLY | O_CREAT, - S_IWUSR | S_IRUSR, NULL); + r = uv_fs_open(NULL, &open_req1, "test_file", + O_WRONLY | O_CREAT | add_flags, S_IWUSR | S_IRUSR, NULL); ASSERT(r >= 0); ASSERT(open_req1.result >= 0); uv_fs_req_cleanup(&open_req1); @@ -2685,7 +3120,8 @@ TEST_IMPL(fs_read_file_eof) { ASSERT(close_req.result == 0); uv_fs_req_cleanup(&close_req); - r = uv_fs_open(NULL, &open_req1, "test_file", O_RDONLY, 0, NULL); + r = uv_fs_open(NULL, &open_req1, "test_file", O_RDONLY | add_flags, 0, + NULL); ASSERT(r >= 0); ASSERT(open_req1.result >= 0); uv_fs_req_cleanup(&open_req1); @@ -2712,13 +3148,17 @@ TEST_IMPL(fs_read_file_eof) { /* Cleanup */ unlink("test_file"); +} +TEST_IMPL(fs_read_file_eof) { + fs_read_file_eof(0); + fs_read_file_eof(UV_FS_O_FILEMAP); MAKE_VALGRIND_HAPPY(); return 0; } -TEST_IMPL(fs_write_multiple_bufs) { +static void fs_write_multiple_bufs(int add_flags) { uv_buf_t iovs[2]; int r; @@ -2727,8 +3167,8 @@ TEST_IMPL(fs_write_multiple_bufs) { loop = uv_default_loop(); - r = uv_fs_open(NULL, &open_req1, "test_file", O_WRONLY | O_CREAT, - S_IWUSR | S_IRUSR, NULL); + r = uv_fs_open(NULL, &open_req1, "test_file", + O_WRONLY | O_CREAT | add_flags, S_IWUSR | S_IRUSR, NULL); ASSERT(r >= 0); ASSERT(open_req1.result >= 0); uv_fs_req_cleanup(&open_req1); @@ -2745,7 +3185,8 @@ TEST_IMPL(fs_write_multiple_bufs) { ASSERT(close_req.result == 0); uv_fs_req_cleanup(&close_req); - r = uv_fs_open(NULL, &open_req1, "test_file", O_RDONLY, 0, NULL); + r = uv_fs_open(NULL, &open_req1, "test_file", O_RDONLY | add_flags, 0, + NULL); ASSERT(r >= 0); ASSERT(open_req1.result >= 0); uv_fs_req_cleanup(&open_req1); @@ -2755,16 +3196,41 @@ TEST_IMPL(fs_write_multiple_bufs) { /* Read the strings back to separate buffers. */ iovs[0] = uv_buf_init(buf, sizeof(test_buf)); iovs[1] = uv_buf_init(buf2, sizeof(test_buf2)); - r = uv_fs_read(NULL, &read_req, open_req1.result, iovs, 2, 0, NULL); + ASSERT(lseek(open_req1.result, 0, SEEK_CUR) == 0); + r = uv_fs_read(NULL, &read_req, open_req1.result, iovs, 2, -1, NULL); ASSERT(r >= 0); - ASSERT(read_req.result >= 0); + ASSERT(read_req.result == sizeof(test_buf) + sizeof(test_buf2)); + ASSERT(strcmp(buf, test_buf) == 0); + ASSERT(strcmp(buf2, test_buf2) == 0); + uv_fs_req_cleanup(&read_req); + + iov = uv_buf_init(buf, sizeof(buf)); + r = uv_fs_read(NULL, &read_req, open_req1.result, &iov, 1, -1, NULL); + ASSERT(r == 0); + ASSERT(read_req.result == 0); + uv_fs_req_cleanup(&read_req); + + /* Read the strings back to separate buffers. */ + iovs[0] = uv_buf_init(buf, sizeof(test_buf)); + iovs[1] = uv_buf_init(buf2, sizeof(test_buf2)); + r = uv_fs_read(NULL, &read_req, open_req1.result, iovs, 2, 0, NULL); + ASSERT(r >= 0); + if (read_req.result == sizeof(test_buf)) { + /* Infer that preadv is not available. */ + uv_fs_req_cleanup(&read_req); + r = uv_fs_read(NULL, &read_req, open_req1.result, &iovs[1], 1, read_req.result, NULL); + ASSERT(r >= 0); + ASSERT(read_req.result == sizeof(test_buf2)); + } else { + ASSERT(read_req.result == sizeof(test_buf) + sizeof(test_buf2)); + } ASSERT(strcmp(buf, test_buf) == 0); ASSERT(strcmp(buf2, test_buf2) == 0); uv_fs_req_cleanup(&read_req); iov = uv_buf_init(buf, sizeof(buf)); r = uv_fs_read(NULL, &read_req, open_req1.result, &iov, 1, - read_req.result, NULL); + sizeof(test_buf) + sizeof(test_buf2), NULL); ASSERT(r == 0); ASSERT(read_req.result == 0); uv_fs_req_cleanup(&read_req); @@ -2776,19 +3242,26 @@ TEST_IMPL(fs_write_multiple_bufs) { /* Cleanup */ unlink("test_file"); +} +TEST_IMPL(fs_write_multiple_bufs) { + fs_write_multiple_bufs(0); + fs_write_multiple_bufs(UV_FS_O_FILEMAP); MAKE_VALGRIND_HAPPY(); return 0; } -TEST_IMPL(fs_write_alotof_bufs) { - const size_t iovcount = 54321; +static void fs_write_alotof_bufs(int add_flags) { + size_t iovcount; + size_t iovmax; uv_buf_t* iovs; char* buffer; size_t index; int r; + iovcount = 54321; + /* Setup. */ unlink("test_file"); @@ -2796,11 +3269,12 @@ TEST_IMPL(fs_write_alotof_bufs) { iovs = malloc(sizeof(*iovs) * iovcount); ASSERT(iovs != NULL); + iovmax = uv_test_getiovmax(); r = uv_fs_open(NULL, &open_req1, "test_file", - O_RDWR | O_CREAT, + O_RDWR | O_CREAT | add_flags, S_IWUSR | S_IRUSR, NULL); ASSERT(r >= 0); @@ -2829,7 +3303,20 @@ TEST_IMPL(fs_write_alotof_bufs) { iovs[index] = uv_buf_init(buffer + index * sizeof(test_buf), sizeof(test_buf)); - r = uv_fs_read(NULL, &read_req, open_req1.result, iovs, iovcount, 0, NULL); + r = uv_fs_close(NULL, &close_req, open_req1.result, NULL); + ASSERT(r == 0); + ASSERT(close_req.result == 0); + uv_fs_req_cleanup(&close_req); + + r = uv_fs_open(NULL, &open_req1, "test_file", O_RDONLY | add_flags, 0, + NULL); + ASSERT(r >= 0); + ASSERT(open_req1.result >= 0); + uv_fs_req_cleanup(&open_req1); + + r = uv_fs_read(NULL, &read_req, open_req1.result, iovs, iovcount, -1, NULL); + if (iovcount > iovmax) + iovcount = iovmax; ASSERT(r >= 0); ASSERT((size_t)read_req.result == sizeof(test_buf) * iovcount); @@ -2841,13 +3328,14 @@ TEST_IMPL(fs_write_alotof_bufs) { uv_fs_req_cleanup(&read_req); free(buffer); + ASSERT(lseek(open_req1.result, write_req.result, SEEK_SET) == write_req.result); iov = uv_buf_init(buf, sizeof(buf)); r = uv_fs_read(NULL, &read_req, open_req1.result, &iov, 1, - read_req.result, + -1, NULL); ASSERT(r == 0); ASSERT(read_req.result == 0); @@ -2861,21 +3349,30 @@ TEST_IMPL(fs_write_alotof_bufs) { /* Cleanup */ unlink("test_file"); free(iovs); +} +TEST_IMPL(fs_write_alotof_bufs) { + fs_write_alotof_bufs(0); + fs_write_alotof_bufs(UV_FS_O_FILEMAP); MAKE_VALGRIND_HAPPY(); return 0; } -TEST_IMPL(fs_write_alotof_bufs_with_offset) { - const size_t iovcount = 54321; +static void fs_write_alotof_bufs_with_offset(int add_flags) { + size_t iovcount; + size_t iovmax; uv_buf_t* iovs; char* buffer; size_t index; int r; int64_t offset; - char* filler = "0123456789"; - int filler_len = strlen(filler); + char* filler; + int filler_len; + + filler = "0123456789"; + filler_len = strlen(filler); + iovcount = 54321; /* Setup. */ unlink("test_file"); @@ -2884,11 +3381,12 @@ TEST_IMPL(fs_write_alotof_bufs_with_offset) { iovs = malloc(sizeof(*iovs) * iovcount); ASSERT(iovs != NULL); + iovmax = uv_test_getiovmax(); r = uv_fs_open(NULL, &open_req1, "test_file", - O_RDWR | O_CREAT, + O_RDWR | O_CREAT | add_flags, S_IWUSR | S_IRUSR, NULL); ASSERT(r >= 0); @@ -2927,6 +3425,10 @@ TEST_IMPL(fs_write_alotof_bufs_with_offset) { r = uv_fs_read(NULL, &read_req, open_req1.result, iovs, iovcount, offset, NULL); ASSERT(r >= 0); + if (r == sizeof(test_buf)) + iovcount = 1; /* Infer that preadv is not available. */ + else if (iovcount > iovmax) + iovcount = iovmax; ASSERT((size_t)read_req.result == sizeof(test_buf) * iovcount); for (index = 0; index < iovcount; ++index) @@ -2940,7 +3442,7 @@ TEST_IMPL(fs_write_alotof_bufs_with_offset) { r = uv_fs_stat(NULL, &stat_req, "test_file", NULL); ASSERT(r == 0); ASSERT((int64_t)((uv_stat_t*)stat_req.ptr)->st_size == - offset + (int64_t)(iovcount * sizeof(test_buf))); + offset + (int64_t)write_req.result); uv_fs_req_cleanup(&stat_req); iov = uv_buf_init(buf, sizeof(buf)); @@ -2949,7 +3451,7 @@ TEST_IMPL(fs_write_alotof_bufs_with_offset) { open_req1.result, &iov, 1, - read_req.result + offset, + offset + write_req.result, NULL); ASSERT(r == 0); ASSERT(read_req.result == 0); @@ -2963,11 +3465,238 @@ TEST_IMPL(fs_write_alotof_bufs_with_offset) { /* Cleanup */ unlink("test_file"); free(iovs); +} +TEST_IMPL(fs_write_alotof_bufs_with_offset) { + fs_write_alotof_bufs_with_offset(0); + fs_write_alotof_bufs_with_offset(UV_FS_O_FILEMAP); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +TEST_IMPL(fs_read_dir) { + int r; + char buf[2]; + loop = uv_default_loop(); + + /* Setup */ + rmdir("test_dir"); + r = uv_fs_mkdir(loop, &mkdir_req, "test_dir", 0755, mkdir_cb); + ASSERT(r == 0); + uv_run(loop, UV_RUN_DEFAULT); + ASSERT(mkdir_cb_count == 1); + /* Setup Done Here */ + + /* Get a file descriptor for the directory */ + r = uv_fs_open(loop, + &open_req1, + "test_dir", + UV_FS_O_RDONLY | UV_FS_O_DIRECTORY, + S_IWUSR | S_IRUSR, + NULL); + ASSERT(r >= 0); + uv_fs_req_cleanup(&open_req1); + + /* Try to read data from the directory */ + iov = uv_buf_init(buf, sizeof(buf)); + r = uv_fs_read(NULL, &read_req, open_req1.result, &iov, 1, 0, NULL); +#if defined(__FreeBSD__) || \ + defined(__OpenBSD__) || \ + defined(__NetBSD__) || \ + defined(__DragonFly__) || \ + defined(_AIX) || \ + defined(__sun) || \ + defined(__MVS__) + /* + * As of now, these operating systems support reading from a directory, + * that too depends on the filesystem this temporary test directory is + * created on. That is why this assertion is a bit lenient. + */ + ASSERT((r >= 0) || (r == UV_EISDIR)); +#else + ASSERT(r == UV_EISDIR); +#endif + uv_fs_req_cleanup(&read_req); + + r = uv_fs_close(NULL, &close_req, open_req1.result, NULL); + ASSERT(r == 0); + uv_fs_req_cleanup(&close_req); + + /* Cleanup */ + rmdir("test_dir"); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +#ifdef _WIN32 + +TEST_IMPL(fs_partial_read) { + RETURN_SKIP("Test not implemented on Windows."); +} + +TEST_IMPL(fs_partial_write) { + RETURN_SKIP("Test not implemented on Windows."); +} + +#else /* !_WIN32 */ + +struct thread_ctx { + pthread_t pid; + int fd; + char* data; + int size; + int interval; + int doread; +}; + +static void thread_main(void* arg) { + const struct thread_ctx* ctx; + int size; + char* data; + + ctx = (struct thread_ctx*)arg; + size = ctx->size; + data = ctx->data; + + while (size > 0) { + ssize_t result; + int nbytes; + nbytes = size < ctx->interval ? size : ctx->interval; + if (ctx->doread) { + result = write(ctx->fd, data, nbytes); + /* Should not see EINTR (or other errors) */ + ASSERT(result == nbytes); + } else { + result = read(ctx->fd, data, nbytes); + /* Should not see EINTR (or other errors), + * but might get a partial read if we are faster than the writer + */ + ASSERT(result > 0 && result <= nbytes); + } + + pthread_kill(ctx->pid, SIGUSR1); + size -= result; + data += result; + } +} + +static void sig_func(uv_signal_t* handle, int signum) { + uv_signal_stop(handle); +} + +static size_t uv_test_fs_buf_offset(uv_buf_t* bufs, size_t size) { + size_t offset; + /* Figure out which bufs are done */ + for (offset = 0; size > 0 && bufs[offset].len <= size; ++offset) + size -= bufs[offset].len; + + /* Fix a partial read/write */ + if (size > 0) { + bufs[offset].base += size; + bufs[offset].len -= size; + } + return offset; +} + +static void test_fs_partial(int doread) { + struct thread_ctx ctx; + uv_thread_t thread; + uv_signal_t signal; + int pipe_fds[2]; + size_t iovcount; + uv_buf_t* iovs; + char* buffer; + size_t index; + + iovcount = 54321; + + iovs = malloc(sizeof(*iovs) * iovcount); + ASSERT(iovs != NULL); + + ctx.pid = pthread_self(); + ctx.doread = doread; + ctx.interval = 1000; + ctx.size = sizeof(test_buf) * iovcount; + ctx.data = malloc(ctx.size); + ASSERT(ctx.data != NULL); + buffer = malloc(ctx.size); + ASSERT(buffer != NULL); + + for (index = 0; index < iovcount; ++index) + iovs[index] = uv_buf_init(buffer + index * sizeof(test_buf), sizeof(test_buf)); + + loop = uv_default_loop(); + + ASSERT(0 == uv_signal_init(loop, &signal)); + ASSERT(0 == uv_signal_start(&signal, sig_func, SIGUSR1)); + + ASSERT(0 == pipe(pipe_fds)); + + ctx.fd = pipe_fds[doread]; + ASSERT(0 == uv_thread_create(&thread, thread_main, &ctx)); + + if (doread) { + uv_buf_t* read_iovs; + int nread; + read_iovs = iovs; + nread = 0; + while (nread < ctx.size) { + int result; + result = uv_fs_read(loop, &read_req, pipe_fds[0], read_iovs, iovcount, -1, NULL); + if (result > 0) { + size_t read_iovcount; + read_iovcount = uv_test_fs_buf_offset(read_iovs, result); + read_iovs += read_iovcount; + iovcount -= read_iovcount; + nread += result; + } else { + ASSERT(result == UV_EINTR); + } + uv_fs_req_cleanup(&read_req); + } + } else { + int result; + result = uv_fs_write(loop, &write_req, pipe_fds[1], iovs, iovcount, -1, NULL); + ASSERT(write_req.result == result); + ASSERT(result == ctx.size); + uv_fs_req_cleanup(&write_req); + } + + ASSERT(0 == memcmp(buffer, ctx.data, ctx.size)); + + ASSERT(0 == uv_thread_join(&thread)); + ASSERT(0 == uv_run(loop, UV_RUN_DEFAULT)); + + ASSERT(0 == close(pipe_fds[1])); + uv_close((uv_handle_t*) &signal, NULL); + + { /* Make sure we read everything that we wrote. */ + int result; + result = uv_fs_read(loop, &read_req, pipe_fds[0], iovs, 1, -1, NULL); + ASSERT(result == 0); + uv_fs_req_cleanup(&read_req); + } + ASSERT(0 == close(pipe_fds[0])); + + free(iovs); + free(buffer); + free(ctx.data); MAKE_VALGRIND_HAPPY(); +} + +TEST_IMPL(fs_partial_read) { + test_fs_partial(1); return 0; } +TEST_IMPL(fs_partial_write) { + test_fs_partial(0); + return 0; +} + +#endif/* _WIN32 */ TEST_IMPL(fs_read_write_null_arguments) { int r; @@ -3155,6 +3884,146 @@ TEST_IMPL(fs_file_pos_after_op_with_offset) { return 0; } +#ifdef _WIN32 +static void fs_file_pos_common() { + int r; + + iov = uv_buf_init("abc", 3); + r = uv_fs_write(NULL, &write_req, open_req1.result, &iov, 1, -1, NULL); + ASSERT(r == 3); + uv_fs_req_cleanup(&write_req); + + /* Read with offset should not change the position */ + iov = uv_buf_init(buf, 1); + r = uv_fs_read(NULL, &read_req, open_req1.result, &iov, 1, 1, NULL); + ASSERT(r == 1); + ASSERT(buf[0] == 'b'); + uv_fs_req_cleanup(&read_req); + + iov = uv_buf_init(buf, sizeof(buf)); + r = uv_fs_read(NULL, &read_req, open_req1.result, &iov, 1, -1, NULL); + ASSERT(r == 0); + uv_fs_req_cleanup(&read_req); + + /* Write without offset should change the position */ + iov = uv_buf_init("d", 1); + r = uv_fs_write(NULL, &write_req, open_req1.result, &iov, 1, -1, NULL); + ASSERT(r == 1); + uv_fs_req_cleanup(&write_req); + + iov = uv_buf_init(buf, sizeof(buf)); + r = uv_fs_read(NULL, &read_req, open_req1.result, &iov, 1, -1, NULL); + ASSERT(r == 0); + uv_fs_req_cleanup(&read_req); +} + +static void fs_file_pos_close_check(const char *contents, int size) { + int r; + + /* Close */ + r = uv_fs_close(NULL, &close_req, open_req1.result, NULL); + ASSERT(r == 0); + uv_fs_req_cleanup(&close_req); + + /* Confirm file contents */ + r = uv_fs_open(NULL, &open_req1, "test_file", O_RDONLY, 0, NULL); + ASSERT(r >= 0); + ASSERT(open_req1.result >= 0); + uv_fs_req_cleanup(&open_req1); + + iov = uv_buf_init(buf, sizeof(buf)); + r = uv_fs_read(NULL, &read_req, open_req1.result, &iov, 1, -1, NULL); + ASSERT(r == size); + ASSERT(strncmp(buf, contents, size) == 0); + uv_fs_req_cleanup(&read_req); + + r = uv_fs_close(NULL, &close_req, open_req1.result, NULL); + ASSERT(r == 0); + uv_fs_req_cleanup(&close_req); + + /* Cleanup */ + unlink("test_file"); +} + +static void fs_file_pos_write(int add_flags) { + int r; + + /* Setup. */ + unlink("test_file"); + + r = uv_fs_open(NULL, + &open_req1, + "test_file", + O_TRUNC | O_CREAT | O_RDWR | add_flags, + S_IWUSR | S_IRUSR, + NULL); + ASSERT(r > 0); + uv_fs_req_cleanup(&open_req1); + + fs_file_pos_common(); + + /* Write with offset should not change the position */ + iov = uv_buf_init("e", 1); + r = uv_fs_write(NULL, &write_req, open_req1.result, &iov, 1, 1, NULL); + ASSERT(r == 1); + uv_fs_req_cleanup(&write_req); + + iov = uv_buf_init(buf, sizeof(buf)); + r = uv_fs_read(NULL, &read_req, open_req1.result, &iov, 1, -1, NULL); + ASSERT(r == 0); + uv_fs_req_cleanup(&read_req); + + fs_file_pos_close_check("aecd", 4); +} +TEST_IMPL(fs_file_pos_write) { + fs_file_pos_write(0); + fs_file_pos_write(UV_FS_O_FILEMAP); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +static void fs_file_pos_append(int add_flags) { + int r; + + /* Setup. */ + unlink("test_file"); + + r = uv_fs_open(NULL, + &open_req1, + "test_file", + O_APPEND | O_CREAT | O_RDWR | add_flags, + S_IWUSR | S_IRUSR, + NULL); + ASSERT(r > 0); + uv_fs_req_cleanup(&open_req1); + + fs_file_pos_common(); + + /* Write with offset appends (ignoring offset) + * but does not change the position */ + iov = uv_buf_init("e", 1); + r = uv_fs_write(NULL, &write_req, open_req1.result, &iov, 1, 1, NULL); + ASSERT(r == 1); + uv_fs_req_cleanup(&write_req); + + iov = uv_buf_init(buf, sizeof(buf)); + r = uv_fs_read(NULL, &read_req, open_req1.result, &iov, 1, -1, NULL); + ASSERT(r == 1); + ASSERT(buf[0] == 'e'); + uv_fs_req_cleanup(&read_req); + + fs_file_pos_close_check("abcde", 5); +} +TEST_IMPL(fs_file_pos_append) { + fs_file_pos_append(0); + fs_file_pos_append(UV_FS_O_FILEMAP); + + MAKE_VALGRIND_HAPPY(); + return 0; +} +#endif + TEST_IMPL(fs_null_req) { /* Verify that all fs functions return UV_EINVAL when the request is NULL. */ int r; @@ -3180,6 +4049,9 @@ TEST_IMPL(fs_null_req) { r = uv_fs_mkdtemp(NULL, NULL, NULL, NULL); ASSERT(r == UV_EINVAL); + r = uv_fs_mkstemp(NULL, NULL, NULL, NULL); + ASSERT(r == UV_EINVAL); + r = uv_fs_rmdir(NULL, NULL, NULL, NULL); ASSERT(r == UV_EINVAL); @@ -3246,6 +4118,9 @@ TEST_IMPL(fs_null_req) { r = uv_fs_futime(NULL, NULL, 0, 0.0, 0.0, NULL); ASSERT(r == UV_EINVAL); + r = uv_fs_statfs(NULL, NULL, NULL, NULL); + ASSERT(r == UV_EINVAL); + /* This should be a no-op. */ uv_fs_req_cleanup(NULL); @@ -3310,10 +4185,57 @@ TEST_IMPL(fs_exclusive_sharing_mode) { #endif #ifdef _WIN32 +TEST_IMPL(fs_file_flag_no_buffering) { + int r; + + /* Setup. */ + unlink("test_file"); + + ASSERT(UV_FS_O_APPEND > 0); + ASSERT(UV_FS_O_CREAT > 0); + ASSERT(UV_FS_O_DIRECT > 0); + ASSERT(UV_FS_O_RDWR > 0); + + /* FILE_APPEND_DATA must be excluded from FILE_GENERIC_WRITE: */ + r = uv_fs_open(NULL, + &open_req1, + "test_file", + UV_FS_O_RDWR | UV_FS_O_CREAT | UV_FS_O_DIRECT, + S_IWUSR | S_IRUSR, + NULL); + ASSERT(r >= 0); + ASSERT(open_req1.result >= 0); + uv_fs_req_cleanup(&open_req1); + + r = uv_fs_close(NULL, &close_req, open_req1.result, NULL); + ASSERT(r == 0); + ASSERT(close_req.result == 0); + uv_fs_req_cleanup(&close_req); + + /* FILE_APPEND_DATA and FILE_FLAG_NO_BUFFERING are mutually exclusive: */ + r = uv_fs_open(NULL, + &open_req2, + "test_file", + UV_FS_O_APPEND | UV_FS_O_DIRECT, + S_IWUSR | S_IRUSR, + NULL); + ASSERT(r == UV_EINVAL); + ASSERT(open_req2.result == UV_EINVAL); + uv_fs_req_cleanup(&open_req2); + + /* Cleanup */ + unlink("test_file"); + + MAKE_VALGRIND_HAPPY(); + return 0; +} +#endif + +#ifdef _WIN32 int call_icacls(const char* command, ...) { char icacls_command[1024]; va_list args; - + va_start(args, command); vsnprintf(icacls_command, ARRAYSIZE(icacls_command), command, args); va_end(args); @@ -3335,7 +4257,7 @@ TEST_IMPL(fs_open_readonly_acl) { attrib -r test_file_icacls del test_file_icacls */ - + /* Setup - clear the ACL and remove the file */ loop = uv_default_loop(); r = uv_os_get_passwd(&pwd); @@ -3345,7 +4267,7 @@ TEST_IMPL(fs_open_readonly_acl) { uv_fs_chmod(loop, &req, "test_file_icacls", S_IWUSR, NULL); unlink("test_file_icacls"); - /* Create the file */ + /* Create the file */ r = uv_fs_open(loop, &open_req1, "test_file_icacls", @@ -3370,7 +4292,7 @@ TEST_IMPL(fs_open_readonly_acl) { if (r != 0) { goto acl_cleanup; } - + /* Try opening the file */ r = uv_fs_open(NULL, &open_req1, "test_file_icacls", O_RDONLY, 0, NULL); if (r < 0) { @@ -3442,4 +4364,55 @@ TEST_IMPL(fs_fchmod_archive_readonly) { return 0; } + +TEST_IMPL(fs_invalid_mkdir_name) { + uv_loop_t* loop; + uv_fs_t req; + int r; + + loop = uv_default_loop(); + r = uv_fs_mkdir(loop, &req, "invalid>", 0, NULL); + ASSERT(r == UV_EINVAL); + + return 0; +} #endif + +TEST_IMPL(fs_statfs) { + uv_fs_t req; + int r; + + loop = uv_default_loop(); + + /* Test the synchronous version. */ + r = uv_fs_statfs(NULL, &req, ".", NULL); + ASSERT(r == 0); + statfs_cb(&req); + ASSERT(statfs_cb_count == 1); + + /* Test the asynchronous version. */ + r = uv_fs_statfs(loop, &req, ".", statfs_cb); + ASSERT(r == 0); + uv_run(loop, UV_RUN_DEFAULT); + ASSERT(statfs_cb_count == 2); + + return 0; +} + +TEST_IMPL(fs_get_system_error) { + uv_fs_t req; + int r; + int system_error; + + r = uv_fs_statfs(NULL, &req, "non_existing_file", NULL); + ASSERT(r != 0); + + system_error = uv_fs_get_system_error(&req); +#ifdef _WIN32 + ASSERT(system_error == ERROR_FILE_NOT_FOUND); +#else + ASSERT(system_error == ENOENT); +#endif + + return 0; +} diff --git a/test/test-get-currentexe.c b/test/test-get-currentexe.c index 0e9d696..5e4a083 100644 --- a/test/test-get-currentexe.c +++ b/test/test-get-currentexe.c @@ -23,28 +23,34 @@ #include "task.h" #include -#define PATHMAX 1024 +#ifndef _WIN32 +#include +#endif + +#define PATHMAX 4096 extern char executable_path[]; TEST_IMPL(get_currentexe) { +/* TODO(gengjiawen): Fix test on QEMU. */ +#if defined(__QEMU__) + RETURN_SKIP("Test does not currently work in QEMU"); +#endif + char buffer[PATHMAX]; + char path[PATHMAX]; size_t size; char* match; - char* path; int r; size = sizeof(buffer) / sizeof(buffer[0]); r = uv_exepath(buffer, &size); ASSERT(!r); - /* uv_exepath can return an absolute path on darwin, so if the test runner - * was run with a relative prefix of "./", we need to strip that prefix off - * executable_path or we'll fail. */ - if (executable_path[0] == '.' && executable_path[1] == '/') { - path = executable_path + 2; - } else { - path = executable_path; - } +#ifdef _WIN32 + snprintf(path, sizeof(path), "%s", executable_path); +#else + ASSERT(NULL != realpath(executable_path, path)); +#endif match = strstr(buffer, path); /* Verify that the path returned from uv_exepath is a subdirectory of diff --git a/test/test-get-memory.c b/test/test-get-memory.c index 2396939..4555ba0 100644 --- a/test/test-get-memory.c +++ b/test/test-get-memory.c @@ -25,14 +25,20 @@ TEST_IMPL(get_memory) { uint64_t free_mem = uv_get_free_memory(); uint64_t total_mem = uv_get_total_memory(); + uint64_t constrained_mem = uv_get_constrained_memory(); - printf("free_mem=%llu, total_mem=%llu\n", + printf("free_mem=%llu, total_mem=%llu, constrained_mem=%llu\n", (unsigned long long) free_mem, - (unsigned long long) total_mem); + (unsigned long long) total_mem, + (unsigned long long) constrained_mem); ASSERT(free_mem > 0); ASSERT(total_mem > 0); + /* On IBMi PASE, the amount of memory in use is always zero. */ +#ifdef __PASE__ + ASSERT(total_mem == free_mem); +#else ASSERT(total_mem > free_mem); - +#endif return 0; } diff --git a/test/test-get-passwd.c b/test/test-get-passwd.c index 8e16fb8..abe8be3 100644 --- a/test/test-get-passwd.c +++ b/test/test-get-passwd.c @@ -24,6 +24,11 @@ #include TEST_IMPL(get_passwd) { +/* TODO(gengjiawen): Fix test on QEMU. */ +#if defined(__QEMU__) + RETURN_SKIP("Test does not currently work in QEMU"); +#endif + uv_passwd_t pwd; size_t len; int r; @@ -38,7 +43,9 @@ TEST_IMPL(get_passwd) { ASSERT(pwd.shell == NULL); #else len = strlen(pwd.shell); +# ifndef __PASE__ ASSERT(len > 0); +# endif #endif len = strlen(pwd.homedir); diff --git a/test/test-getaddrinfo.c b/test/test-getaddrinfo.c index 03dc126..f2b4e03 100644 --- a/test/test-getaddrinfo.c +++ b/test/test-getaddrinfo.c @@ -106,6 +106,10 @@ TEST_IMPL(getaddrinfo_fail) { TEST_IMPL(getaddrinfo_fail_sync) { +/* TODO(gengjiawen): Fix test on QEMU. */ +#if defined(__QEMU__) + RETURN_SKIP("Test does not currently work in QEMU"); +#endif uv_getaddrinfo_t req; /* Use a FQDN by ending in a period */ @@ -144,6 +148,10 @@ TEST_IMPL(getaddrinfo_basic) { TEST_IMPL(getaddrinfo_basic_sync) { +/* TODO(gengjiawen): Fix test on QEMU. */ +#if defined(__QEMU__) + RETURN_SKIP("Test does not currently work in QEMU"); +#endif uv_getaddrinfo_t req; ASSERT(0 == uv_getaddrinfo(uv_default_loop(), diff --git a/test/test-gethostname.c b/test/test-gethostname.c index 5229804..1a9816d 100644 --- a/test/test-gethostname.c +++ b/test/test-gethostname.c @@ -23,12 +23,8 @@ #include "task.h" #include -#ifndef MAXHOSTNAMELEN -# define MAXHOSTNAMELEN 256 -#endif - TEST_IMPL(gethostname) { - char buf[MAXHOSTNAMELEN + 1]; + char buf[UV_MAXHOSTNAMESIZE]; size_t size; size_t enobufs_size; int r; @@ -52,10 +48,10 @@ TEST_IMPL(gethostname) { ASSERT(enobufs_size > 1); /* Successfully get the hostname */ - size = MAXHOSTNAMELEN + 1; + size = UV_MAXHOSTNAMESIZE; r = uv_os_gethostname(buf, &size); ASSERT(r == 0); - ASSERT(size > 1 && size == strlen(buf)); + ASSERT(size > 0 && size == strlen(buf)); ASSERT(size + 1 == enobufs_size); return 0; diff --git a/test/test-getnameinfo.c b/test/test-getnameinfo.c index b139161..3767ffd 100644 --- a/test/test-getnameinfo.c +++ b/test/test-getnameinfo.c @@ -66,6 +66,11 @@ TEST_IMPL(getnameinfo_basic_ip4) { TEST_IMPL(getnameinfo_basic_ip4_sync) { +/* TODO(gengjiawen): Fix test on QEMU. */ +#if defined(__QEMU__) + RETURN_SKIP("Test does not currently work in QEMU"); +#endif + ASSERT(0 == uv_ip4_addr(address_ip4, port, &addr4)); ASSERT(0 == uv_getnameinfo(uv_default_loop(), diff --git a/test/test-gettimeofday.c b/test/test-gettimeofday.c new file mode 100644 index 0000000..4ebc11f --- /dev/null +++ b/test/test-gettimeofday.c @@ -0,0 +1,39 @@ +/* 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" + +TEST_IMPL(gettimeofday) { + uv_timeval64_t tv; + int r; + + tv.tv_sec = 0; + r = uv_gettimeofday(&tv); + ASSERT(r == 0); + ASSERT(tv.tv_sec != 0); + + /* Test invalid input. */ + r = uv_gettimeofday(NULL); + ASSERT(r == UV_EINVAL); + + return 0; +} diff --git a/test/test-homedir.c b/test/test-homedir.c index 856534a..508351f 100644 --- a/test/test-homedir.c +++ b/test/test-homedir.c @@ -23,7 +23,7 @@ #include "task.h" #include -#define PATHMAX 1024 +#define PATHMAX 4096 #define SMALLPATH 1 TEST_IMPL(homedir) { diff --git a/test/test-hrtime.c b/test/test-hrtime.c index fbe9a68..9d461d9 100644 --- a/test/test-hrtime.c +++ b/test/test-hrtime.c @@ -41,13 +41,11 @@ TEST_IMPL(hrtime) { diff = b - a; - /* printf("i= %d diff = %llu\n", i, (unsigned long long int) diff); */ - /* The windows Sleep() function has only a resolution of 10-20 ms. Check - * that the difference between the two hrtime values is somewhat in the - * range we expect it to be. */ + * that the difference between the two hrtime values has a reasonable + * lower bound. + */ ASSERT(diff > (uint64_t) 25 * NANOSEC / MILLISEC); - ASSERT(diff < (uint64_t) 80 * NANOSEC / MILLISEC); --i; } return 0; diff --git a/test/test-idna.c b/test/test-idna.c new file mode 100644 index 0000000..b76853c --- /dev/null +++ b/test/test-idna.c @@ -0,0 +1,195 @@ +/* Copyright The libuv project and 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 "task.h" +#include "../src/idna.c" +#include + +TEST_IMPL(utf8_decode1) { + const char* p; + char b[32]; + int i; + + /* ASCII. */ + p = b; + snprintf(b, sizeof(b), "%c\x7F", 0x00); + ASSERT(0 == uv__utf8_decode1(&p, b + sizeof(b))); + ASSERT(p == b + 1); + ASSERT(127 == uv__utf8_decode1(&p, b + sizeof(b))); + ASSERT(p == b + 2); + + /* Two-byte sequences. */ + p = b; + snprintf(b, sizeof(b), "\xC2\x80\xDF\xBF"); + ASSERT(128 == uv__utf8_decode1(&p, b + sizeof(b))); + ASSERT(p == b + 2); + ASSERT(0x7FF == uv__utf8_decode1(&p, b + sizeof(b))); + ASSERT(p == b + 4); + + /* Three-byte sequences. */ + p = b; + snprintf(b, sizeof(b), "\xE0\xA0\x80\xEF\xBF\xBF"); + ASSERT(0x800 == uv__utf8_decode1(&p, b + sizeof(b))); + ASSERT(p == b + 3); + ASSERT(0xFFFF == uv__utf8_decode1(&p, b + sizeof(b))); + ASSERT(p == b + 6); + + /* Four-byte sequences. */ + p = b; + snprintf(b, sizeof(b), "\xF0\x90\x80\x80\xF4\x8F\xBF\xBF"); + ASSERT(0x10000 == uv__utf8_decode1(&p, b + sizeof(b))); + ASSERT(p == b + 4); + ASSERT(0x10FFFF == uv__utf8_decode1(&p, b + sizeof(b))); + ASSERT(p == b + 8); + + /* Four-byte sequences > U+10FFFF; disallowed. */ + p = b; + snprintf(b, sizeof(b), "\xF4\x90\xC0\xC0\xF7\xBF\xBF\xBF"); + ASSERT((unsigned) -1 == uv__utf8_decode1(&p, b + sizeof(b))); + ASSERT(p == b + 4); + ASSERT((unsigned) -1 == uv__utf8_decode1(&p, b + sizeof(b))); + ASSERT(p == b + 8); + + /* Overlong; disallowed. */ + p = b; + snprintf(b, sizeof(b), "\xC0\x80\xC1\x80"); + ASSERT((unsigned) -1 == uv__utf8_decode1(&p, b + sizeof(b))); + ASSERT(p == b + 2); + ASSERT((unsigned) -1 == uv__utf8_decode1(&p, b + sizeof(b))); + ASSERT(p == b + 4); + + /* Surrogate pairs; disallowed. */ + p = b; + snprintf(b, sizeof(b), "\xED\xA0\x80\xED\xA3\xBF"); + ASSERT((unsigned) -1 == uv__utf8_decode1(&p, b + sizeof(b))); + ASSERT(p == b + 3); + ASSERT((unsigned) -1 == uv__utf8_decode1(&p, b + sizeof(b))); + ASSERT(p == b + 6); + + /* Simply illegal. */ + p = b; + snprintf(b, sizeof(b), "\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF"); + + for (i = 1; i <= 8; i++) { + ASSERT((unsigned) -1 == uv__utf8_decode1(&p, b + sizeof(b))); + ASSERT(p == b + i); + } + + return 0; +} + +/* Doesn't work on z/OS because that platform uses EBCDIC, not ASCII. */ +#ifndef __MVS__ + +#define F(input, err) \ + do { \ + char d[256] = {0}; \ + static const char s[] = "" input ""; \ + ASSERT(err == uv__idna_toascii(s, s + sizeof(s) - 1, d, d + sizeof(d))); \ + } while (0) + +#define T(input, expected) \ + do { \ + long n; \ + char d1[256] = {0}; \ + char d2[256] = {0}; \ + static const char s[] = "" input ""; \ + n = uv__idna_toascii(s, s + sizeof(s) - 1, d1, d1 + sizeof(d1)); \ + ASSERT(n == sizeof(expected)); \ + ASSERT(0 == memcmp(d1, expected, n)); \ + /* Sanity check: encoding twice should not change the output. */ \ + n = uv__idna_toascii(d1, d1 + strlen(d1), d2, d2 + sizeof(d2)); \ + ASSERT(n == sizeof(expected)); \ + ASSERT(0 == memcmp(d2, expected, n)); \ + ASSERT(0 == memcmp(d1, d2, sizeof(d2))); \ + } while (0) + +TEST_IMPL(idna_toascii) { + /* Illegal inputs. */ + F("\xC0\x80\xC1\x80", UV_EINVAL); /* Overlong UTF-8 sequence. */ + F("\xC0\x80\xC1\x80.com", UV_EINVAL); /* Overlong UTF-8 sequence. */ + /* No conversion. */ + T("", ""); + T(".", "."); + T(".com", ".com"); + T("example", "example"); + T("example-", "example-"); + T("straße.de", "xn--strae-oqa.de"); + /* Test cases adapted from punycode.js. Most are from RFC 3492. */ + T("foo.bar", "foo.bar"); + T("mañana.com", "xn--maana-pta.com"); + T("example.com.", "example.com."); + T("bücher.com", "xn--bcher-kva.com"); + T("café.com", "xn--caf-dma.com"); + T("café.café.com", "xn--caf-dma.xn--caf-dma.com"); + T("☃-⌘.com", "xn----dqo34k.com"); + T("퐀☃-⌘.com", "xn----dqo34kn65z.com"); + T("💩.la", "xn--ls8h.la"); + T("mañana.com", "xn--maana-pta.com"); + T("mañana。com", "xn--maana-pta.com"); + T("mañana.com", "xn--maana-pta.com"); + T("mañana。com", "xn--maana-pta.com"); + T("ü", "xn--tda"); + T(".ü", ".xn--tda"); + T("ü.ü", "xn--tda.xn--tda"); + T("ü.ü.", "xn--tda.xn--tda."); + T("üëäö♥", "xn--4can8av2009b"); + T("Willst du die Blüthe des frühen, die Früchte des späteren Jahres", + "xn--Willst du die Blthe des frhen, " + "die Frchte des spteren Jahres-x9e96lkal"); + T("ليهمابتكلموشعربي؟", "xn--egbpdaj6bu4bxfgehfvwxn"); + T("他们为什么不说中文", "xn--ihqwcrb4cv8a8dqg056pqjye"); + T("他們爲什麽不說中文", "xn--ihqwctvzc91f659drss3x8bo0yb"); + T("Pročprostěnemluvíčesky", "xn--Proprostnemluvesky-uyb24dma41a"); + T("למההםפשוטלאמדבריםעברית", "xn--4dbcagdahymbxekheh6e0a7fei0b"); + T("यहलोगहिन्दीक्योंनहींबोलसकतेहैं", + "xn--i1baa7eci9glrd9b2ae1bj0hfcgg6iyaf8o0a1dig0cd"); + T("なぜみんな日本語を話してくれないのか", + "xn--n8jok5ay5dzabd5bym9f0cm5685rrjetr6pdxa"); + T("세계의모든사람들이한국어를이해한다면얼마나좋을까", + "xn--989aomsvi5e83db1d2a355cv1e0vak1d" + "wrv93d5xbh15a0dt30a5jpsd879ccm6fea98c"); + T("почемужеонинеговорятпорусски", "xn--b1abfaaepdrnnbgefbadotcwatmq2g4l"); + T("PorquénopuedensimplementehablarenEspañol", + "xn--PorqunopuedensimplementehablarenEspaol-fmd56a"); + T("TạisaohọkhôngthểchỉnóitiếngViệt", + "xn--TisaohkhngthchnitingVit-kjcr8268qyxafd2f1b9g"); + T("3年B組金八先生", "xn--3B-ww4c5e180e575a65lsy2b"); + T("安室奈美恵-with-SUPER-MONKEYS", + "xn---with-SUPER-MONKEYS-pc58ag80a8qai00g7n9n"); + T("Hello-Another-Way-それぞれの場所", + "xn--Hello-Another-Way--fc4qua05auwb3674vfr0b"); + T("ひとつ屋根の下2", "xn--2-u9tlzr9756bt3uc0v"); + T("MajiでKoiする5秒前", "xn--MajiKoi5-783gue6qz075azm5e"); + T("パフィーdeルンバ", "xn--de-jg4avhby1noc0d"); + T("そのスピードで", "xn--d9juau41awczczp"); + T("-> $1.00 <-", "-> $1.00 <-"); + /* Test cases from https://unicode.org/reports/tr46/ */ + T("faß.de", "xn--fa-hia.de"); + T("βόλος.com", "xn--nxasmm1c.com"); + T("ශ්‍රී.com", "xn--10cl1a0b660p.com"); + T("نامه‌ای.com", "xn--mgba3gch31f060k.com"); + return 0; +} + +#undef T + +#endif /* __MVS__ */ diff --git a/test/test-ip4-addr.c b/test/test-ip4-addr.c index 3d6e0cf..dfefb0f 100644 --- a/test/test-ip4-addr.c +++ b/test/test-ip4-addr.c @@ -27,8 +27,13 @@ TEST_IMPL(ip4_addr) { - struct sockaddr_in addr; + char dst[16]; + + ASSERT(0 == uv_inet_ntop(AF_INET, "\xFF\xFF\xFF\xFF", dst, sizeof(dst))); + ASSERT(0 == strcmp(dst, "255.255.255.255")); + ASSERT(UV_ENOSPC == uv_inet_ntop(AF_INET, "\xFF\xFF\xFF\xFF", + dst, sizeof(dst) - 1)); ASSERT(0 == uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); ASSERT(0 == uv_ip4_addr("255.255.255.255", TEST_PORT, &addr)); @@ -37,6 +42,10 @@ TEST_IMPL(ip4_addr) { ASSERT(UV_EINVAL == uv_ip4_addr("2555.0.0.0", TEST_PORT, &addr)); ASSERT(UV_EINVAL == uv_ip4_addr("255", TEST_PORT, &addr)); +#ifdef SIN6_LEN + ASSERT(addr.sin_len == sizeof(addr)); +#endif + /* for broken address family */ ASSERT(UV_EAFNOSUPPORT == uv_inet_pton(42, "127.0.0.1", &addr.sin_addr.s_addr)); diff --git a/test/test-ip6-addr.c b/test/test-ip6-addr.c index 25570dc..39d5706 100644 --- a/test/test-ip6-addr.c +++ b/test/test-ip6-addr.c @@ -83,7 +83,7 @@ TEST_IMPL(ip6_addr_link_local) { ASSERT(0 == r); #ifdef _WIN32 /* On Windows, the interface identifier is the numeric string of the index. */ - ASSERT(strtol(interface_id, NULL, 10) == iface_index); + ASSERT(strtoul(interface_id, NULL, 10) == iface_index); #else /* On Unix/Linux, the interface identifier is the interface device name. */ ASSERT(0 == strcmp(device_name, interface_id)); @@ -160,3 +160,12 @@ TEST_IMPL(ip6_pton) { #undef GOOD_ADDR_LIST #undef BAD_ADDR_LIST + +#ifdef SIN6_LEN +TEST_IMPL(ip6_sin6_len) { + struct sockaddr_in6 s; + ASSERT(uv_ip6_addr("::", 0, &s) < 0); + ASSERT(s.sin6_len == sizeof(s)); + return 0; +} +#endif diff --git a/test/test-ipc-heavy-traffic-deadlock-bug.c b/test/test-ipc-heavy-traffic-deadlock-bug.c index 240fc64..753fb7b 100644 --- a/test/test-ipc-heavy-traffic-deadlock-bug.c +++ b/test/test-ipc-heavy-traffic-deadlock-bug.c @@ -55,7 +55,7 @@ static void write_cb(uv_write_t* req, int status) { } static void shutdown_cb(uv_shutdown_t* req, int status) { - ASSERT(status == 0); + ASSERT(status == 0 || status == UV_ENOTCONN); uv_close((uv_handle_t*) req->handle, NULL); } @@ -150,6 +150,7 @@ int ipc_helper_heavy_traffic_deadlock_bug(void) { r = uv_pipe_open(&pipe, 0); ASSERT(r == 0); + notify_parent_process(); do_writes_and_reads((uv_stream_t*) &pipe); uv_sleep(100); diff --git a/test/test-ipc-send-recv.c b/test/test-ipc-send-recv.c index 3dedc86..12d4e33 100644 --- a/test/test-ipc-send-recv.c +++ b/test/test-ipc-send-recv.c @@ -149,7 +149,6 @@ static void connect_cb(uv_connect_t* req, int status) { &ctx.send.stream, NULL); ASSERT(r == 0); - ASSERT(ctx.write_req.send_handle == &ctx.send.stream); /* Perform two writes to the same pipe to make sure that on Windows we are * not running into issue 505: @@ -161,7 +160,6 @@ static void connect_cb(uv_connect_t* req, int status) { &ctx.send2.stream, NULL); ASSERT(r == 0); - ASSERT(ctx.write_req2.send_handle == &ctx.send2.stream); r = uv_read_start((uv_stream_t*)&ctx.channel, alloc_cb, recv_cb); ASSERT(r == 0); @@ -346,7 +344,6 @@ static void read_cb(uv_stream_t* handle, &recv->stream, write2_cb); ASSERT(r == 0); - ASSERT(write_req->send_handle == &recv->stream); } while (uv_pipe_pending_count(pipe) > 0); } @@ -397,6 +394,7 @@ int run_ipc_send_recv_helper(uv_loop_t* loop, int inprocess) { send_recv_start(); } + notify_parent_process(); r = uv_run(loop, UV_RUN_DEFAULT); ASSERT(r == 0); diff --git a/test/test-ipc.c b/test/test-ipc.c index 200f68d..39ef4f1 100644 --- a/test/test-ipc.c +++ b/test/test-ipc.c @@ -39,12 +39,15 @@ static int local_conn_accepted; static int remote_conn_accepted; static int tcp_server_listening; static uv_write_t write_req; +static uv_write_t write_req2; static uv_write_t conn_notify_req; static int close_cb_called; static int connection_accepted; static int tcp_conn_read_cb_called; static int tcp_conn_write_cb_called; static int closed_handle_data_read; +static int closed_handle_write; +static int send_zero_write; typedef struct { uv_connect_t conn_req; @@ -54,7 +57,15 @@ typedef struct { #define CONN_COUNT 100 #define BACKLOG 128 -#define LARGE_SIZE 1000000 +#define LARGE_SIZE 100000 + +static uv_buf_t large_buf; +static char buffer[LARGE_SIZE]; +static uv_write_t write_reqs[300]; +static int write_reqs_completed; + +static unsigned int write_until_data_queued(void); +static void send_handle_and_close(void); static void close_server_conn_cb(uv_handle_t* handle) { @@ -68,16 +79,16 @@ static void on_connection(uv_stream_t* server, int status) { if (!local_conn_accepted) { /* Accept the connection and close it. Also and close the server. */ - ASSERT(status == 0); - ASSERT((uv_stream_t*)&tcp_server == server); + ASSERT_EQ(status, 0); + ASSERT_PTR_EQ(&tcp_server, server); conn = malloc(sizeof(*conn)); - ASSERT(conn); + ASSERT_NOT_NULL(conn); r = uv_tcp_init(server->loop, conn); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_accept(server, (uv_stream_t*)conn); - ASSERT(r == 0); + ASSERT_EQ(r, 0); uv_close((uv_handle_t*)conn, close_server_conn_cb); uv_close((uv_handle_t*)server, NULL); @@ -91,7 +102,8 @@ static void exit_cb(uv_process_t* process, int term_signal) { printf("exit_cb\n"); exit_cb_called++; - ASSERT(exit_status == 0); + ASSERT_EQ(exit_status, 0); + ASSERT_EQ(term_signal, 0); uv_close((uv_handle_t*)process, NULL); } @@ -122,18 +134,17 @@ static void make_many_connections(void) { for (i = 0; i < CONN_COUNT; i++) { conn = malloc(sizeof(*conn)); - ASSERT(conn); + ASSERT_NOT_NULL(conn); r = uv_tcp_init(uv_default_loop(), &conn->conn); - ASSERT(r == 0); - - ASSERT(0 == uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); + ASSERT_EQ(r, 0); + ASSERT_EQ(0, uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); r = uv_tcp_connect(&conn->conn_req, (uv_tcp_t*) &conn->conn, (const struct sockaddr*) &addr, connect_cb); - ASSERT(r == 0); + ASSERT_EQ(r, 0); conn->conn.data = conn; } @@ -170,36 +181,38 @@ static void on_read(uv_stream_t* handle, pending = uv_pipe_pending_type(pipe); if (!tcp_server_listening) { - ASSERT(1 == uv_pipe_pending_count(pipe)); - ASSERT(nread > 0 && buf->base && pending != UV_UNKNOWN_HANDLE); + ASSERT_EQ(1, uv_pipe_pending_count(pipe)); + ASSERT_GT(nread, 0); + ASSERT_NOT_NULL(buf->base); + ASSERT_NE(pending, UV_UNKNOWN_HANDLE); read_cb_called++; /* Accept the pending TCP server, and start listening on it. */ - ASSERT(pending == UV_TCP); + ASSERT_EQ(pending, UV_TCP); r = uv_tcp_init(uv_default_loop(), &tcp_server); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_accept((uv_stream_t*)pipe, (uv_stream_t*)&tcp_server); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_listen((uv_stream_t*)&tcp_server, BACKLOG, on_connection); - ASSERT(r == 0); + ASSERT_EQ(r, 0); tcp_server_listening = 1; /* Make sure that the expected data is correctly multiplexed. */ - ASSERT(memcmp("hello\n", buf->base, nread) == 0); + ASSERT_MEM_EQ("hello\n", buf->base, nread); outbuf = uv_buf_init("world\n", 6); r = uv_write(&write_req, (uv_stream_t*)pipe, &outbuf, 1, NULL); - ASSERT(r == 0); + ASSERT_EQ(r, 0); /* Create a bunch of connections to get both servers to accept. */ make_many_connections(); } else if (memcmp("accepted_connection\n", buf->base, nread) == 0) { /* Remote server has accepted a connection. Close the channel. */ - ASSERT(0 == uv_pipe_pending_count(pipe)); - ASSERT(pending == UV_UNKNOWN_HANDLE); + ASSERT_EQ(0, uv_pipe_pending_count(pipe)); + ASSERT_EQ(pending, UV_UNKNOWN_HANDLE); remote_conn_accepted = 1; uv_close((uv_handle_t*)&channel, NULL); } @@ -235,37 +248,39 @@ static void on_read_listen_after_bound_twice(uv_stream_t* handle, fprintf(stderr, "got %d bytes\n", (int)nread); - ASSERT(uv_pipe_pending_count(pipe) > 0); + ASSERT_GT(uv_pipe_pending_count(pipe), 0); pending = uv_pipe_pending_type(pipe); - ASSERT(nread > 0 && buf->base && pending != UV_UNKNOWN_HANDLE); + ASSERT_GT(nread, 0); + ASSERT_NOT_NULL(buf->base); + ASSERT_NE(pending, UV_UNKNOWN_HANDLE); read_cb_called++; if (read_cb_called == 1) { /* Accept the first TCP server, and start listening on it. */ - ASSERT(pending == UV_TCP); + ASSERT_EQ(pending, UV_TCP); r = uv_tcp_init(uv_default_loop(), &tcp_server); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_accept((uv_stream_t*)pipe, (uv_stream_t*)&tcp_server); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_listen((uv_stream_t*)&tcp_server, BACKLOG, on_connection); - ASSERT(r == 0); + ASSERT_EQ(r, 0); } else if (read_cb_called == 2) { /* Accept the second TCP server, and start listening on it. */ - ASSERT(pending == UV_TCP); + ASSERT_EQ(pending, UV_TCP); r = uv_tcp_init(uv_default_loop(), &tcp_server2); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_accept((uv_stream_t*)pipe, (uv_stream_t*)&tcp_server2); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_listen((uv_stream_t*)&tcp_server2, BACKLOG, on_connection); - ASSERT(r == UV_EADDRINUSE); + ASSERT_EQ(r, UV_EADDRINUSE); uv_close((uv_handle_t*)&tcp_server, NULL); uv_close((uv_handle_t*)&tcp_server2, NULL); - ASSERT(0 == uv_pipe_pending_count(pipe)); + ASSERT_EQ(0, uv_pipe_pending_count(pipe)); uv_close((uv_handle_t*)&channel, NULL); } @@ -284,12 +299,12 @@ void spawn_helper(uv_pipe_t* channel, uv_stdio_container_t stdio[3]; r = uv_pipe_init(uv_default_loop(), channel, 1); - ASSERT(r == 0); - ASSERT(channel->ipc); + ASSERT_EQ(r, 0); + ASSERT_NE(channel->ipc, 0); exepath_size = sizeof(exepath); r = uv_exepath(exepath, &exepath_size); - ASSERT(r == 0); + ASSERT_EQ(r, 0); exepath[exepath_size] = '\0'; args[0] = exepath; @@ -311,13 +326,13 @@ void spawn_helper(uv_pipe_t* channel, stdio[2].data.fd = 2; r = uv_spawn(uv_default_loop(), process, &options); - ASSERT(r == 0); + ASSERT_EQ(r, 0); } static void on_tcp_write(uv_write_t* req, int status) { - ASSERT(status == 0); - ASSERT(req->handle == (uv_stream_t*)&tcp_connection); + ASSERT_EQ(status, 0); + ASSERT_PTR_EQ(req->handle, &tcp_connection); tcp_write_cb_called++; } @@ -331,9 +346,9 @@ static void on_read_alloc(uv_handle_t* handle, static void on_tcp_read(uv_stream_t* tcp, ssize_t nread, const uv_buf_t* buf) { - ASSERT(nread > 0); - ASSERT(memcmp("hello again\n", buf->base, nread) == 0); - ASSERT(tcp == (uv_stream_t*)&tcp_connection); + ASSERT_GT(nread, 0); + ASSERT_MEM_EQ("hello again\n", buf->base, nread); + ASSERT_PTR_EQ(tcp, &tcp_connection); free(buf->base); tcp_read_cb_called++; @@ -370,31 +385,33 @@ static void on_read_connection(uv_stream_t* handle, fprintf(stderr, "got %d bytes\n", (int)nread); - ASSERT(1 == uv_pipe_pending_count(pipe)); + ASSERT_EQ(1, uv_pipe_pending_count(pipe)); pending = uv_pipe_pending_type(pipe); - ASSERT(nread > 0 && buf->base && pending != UV_UNKNOWN_HANDLE); + ASSERT_GT(nread, 0); + ASSERT_NOT_NULL(buf->base); + ASSERT_NE(pending, UV_UNKNOWN_HANDLE); read_cb_called++; /* Accept the pending TCP connection */ - ASSERT(pending == UV_TCP); + ASSERT_EQ(pending, UV_TCP); r = uv_tcp_init(uv_default_loop(), &tcp_connection); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_accept(handle, (uv_stream_t*)&tcp_connection); - ASSERT(r == 0); + ASSERT_EQ(r, 0); /* Make sure that the expected data is correctly multiplexed. */ - ASSERT(memcmp("hello\n", buf->base, nread) == 0); + ASSERT_MEM_EQ("hello\n", buf->base, nread); /* Write/read to/from the connection */ outbuf = uv_buf_init("world\n", 6); r = uv_write(&write_req, (uv_stream_t*)&tcp_connection, &outbuf, 1, on_tcp_write); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_read_start((uv_stream_t*)&tcp_connection, on_read_alloc, on_tcp_read); - ASSERT(r == 0); + ASSERT_EQ(r, 0); free(buf->base); } @@ -420,6 +437,14 @@ static void on_read_closed_handle(uv_stream_t* handle, #endif +static void on_read_send_zero(uv_stream_t* handle, + ssize_t nread, + const uv_buf_t* buf) { + ASSERT(nread == 0 || nread == UV_EOF); + free(buf->base); +} + + static int run_ipc_test(const char* helper, uv_read_cb read_cb) { uv_process_t process; int r; @@ -428,7 +453,7 @@ static int run_ipc_test(const char* helper, uv_read_cb read_cb) { uv_read_start((uv_stream_t*)&channel, on_alloc, read_cb); r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); - ASSERT(r == 0); + ASSERT_EQ(r, 0); MAKE_VALGRIND_HAPPY(); return 0; @@ -440,10 +465,10 @@ TEST_IMPL(ipc_listen_before_write) { RETURN_SKIP(NO_SEND_HANDLE_ON_PIPE); #endif int r = run_ipc_test("ipc_helper_listen_before_write", on_read); - ASSERT(local_conn_accepted == 1); - ASSERT(remote_conn_accepted == 1); - ASSERT(read_cb_called == 1); - ASSERT(exit_cb_called == 1); + ASSERT_EQ(local_conn_accepted, 1); + ASSERT_EQ(remote_conn_accepted, 1); + ASSERT_EQ(read_cb_called, 1); + ASSERT_EQ(exit_cb_called, 1); return r; } @@ -453,10 +478,10 @@ TEST_IMPL(ipc_listen_after_write) { RETURN_SKIP(NO_SEND_HANDLE_ON_PIPE); #endif int r = run_ipc_test("ipc_helper_listen_after_write", on_read); - ASSERT(local_conn_accepted == 1); - ASSERT(remote_conn_accepted == 1); - ASSERT(read_cb_called == 1); - ASSERT(exit_cb_called == 1); + ASSERT_EQ(local_conn_accepted, 1); + ASSERT_EQ(remote_conn_accepted, 1); + ASSERT_EQ(read_cb_called, 1); + ASSERT_EQ(exit_cb_called, 1); return r; } @@ -466,10 +491,10 @@ TEST_IMPL(ipc_tcp_connection) { RETURN_SKIP(NO_SEND_HANDLE_ON_PIPE); #endif int r = run_ipc_test("ipc_helper_tcp_connection", on_read_connection); - ASSERT(read_cb_called == 1); - ASSERT(tcp_write_cb_called == 1); - ASSERT(tcp_read_cb_called == 1); - ASSERT(exit_cb_called == 1); + ASSERT_EQ(read_cb_called, 1); + ASSERT_EQ(tcp_write_cb_called, 1); + ASSERT_EQ(tcp_read_cb_called, 1); + ASSERT_EQ(exit_cb_called, 1); return r; } @@ -477,7 +502,7 @@ TEST_IMPL(ipc_tcp_connection) { TEST_IMPL(ipc_closed_handle) { int r; r = run_ipc_test("ipc_helper_closed_handle", on_read_closed_handle); - ASSERT(r == 0); + ASSERT_EQ(r, 0); return 0; } #endif @@ -489,20 +514,20 @@ TEST_IMPL(listen_with_simultaneous_accepts) { int r; struct sockaddr_in addr; - ASSERT(0 == uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); + ASSERT_EQ(0, uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); r = uv_tcp_init(uv_default_loop(), &server); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_tcp_bind(&server, (const struct sockaddr*) &addr, 0); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_tcp_simultaneous_accepts(&server, 1); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_listen((uv_stream_t*)&server, SOMAXCONN, NULL); - ASSERT(r == 0); - ASSERT(server.reqs_pending == 32); + ASSERT_EQ(r, 0); + ASSERT_EQ(server.reqs_pending, 32); MAKE_VALGRIND_HAPPY(); return 0; @@ -514,20 +539,20 @@ TEST_IMPL(listen_no_simultaneous_accepts) { int r; struct sockaddr_in addr; - ASSERT(0 == uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); + ASSERT_EQ(0, uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); r = uv_tcp_init(uv_default_loop(), &server); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_tcp_bind(&server, (const struct sockaddr*) &addr, 0); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_tcp_simultaneous_accepts(&server, 0); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_listen((uv_stream_t*)&server, SOMAXCONN, NULL); - ASSERT(r == 0); - ASSERT(server.reqs_pending == 1); + ASSERT_EQ(r, 0); + ASSERT_EQ(server.reqs_pending, 1); MAKE_VALGRIND_HAPPY(); return 0; @@ -538,12 +563,19 @@ TEST_IMPL(ipc_listen_after_bind_twice) { RETURN_SKIP(NO_SEND_HANDLE_ON_PIPE); #endif int r = run_ipc_test("ipc_helper_bind_twice", on_read_listen_after_bound_twice); - ASSERT(read_cb_called == 2); - ASSERT(exit_cb_called == 1); + ASSERT_EQ(read_cb_called, 2); + ASSERT_EQ(exit_cb_called, 1); return r; } #endif +TEST_IMPL(ipc_send_zero) { + int r; + r = run_ipc_test("ipc_helper_send_zero", on_read_send_zero); + ASSERT_EQ(r, 0); + return 0; +} + /* Everything here runs in a child process. */ @@ -562,7 +594,7 @@ static void conn_notify_write_cb(uv_write_t* req, int status) { static void tcp_connection_write_cb(uv_write_t* req, int status) { - ASSERT((uv_handle_t*)&conn.conn == (uv_handle_t*)req->handle); + ASSERT_PTR_EQ(&conn.conn, req->handle); uv_close((uv_handle_t*)req->handle, close_cb); uv_close((uv_handle_t*)&channel, close_cb); uv_close((uv_handle_t*)&tcp_server, close_cb); @@ -571,16 +603,27 @@ static void tcp_connection_write_cb(uv_write_t* req, int status) { static void closed_handle_large_write_cb(uv_write_t* req, int status) { - ASSERT(status == 0); + ASSERT_EQ(status, 0); ASSERT(closed_handle_data_read = LARGE_SIZE); + if (++write_reqs_completed == ARRAY_SIZE(write_reqs)) { + write_reqs_completed = 0; + if (write_until_data_queued() > 0) + send_handle_and_close(); + } } static void closed_handle_write_cb(uv_write_t* req, int status) { - ASSERT(status == UV_EBADF); + ASSERT_EQ(status, UV_EBADF); + closed_handle_write = 1; } +static void send_zero_write_cb(uv_write_t* req, int status) { + ASSERT_EQ(status, 0); + send_zero_write++; +} + static void on_tcp_child_process_read(uv_stream_t* tcp, ssize_t nread, const uv_buf_t* buf) { @@ -597,15 +640,15 @@ static void on_tcp_child_process_read(uv_stream_t* tcp, abort(); } - ASSERT(nread > 0); - ASSERT(memcmp("world\n", buf->base, nread) == 0); + ASSERT_GT(nread, 0); + ASSERT_MEM_EQ("world\n", buf->base, nread); on_pipe_read_called++; free(buf->base); /* Write to the socket */ outbuf = uv_buf_init("hello again\n", 12); r = uv_write(&conn.tcp_write_req, tcp, &outbuf, 1, tcp_connection_write_cb); - ASSERT(r == 0); + ASSERT_EQ(r, 0); tcp_conn_read_cb_called++; } @@ -614,9 +657,9 @@ static void on_tcp_child_process_read(uv_stream_t* tcp, static void connect_child_process_cb(uv_connect_t* req, int status) { int r; - ASSERT(status == 0); + ASSERT_EQ(status, 0); r = uv_read_start(req->handle, on_read_alloc, on_tcp_child_process_read); - ASSERT(r == 0); + ASSERT_EQ(r, 0); } @@ -629,21 +672,21 @@ static void ipc_on_connection(uv_stream_t* server, int status) { * Accept the connection and close it. Also let the other * side know. */ - ASSERT(status == 0); - ASSERT((uv_stream_t*)&tcp_server == server); + ASSERT_EQ(status, 0); + ASSERT_PTR_EQ(&tcp_server, server); r = uv_tcp_init(server->loop, &conn.conn); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_accept(server, (uv_stream_t*)&conn.conn); - ASSERT(r == 0); + ASSERT_EQ(r, 0); uv_close((uv_handle_t*)&conn.conn, close_cb); buf = uv_buf_init("accepted_connection\n", 20); r = uv_write2(&conn_notify_req, (uv_stream_t*)&channel, &buf, 1, NULL, conn_notify_write_cb); - ASSERT(r == 0); + ASSERT_EQ(r, 0); connection_accepted = 1; } @@ -655,28 +698,28 @@ static void ipc_on_connection_tcp_conn(uv_stream_t* server, int status) { uv_buf_t buf; uv_tcp_t* conn; - ASSERT(status == 0); - ASSERT((uv_stream_t*)&tcp_server == server); + ASSERT_EQ(status, 0); + ASSERT_PTR_EQ(&tcp_server, server); conn = malloc(sizeof(*conn)); - ASSERT(conn); + ASSERT_NOT_NULL(conn); r = uv_tcp_init(server->loop, conn); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_accept(server, (uv_stream_t*)conn); - ASSERT(r == 0); + ASSERT_EQ(r, 0); /* Send the accepted connection to the other process */ buf = uv_buf_init("hello\n", 6); r = uv_write2(&conn_notify_req, (uv_stream_t*)&channel, &buf, 1, (uv_stream_t*)conn, NULL); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_read_start((uv_stream_t*) conn, on_read_alloc, on_tcp_child_process_read); - ASSERT(r == 0); + ASSERT_EQ(r, 0); uv_close((uv_handle_t*)conn, close_cb); } @@ -688,47 +731,47 @@ int ipc_helper(int listen_after_write) { * over which a handle will be transmitted. */ struct sockaddr_in addr; - uv_write_t write_req; int r; uv_buf_t buf; - ASSERT(0 == uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); + ASSERT_EQ(0, uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); r = uv_pipe_init(uv_default_loop(), &channel, 1); - ASSERT(r == 0); + ASSERT_EQ(r, 0); uv_pipe_open(&channel, 0); - ASSERT(1 == uv_is_readable((uv_stream_t*) &channel)); - ASSERT(1 == uv_is_writable((uv_stream_t*) &channel)); - ASSERT(0 == uv_is_closing((uv_handle_t*) &channel)); + ASSERT_EQ(1, uv_is_readable((uv_stream_t*) &channel)); + ASSERT_EQ(1, uv_is_writable((uv_stream_t*) &channel)); + ASSERT_EQ(0, uv_is_closing((uv_handle_t*) &channel)); r = uv_tcp_init(uv_default_loop(), &tcp_server); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_tcp_bind(&tcp_server, (const struct sockaddr*) &addr, 0); - ASSERT(r == 0); + ASSERT_EQ(r, 0); if (!listen_after_write) { r = uv_listen((uv_stream_t*)&tcp_server, BACKLOG, ipc_on_connection); - ASSERT(r == 0); + ASSERT_EQ(r, 0); } buf = uv_buf_init("hello\n", 6); r = uv_write2(&write_req, (uv_stream_t*)&channel, &buf, 1, (uv_stream_t*)&tcp_server, NULL); - ASSERT(r == 0); + ASSERT_EQ(r, 0); if (listen_after_write) { r = uv_listen((uv_stream_t*)&tcp_server, BACKLOG, ipc_on_connection); - ASSERT(r == 0); + ASSERT_EQ(r, 0); } + notify_parent_process(); r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); - ASSERT(r == 0); + ASSERT_EQ(r, 0); - ASSERT(connection_accepted == 1); - ASSERT(close_cb_called == 3); + ASSERT_EQ(connection_accepted, 1); + ASSERT_EQ(close_cb_called, 3); MAKE_VALGRIND_HAPPY(); return 0; @@ -745,96 +788,112 @@ int ipc_helper_tcp_connection(void) { struct sockaddr_in addr; r = uv_pipe_init(uv_default_loop(), &channel, 1); - ASSERT(r == 0); + ASSERT_EQ(r, 0); uv_pipe_open(&channel, 0); - ASSERT(1 == uv_is_readable((uv_stream_t*) &channel)); - ASSERT(1 == uv_is_writable((uv_stream_t*) &channel)); - ASSERT(0 == uv_is_closing((uv_handle_t*) &channel)); + ASSERT_EQ(1, uv_is_readable((uv_stream_t*) &channel)); + ASSERT_EQ(1, uv_is_writable((uv_stream_t*) &channel)); + ASSERT_EQ(0, uv_is_closing((uv_handle_t*) &channel)); r = uv_tcp_init(uv_default_loop(), &tcp_server); - ASSERT(r == 0); + ASSERT_EQ(r, 0); - ASSERT(0 == uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); + ASSERT_EQ(0, uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); r = uv_tcp_bind(&tcp_server, (const struct sockaddr*) &addr, 0); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_listen((uv_stream_t*)&tcp_server, BACKLOG, ipc_on_connection_tcp_conn); - ASSERT(r == 0); + ASSERT_EQ(r, 0); /* Make a connection to the server */ r = uv_tcp_init(uv_default_loop(), &conn.conn); - ASSERT(r == 0); + ASSERT_EQ(r, 0); - ASSERT(0 == uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); + ASSERT_EQ(0, uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); r = uv_tcp_connect(&conn.conn_req, (uv_tcp_t*) &conn.conn, (const struct sockaddr*) &addr, connect_child_process_cb); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); - ASSERT(r == 0); + ASSERT_EQ(r, 0); - ASSERT(tcp_conn_read_cb_called == 1); - ASSERT(tcp_conn_write_cb_called == 1); - ASSERT(close_cb_called == 4); + ASSERT_EQ(tcp_conn_read_cb_called, 1); + ASSERT_EQ(tcp_conn_write_cb_called, 1); + ASSERT_EQ(close_cb_called, 4); MAKE_VALGRIND_HAPPY(); return 0; } - -int ipc_helper_closed_handle(void) { +static unsigned int write_until_data_queued() { + unsigned int i; int r; - struct sockaddr_in addr; - uv_write_t write_req; - uv_write_t write_req2; - uv_buf_t buf; - char buffer[LARGE_SIZE]; - - r = uv_pipe_init(uv_default_loop(), &channel, 1); - ASSERT(r == 0); - uv_pipe_open(&channel, 0); + i = 0; + do { + r = uv_write(&write_reqs[i], + (uv_stream_t*)&channel, + &large_buf, + 1, + closed_handle_large_write_cb); + ASSERT_EQ(r, 0); + i++; + } while (channel.write_queue_size == 0 && + i < ARRAY_SIZE(write_reqs)); - ASSERT(1 == uv_is_readable((uv_stream_t*) &channel)); - ASSERT(1 == uv_is_writable((uv_stream_t*) &channel)); - ASSERT(0 == uv_is_closing((uv_handle_t*) &channel)); + return channel.write_queue_size; +} - memset(buffer, '.', LARGE_SIZE); - buf = uv_buf_init(buffer, LARGE_SIZE); +static void send_handle_and_close() { + int r; + struct sockaddr_in addr; r = uv_tcp_init(uv_default_loop(), &tcp_server); - ASSERT(r == 0); + ASSERT_EQ(r, 0); - ASSERT(0 == uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); + ASSERT_EQ(0, uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); r = uv_tcp_bind(&tcp_server, (const struct sockaddr*) &addr, 0); - ASSERT(r == 0); + ASSERT_EQ(r, 0); - r = uv_write(&write_req, - (uv_stream_t*)&channel, - &buf, - 1, - closed_handle_large_write_cb); - ASSERT(r == 0); - - r = uv_write2(&write_req2, + r = uv_write2(&write_req, (uv_stream_t*)&channel, - &buf, + &large_buf, 1, (uv_stream_t*)&tcp_server, closed_handle_write_cb); - ASSERT(r == 0); + ASSERT_EQ(r, 0); uv_close((uv_handle_t*)&tcp_server, NULL); +} + +int ipc_helper_closed_handle(void) { + int r; + + memset(buffer, '.', LARGE_SIZE); + large_buf = uv_buf_init(buffer, LARGE_SIZE); + + r = uv_pipe_init(uv_default_loop(), &channel, 1); + ASSERT_EQ(r, 0); + + uv_pipe_open(&channel, 0); + + ASSERT_EQ(1, uv_is_readable((uv_stream_t*) &channel)); + ASSERT_EQ(1, uv_is_writable((uv_stream_t*) &channel)); + ASSERT_EQ(0, uv_is_closing((uv_handle_t*) &channel)); + + if (write_until_data_queued() > 0) + send_handle_and_close(); r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); - ASSERT(r == 0); + ASSERT_EQ(r, 0); + + ASSERT_EQ(closed_handle_write, 1); MAKE_VALGRIND_HAPPY(); return 0; @@ -847,43 +906,73 @@ int ipc_helper_bind_twice(void) { * over which two handles will be transmitted. */ struct sockaddr_in addr; - uv_write_t write_req; - uv_write_t write_req2; int r; uv_buf_t buf; - ASSERT(0 == uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); + ASSERT_EQ(0, uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); r = uv_pipe_init(uv_default_loop(), &channel, 1); - ASSERT(r == 0); + ASSERT_EQ(r, 0); uv_pipe_open(&channel, 0); - ASSERT(1 == uv_is_readable((uv_stream_t*) &channel)); - ASSERT(1 == uv_is_writable((uv_stream_t*) &channel)); - ASSERT(0 == uv_is_closing((uv_handle_t*) &channel)); + ASSERT_EQ(1, uv_is_readable((uv_stream_t*) &channel)); + ASSERT_EQ(1, uv_is_writable((uv_stream_t*) &channel)); + ASSERT_EQ(0, uv_is_closing((uv_handle_t*) &channel)); buf = uv_buf_init("hello\n", 6); r = uv_tcp_init(uv_default_loop(), &tcp_server); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_tcp_init(uv_default_loop(), &tcp_server2); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_tcp_bind(&tcp_server, (const struct sockaddr*) &addr, 0); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_tcp_bind(&tcp_server2, (const struct sockaddr*) &addr, 0); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_write2(&write_req, (uv_stream_t*)&channel, &buf, 1, (uv_stream_t*)&tcp_server, NULL); - ASSERT(r == 0); + ASSERT_EQ(r, 0); r = uv_write2(&write_req2, (uv_stream_t*)&channel, &buf, 1, (uv_stream_t*)&tcp_server2, NULL); - ASSERT(r == 0); + ASSERT_EQ(r, 0); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + ASSERT_EQ(r, 0); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +int ipc_helper_send_zero(void) { + int r; + uv_buf_t zero_buf; + + zero_buf = uv_buf_init(0, 0); + + r = uv_pipe_init(uv_default_loop(), &channel, 0); + ASSERT_EQ(r, 0); + + uv_pipe_open(&channel, 0); + + ASSERT_EQ(1, uv_is_readable((uv_stream_t*) &channel)); + ASSERT_EQ(1, uv_is_writable((uv_stream_t*) &channel)); + ASSERT_EQ(0, uv_is_closing((uv_handle_t*) &channel)); + + r = uv_write(&write_req, + (uv_stream_t*)&channel, + &zero_buf, + 1, + send_zero_write_cb); + + ASSERT_EQ(r, 0); r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); - ASSERT(r == 0); + ASSERT_EQ(r, 0); + + ASSERT_EQ(send_zero_write, 1); MAKE_VALGRIND_HAPPY(); return 0; diff --git a/test/test-list.h b/test/test-list.h index b501722..24a8a65 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -37,6 +37,8 @@ TEST_DECLARE (default_loop_close) TEST_DECLARE (barrier_1) TEST_DECLARE (barrier_2) TEST_DECLARE (barrier_3) +TEST_DECLARE (barrier_serial_thread) +TEST_DECLARE (barrier_serial_thread_single) TEST_DECLARE (condvar_1) TEST_DECLARE (condvar_2) TEST_DECLARE (condvar_3) @@ -50,10 +52,31 @@ TEST_DECLARE (tty) TEST_DECLARE (tty_raw) TEST_DECLARE (tty_empty_write) TEST_DECLARE (tty_large_write) +TEST_DECLARE (tty_raw_cancel) +TEST_DECLARE (tty_duplicate_vt100_fn_key) +TEST_DECLARE (tty_duplicate_alt_modifier_key) +TEST_DECLARE (tty_composing_character) +TEST_DECLARE (tty_cursor_up) +TEST_DECLARE (tty_cursor_down) +TEST_DECLARE (tty_cursor_forward) +TEST_DECLARE (tty_cursor_back) +TEST_DECLARE (tty_cursor_next_line) +TEST_DECLARE (tty_cursor_previous_line) +TEST_DECLARE (tty_cursor_horizontal_move_absolute) +TEST_DECLARE (tty_cursor_move_absolute) +TEST_DECLARE (tty_hide_show_cursor) +TEST_DECLARE (tty_set_cursor_shape) +TEST_DECLARE (tty_erase) +TEST_DECLARE (tty_erase_line) +TEST_DECLARE (tty_set_style) +TEST_DECLARE (tty_save_restore_cursor_position) +TEST_DECLARE (tty_full_reset) +TEST_DECLARE (tty_escape_sequence_processing) #endif TEST_DECLARE (tty_file) TEST_DECLARE (tty_pty) TEST_DECLARE (stdio_over_pipes) +TEST_DECLARE (stdio_emulate_iocp) TEST_DECLARE (ip6_pton) TEST_DECLARE (connect_unspecified) TEST_DECLARE (ipc_heavy_traffic_deadlock_bug) @@ -66,6 +89,7 @@ TEST_DECLARE (ipc_send_recv_pipe_inprocess) TEST_DECLARE (ipc_send_recv_tcp) TEST_DECLARE (ipc_send_recv_tcp_inprocess) TEST_DECLARE (ipc_tcp_connection) +TEST_DECLARE (ipc_send_zero) #ifndef _WIN32 TEST_DECLARE (ipc_closed_handle) #endif @@ -84,6 +108,7 @@ TEST_DECLARE (tcp_write_after_connect) TEST_DECLARE (tcp_writealot) TEST_DECLARE (tcp_write_fail) TEST_DECLARE (tcp_try_write) +TEST_DECLARE (tcp_try_write_error) TEST_DECLARE (tcp_write_queue_order) TEST_DECLARE (tcp_open) TEST_DECLARE (tcp_open_twice) @@ -104,6 +129,10 @@ TEST_DECLARE (tcp_connect_error_fault) TEST_DECLARE (tcp_connect_timeout) TEST_DECLARE (tcp_close_while_connecting) TEST_DECLARE (tcp_close) +TEST_DECLARE (tcp_close_reset_accepted) +TEST_DECLARE (tcp_close_reset_accepted_after_shutdown) +TEST_DECLARE (tcp_close_reset_client) +TEST_DECLARE (tcp_close_reset_client_after_shutdown) TEST_DECLARE (tcp_create_early) TEST_DECLARE (tcp_create_early_bad_bind) TEST_DECLARE (tcp_create_early_bad_domain) @@ -125,6 +154,7 @@ TEST_DECLARE (tcp_write_ready) TEST_DECLARE (udp_alloc_cb_fail) TEST_DECLARE (udp_bind) TEST_DECLARE (udp_bind_reuseaddr) +TEST_DECLARE (udp_connect) TEST_DECLARE (udp_create_early) TEST_DECLARE (udp_create_early_bad_bind) TEST_DECLARE (udp_create_early_bad_domain) @@ -145,6 +175,11 @@ TEST_DECLARE (udp_options6) TEST_DECLARE (udp_no_autobind) TEST_DECLARE (udp_open) TEST_DECLARE (udp_open_twice) +TEST_DECLARE (udp_open_bound) +TEST_DECLARE (udp_open_connect) +#ifndef _WIN32 +TEST_DECLARE (udp_send_unix) +#endif TEST_DECLARE (udp_try_send) TEST_DECLARE (pipe_bind_error_addrinuse) TEST_DECLARE (pipe_bind_error_addrnotavail) @@ -179,6 +214,7 @@ TEST_DECLARE (timer_huge_timeout) TEST_DECLARE (timer_huge_repeat) TEST_DECLARE (timer_run_once) TEST_DECLARE (timer_from_check) +TEST_DECLARE (timer_is_closing) TEST_DECLARE (timer_null_callback) TEST_DECLARE (timer_early_check) TEST_DECLARE (idle_starvation) @@ -223,6 +259,7 @@ TEST_DECLARE (async_null_cb) TEST_DECLARE (eintr_handling) TEST_DECLARE (get_currentexe) TEST_DECLARE (process_title) +TEST_DECLARE (process_title_big_argv) TEST_DECLARE (process_title_threadsafe) TEST_DECLARE (cwd_and_chdir) TEST_DECLARE (get_memory) @@ -242,6 +279,7 @@ TEST_DECLARE (getnameinfo_basic_ip4_sync) TEST_DECLARE (getnameinfo_basic_ip6) TEST_DECLARE (getsockname_tcp) TEST_DECLARE (getsockname_udp) +TEST_DECLARE (gettimeofday) TEST_DECLARE (fail_always) TEST_DECLARE (pass_always) TEST_DECLARE (socket_buffer_size) @@ -249,6 +287,7 @@ TEST_DECLARE (spawn_fails) #ifndef _WIN32 TEST_DECLARE (spawn_fails_check_for_waitpid_cleanup) #endif +TEST_DECLARE (spawn_empty_env) TEST_DECLARE (spawn_exit_code) TEST_DECLARE (spawn_stdout) TEST_DECLARE (spawn_stdin) @@ -273,6 +312,10 @@ TEST_DECLARE (spawn_quoted_path) TEST_DECLARE (spawn_tcp_server) TEST_DECLARE (fs_poll) TEST_DECLARE (fs_poll_getpath) +TEST_DECLARE (fs_poll_close_request) +TEST_DECLARE (fs_poll_close_request_multi_start_stop) +TEST_DECLARE (fs_poll_close_request_multi_stop_start) +TEST_DECLARE (fs_poll_close_request_stop_when_active) TEST_DECLARE (kill) TEST_DECLARE (kill_invalid_signum) TEST_DECLARE (fs_file_noent) @@ -283,7 +326,9 @@ TEST_DECLARE (fs_file_sync) TEST_DECLARE (fs_file_write_null_buffer) TEST_DECLARE (fs_async_dir) TEST_DECLARE (fs_async_sendfile) +TEST_DECLARE (fs_async_sendfile_nodata) TEST_DECLARE (fs_mkdtemp) +TEST_DECLARE (fs_mkstemp) TEST_DECLARE (fs_fstat) TEST_DECLARE (fs_access) TEST_DECLARE (fs_chmod) @@ -301,11 +346,19 @@ TEST_DECLARE (fs_symlink_dir) #ifdef _WIN32 TEST_DECLARE (fs_symlink_junction) TEST_DECLARE (fs_non_symlink_reparse_point) +TEST_DECLARE (fs_lstat_windows_store_apps) +TEST_DECLARE (fs_open_flags) +#endif +#if defined(_WIN32) && !defined(USING_UV_SHARED) +TEST_DECLARE (fs_fd_hash) #endif TEST_DECLARE (fs_utime) TEST_DECLARE (fs_futime) +TEST_DECLARE (fs_lutime) TEST_DECLARE (fs_file_open_append) +TEST_DECLARE (fs_statfs) TEST_DECLARE (fs_stat_missing_path) +TEST_DECLARE (fs_read_bufs) TEST_DECLARE (fs_read_file_eof) TEST_DECLARE (fs_event_watch_dir) TEST_DECLARE (fs_event_watch_dir_recursive) @@ -332,6 +385,10 @@ TEST_DECLARE (fs_scandir_empty_dir) TEST_DECLARE (fs_scandir_non_existent_dir) TEST_DECLARE (fs_scandir_file) TEST_DECLARE (fs_open_dir) +TEST_DECLARE (fs_readdir_empty_dir) +TEST_DECLARE (fs_readdir_file) +TEST_DECLARE (fs_readdir_non_empty_dir) +TEST_DECLARE (fs_readdir_non_existing_dir) TEST_DECLARE (fs_rename_to_existing_file) TEST_DECLARE (fs_write_multiple_bufs) TEST_DECLARE (fs_read_write_null_arguments) @@ -339,23 +396,34 @@ TEST_DECLARE (get_osfhandle_valid_handle) TEST_DECLARE (open_osfhandle_valid_handle) TEST_DECLARE (fs_write_alotof_bufs) TEST_DECLARE (fs_write_alotof_bufs_with_offset) +TEST_DECLARE (fs_partial_read) +TEST_DECLARE (fs_partial_write) TEST_DECLARE (fs_file_pos_after_op_with_offset) TEST_DECLARE (fs_null_req) +TEST_DECLARE (fs_read_dir) #ifdef _WIN32 +TEST_DECLARE (fs_file_pos_write) +TEST_DECLARE (fs_file_pos_append) TEST_DECLARE (fs_exclusive_sharing_mode) +TEST_DECLARE (fs_file_flag_no_buffering) TEST_DECLARE (fs_open_readonly_acl) TEST_DECLARE (fs_fchmod_archive_readonly) +TEST_DECLARE (fs_invalid_mkdir_name) #endif +TEST_DECLARE (fs_get_system_error) +TEST_DECLARE (strscpy) TEST_DECLARE (threadpool_queue_work_simple) TEST_DECLARE (threadpool_queue_work_einval) TEST_DECLARE (threadpool_multiple_event_loops) TEST_DECLARE (threadpool_cancel_getaddrinfo) TEST_DECLARE (threadpool_cancel_getnameinfo) +TEST_DECLARE (threadpool_cancel_random) TEST_DECLARE (threadpool_cancel_work) TEST_DECLARE (threadpool_cancel_fs) TEST_DECLARE (threadpool_cancel_single) TEST_DECLARE (thread_local_storage) TEST_DECLARE (thread_stack_size) +TEST_DECLARE (thread_stack_size_explicit) TEST_DECLARE (thread_mutex) TEST_DECLARE (thread_mutex_recursive) TEST_DECLARE (thread_rwlock) @@ -405,6 +473,8 @@ TEST_DECLARE (we_get_signals) TEST_DECLARE (we_get_signal_one_shot) TEST_DECLARE (we_get_signals_mixed) TEST_DECLARE (signal_multiple_loops) +TEST_DECLARE (signal_pending_on_close) +TEST_DECLARE (signal_close_loop_alive) TEST_DECLARE (closed_fd_events) #endif #ifdef __APPLE__ @@ -418,6 +488,9 @@ HELPER_DECLARE (pipe_echo_server) TEST_DECLARE (queue_foreach_delete) +TEST_DECLARE (random_async) +TEST_DECLARE (random_sync) + TEST_DECLARE (handle_type_name) TEST_DECLARE (req_type_name) TEST_DECLARE (getters_setters) @@ -428,14 +501,22 @@ TEST_DECLARE (fork_socketpair) TEST_DECLARE (fork_socketpair_started) TEST_DECLARE (fork_signal_to_child) TEST_DECLARE (fork_signal_to_child_closed) +#ifndef __APPLE__ /* This is forbidden in a fork child: The process has forked + and you cannot use this CoreFoundation functionality + safely. You MUST exec(). */ TEST_DECLARE (fork_fs_events_child) TEST_DECLARE (fork_fs_events_child_dir) TEST_DECLARE (fork_fs_events_file_parent_child) +#endif #ifndef __MVS__ TEST_DECLARE (fork_threadpool_queue_work_simple) #endif #endif +TEST_DECLARE (idna_toascii) +TEST_DECLARE (utf8_decode1) +TEST_DECLARE (uname) + TASK_LIST_START TEST_ENTRY_CUSTOM (platform_output, 0, 1, 5000) @@ -456,6 +537,8 @@ TASK_LIST_START TEST_ENTRY (barrier_1) TEST_ENTRY (barrier_2) TEST_ENTRY (barrier_3) + TEST_ENTRY (barrier_serial_thread) + TEST_ENTRY (barrier_serial_thread_single) TEST_ENTRY (condvar_1) TEST_ENTRY (condvar_2) TEST_ENTRY (condvar_3) @@ -480,10 +563,31 @@ TASK_LIST_START TEST_ENTRY (tty_raw) TEST_ENTRY (tty_empty_write) TEST_ENTRY (tty_large_write) + TEST_ENTRY (tty_raw_cancel) + TEST_ENTRY (tty_duplicate_vt100_fn_key) + TEST_ENTRY (tty_duplicate_alt_modifier_key) + TEST_ENTRY (tty_composing_character) + TEST_ENTRY (tty_cursor_up) + TEST_ENTRY (tty_cursor_down) + TEST_ENTRY (tty_cursor_forward) + TEST_ENTRY (tty_cursor_back) + TEST_ENTRY (tty_cursor_next_line) + TEST_ENTRY (tty_cursor_previous_line) + TEST_ENTRY (tty_cursor_horizontal_move_absolute) + TEST_ENTRY (tty_cursor_move_absolute) + TEST_ENTRY (tty_hide_show_cursor) + TEST_ENTRY (tty_set_cursor_shape) + TEST_ENTRY (tty_erase) + TEST_ENTRY (tty_erase_line) + TEST_ENTRY (tty_set_style) + TEST_ENTRY (tty_save_restore_cursor_position) + TEST_ENTRY (tty_full_reset) + TEST_ENTRY (tty_escape_sequence_processing) #endif TEST_ENTRY (tty_file) TEST_ENTRY (tty_pty) TEST_ENTRY (stdio_over_pipes) + TEST_ENTRY (stdio_emulate_iocp) TEST_ENTRY (ip6_pton) TEST_ENTRY (connect_unspecified) TEST_ENTRY (ipc_heavy_traffic_deadlock_bug) @@ -496,6 +600,7 @@ TASK_LIST_START TEST_ENTRY (ipc_send_recv_tcp) TEST_ENTRY (ipc_send_recv_tcp_inprocess) TEST_ENTRY (ipc_tcp_connection) + TEST_ENTRY (ipc_send_zero) #ifndef _WIN32 TEST_ENTRY (ipc_closed_handle) #endif @@ -538,6 +643,7 @@ TASK_LIST_START TEST_HELPER (tcp_write_fail, tcp4_echo_server) TEST_ENTRY (tcp_try_write) + TEST_ENTRY (tcp_try_write_error) TEST_ENTRY (tcp_write_queue_order) @@ -567,6 +673,10 @@ TASK_LIST_START TEST_ENTRY (tcp_connect_timeout) TEST_ENTRY (tcp_close_while_connecting) TEST_ENTRY (tcp_close) + TEST_ENTRY (tcp_close_reset_accepted) + TEST_ENTRY (tcp_close_reset_accepted_after_shutdown) + TEST_ENTRY (tcp_close_reset_client) + TEST_ENTRY (tcp_close_reset_client_after_shutdown) TEST_ENTRY (tcp_create_early) TEST_ENTRY (tcp_create_early_bad_bind) TEST_ENTRY (tcp_create_early_bad_domain) @@ -591,6 +701,7 @@ TASK_LIST_START TEST_ENTRY (udp_alloc_cb_fail) TEST_ENTRY (udp_bind) TEST_ENTRY (udp_bind_reuseaddr) + TEST_ENTRY (udp_connect) TEST_ENTRY (udp_create_early) TEST_ENTRY (udp_create_early_bad_bind) TEST_ENTRY (udp_create_early_bad_domain) @@ -612,8 +723,12 @@ TASK_LIST_START TEST_ENTRY (udp_try_send) TEST_ENTRY (udp_open) - TEST_HELPER (udp_open, udp4_echo_server) TEST_ENTRY (udp_open_twice) + TEST_ENTRY (udp_open_bound) + TEST_ENTRY (udp_open_connect) +#ifndef _WIN32 + TEST_ENTRY (udp_send_unix) +#endif TEST_ENTRY (pipe_bind_error_addrinuse) TEST_ENTRY (pipe_bind_error_addrnotavail) @@ -657,6 +772,7 @@ TASK_LIST_START TEST_ENTRY (timer_huge_repeat) TEST_ENTRY (timer_run_once) TEST_ENTRY (timer_from_check) + TEST_ENTRY (timer_is_closing) TEST_ENTRY (timer_null_callback) TEST_ENTRY (timer_early_check) @@ -709,6 +825,7 @@ TASK_LIST_START TEST_ENTRY (get_currentexe) TEST_ENTRY (process_title) + TEST_ENTRY (process_title_big_argv) TEST_ENTRY (process_title_threadsafe) TEST_ENTRY (cwd_and_chdir) @@ -725,7 +842,7 @@ TASK_LIST_START TEST_ENTRY (tmpdir) - TEST_ENTRY (hrtime) + TEST_ENTRY_CUSTOM (hrtime, 0, 0, 10000) TEST_ENTRY_CUSTOM (getaddrinfo_fail, 0, 0, 10000) TEST_ENTRY_CUSTOM (getaddrinfo_fail_sync, 0, 0, 10000) @@ -743,6 +860,8 @@ TASK_LIST_START TEST_ENTRY (getsockname_tcp) TEST_ENTRY (getsockname_udp) + TEST_ENTRY (gettimeofday) + TEST_ENTRY (poll_duplex) TEST_ENTRY (poll_unidirectional) TEST_ENTRY (poll_close) @@ -765,6 +884,7 @@ TASK_LIST_START #ifndef _WIN32 TEST_ENTRY (spawn_fails_check_for_waitpid_cleanup) #endif + TEST_ENTRY (spawn_empty_env) TEST_ENTRY (spawn_exit_code) TEST_ENTRY (spawn_stdout) TEST_ENTRY (spawn_stdin) @@ -789,6 +909,10 @@ TASK_LIST_START TEST_ENTRY (spawn_tcp_server) TEST_ENTRY (fs_poll) TEST_ENTRY (fs_poll_getpath) + TEST_ENTRY (fs_poll_close_request) + TEST_ENTRY (fs_poll_close_request_multi_start_stop) + TEST_ENTRY (fs_poll_close_request_multi_stop_start) + TEST_ENTRY (fs_poll_close_request_stop_when_active) TEST_ENTRY (kill) TEST_ENTRY (kill_invalid_signum) @@ -816,6 +940,8 @@ TASK_LIST_START TEST_ENTRY (we_get_signal_one_shot) TEST_ENTRY (we_get_signals_mixed) TEST_ENTRY (signal_multiple_loops) + TEST_ENTRY (signal_pending_on_close) + TEST_ENTRY (signal_close_loop_alive) TEST_ENTRY (closed_fd_events) #endif @@ -832,7 +958,9 @@ TASK_LIST_START TEST_ENTRY (fs_file_write_null_buffer) TEST_ENTRY (fs_async_dir) TEST_ENTRY (fs_async_sendfile) + TEST_ENTRY (fs_async_sendfile_nodata) TEST_ENTRY (fs_mkdtemp) + TEST_ENTRY (fs_mkstemp) TEST_ENTRY (fs_fstat) TEST_ENTRY (fs_access) TEST_ENTRY (fs_chmod) @@ -844,6 +972,7 @@ TASK_LIST_START TEST_ENTRY (fs_chown) TEST_ENTRY (fs_utime) TEST_ENTRY (fs_futime) + TEST_ENTRY (fs_lutime) TEST_ENTRY (fs_readlink) TEST_ENTRY (fs_realpath) TEST_ENTRY (fs_symlink) @@ -851,8 +980,15 @@ TASK_LIST_START #ifdef _WIN32 TEST_ENTRY (fs_symlink_junction) TEST_ENTRY (fs_non_symlink_reparse_point) + TEST_ENTRY (fs_lstat_windows_store_apps) + TEST_ENTRY (fs_open_flags) +#endif +#if defined(_WIN32) && !defined(USING_UV_SHARED) + TEST_ENTRY (fs_fd_hash) #endif + TEST_ENTRY (fs_statfs) TEST_ENTRY (fs_stat_missing_path) + TEST_ENTRY (fs_read_bufs) TEST_ENTRY (fs_read_file_eof) TEST_ENTRY (fs_file_open_append) TEST_ENTRY (fs_event_watch_dir) @@ -880,30 +1016,45 @@ TASK_LIST_START TEST_ENTRY (fs_scandir_non_existent_dir) TEST_ENTRY (fs_scandir_file) TEST_ENTRY (fs_open_dir) + TEST_ENTRY (fs_readdir_empty_dir) + TEST_ENTRY (fs_readdir_file) + TEST_ENTRY (fs_readdir_non_empty_dir) + TEST_ENTRY (fs_readdir_non_existing_dir) TEST_ENTRY (fs_rename_to_existing_file) TEST_ENTRY (fs_write_multiple_bufs) TEST_ENTRY (fs_write_alotof_bufs) TEST_ENTRY (fs_write_alotof_bufs_with_offset) + TEST_ENTRY (fs_partial_read) + TEST_ENTRY (fs_partial_write) TEST_ENTRY (fs_read_write_null_arguments) TEST_ENTRY (fs_file_pos_after_op_with_offset) TEST_ENTRY (fs_null_req) + TEST_ENTRY (fs_read_dir) #ifdef _WIN32 + TEST_ENTRY (fs_file_pos_write) + TEST_ENTRY (fs_file_pos_append) TEST_ENTRY (fs_exclusive_sharing_mode) + TEST_ENTRY (fs_file_flag_no_buffering) TEST_ENTRY (fs_open_readonly_acl) TEST_ENTRY (fs_fchmod_archive_readonly) + TEST_ENTRY (fs_invalid_mkdir_name) #endif + TEST_ENTRY (fs_get_system_error) TEST_ENTRY (get_osfhandle_valid_handle) TEST_ENTRY (open_osfhandle_valid_handle) + TEST_ENTRY (strscpy) TEST_ENTRY (threadpool_queue_work_simple) TEST_ENTRY (threadpool_queue_work_einval) - TEST_ENTRY (threadpool_multiple_event_loops) + TEST_ENTRY_CUSTOM (threadpool_multiple_event_loops, 0, 0, 60000) TEST_ENTRY (threadpool_cancel_getaddrinfo) TEST_ENTRY (threadpool_cancel_getnameinfo) + TEST_ENTRY (threadpool_cancel_random) TEST_ENTRY (threadpool_cancel_work) TEST_ENTRY (threadpool_cancel_fs) TEST_ENTRY (threadpool_cancel_single) TEST_ENTRY (thread_local_storage) TEST_ENTRY (thread_stack_size) + TEST_ENTRY (thread_stack_size_explicit) TEST_ENTRY (thread_mutex) TEST_ENTRY (thread_mutex_recursive) TEST_ENTRY (thread_rwlock) @@ -916,6 +1067,9 @@ TASK_LIST_START TEST_ENTRY (queue_foreach_delete) + TEST_ENTRY (random_async) + TEST_ENTRY (random_sync) + TEST_ENTRY (handle_type_name) TEST_ENTRY (req_type_name) TEST_ENTRY (getters_setters) @@ -926,14 +1080,24 @@ TASK_LIST_START TEST_ENTRY (fork_socketpair_started) TEST_ENTRY (fork_signal_to_child) TEST_ENTRY (fork_signal_to_child_closed) +#ifndef __APPLE__ TEST_ENTRY (fork_fs_events_child) TEST_ENTRY (fork_fs_events_child_dir) TEST_ENTRY (fork_fs_events_file_parent_child) +#endif #ifndef __MVS__ TEST_ENTRY (fork_threadpool_queue_work_simple) #endif #endif + TEST_ENTRY (utf8_decode1) + TEST_ENTRY (uname) + +/* Doesn't work on z/OS because that platform uses EBCDIC, not ASCII. */ +#ifndef __MVS__ + TEST_ENTRY (idna_toascii) +#endif + #if 0 /* These are for testing the test runner. */ TEST_ENTRY (fail_always) diff --git a/test/test-loop-handles.c b/test/test-loop-handles.c index 6471cd0..1310988 100644 --- a/test/test-loop-handles.c +++ b/test/test-loop-handles.c @@ -320,7 +320,7 @@ TEST_IMPL(loop_handles) { ASSERT(prepare_1_cb_called == ITERATIONS); ASSERT(prepare_1_close_cb_called == 1); - ASSERT(prepare_2_cb_called == floor(ITERATIONS / 2.0)); + ASSERT(prepare_2_cb_called == ITERATIONS / 2); ASSERT(prepare_2_close_cb_called == 1); ASSERT(check_cb_called == ITERATIONS); diff --git a/test/test-pipe-close-stdout-read-stdin.c b/test/test-pipe-close-stdout-read-stdin.c index c8804b0..126be2c 100644 --- a/test/test-pipe-close-stdout-read-stdin.c +++ b/test/test-pipe-close-stdout-read-stdin.c @@ -105,4 +105,8 @@ TEST_IMPL(pipe_close_stdout_read_stdin) { return 0; } +#else + +typedef int file_has_no_tests; /* ISO C forbids an empty translation unit. */ + #endif /* ifndef _WIN32 */ diff --git a/test/test-pipe-getsockname.c b/test/test-pipe-getsockname.c index d1628a6..48ee400 100644 --- a/test/test-pipe-getsockname.c +++ b/test/test-pipe-getsockname.c @@ -171,7 +171,7 @@ TEST_IMPL(pipe_getsockname_abstract) { socklen_t sun_len; char abstract_pipe[] = "\0test-pipe"; - sock = socket(AF_LOCAL, SOCK_STREAM, 0); + sock = socket(AF_UNIX, SOCK_STREAM, 0); ASSERT(sock != -1); sun_len = sizeof sun; diff --git a/test/test-platform-output.c b/test/test-platform-output.c index 4025fba..f547ddf 100644 --- a/test/test-platform-output.c +++ b/test/test-platform-output.c @@ -25,6 +25,11 @@ TEST_IMPL(platform_output) { +/* TODO(gengjiawen): Fix test on QEMU. */ +#if defined(__QEMU__) + RETURN_SKIP("Test does not currently work in QEMU"); +#endif + char buffer[512]; size_t rss; size_t size; @@ -35,6 +40,7 @@ TEST_IMPL(platform_output) { uv_cpu_info_t* cpus; uv_interface_address_t* interfaces; uv_passwd_t pwd; + uv_utsname_t uname; int count; int i; int err; @@ -49,7 +55,7 @@ TEST_IMPL(platform_output) { printf("uv_cwd: %s\n", buffer); err = uv_resident_set_memory(&rss); -#if defined(__CYGWIN__) || defined(__MSYS__) +#if defined(__MSYS__) ASSERT(err == UV_ENOSYS); #else ASSERT(err == 0); @@ -57,9 +63,13 @@ TEST_IMPL(platform_output) { #endif err = uv_uptime(&uptime); +#if defined(__PASE__) + ASSERT(err == UV_ENOSYS); +#else ASSERT(err == 0); ASSERT(uptime > 0); printf("uv_uptime: %f\n", uptime); +#endif err = uv_getrusage(&rusage); ASSERT(err == 0); @@ -153,5 +163,13 @@ TEST_IMPL(platform_output) { ASSERT(ppid > 0); printf("uv_os_getppid: %d\n", (int) ppid); + err = uv_os_uname(&uname); + ASSERT(err == 0); + printf("uv_os_uname:\n"); + printf(" sysname: %s\n", uname.sysname); + printf(" release: %s\n", uname.release); + printf(" version: %s\n", uname.version); + printf(" machine: %s\n", uname.machine); + return 0; } diff --git a/test/test-poll-close-doesnt-corrupt-stack.c b/test/test-poll-close-doesnt-corrupt-stack.c index 1dfc80e..1d7e84f 100644 --- a/test/test-poll-close-doesnt-corrupt-stack.c +++ b/test/test-poll-close-doesnt-corrupt-stack.c @@ -31,11 +31,9 @@ # define NO_INLINE __attribute__ ((noinline)) #endif - -uv_os_sock_t sock; -uv_poll_t handle; - #ifdef _WIN32 +static uv_os_sock_t sock; +static uv_poll_t handle; static int close_cb_called = 0; @@ -49,7 +47,7 @@ static void poll_cb(uv_poll_t* h, int status, int events) { } -static void NO_INLINE close_socket_and_verify_stack() { +static void NO_INLINE close_socket_and_verify_stack(void) { const uint32_t MARKER = 0xDEADBEEF; const int VERIFY_AFTER = 10; /* ms */ int r; diff --git a/test/test-poll-closesocket.c b/test/test-poll-closesocket.c index ecaa9e5..1a1c364 100644 --- a/test/test-poll-closesocket.c +++ b/test/test-poll-closesocket.c @@ -25,10 +25,9 @@ #include "uv.h" #include "task.h" -uv_os_sock_t sock; -uv_poll_t handle; - #ifdef _WIN32 +static uv_os_sock_t sock; +static uv_poll_t handle; static int close_cb_called = 0; diff --git a/test/test-poll-oob.c b/test/test-poll-oob.c index 2a6da84..77ffe31 100644 --- a/test/test-poll-oob.c +++ b/test/test-poll-oob.c @@ -202,4 +202,9 @@ TEST_IMPL(poll_oob) { MAKE_VALGRIND_HAPPY(); return 0; } + +#else + +typedef int file_has_no_tests; /* ISO C forbids an empty translation unit. */ + #endif diff --git a/test/test-poll.c b/test/test-poll.c index 0d1b1d7..edd75d3 100644 --- a/test/test-poll.c +++ b/test/test-poll.c @@ -82,9 +82,9 @@ static int closed_connections = 0; static int valid_writable_wakeups = 0; static int spurious_writable_wakeups = 0; -#if !defined(_AIX) && !defined(__MVS__) +#if !defined(__sun) && !defined(_AIX) && !defined(__MVS__) static int disconnects = 0; -#endif /* !_AIX && !__MVS__ */ +#endif /* !__sun && !_AIX && !__MVS__ */ static int got_eagain(void) { #ifdef _WIN32 @@ -222,7 +222,10 @@ static void connection_poll_cb(uv_poll_t* handle, int status, int events) { case 1: { /* Read a couple of bytes. */ static char buffer[74]; - r = recv(context->sock, buffer, sizeof buffer, 0); + + do + r = recv(context->sock, buffer, sizeof buffer, 0); + while (r == -1 && errno == EINTR); ASSERT(r >= 0); if (r > 0) { @@ -240,12 +243,16 @@ static void connection_poll_cb(uv_poll_t* handle, int status, int events) { case 3: { /* Read until EAGAIN. */ static char buffer[931]; - r = recv(context->sock, buffer, sizeof buffer, 0); - ASSERT(r >= 0); - while (r > 0) { + for (;;) { + do + r = recv(context->sock, buffer, sizeof buffer, 0); + while (r == -1 && errno == EINTR); + + if (r <= 0) + break; + context->read += r; - r = recv(context->sock, buffer, sizeof buffer, 0); } if (r == 0) { @@ -301,7 +308,9 @@ static void connection_poll_cb(uv_poll_t* handle, int status, int events) { int send_bytes = MIN(TRANSFER_BYTES - context->sent, sizeof buffer); ASSERT(send_bytes > 0); - r = send(context->sock, buffer, send_bytes, 0); + do + r = send(context->sock, buffer, send_bytes, 0); + while (r == -1 && errno == EINTR); if (r < 0) { ASSERT(got_eagain()); @@ -323,7 +332,9 @@ static void connection_poll_cb(uv_poll_t* handle, int status, int events) { int send_bytes = MIN(TRANSFER_BYTES - context->sent, sizeof buffer); ASSERT(send_bytes > 0); - r = send(context->sock, buffer, send_bytes, 0); + do + r = send(context->sock, buffer, send_bytes, 0); + while (r == -1 && errno == EINTR); if (r < 0) { ASSERT(got_eagain()); @@ -339,12 +350,18 @@ static void connection_poll_cb(uv_poll_t* handle, int status, int events) { send_bytes = MIN(TRANSFER_BYTES - context->sent, sizeof buffer); ASSERT(send_bytes > 0); - r = send(context->sock, buffer, send_bytes, 0); + do + r = send(context->sock, buffer, send_bytes, 0); + while (r == -1 && errno == EINTR); + ASSERT(r != 0); + + if (r < 0) { + ASSERT(got_eagain()); + break; + } - if (r <= 0) break; context->sent += r; } - ASSERT(r > 0 || got_eagain()); break; } @@ -391,7 +408,7 @@ static void connection_poll_cb(uv_poll_t* handle, int status, int events) { new_events &= ~UV_WRITABLE; } } -#if !defined(_AIX) && !defined(__MVS__) +#if !defined(__sun) && !defined(_AIX) && !defined(__MVS__) if (events & UV_DISCONNECT) { context->got_disconnect = 1; ++disconnects; @@ -399,9 +416,9 @@ static void connection_poll_cb(uv_poll_t* handle, int status, int events) { } if (context->got_fin && context->sent_fin && context->got_disconnect) { -#else /* _AIX && __MVS__ */ +#else /* __sun && _AIX && __MVS__ */ if (context->got_fin && context->sent_fin) { -#endif /* !_AIX && !__MVS__ */ +#endif /* !__sun && !_AIX && !__MVS__ */ /* Sent and received FIN. Close and destroy context. */ close_socket(context->sock); destroy_connection_context(context); @@ -569,16 +586,25 @@ static void start_poll_test(void) { spurious_writable_wakeups > 20); ASSERT(closed_connections == NUM_CLIENTS * 2); -#if !defined(_AIX) && !defined(__MVS__) +#if !defined(__sun) && !defined(_AIX) && !defined(__MVS__) ASSERT(disconnects == NUM_CLIENTS * 2); #endif MAKE_VALGRIND_HAPPY(); } - + +/* Issuing a shutdown() on IBM i PASE with parameter SHUT_WR + * also sends a normal close sequence to the partner program. + * This leads to timing issues and ECONNRESET failures in the + * test 'poll_duplex' and 'poll_unidirectional'. + * + * https://www.ibm.com/support/knowledgecenter/en/ssw_ibm_i_74/apis/shutdn.htm + */ TEST_IMPL(poll_duplex) { #if defined(NO_SELF_CONNECT) RETURN_SKIP(NO_SELF_CONNECT); +#elif defined(__PASE__) + RETURN_SKIP("API shutdown() may lead to timing issue on IBM i PASE"); #endif test_mode = DUPLEX; start_poll_test(); @@ -589,6 +615,8 @@ TEST_IMPL(poll_duplex) { TEST_IMPL(poll_unidirectional) { #if defined(NO_SELF_CONNECT) RETURN_SKIP(NO_SELF_CONNECT); +#elif defined(__PASE__) + RETURN_SKIP("API shutdown() may lead to timing issue on IBM i PASE"); #endif test_mode = UNIDIRECTIONAL; start_poll_test(); diff --git a/test/test-process-priority.c b/test/test-process-priority.c index ebee6b9..b3d0a85 100644 --- a/test/test-process-priority.c +++ b/test/test-process-priority.c @@ -54,8 +54,10 @@ TEST_IMPL(process_priority) { #ifndef _WIN32 ASSERT(priority == i); #else + /* On Windows, only elevated users can set UV_PRIORITY_HIGHEST. Other + users will silently be set to UV_PRIORITY_HIGH. */ if (i < UV_PRIORITY_HIGH) - ASSERT(priority == UV_PRIORITY_HIGHEST); + ASSERT(priority == UV_PRIORITY_HIGHEST || priority == UV_PRIORITY_HIGH); else if (i < UV_PRIORITY_ABOVE_NORMAL) ASSERT(priority == UV_PRIORITY_HIGH); else if (i < UV_PRIORITY_NORMAL) diff --git a/test/test-process-title-threadsafe.c b/test/test-process-title-threadsafe.c index cc3fd41..3b4168b 100644 --- a/test/test-process-title-threadsafe.c +++ b/test/test-process-title-threadsafe.c @@ -26,7 +26,7 @@ #include #ifdef __APPLE__ -# define NUM_ITERATIONS 10 +# define NUM_ITERATIONS 5 #else # define NUM_ITERATIONS 50 #endif @@ -40,14 +40,23 @@ static const char* titles[] = { static void getter_thread_body(void* arg) { char buffer[512]; + size_t len; for (;;) { ASSERT(0 == uv_get_process_title(buffer, sizeof(buffer))); + + /* The maximum size of the process title on some platforms depends on + * the total size of the argv vector. It's therefore possible to read + * back a title that's shorter than what we submitted. + */ + len = strlen(buffer); + ASSERT_GT(len, 0); + ASSERT( - 0 == strcmp(buffer, titles[0]) || - 0 == strcmp(buffer, titles[1]) || - 0 == strcmp(buffer, titles[2]) || - 0 == strcmp(buffer, titles[3])); + 0 == strncmp(buffer, titles[0], len) || + 0 == strncmp(buffer, titles[1], len) || + 0 == strncmp(buffer, titles[2], len) || + 0 == strncmp(buffer, titles[3], len)); uv_sleep(0); } @@ -72,9 +81,9 @@ TEST_IMPL(process_title_threadsafe) { int i; #if defined(__sun) || defined(__CYGWIN__) || defined(__MSYS__) || \ - defined(__MVS__) + defined(__MVS__) || defined(__PASE__) RETURN_SKIP("uv_(get|set)_process_title is not implemented."); -#else +#endif ASSERT(0 == uv_set_process_title(titles[0])); ASSERT(0 == uv_thread_create(&getter_thread, getter_thread_body, NULL)); @@ -86,5 +95,4 @@ TEST_IMPL(process_title_threadsafe) { ASSERT(0 == uv_thread_join(&setter_threads[i])); return 0; -#endif } diff --git a/test/test-process-title.c b/test/test-process-title.c index 886f83a..35a1480 100644 --- a/test/test-process-title.c +++ b/test/test-process-title.c @@ -60,9 +60,11 @@ static void uv_get_process_title_edge_cases(void) { TEST_IMPL(process_title) { -#if defined(__sun) || defined(__CYGWIN__) || defined(__MSYS__) +#if defined(__sun) || defined(__CYGWIN__) || defined(__MSYS__) || \ + defined(__PASE__) RETURN_SKIP("uv_(get|set)_process_title is not implemented."); -#else +#endif + /* Check for format string vulnerabilities. */ set_title("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s"); set_title("new title"); @@ -71,5 +73,63 @@ TEST_IMPL(process_title) { uv_get_process_title_edge_cases(); return 0; +} + + +static void exit_cb(uv_process_t* process, int64_t status, int signo) { + ASSERT(status == 0); + ASSERT(signo == 0); + uv_close((uv_handle_t*) process, NULL); +} + + +TEST_IMPL(process_title_big_argv) { + uv_process_options_t options; + uv_process_t process; + size_t exepath_size; + char exepath[1024]; + char jumbo[512]; + char* args[5]; + +#ifdef _WIN32 + /* Remove once https://github.com/libuv/libuv/issues/2667 is fixed. */ + uv_set_process_title("run-tests"); #endif + + exepath_size = sizeof(exepath) - 1; + ASSERT(0 == uv_exepath(exepath, &exepath_size)); + exepath[exepath_size] = '\0'; + + memset(jumbo, 'x', sizeof(jumbo) - 1); + jumbo[sizeof(jumbo) - 1] = '\0'; + + /* Note: need to pass three arguments, not two, otherwise + * run-tests.c thinks it's the name of a test to run. + */ + args[0] = exepath; + args[1] = "process_title_big_argv_helper"; + args[2] = jumbo; + args[3] = jumbo; + args[4] = NULL; + + memset(&options, 0, sizeof(options)); + options.file = exepath; + options.args = args; + options.exit_cb = exit_cb; + + ASSERT(0 == uv_spawn(uv_default_loop(), &process, &options)); + ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + +/* Called by process_title_big_argv_helper. */ +void process_title_big_argv(void) { + char buf[256] = "fail"; + + /* Return value deliberately ignored. */ + uv_get_process_title(buf, sizeof(buf)); + ASSERT(0 != strcmp(buf, "fail")); } diff --git a/test/test-queue-foreach-delete.c b/test/test-queue-foreach-delete.c index 45da225..049ea77 100644 --- a/test/test-queue-foreach-delete.c +++ b/test/test-queue-foreach-delete.c @@ -65,11 +65,11 @@ static const unsigned first_handle_number_fs_event = 0; #endif -#define DEFINE_GLOBALS_AND_CBS(name) \ +#define DEFINE_GLOBALS_AND_CBS(name, ...) \ static uv_##name##_t (name)[3]; \ static unsigned name##_cb_calls[3]; \ \ - static void name##2_cb(uv_##name##_t* handle) { \ + static void name##2_cb(__VA_ARGS__) { \ ASSERT(handle == &(name)[2]); \ if (first_handle_number_##name == 2) { \ uv_close((uv_handle_t*)&(name)[2], NULL); \ @@ -78,12 +78,12 @@ static const unsigned first_handle_number_fs_event = 0; name##_cb_calls[2]++; \ } \ \ - static void name##1_cb(uv_##name##_t* handle) { \ + static void name##1_cb(__VA_ARGS__) { \ ASSERT(handle == &(name)[1]); \ ASSERT(0 && "Shouldn't be called" && (&name[0])); \ } \ \ - static void name##0_cb(uv_##name##_t* handle) { \ + static void name##0_cb(__VA_ARGS__) { \ ASSERT(handle == &(name)[0]); \ if (first_handle_number_##name == 0) { \ uv_close((uv_handle_t*)&(name)[0], NULL); \ @@ -93,9 +93,9 @@ static const unsigned first_handle_number_fs_event = 0; } \ \ static const uv_##name##_cb name##_cbs[] = { \ - (uv_##name##_cb)name##0_cb, \ - (uv_##name##_cb)name##1_cb, \ - (uv_##name##_cb)name##2_cb, \ + name##0_cb, \ + name##1_cb, \ + name##2_cb, \ }; #define INIT_AND_START(name, loop) \ @@ -118,12 +118,16 @@ static const unsigned first_handle_number_fs_event = 0; ASSERT(name##_cb_calls[2] == 1); \ } while (0) -DEFINE_GLOBALS_AND_CBS(idle) -DEFINE_GLOBALS_AND_CBS(prepare) -DEFINE_GLOBALS_AND_CBS(check) +DEFINE_GLOBALS_AND_CBS(idle, uv_idle_t* handle) +DEFINE_GLOBALS_AND_CBS(prepare, uv_prepare_t* handle) +DEFINE_GLOBALS_AND_CBS(check, uv_check_t* handle) #ifdef __linux__ -DEFINE_GLOBALS_AND_CBS(fs_event) +DEFINE_GLOBALS_AND_CBS(fs_event, + uv_fs_event_t* handle, + const char* filename, + int events, + int status) static const char watched_dir[] = "."; static uv_timer_t timer; diff --git a/test/test-random.c b/test/test-random.c new file mode 100644 index 0000000..2e3ce44 --- /dev/null +++ b/test/test-random.c @@ -0,0 +1,94 @@ +/* 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. + */ + +#include "uv.h" +#include "task.h" + +#include + +static char scratch[256]; +static int random_cb_called; + + +static void random_cb(uv_random_t* req, int status, void* buf, size_t buflen) { + char zero[sizeof(scratch)]; + + memset(zero, 0, sizeof(zero)); + + ASSERT(0 == status); + ASSERT(buf == (void*) scratch); + + if (random_cb_called == 0) { + ASSERT(buflen == 0); + ASSERT(0 == memcmp(scratch, zero, sizeof(zero))); + } else { + ASSERT(buflen == sizeof(scratch)); + /* Buy a lottery ticket if you manage to trip this assertion. */ + ASSERT(0 != memcmp(scratch, zero, sizeof(zero))); + } + + random_cb_called++; +} + + +TEST_IMPL(random_async) { + uv_random_t req; + uv_loop_t* loop; + + loop = uv_default_loop(); + ASSERT(UV_EINVAL == uv_random(loop, &req, scratch, sizeof(scratch), -1, + random_cb)); + ASSERT(UV_E2BIG == uv_random(loop, &req, scratch, -1, -1, random_cb)); + + ASSERT(0 == uv_random(loop, &req, scratch, 0, 0, random_cb)); + ASSERT(0 == random_cb_called); + + ASSERT(0 == uv_run(loop, UV_RUN_DEFAULT)); + ASSERT(1 == random_cb_called); + + ASSERT(0 == uv_random(loop, &req, scratch, sizeof(scratch), 0, random_cb)); + ASSERT(1 == random_cb_called); + + ASSERT(0 == uv_run(loop, UV_RUN_DEFAULT)); + ASSERT(2 == random_cb_called); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + +TEST_IMPL(random_sync) { + char zero[256]; + char buf[256]; + + ASSERT(UV_EINVAL == uv_random(NULL, NULL, buf, sizeof(buf), -1, NULL)); + ASSERT(UV_E2BIG == uv_random(NULL, NULL, buf, -1, -1, NULL)); + + memset(buf, 0, sizeof(buf)); + ASSERT(0 == uv_random(NULL, NULL, buf, sizeof(buf), 0, NULL)); + + /* Buy a lottery ticket if you manage to trip this assertion. */ + memset(zero, 0, sizeof(zero)); + ASSERT(0 != memcmp(buf, zero, sizeof(zero))); + + MAKE_VALGRIND_HAPPY(); + return 0; +} diff --git a/test/test-signal-multiple-loops.c b/test/test-signal-multiple-loops.c index 79242fc..4281d23 100644 --- a/test/test-signal-multiple-loops.c +++ b/test/test-signal-multiple-loops.c @@ -295,4 +295,8 @@ TEST_IMPL(signal_multiple_loops) { return 0; } -#endif /* !_WIN32 */ +#else + +typedef int file_has_no_tests; /* ISO C forbids an empty translation unit. */ + +#endif /* !_WIN32 */ diff --git a/test/test-signal-pending-on-close.c b/test/test-signal-pending-on-close.c new file mode 100644 index 0000000..23b9ec8 --- /dev/null +++ b/test/test-signal-pending-on-close.c @@ -0,0 +1,119 @@ +/* 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. +*/ +#ifndef _WIN32 + +#include "uv.h" +#include "task.h" + +#include +#include + +static uv_loop_t loop; +static uv_signal_t signal_hdl; +static uv_pipe_t pipe_hdl; +static uv_write_t write_req; +static char* buf; +static int close_cb_called; + + +static void stop_loop_cb(uv_signal_t* signal, int signum) { + ASSERT(signum == SIGPIPE); + uv_stop(signal->loop); +} + +static void signal_cb(uv_signal_t* signal, int signum) { + ASSERT(0); +} + +static void close_cb(uv_handle_t *handle) { + close_cb_called++; +} + + +static void write_cb(uv_write_t* req, int status) { + ASSERT(req != NULL); + ASSERT(status == UV_EPIPE); + free(buf); + uv_close((uv_handle_t *) &pipe_hdl, close_cb); + uv_close((uv_handle_t *) &signal_hdl, close_cb); +} + + +TEST_IMPL(signal_pending_on_close) { + int pipefds[2]; + uv_buf_t buffer; + int r; + + ASSERT(0 == uv_loop_init(&loop)); + + ASSERT(0 == uv_signal_init(&loop, &signal_hdl)); + + ASSERT(0 == uv_signal_start(&signal_hdl, signal_cb, SIGPIPE)); + + ASSERT(0 == pipe(pipefds)); + + ASSERT(0 == uv_pipe_init(&loop, &pipe_hdl, 0)); + + ASSERT(0 == uv_pipe_open(&pipe_hdl, pipefds[1])); + + /* Write data large enough so it needs loop iteration */ + buf = malloc(1<<24); + ASSERT(buf != NULL); + memset(buf, '.', 1<<24); + buffer = uv_buf_init(buf, 1<<24); + + r = uv_write(&write_req, (uv_stream_t *) &pipe_hdl, &buffer, 1, write_cb); + ASSERT(0 == r); + + /* cause a SIGPIPE on write in next iteration */ + close(pipefds[0]); + + ASSERT(0 == uv_run(&loop, UV_RUN_DEFAULT)); + + ASSERT(0 == uv_loop_close(&loop)); + + ASSERT(2 == close_cb_called); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + +TEST_IMPL(signal_close_loop_alive) { + ASSERT(0 == uv_loop_init(&loop)); + ASSERT(0 == uv_signal_init(&loop, &signal_hdl)); + ASSERT(0 == uv_signal_start(&signal_hdl, stop_loop_cb, SIGPIPE)); + uv_unref((uv_handle_t*) &signal_hdl); + + ASSERT(0 == uv_kill(uv_os_getpid(), SIGPIPE)); + ASSERT(0 == uv_run(&loop, UV_RUN_DEFAULT)); + uv_close((uv_handle_t*) &signal_hdl, close_cb); + ASSERT(1 == uv_loop_alive(&loop)); + + ASSERT(0 == uv_run(&loop, UV_RUN_DEFAULT)); + ASSERT(0 == uv_loop_close(&loop)); + ASSERT(1 == close_cb_called); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +#endif diff --git a/test/test-spawn.c b/test/test-spawn.c index 4fcd905..5e1c6d6 100644 --- a/test/test-spawn.c +++ b/test/test-spawn.c @@ -49,7 +49,9 @@ static char exepath[1024]; static size_t exepath_size = 1024; static char* args[5]; static int no_term_signal; +#ifndef _WIN32 static int timer_counter; +#endif static uv_tcp_t tcp_server; #define OUTPUT_SIZE 1024 @@ -138,10 +140,12 @@ static void on_read(uv_stream_t* tcp, ssize_t nread, const uv_buf_t* buf) { } +#ifndef _WIN32 static void on_read_once(uv_stream_t* tcp, ssize_t nread, const uv_buf_t* buf) { uv_read_stop(tcp); on_read(tcp, nread, buf); } +#endif static void write_cb(uv_write_t* req, int status) { @@ -173,9 +177,11 @@ static void timer_cb(uv_timer_t* handle) { } +#ifndef _WIN32 static void timer_counter_cb(uv_timer_t* handle) { ++timer_counter; } +#endif TEST_IMPL(spawn_fails) { @@ -226,6 +232,35 @@ TEST_IMPL(spawn_fails_check_for_waitpid_cleanup) { #endif +TEST_IMPL(spawn_empty_env) { + char* env[1]; + + /* The autotools dynamic library build requires the presence of + * DYLD_LIBARY_PATH (macOS) or LD_LIBRARY_PATH/LIBPATH (other Unices) + * in the environment, but of course that doesn't work with + * the empty environment that we're testing here. + */ + if (NULL != getenv("DYLD_LIBRARY_PATH") || + NULL != getenv("LD_LIBRARY_PATH") || + NULL != getenv("LIBPATH")) { + RETURN_SKIP("doesn't work with DYLD_LIBRARY_PATH/LD_LIBRARY_PATH/LIBPATH"); + } + + init_process_options("spawn_helper1", exit_cb); + options.env = env; + env[0] = NULL; + + ASSERT(0 == uv_spawn(uv_default_loop(), &process, &options)); + ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + + ASSERT(exit_cb_called == 1); + ASSERT(close_cb_called == 1); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + TEST_IMPL(spawn_exit_code) { int r; @@ -1172,6 +1207,7 @@ TEST_IMPL(argument_escaping) { for (i = 0; i < count; ++i) { free(test_output[i]); } + free(test_output); result = make_program_args(verbatim, 1, &verbatim_output); ASSERT(result == 0); @@ -1197,7 +1233,7 @@ TEST_IMPL(argument_escaping) { int make_program_env(char** env_block, WCHAR** dst_ptr); TEST_IMPL(environment_creation) { - int i; + size_t i; char* environment[] = { "FOO=BAR", "SYSTEM=ROOT", /* substring of a supplied var name */ @@ -1385,6 +1421,8 @@ TEST_IMPL(spawn_setuid_fails) { int r; /* if root, become nobody. */ + /* On IBMi PASE, there is no nobody user. */ +#ifndef __PASE__ uv_uid_t uid = getuid(); if (uid == 0) { struct passwd* pw; @@ -1393,11 +1431,25 @@ TEST_IMPL(spawn_setuid_fails) { ASSERT(0 == setgid(pw->pw_gid)); ASSERT(0 == setuid(pw->pw_uid)); } +#endif /* !__PASE__ */ init_process_options("spawn_helper1", fail_cb); options.flags |= UV_PROCESS_SETUID; + /* On IBMi PASE, there is no root user. User may grant + * root-like privileges, including setting uid to 0. + */ +#if defined(__PASE__) + options.uid = -1; +#else options.uid = 0; +#endif + + /* These flags should be ignored on Unices. */ + options.flags |= UV_PROCESS_WINDOWS_HIDE; + options.flags |= UV_PROCESS_WINDOWS_HIDE_CONSOLE; + options.flags |= UV_PROCESS_WINDOWS_HIDE_GUI; + options.flags |= UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS; r = uv_spawn(uv_default_loop(), &process, &options); #if defined(__CYGWIN__) @@ -1420,6 +1472,8 @@ TEST_IMPL(spawn_setgid_fails) { int r; /* if root, become nobody. */ + /* On IBMi PASE, there is no nobody user. */ +#ifndef __PASE__ uv_uid_t uid = getuid(); if (uid == 0) { struct passwd* pw; @@ -1428,11 +1482,15 @@ TEST_IMPL(spawn_setgid_fails) { ASSERT(0 == setgid(pw->pw_gid)); ASSERT(0 == setuid(pw->pw_uid)); } +#endif /* !__PASE__ */ init_process_options("spawn_helper1", fail_cb); options.flags |= UV_PROCESS_SETGID; -#if defined(__MVS__) + /* On IBMi PASE, there is no root user. User may grant + * root-like privileges, including setting gid to 0. + */ +#if defined(__MVS__) || defined(__PASE__) options.gid = -1; #else options.gid = 0; @@ -1631,7 +1689,7 @@ TEST_IMPL(spawn_reads_child_path) { */ #if defined(__APPLE__) static const char dyld_path_var[] = "DYLD_LIBRARY_PATH"; -#elif defined __MVS__ +#elif defined(__MVS__) || defined(__PASE__) static const char dyld_path_var[] = "LIBPATH"; #else static const char dyld_path_var[] = "LD_LIBRARY_PATH"; @@ -1831,7 +1889,7 @@ TEST_IMPL(spawn_quoted_path) { /* Helper for child process of spawn_inherit_streams */ #ifndef _WIN32 -int spawn_stdin_stdout(void) { +void spawn_stdin_stdout(void) { char buf[1024]; char* pbuf; for (;;) { @@ -1840,7 +1898,7 @@ int spawn_stdin_stdout(void) { r = read(0, buf, sizeof buf); } while (r == -1 && errno == EINTR); if (r == 0) { - return 1; + return; } ASSERT(r > 0); c = r; @@ -1854,10 +1912,9 @@ int spawn_stdin_stdout(void) { c = c - w; } } - return 2; } #else -int spawn_stdin_stdout(void) { +void spawn_stdin_stdout(void) { char buf[1024]; char* pbuf; HANDLE h_stdin = GetStdHandle(STD_INPUT_HANDLE); @@ -1870,7 +1927,7 @@ int spawn_stdin_stdout(void) { DWORD to_write; if (!ReadFile(h_stdin, buf, sizeof buf, &n_read, NULL)) { ASSERT(GetLastError() == ERROR_BROKEN_PIPE); - return 1; + return; } to_write = n_read; pbuf = buf; @@ -1880,6 +1937,5 @@ int spawn_stdin_stdout(void) { pbuf += n_written; } } - return 2; } #endif /* !_WIN32 */ diff --git a/test/test-stdio-over-pipes.c b/test/test-stdio-over-pipes.c index 1574476..1aed471 100644 --- a/test/test-stdio-over-pipes.c +++ b/test/test-stdio-over-pipes.c @@ -94,7 +94,7 @@ static void after_write(uv_write_t* req, int status) { } -static void on_read(uv_stream_t* tcp, ssize_t nread, const uv_buf_t* rdbuf) { +static void on_read(uv_stream_t* pipe, ssize_t nread, const uv_buf_t* rdbuf) { uv_write_t* req; uv_buf_t wrbuf; int r; @@ -103,11 +103,11 @@ static void on_read(uv_stream_t* tcp, ssize_t nread, const uv_buf_t* rdbuf) { if (nread > 0) { output_used += nread; - if (output_used == 12) { + if (output_used % 12 == 0) { ASSERT(memcmp("hello world\n", output, 12) == 0); - wrbuf = uv_buf_init(output, output_used); + wrbuf = uv_buf_init(output, 12); req = malloc(sizeof(*req)); - r = uv_write(req, (uv_stream_t*)&in, &wrbuf, 1, after_write); + r = uv_write(req, (uv_stream_t*) &in, &wrbuf, 1, after_write); ASSERT(r == 0); } } @@ -116,10 +116,10 @@ static void on_read(uv_stream_t* tcp, ssize_t nread, const uv_buf_t* rdbuf) { } -TEST_IMPL(stdio_over_pipes) { +static void test_stdio_over_pipes(int overlapped) { int r; uv_process_t process; - uv_stdio_container_t stdio[2]; + uv_stdio_container_t stdio[3]; loop = uv_default_loop(); @@ -129,11 +129,15 @@ TEST_IMPL(stdio_over_pipes) { uv_pipe_init(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; + options.stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE | + (overlapped ? UV_OVERLAPPED_PIPE : 0); + options.stdio[0].data.stream = (uv_stream_t*) ∈ + options.stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE | + (overlapped ? UV_OVERLAPPED_PIPE : 0); + options.stdio[1].data.stream = (uv_stream_t*) &out; + options.stdio[2].flags = UV_INHERIT_FD; + options.stdio[2].data.fd = 2; + options.stdio_count = 3; r = uv_spawn(loop, &process, &options); ASSERT(r == 0); @@ -145,13 +149,22 @@ TEST_IMPL(stdio_over_pipes) { ASSERT(r == 0); ASSERT(on_read_cb_called > 1); - ASSERT(after_write_cb_called == 1); + ASSERT(after_write_cb_called == 2); ASSERT(exit_cb_called == 1); ASSERT(close_cb_called == 3); - ASSERT(memcmp("hello world\n", output, 12) == 0); - ASSERT(output_used == 12); + ASSERT(memcmp("hello world\nhello world\n", output, 24) == 0); + ASSERT(output_used == 24); MAKE_VALGRIND_HAPPY(); +} + +TEST_IMPL(stdio_over_pipes) { + test_stdio_over_pipes(0); + return 0; +} + +TEST_IMPL(stdio_emulate_iocp) { + test_stdio_over_pipes(1); return 0; } @@ -160,18 +173,19 @@ TEST_IMPL(stdio_over_pipes) { static int on_pipe_read_called; static int after_write_called; -static uv_pipe_t stdin_pipe; -static uv_pipe_t stdout_pipe; +static uv_pipe_t stdin_pipe1; +static uv_pipe_t stdout_pipe1; +static uv_pipe_t stdin_pipe2; +static uv_pipe_t stdout_pipe2; -static void on_pipe_read(uv_stream_t* tcp, ssize_t nread, const uv_buf_t* buf) { +static void on_pipe_read(uv_stream_t* pipe, ssize_t nread, const uv_buf_t* buf) { ASSERT(nread > 0); ASSERT(memcmp("hello world\n", buf->base, nread) == 0); on_pipe_read_called++; free(buf->base); - uv_close((uv_handle_t*)&stdin_pipe, close_cb); - uv_close((uv_handle_t*)&stdout_pipe, close_cb); + uv_read_stop(pipe); } @@ -204,51 +218,81 @@ int stdio_over_pipes_helper(void) { uv_write_t write_req[ARRAY_SIZE(buffers)]; uv_buf_t buf[ARRAY_SIZE(buffers)]; unsigned int i; + int j; int r; uv_loop_t* loop = uv_default_loop(); ASSERT(UV_NAMED_PIPE == uv_guess_handle(0)); ASSERT(UV_NAMED_PIPE == uv_guess_handle(1)); - r = uv_pipe_init(loop, &stdin_pipe, 0); + r = uv_pipe_init(loop, &stdin_pipe1, 0); + ASSERT(r == 0); + r = uv_pipe_init(loop, &stdout_pipe1, 0); + ASSERT(r == 0); + r = uv_pipe_init(loop, &stdin_pipe2, 0); ASSERT(r == 0); - r = uv_pipe_init(loop, &stdout_pipe, 0); + r = uv_pipe_init(loop, &stdout_pipe2, 0); ASSERT(r == 0); - uv_pipe_open(&stdin_pipe, 0); - uv_pipe_open(&stdout_pipe, 1); + uv_pipe_open(&stdin_pipe1, 0); + uv_pipe_open(&stdout_pipe1, 1); + uv_pipe_open(&stdin_pipe2, 0); + uv_pipe_open(&stdout_pipe2, 1); - /* Unref both stdio handles to make sure that all writes complete. */ - uv_unref((uv_handle_t*)&stdin_pipe); - uv_unref((uv_handle_t*)&stdout_pipe); + for (j = 0; j < 2; j++) { + /* Unref both stdio handles to make sure that all writes complete. */ + uv_unref((uv_handle_t*) &stdin_pipe1); + uv_unref((uv_handle_t*) &stdout_pipe1); + uv_unref((uv_handle_t*) &stdin_pipe2); + uv_unref((uv_handle_t*) &stdout_pipe2); - for (i = 0; i < ARRAY_SIZE(buffers); i++) { - buf[i] = uv_buf_init((char*)buffers[i], strlen(buffers[i])); - } + for (i = 0; i < ARRAY_SIZE(buffers); i++) { + buf[i] = uv_buf_init((char*) buffers[i], strlen(buffers[i])); + } - for (i = 0; i < ARRAY_SIZE(buffers); i++) { - r = uv_write(&write_req[i], (uv_stream_t*)&stdout_pipe, &buf[i], 1, - after_pipe_write); - ASSERT(r == 0); - } + for (i = 0; i < ARRAY_SIZE(buffers); i++) { + r = uv_write(&write_req[i], + (uv_stream_t*) (j == 0 ? &stdout_pipe1 : &stdout_pipe2), + &buf[i], + 1, + after_pipe_write); + ASSERT(r == 0); + } - uv_run(loop, UV_RUN_DEFAULT); + notify_parent_process(); + uv_run(loop, UV_RUN_DEFAULT); - ASSERT(after_write_called == 7); - ASSERT(on_pipe_read_called == 0); - ASSERT(close_cb_called == 0); + ASSERT(after_write_called == 7 * (j + 1)); + ASSERT(on_pipe_read_called == j); + ASSERT(close_cb_called == 0); - uv_ref((uv_handle_t*)&stdout_pipe); - uv_ref((uv_handle_t*)&stdin_pipe); + uv_ref((uv_handle_t*) &stdout_pipe1); + uv_ref((uv_handle_t*) &stdin_pipe1); + uv_ref((uv_handle_t*) &stdout_pipe2); + uv_ref((uv_handle_t*) &stdin_pipe2); - r = uv_read_start((uv_stream_t*)&stdin_pipe, on_read_alloc, on_pipe_read); - ASSERT(r == 0); + r = uv_read_start((uv_stream_t*) (j == 0 ? &stdin_pipe1 : &stdin_pipe2), + on_read_alloc, + on_pipe_read); + ASSERT(r == 0); + + uv_run(loop, UV_RUN_DEFAULT); + + ASSERT(after_write_called == 7 * (j + 1)); + ASSERT(on_pipe_read_called == j + 1); + ASSERT(close_cb_called == 0); + } + + uv_close((uv_handle_t*)&stdin_pipe1, close_cb); + uv_close((uv_handle_t*)&stdout_pipe1, close_cb); + uv_close((uv_handle_t*)&stdin_pipe2, close_cb); + uv_close((uv_handle_t*)&stdout_pipe2, close_cb); uv_run(loop, UV_RUN_DEFAULT); - ASSERT(after_write_called == 7); - ASSERT(on_pipe_read_called == 1); - ASSERT(close_cb_called == 2); + ASSERT(after_write_called == 14); + ASSERT(on_pipe_read_called == 2); + ASSERT(close_cb_called == 4); MAKE_VALGRIND_HAPPY(); return 0; diff --git a/test/test-strscpy.c b/test/test-strscpy.c new file mode 100644 index 0000000..4e7db6f --- /dev/null +++ b/test/test-strscpy.c @@ -0,0 +1,53 @@ +/* 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 + +#include "../src/strscpy.h" +#include "../src/strscpy.c" + +TEST_IMPL(strscpy) { + char d[4]; + + ASSERT(0 == uv__strscpy(d, "", 0)); + ASSERT(0 == uv__strscpy(d, "x", 0)); + + memset(d, 0, sizeof(d)); + ASSERT(1 == uv__strscpy(d, "x", sizeof(d))); + ASSERT(0 == memcmp(d, "x\0\0", sizeof(d))); + + memset(d, 0, sizeof(d)); + ASSERT(2 == uv__strscpy(d, "xy", sizeof(d))); + ASSERT(0 == memcmp(d, "xy\0", sizeof(d))); + + ASSERT(3 == uv__strscpy(d, "xyz", sizeof(d))); + ASSERT(0 == memcmp(d, "xyz", sizeof(d))); + + ASSERT(UV_E2BIG == uv__strscpy(d, "xyzz", sizeof(d))); + ASSERT(0 == memcmp(d, "xyz", sizeof(d))); + + ASSERT(UV_E2BIG == uv__strscpy(d, "xyzzy", sizeof(d))); + ASSERT(0 == memcmp(d, "xyz", sizeof(d))); + + return 0; +} diff --git a/test/test-tcp-bind-error.c b/test/test-tcp-bind-error.c index 1456d08..f95efd9 100644 --- a/test/test-tcp-bind-error.c +++ b/test/test-tcp-bind-error.c @@ -239,11 +239,7 @@ TEST_IMPL(tcp_bind_writable_flags) { r = uv_write(&write_req, (uv_stream_t*) &server, &buf, 1, NULL); ASSERT(r == UV_EPIPE); r = uv_shutdown(&shutdown_req, (uv_stream_t*) &server, NULL); -#ifdef _WIN32 - ASSERT(r == UV_EPIPE); -#else ASSERT(r == UV_ENOTCONN); -#endif r = uv_read_start((uv_stream_t*) &server, NULL, NULL); ASSERT(r == UV_ENOTCONN); diff --git a/test/test-tcp-close-accept.c b/test/test-tcp-close-accept.c index e487839..624262b 100644 --- a/test/test-tcp-close-accept.c +++ b/test/test-tcp-close-accept.c @@ -191,4 +191,8 @@ TEST_IMPL(tcp_close_accept) { return 0; } -#endif /* !_WIN32 */ +#else + +typedef int file_has_no_tests; /* ISO C forbids an empty translation unit. */ + +#endif /* !_WIN32 */ diff --git a/test/test-tcp-close-reset.c b/test/test-tcp-close-reset.c new file mode 100644 index 0000000..7ca55c4 --- /dev/null +++ b/test/test-tcp-close-reset.c @@ -0,0 +1,290 @@ +/* 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 +#include /* memset */ + +static uv_loop_t* loop; +static uv_tcp_t tcp_server; +static uv_tcp_t tcp_client; +static uv_tcp_t tcp_accepted; +static uv_connect_t connect_req; +static uv_shutdown_t shutdown_req; +static uv_write_t write_reqs[4]; + +static int client_close; +static int shutdown_before_close; + +static int write_cb_called; +static int close_cb_called; +static int shutdown_cb_called; + +static void connect_cb(uv_connect_t* req, int status); +static void write_cb(uv_write_t* req, int status); +static void close_cb(uv_handle_t* handle); +static void shutdown_cb(uv_shutdown_t* req, int status); + +static int read_size; + + +static void do_write(uv_tcp_t* handle) { + uv_buf_t buf; + unsigned i; + int r; + + buf = uv_buf_init("PING", 4); + for (i = 0; i < ARRAY_SIZE(write_reqs); i++) { + r = uv_write(&write_reqs[i], (uv_stream_t*) handle, &buf, 1, write_cb); + ASSERT(r == 0); + } +} + + +static void do_close(uv_tcp_t* handle) { + if (shutdown_before_close == 1) { + ASSERT(0 == uv_shutdown(&shutdown_req, (uv_stream_t*) handle, shutdown_cb)); + ASSERT(UV_EINVAL == uv_tcp_close_reset(handle, close_cb)); + } else { + ASSERT(0 == uv_tcp_close_reset(handle, close_cb)); + ASSERT(UV_ENOTCONN == uv_shutdown(&shutdown_req, (uv_stream_t*) handle, shutdown_cb)); + } + + uv_close((uv_handle_t*) &tcp_server, NULL); +} + +static void alloc_cb(uv_handle_t* handle, size_t size, uv_buf_t* buf) { + static char slab[1024]; + buf->base = slab; + buf->len = sizeof(slab); +} + +static void read_cb2(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { + ASSERT((uv_tcp_t*)stream == &tcp_client); + if (nread == UV_EOF) + uv_close((uv_handle_t*) stream, NULL); +} + + +static void connect_cb(uv_connect_t* conn_req, int status) { + ASSERT(conn_req == &connect_req); + uv_read_start((uv_stream_t*) &tcp_client, alloc_cb, read_cb2); + do_write(&tcp_client); + if (client_close) + do_close(&tcp_client); +} + + +static void write_cb(uv_write_t* req, int status) { + /* write callbacks should run before the close callback */ + ASSERT(close_cb_called == 0); + ASSERT(req->handle == (uv_stream_t*)&tcp_client); + write_cb_called++; +} + + +static void close_cb(uv_handle_t* handle) { + if (client_close) + ASSERT(handle == (uv_handle_t*) &tcp_client); + else + ASSERT(handle == (uv_handle_t*) &tcp_accepted); + + close_cb_called++; +} + +static void shutdown_cb(uv_shutdown_t* req, int status) { + if (client_close) + ASSERT(req->handle == (uv_stream_t*) &tcp_client); + else + ASSERT(req->handle == (uv_stream_t*) &tcp_accepted); + + shutdown_cb_called++; +} + + +static void read_cb(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { + ASSERT((uv_tcp_t*)stream == &tcp_accepted); + if (nread < 0) { + uv_close((uv_handle_t*) stream, NULL); + } else { + read_size += nread; + if (read_size == 16 && client_close == 0) + do_close(&tcp_accepted); + } +} + + +static void connection_cb(uv_stream_t* server, int status) { + ASSERT(status == 0); + + ASSERT(0 == uv_tcp_init(loop, &tcp_accepted)); + ASSERT(0 == uv_accept(server, (uv_stream_t*) &tcp_accepted)); + + uv_read_start((uv_stream_t*) &tcp_accepted, alloc_cb, read_cb); +} + + +static void start_server(uv_loop_t* loop, uv_tcp_t* handle) { + struct sockaddr_in addr; + int r; + + ASSERT(0 == uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); + + r = uv_tcp_init(loop, handle); + ASSERT(r == 0); + + r = uv_tcp_bind(handle, (const struct sockaddr*) &addr, 0); + ASSERT(r == 0); + + r = uv_listen((uv_stream_t*)handle, 128, connection_cb); + ASSERT(r == 0); +} + + +static void do_connect(uv_loop_t* loop, uv_tcp_t* tcp_client) { + struct sockaddr_in addr; + int r; + + ASSERT(0 == uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); + + r = uv_tcp_init(loop, tcp_client); + ASSERT(r == 0); + + r = uv_tcp_connect(&connect_req, + tcp_client, + (const struct sockaddr*) &addr, + connect_cb); + ASSERT(r == 0); +} + + +/* Check that pending write requests have their callbacks + * invoked when the handle is closed. + */ +TEST_IMPL(tcp_close_reset_client) { + int r; + + loop = uv_default_loop(); + + start_server(loop, &tcp_server); + + client_close = 1; + shutdown_before_close = 0; + + do_connect(loop, &tcp_client); + + ASSERT(write_cb_called == 0); + ASSERT(close_cb_called == 0); + ASSERT(shutdown_cb_called == 0); + + r = uv_run(loop, UV_RUN_DEFAULT); + ASSERT(r == 0); + + ASSERT(write_cb_called == 4); + ASSERT(close_cb_called == 1); + ASSERT(shutdown_cb_called == 0); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +TEST_IMPL(tcp_close_reset_client_after_shutdown) { + int r; + + loop = uv_default_loop(); + + start_server(loop, &tcp_server); + + client_close = 1; + shutdown_before_close = 1; + + do_connect(loop, &tcp_client); + + ASSERT(write_cb_called == 0); + ASSERT(close_cb_called == 0); + ASSERT(shutdown_cb_called == 0); + + r = uv_run(loop, UV_RUN_DEFAULT); + ASSERT(r == 0); + + ASSERT(write_cb_called == 4); + ASSERT(close_cb_called == 0); + ASSERT(shutdown_cb_called == 1); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +TEST_IMPL(tcp_close_reset_accepted) { + int r; + + loop = uv_default_loop(); + + start_server(loop, &tcp_server); + + client_close = 0; + shutdown_before_close = 0; + + do_connect(loop, &tcp_client); + + ASSERT(write_cb_called == 0); + ASSERT(close_cb_called == 0); + ASSERT(shutdown_cb_called == 0); + + r = uv_run(loop, UV_RUN_DEFAULT); + ASSERT(r == 0); + + ASSERT(write_cb_called == 4); + ASSERT(close_cb_called == 1); + ASSERT(shutdown_cb_called == 0); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +TEST_IMPL(tcp_close_reset_accepted_after_shutdown) { + int r; + + loop = uv_default_loop(); + + start_server(loop, &tcp_server); + + client_close = 0; + shutdown_before_close = 1; + + do_connect(loop, &tcp_client); + + ASSERT(write_cb_called == 0); + ASSERT(close_cb_called == 0); + ASSERT(shutdown_cb_called == 0); + + r = uv_run(loop, UV_RUN_DEFAULT); + ASSERT(r == 0); + + ASSERT(write_cb_called == 4); + ASSERT(close_cb_called == 0); + ASSERT(shutdown_cb_called == 1); + + MAKE_VALGRIND_HAPPY(); + return 0; +} diff --git a/test/test-tcp-oob.c b/test/test-tcp-oob.c index ca2361f..53f8231 100644 --- a/test/test-tcp-oob.c +++ b/test/test-tcp-oob.c @@ -138,4 +138,9 @@ TEST_IMPL(tcp_oob) { MAKE_VALGRIND_HAPPY(); return 0; } -#endif + +#else + +typedef int file_has_no_tests; /* ISO C forbids an empty translation unit. */ + +#endif /* !_WIN32 */ diff --git a/test/test-tcp-try-write-error.c b/test/test-tcp-try-write-error.c new file mode 100644 index 0000000..2201d0e --- /dev/null +++ b/test/test-tcp-try-write-error.c @@ -0,0 +1,109 @@ +/* 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. + */ + +#include "uv.h" +#include "task.h" + +#include +#include +#include + +static uv_tcp_t server; +static uv_tcp_t client; +static uv_tcp_t incoming; +static int connect_cb_called; +static int close_cb_called; +static int connection_cb_called; + + +static void close_cb(uv_handle_t* handle) { + close_cb_called++; +} + +static void incoming_close_cb(uv_handle_t* handle) { + uv_buf_t buf; + int r = 1; + + close_cb_called++; + + buf = uv_buf_init("meow", 4); + while (r > 0) + r = uv_try_write((uv_stream_t*) &client, &buf, 1); + fprintf(stderr, "uv_try_write error: %d %s\n", r, uv_strerror(r)); + ASSERT(r == UV_EPIPE || r == UV_ECONNABORTED || r == UV_ECONNRESET); + ASSERT(client.write_queue_size == 0); +} + + +static void connect_cb(uv_connect_t* req, int status) { + ASSERT(status == 0); + connect_cb_called++; +} + + +static void connection_cb(uv_stream_t* tcp, int status) { + ASSERT(status == 0); + + ASSERT(0 == uv_tcp_init(tcp->loop, &incoming)); + ASSERT(0 == uv_accept(tcp, (uv_stream_t*) &incoming)); + + connection_cb_called++; + uv_close((uv_handle_t*) &incoming, incoming_close_cb); + uv_close((uv_handle_t*) tcp, close_cb); +} + + +static void start_server(void) { + struct sockaddr_in addr; + + ASSERT(0 == uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); + + ASSERT(0 == uv_tcp_init(uv_default_loop(), &server)); + ASSERT(0 == uv_tcp_bind(&server, (struct sockaddr*) &addr, 0)); + ASSERT(0 == uv_listen((uv_stream_t*) &server, 128, connection_cb)); +} + + +TEST_IMPL(tcp_try_write_error) { + uv_connect_t connect_req; + struct sockaddr_in addr; + + start_server(); + + ASSERT(0 == uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); + + ASSERT(0 == uv_tcp_init(uv_default_loop(), &client)); + ASSERT(0 == uv_tcp_connect(&connect_req, + &client, + (struct sockaddr*) &addr, + connect_cb)); + + ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + uv_close((uv_handle_t*) &client, close_cb); + ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + + ASSERT(connect_cb_called == 1); + ASSERT(close_cb_called == 3); + ASSERT(connection_cb_called == 1); + + MAKE_VALGRIND_HAPPY(); + return 0; +} diff --git a/test/test-tcp-write-after-connect.c b/test/test-tcp-write-after-connect.c index aa03228..8a698f4 100644 --- a/test/test-tcp-write-after-connect.c +++ b/test/test-tcp-write-after-connect.c @@ -43,6 +43,11 @@ static void connect_cb(uv_connect_t *req, int status) { TEST_IMPL(tcp_write_after_connect) { +/* TODO(gengjiawen): Fix test on QEMU. */ +#if defined(__QEMU__) + RETURN_SKIP("Test does not currently work in QEMU"); +#endif + struct sockaddr_in sa; ASSERT(0 == uv_ip4_addr("127.0.0.1", TEST_PORT, &sa)); ASSERT(0 == uv_loop_init(&loop)); @@ -65,4 +70,8 @@ TEST_IMPL(tcp_write_after_connect) { return 0; } -#endif +#else + +typedef int file_has_no_tests; /* ISO C forbids an empty translation unit. */ + +#endif /* !_WIN32 */ diff --git a/test/test-thread.c b/test/test-thread.c index 955c9f2..f53bce0 100644 --- a/test/test-thread.c +++ b/test/test-thread.c @@ -26,6 +26,10 @@ #include #include /* memset */ +#ifdef __POSIX__ +#include +#endif + struct getaddrinfo_req { uv_thread_t thread_id; unsigned int counter; @@ -74,6 +78,10 @@ static void getaddrinfo_do(struct getaddrinfo_req* req) { static void getaddrinfo_cb(uv_getaddrinfo_t* handle, int status, struct addrinfo* res) { +/* TODO(gengjiawen): Fix test on QEMU. */ +#if defined(__QEMU__) + RETURN_SKIP("Test does not currently work in QEMU"); +#endif struct getaddrinfo_req* req; ASSERT(status == 0); @@ -207,10 +215,15 @@ TEST_IMPL(thread_local_storage) { static void thread_check_stack(void* arg) { #if defined(__APPLE__) + size_t expected; + expected = arg == NULL ? 0 : ((uv_thread_options_t*)arg)->stack_size; /* 512 kB is the default stack size of threads other than the main thread * on MacOS. */ - ASSERT(pthread_get_stacksize_np(pthread_self()) > 512*1024); + if (expected == 0) + expected = 512 * 1024; + ASSERT(pthread_get_stacksize_np(pthread_self()) >= expected); #elif defined(__linux__) && defined(__GLIBC__) + size_t expected; struct rlimit lim; size_t stack_size; pthread_attr_t attr; @@ -219,7 +232,11 @@ static void thread_check_stack(void* arg) { lim.rlim_cur = 2 << 20; /* glibc default. */ ASSERT(0 == pthread_getattr_np(pthread_self(), &attr)); ASSERT(0 == pthread_attr_getstacksize(&attr, &stack_size)); - ASSERT(stack_size >= lim.rlim_cur); + expected = arg == NULL ? 0 : ((uv_thread_options_t*)arg)->stack_size; + if (expected == 0) + expected = (size_t)lim.rlim_cur; + ASSERT(stack_size >= expected); + ASSERT(0 == pthread_attr_destroy(&attr)); #endif } @@ -230,3 +247,44 @@ TEST_IMPL(thread_stack_size) { ASSERT(0 == uv_thread_join(&thread)); return 0; } + +TEST_IMPL(thread_stack_size_explicit) { + uv_thread_t thread; + uv_thread_options_t options; + + options.flags = UV_THREAD_HAS_STACK_SIZE; + options.stack_size = 1024 * 1024; + ASSERT(0 == uv_thread_create_ex(&thread, &options, + thread_check_stack, &options)); + ASSERT(0 == uv_thread_join(&thread)); + + options.stack_size = 8 * 1024 * 1024; /* larger than most default os sizes */ + ASSERT(0 == uv_thread_create_ex(&thread, &options, + thread_check_stack, &options)); + ASSERT(0 == uv_thread_join(&thread)); + + options.stack_size = 0; + ASSERT(0 == uv_thread_create_ex(&thread, &options, + thread_check_stack, &options)); + ASSERT(0 == uv_thread_join(&thread)); + +#ifdef PTHREAD_STACK_MIN + options.stack_size = PTHREAD_STACK_MIN - 42; /* unaligned size */ + ASSERT(0 == uv_thread_create_ex(&thread, &options, + thread_check_stack, &options)); + ASSERT(0 == uv_thread_join(&thread)); + + options.stack_size = PTHREAD_STACK_MIN / 2 - 42; /* unaligned size */ + ASSERT(0 == uv_thread_create_ex(&thread, &options, + thread_check_stack, &options)); + ASSERT(0 == uv_thread_join(&thread)); +#endif + + /* unaligned size, should be larger than PTHREAD_STACK_MIN */ + options.stack_size = 1234567; + ASSERT(0 == uv_thread_create_ex(&thread, &options, + thread_check_stack, &options)); + ASSERT(0 == uv_thread_join(&thread)); + + return 0; +} diff --git a/test/test-threadpool-cancel.c b/test/test-threadpool-cancel.c index dd13d8a..be252c6 100644 --- a/test/test-threadpool-cancel.c +++ b/test/test-threadpool-cancel.c @@ -37,6 +37,11 @@ struct cancel_info { uv_timer_t timer_handle; }; +struct random_info { + uv_random_t random_req; + char buf[1]; +}; + static unsigned fs_cb_called; static unsigned done_cb_called; static unsigned done2_cb_called; @@ -143,6 +148,19 @@ static void nop_done_cb(uv_work_t* req, int status) { } +static void nop_random_cb(uv_random_t* req, int status, void* buf, size_t len) { + struct random_info* ri; + + ri = container_of(req, struct random_info, random_req); + + ASSERT(status == UV_ECANCELED); + ASSERT(buf == (void*) ri->buf); + ASSERT(len == sizeof(ri->buf)); + + done_cb_called++; +} + + TEST_IMPL(threadpool_cancel_getaddrinfo) { uv_getaddrinfo_t reqs[4]; struct cancel_info ci; @@ -212,6 +230,29 @@ TEST_IMPL(threadpool_cancel_getnameinfo) { } +TEST_IMPL(threadpool_cancel_random) { + struct random_info req; + uv_loop_t* loop; + + saturate_threadpool(); + loop = uv_default_loop(); + ASSERT(0 == uv_random(loop, + &req.random_req, + &req.buf, + sizeof(req.buf), + 0, + nop_random_cb)); + ASSERT(0 == uv_cancel((uv_req_t*) &req)); + ASSERT(0 == done_cb_called); + unblock_threadpool(); + ASSERT(0 == uv_run(loop, UV_RUN_DEFAULT)); + ASSERT(1 == done_cb_called); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + TEST_IMPL(threadpool_cancel_work) { struct cancel_info ci; uv_work_t reqs[16]; diff --git a/test/test-timer.c b/test/test-timer.c index 080a730..c667da0 100644 --- a/test/test-timer.c +++ b/test/test-timer.c @@ -292,6 +292,19 @@ TEST_IMPL(timer_run_once) { } +TEST_IMPL(timer_is_closing) { + uv_timer_t handle; + + ASSERT(0 == uv_timer_init(uv_default_loop(), &handle)); + uv_close((uv_handle_t *)&handle, NULL); + + ASSERT(UV_EINVAL == uv_timer_start(&handle, never_cb, 100, 100)); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + TEST_IMPL(timer_null_callback) { uv_timer_t handle; diff --git a/test/test-tmpdir.c b/test/test-tmpdir.c index 29e8055..86f72e2 100644 --- a/test/test-tmpdir.c +++ b/test/test-tmpdir.c @@ -23,7 +23,7 @@ #include "task.h" #include -#define PATHMAX 1024 +#define PATHMAX 4096 #define SMALLPATH 1 TEST_IMPL(tmpdir) { @@ -67,5 +67,16 @@ TEST_IMPL(tmpdir) { r = uv_os_tmpdir(tmpdir, &len); ASSERT(r == UV_EINVAL); +#ifdef _WIN32 + const char *name = "TMP"; + char tmpdir_win[] = "C:\\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + r = uv_os_setenv(name, tmpdir_win); + ASSERT(r == 0); + char tmpdirx[PATHMAX]; + size_t lenx = sizeof tmpdirx; + r = uv_os_tmpdir(tmpdirx, &lenx); + ASSERT(r == 0); +#endif + return 0; } diff --git a/test/test-tty-duplicate-key.c b/test/test-tty-duplicate-key.c new file mode 100644 index 0000000..1ec2f36 --- /dev/null +++ b/test/test-tty-duplicate-key.c @@ -0,0 +1,321 @@ +/* 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. + */ + +#ifdef _WIN32 + +#include "uv.h" +#include "task.h" + +#include +#include +#include +#include + +#define ESC "\x1b" +#define EUR_UTF8 "\xe2\x82\xac" +#define EUR_UNICODE 0x20AC + + +const char* expect_str = NULL; +ssize_t expect_nread = 0; + +static void dump_str(const char* str, ssize_t len) { + ssize_t i; + for (i = 0; i < len; i++) { + fprintf(stderr, "%#02x ", *(str + i)); + } +} + +static void print_err_msg(const char* expect, ssize_t expect_len, + const char* found, ssize_t found_len) { + fprintf(stderr, "expect "); + dump_str(expect, expect_len); + fprintf(stderr, ", but found "); + dump_str(found, found_len); + fprintf(stderr, "\n"); +} + +static void tty_alloc(uv_handle_t* handle, size_t size, uv_buf_t* buf) { + buf->base = malloc(size); + ASSERT(buf->base != NULL); + buf->len = size; +} + +static void tty_read(uv_stream_t* tty_in, ssize_t nread, const uv_buf_t* buf) { + if (nread > 0) { + if (nread != expect_nread) { + fprintf(stderr, "expected nread %ld, but found %ld\n", + (long)expect_nread, (long)nread); + print_err_msg(expect_str, expect_nread, buf->base, nread); + ASSERT(FALSE); + } + if (strncmp(buf->base, expect_str, nread) != 0) { + print_err_msg(expect_str, expect_nread, buf->base, nread); + ASSERT(FALSE); + } + uv_close((uv_handle_t*) tty_in, NULL); + } else { + ASSERT(nread == 0); + } +} + +static void make_key_event_records(WORD virt_key, DWORD ctr_key_state, + BOOL is_wsl, INPUT_RECORD* records) { +# define KEV(I) records[(I)].Event.KeyEvent + BYTE kb_state[256] = {0}; + WCHAR buf[2]; + int ret; + + records[0].EventType = records[1].EventType = KEY_EVENT; + KEV(0).bKeyDown = TRUE; + KEV(1).bKeyDown = FALSE; + KEV(0).wVirtualKeyCode = KEV(1).wVirtualKeyCode = virt_key; + KEV(0).wRepeatCount = KEV(1).wRepeatCount = 1; + KEV(0).wVirtualScanCode = KEV(1).wVirtualScanCode = + MapVirtualKeyW(virt_key, MAPVK_VK_TO_VSC); + KEV(0).dwControlKeyState = KEV(1).dwControlKeyState = ctr_key_state; + if (ctr_key_state & LEFT_ALT_PRESSED) { + kb_state[VK_LMENU] = 0x01; + } + if (ctr_key_state & RIGHT_ALT_PRESSED) { + kb_state[VK_RMENU] = 0x01; + } + if (ctr_key_state & LEFT_CTRL_PRESSED) { + kb_state[VK_LCONTROL] = 0x01; + } + if (ctr_key_state & RIGHT_CTRL_PRESSED) { + kb_state[VK_RCONTROL] = 0x01; + } + if (ctr_key_state & SHIFT_PRESSED) { + kb_state[VK_SHIFT] = 0x01; + } + ret = ToUnicode(virt_key, KEV(0).wVirtualScanCode, kb_state, buf, 2, 0); + if (ret == 1) { + if(!is_wsl && + ((ctr_key_state & LEFT_ALT_PRESSED) || + (ctr_key_state & RIGHT_ALT_PRESSED))) { + /* + * If ALT key is pressed, the UnicodeChar value of the keyup event is + * set to 0 on nomal console. Emulate this behavior. + * See https://github.com/Microsoft/console/issues/320 + */ + KEV(0).uChar.UnicodeChar = buf[0]; + KEV(1).uChar.UnicodeChar = 0; + } else{ + /* + * In WSL UnicodeChar is normally set. This behavior cause #2111. + */ + KEV(0).uChar.UnicodeChar = KEV(1).uChar.UnicodeChar = buf[0]; + } + } else { + KEV(0).uChar.UnicodeChar = KEV(1).uChar.UnicodeChar = 0; + } +# undef KEV +} + +TEST_IMPL(tty_duplicate_vt100_fn_key) { + int r; + int ttyin_fd; + uv_tty_t tty_in; + uv_loop_t* loop; + HANDLE handle; + INPUT_RECORD records[2]; + DWORD written; + + loop = uv_default_loop(); + + /* Make sure we have an FD that refers to a tty */ + handle = CreateFileA("conin$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + ASSERT(handle != INVALID_HANDLE_VALUE); + ttyin_fd = _open_osfhandle((intptr_t) handle, 0); + ASSERT(ttyin_fd >= 0); + ASSERT(UV_TTY == uv_guess_handle(ttyin_fd)); + + r = uv_tty_init(uv_default_loop(), &tty_in, ttyin_fd, 1); /* Readable. */ + ASSERT(r == 0); + ASSERT(uv_is_readable((uv_stream_t*) &tty_in)); + ASSERT(!uv_is_writable((uv_stream_t*) &tty_in)); + + r = uv_read_start((uv_stream_t*)&tty_in, tty_alloc, tty_read); + ASSERT(r == 0); + + expect_str = ESC"[[A"; + expect_nread = strlen(expect_str); + + /* Turn on raw mode. */ + r = uv_tty_set_mode(&tty_in, UV_TTY_MODE_RAW); + ASSERT(r == 0); + + /* + * Send F1 keystrokes. Test of issue cause by #2114 that vt100 fn key + * duplicate. + */ + make_key_event_records(VK_F1, 0, TRUE, records); + WriteConsoleInputW(handle, records, ARRAY_SIZE(records), &written); + ASSERT(written == ARRAY_SIZE(records)); + + uv_run(loop, UV_RUN_DEFAULT); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +TEST_IMPL(tty_duplicate_alt_modifier_key) { + int r; + int ttyin_fd; + uv_tty_t tty_in; + uv_loop_t* loop; + HANDLE handle; + INPUT_RECORD records[2]; + INPUT_RECORD alt_records[2]; + DWORD written; + + loop = uv_default_loop(); + + /* Make sure we have an FD that refers to a tty */ + handle = CreateFileA("conin$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + ASSERT(handle != INVALID_HANDLE_VALUE); + ttyin_fd = _open_osfhandle((intptr_t) handle, 0); + ASSERT(ttyin_fd >= 0); + ASSERT(UV_TTY == uv_guess_handle(ttyin_fd)); + + r = uv_tty_init(uv_default_loop(), &tty_in, ttyin_fd, 1); /* Readable. */ + ASSERT(r == 0); + ASSERT(uv_is_readable((uv_stream_t*) &tty_in)); + ASSERT(!uv_is_writable((uv_stream_t*) &tty_in)); + + r = uv_read_start((uv_stream_t*)&tty_in, tty_alloc, tty_read); + ASSERT(r == 0); + + expect_str = ESC"a"ESC"a"; + expect_nread = strlen(expect_str); + + /* Turn on raw mode. */ + r = uv_tty_set_mode(&tty_in, UV_TTY_MODE_RAW); + ASSERT(r == 0); + + /* Emulate transmission of M-a at normal console */ + make_key_event_records(VK_MENU, 0, TRUE, alt_records); + WriteConsoleInputW(handle, &alt_records[0], 1, &written); + ASSERT(written == 1); + make_key_event_records(L'A', LEFT_ALT_PRESSED, FALSE, records); + WriteConsoleInputW(handle, records, ARRAY_SIZE(records), &written); + ASSERT(written == 2); + WriteConsoleInputW(handle, &alt_records[1], 1, &written); + ASSERT(written == 1); + + /* Emulate transmission of M-a at WSL(#2111) */ + make_key_event_records(VK_MENU, 0, TRUE, alt_records); + WriteConsoleInputW(handle, &alt_records[0], 1, &written); + ASSERT(written == 1); + make_key_event_records(L'A', LEFT_ALT_PRESSED, TRUE, records); + WriteConsoleInputW(handle, records, ARRAY_SIZE(records), &written); + ASSERT(written == 2); + WriteConsoleInputW(handle, &alt_records[1], 1, &written); + ASSERT(written == 1); + + uv_run(loop, UV_RUN_DEFAULT); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +TEST_IMPL(tty_composing_character) { + int r; + int ttyin_fd; + uv_tty_t tty_in; + uv_loop_t* loop; + HANDLE handle; + INPUT_RECORD records[2]; + INPUT_RECORD alt_records[2]; + DWORD written; + + loop = uv_default_loop(); + + /* Make sure we have an FD that refers to a tty */ + handle = CreateFileA("conin$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + ASSERT(handle != INVALID_HANDLE_VALUE); + ttyin_fd = _open_osfhandle((intptr_t) handle, 0); + ASSERT(ttyin_fd >= 0); + ASSERT(UV_TTY == uv_guess_handle(ttyin_fd)); + + r = uv_tty_init(uv_default_loop(), &tty_in, ttyin_fd, 1); /* Readable. */ + ASSERT(r == 0); + ASSERT(uv_is_readable((uv_stream_t*) &tty_in)); + ASSERT(!uv_is_writable((uv_stream_t*) &tty_in)); + + r = uv_read_start((uv_stream_t*)&tty_in, tty_alloc, tty_read); + ASSERT(r == 0); + + expect_str = EUR_UTF8; + expect_nread = strlen(expect_str); + + /* Turn on raw mode. */ + r = uv_tty_set_mode(&tty_in, UV_TTY_MODE_RAW); + ASSERT(r == 0); + + /* Emulate EUR inputs by LEFT ALT+NUMPAD ASCII KeyComos */ + make_key_event_records(VK_MENU, 0, FALSE, alt_records); + alt_records[1].Event.KeyEvent.uChar.UnicodeChar = EUR_UNICODE; + WriteConsoleInputW(handle, &alt_records[0], 1, &written); + make_key_event_records(VK_NUMPAD0, LEFT_ALT_PRESSED, FALSE, records); + WriteConsoleInputW(handle, records, ARRAY_SIZE(records), &written); + ASSERT(written == ARRAY_SIZE(records)); + make_key_event_records(VK_NUMPAD1, LEFT_ALT_PRESSED, FALSE, records); + WriteConsoleInputW(handle, records, ARRAY_SIZE(records), &written); + ASSERT(written == ARRAY_SIZE(records)); + make_key_event_records(VK_NUMPAD2, LEFT_ALT_PRESSED, FALSE, records); + WriteConsoleInputW(handle, records, ARRAY_SIZE(records), &written); + ASSERT(written == ARRAY_SIZE(records)); + make_key_event_records(VK_NUMPAD8, LEFT_ALT_PRESSED, FALSE, records); + WriteConsoleInputW(handle, records, ARRAY_SIZE(records), &written); + ASSERT(written == ARRAY_SIZE(records)); + WriteConsoleInputW(handle, &alt_records[1], 1, &written); + + uv_run(loop, UV_RUN_DEFAULT); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +#else + +typedef int file_has_no_tests; /* ISO C forbids an empty translation unit. */ + +#endif /* ifndef _WIN32 */ diff --git a/test/test-tty-escape-sequence-processing.c b/test/test-tty-escape-sequence-processing.c new file mode 100644 index 0000000..c4461e9 --- /dev/null +++ b/test/test-tty-escape-sequence-processing.c @@ -0,0 +1,1621 @@ +/* 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. + */ + +#ifdef _WIN32 + +#include "task.h" +#include "uv.h" + +#include +#include + +#include +#include + +#define ESC "\033" +#define CSI ESC "[" +#define ST ESC "\\" +#define BEL "\x07" +#define HELLO "Hello" + +#define FOREGROUND_WHITE (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE) +#define FOREGROUND_BLACK 0 +#define FOREGROUND_YELLOW (FOREGROUND_RED | FOREGROUND_GREEN) +#define FOREGROUND_CYAN (FOREGROUND_GREEN | FOREGROUND_BLUE) +#define FOREGROUND_MAGENTA (FOREGROUND_RED | FOREGROUND_BLUE) +#define BACKGROUND_WHITE (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE) +#define BACKGROUND_BLACK 0 +#define BACKGROUND_YELLOW (BACKGROUND_RED | BACKGROUND_GREEN) +#define BACKGROUND_CYAN (BACKGROUND_GREEN | BACKGROUND_BLUE) +#define BACKGROUND_MAGENTA (BACKGROUND_RED | BACKGROUND_BLUE) + +#define F_INTENSITY 1 +#define FB_INTENSITY 2 +#define B_INTENSITY 5 +#define INVERSE 7 +#define F_INTENSITY_OFF1 21 +#define F_INTENSITY_OFF2 22 +#define B_INTENSITY_OFF 25 +#define INVERSE_OFF 27 +#define F_BLACK 30 +#define F_RED 31 +#define F_GREEN 32 +#define F_YELLOW 33 +#define F_BLUE 34 +#define F_MAGENTA 35 +#define F_CYAN 36 +#define F_WHITE 37 +#define F_DEFAULT 39 +#define B_BLACK 40 +#define B_RED 41 +#define B_GREEN 42 +#define B_YELLOW 43 +#define B_BLUE 44 +#define B_MAGENTA 45 +#define B_CYAN 46 +#define B_WHITE 47 +#define B_DEFAULT 49 + +#define CURSOR_SIZE_SMALL 25 +#define CURSOR_SIZE_MIDDLE 50 +#define CURSOR_SIZE_LARGE 100 + +struct screen_info { + CONSOLE_SCREEN_BUFFER_INFO csbi; + int top; + int width; + int height; + int length; + WORD default_attr; +}; + +struct captured_screen { + char* text; + WORD* attributes; + struct screen_info si; +}; + +static void get_screen_info(uv_tty_t* tty_out, struct screen_info* si) { + ASSERT(GetConsoleScreenBufferInfo(tty_out->handle, &(si->csbi))); + si->width = si->csbi.dwSize.X; + si->height = si->csbi.srWindow.Bottom - si->csbi.srWindow.Top + 1; + si->length = si->width * si->height; + si->default_attr = si->csbi.wAttributes; + si->top = si->csbi.srWindow.Top; +} + +static void set_cursor_position(uv_tty_t* tty_out, COORD pos) { + HANDLE handle = tty_out->handle; + CONSOLE_SCREEN_BUFFER_INFO info; + ASSERT(GetConsoleScreenBufferInfo(handle, &info)); + pos.X -= 1; + pos.Y += info.srWindow.Top - 1; + ASSERT(SetConsoleCursorPosition(handle, pos)); +} + +static void get_cursor_position(uv_tty_t* tty_out, COORD* cursor_position) { + HANDLE handle = tty_out->handle; + CONSOLE_SCREEN_BUFFER_INFO info; + ASSERT(GetConsoleScreenBufferInfo(handle, &info)); + cursor_position->X = info.dwCursorPosition.X + 1; + cursor_position->Y = info.dwCursorPosition.Y - info.srWindow.Top + 1; +} + +static void set_cursor_to_home(uv_tty_t* tty_out) { + COORD origin = {1, 1}; + set_cursor_position(tty_out, origin); +} + +static CONSOLE_CURSOR_INFO get_cursor_info(uv_tty_t* tty_out) { + HANDLE handle = tty_out->handle; + CONSOLE_CURSOR_INFO info; + ASSERT(GetConsoleCursorInfo(handle, &info)); + return info; +} + +static void set_cursor_size(uv_tty_t* tty_out, DWORD size) { + CONSOLE_CURSOR_INFO info = get_cursor_info(tty_out); + info.dwSize = size; + ASSERT(SetConsoleCursorInfo(tty_out->handle, &info)); +} + +static DWORD get_cursor_size(uv_tty_t* tty_out) { + return get_cursor_info(tty_out).dwSize; +} + +static void set_cursor_visibility(uv_tty_t* tty_out, BOOL visible) { + CONSOLE_CURSOR_INFO info = get_cursor_info(tty_out); + info.bVisible = visible; + ASSERT(SetConsoleCursorInfo(tty_out->handle, &info)); +} + +static BOOL get_cursor_visibility(uv_tty_t* tty_out) { + return get_cursor_info(tty_out).bVisible; +} + +static BOOL is_scrolling(uv_tty_t* tty_out, struct screen_info si) { + CONSOLE_SCREEN_BUFFER_INFO info; + ASSERT(GetConsoleScreenBufferInfo(tty_out->handle, &info)); + return info.srWindow.Top != si.top; +} + +static void write_console(uv_tty_t* tty_out, char* src) { + int r; + uv_buf_t buf; + + buf.base = src; + buf.len = strlen(buf.base); + + r = uv_try_write((uv_stream_t*) tty_out, &buf, 1); + ASSERT(r >= 0); + ASSERT((unsigned int) r == buf.len); +} + +static void setup_screen(uv_tty_t* tty_out) { + DWORD length, number_of_written; + COORD origin; + CONSOLE_SCREEN_BUFFER_INFO info; + ASSERT(GetConsoleScreenBufferInfo(tty_out->handle, &info)); + length = info.dwSize.X * (info.srWindow.Bottom - info.srWindow.Top + 1); + origin.X = 0; + origin.Y = info.srWindow.Top; + ASSERT(FillConsoleOutputCharacter( + tty_out->handle, '.', length, origin, &number_of_written)); + ASSERT(length == number_of_written); +} + +static void clear_screen(uv_tty_t* tty_out, struct screen_info* si) { + DWORD length, number_of_written; + COORD origin; + CONSOLE_SCREEN_BUFFER_INFO info; + ASSERT(GetConsoleScreenBufferInfo(tty_out->handle, &info)); + length = (info.srWindow.Bottom - info.srWindow.Top + 1) * info.dwSize.X - 1; + origin.X = 0; + origin.Y = info.srWindow.Top; + FillConsoleOutputCharacterA( + tty_out->handle, ' ', length, origin, &number_of_written); + ASSERT(length == number_of_written); + FillConsoleOutputAttribute( + tty_out->handle, si->default_attr, length, origin, &number_of_written); + ASSERT(length == number_of_written); +} + +static void free_screen(struct captured_screen* cs) { + free(cs->text); + cs->text = NULL; + free(cs->attributes); + cs->attributes = NULL; +} + +static void capture_screen(uv_tty_t* tty_out, struct captured_screen* cs) { + DWORD length; + COORD origin; + get_screen_info(tty_out, &(cs->si)); + origin.X = 0; + origin.Y = cs->si.csbi.srWindow.Top; + cs->text = malloc(cs->si.length * sizeof(*cs->text)); + ASSERT(cs->text != NULL); + cs->attributes = (WORD*) malloc(cs->si.length * sizeof(*cs->attributes)); + ASSERT(cs->attributes != NULL); + ASSERT(ReadConsoleOutputCharacter( + tty_out->handle, cs->text, cs->si.length, origin, &length)); + ASSERT((unsigned int) cs->si.length == length); + ASSERT(ReadConsoleOutputAttribute( + tty_out->handle, cs->attributes, cs->si.length, origin, &length)); + ASSERT((unsigned int) cs->si.length == length); +} + +static void make_expect_screen_erase(struct captured_screen* cs, + COORD cursor_position, + int dir, + BOOL entire_screen) { + /* beginning of line */ + char* start; + char* end; + start = cs->text + cs->si.width * (cursor_position.Y - 1); + if (dir == 0) { + if (entire_screen) { + /* erase to end of screen */ + end = cs->text + cs->si.length; + } else { + /* erase to end of line */ + end = start + cs->si.width; + } + /* erase from postition of cursor */ + start += cursor_position.X - 1; + } else if (dir == 1) { + /* erase to position of cursor */ + end = start + cursor_position.X; + if (entire_screen) { + /* erase form beginning of screen */ + start = cs->text; + } + } else if (dir == 2) { + if (entire_screen) { + /* erase form beginning of screen */ + start = cs->text; + /* erase to end of screen */ + end = cs->text + cs->si.length; + } else { + /* erase to end of line */ + end = start + cs->si.width; + } + } else { + ASSERT(FALSE); + } + ASSERT(start < end); + ASSERT(end - cs->text <= cs->si.length); + for (; start < end; start++) { + *start = ' '; + } +} + +static void make_expect_screen_write(struct captured_screen* cs, + COORD cursor_position, + const char* text) { + /* postion of cursor */ + char* start; + start = cs->text + cs->si.width * (cursor_position.Y - 1) + + cursor_position.X - 1; + size_t length = strlen(text); + size_t remain_length = cs->si.length - (cs->text - start); + length = length > remain_length ? remain_length : length; + memcpy(start, text, length); +} + +static void make_expect_screen_set_attr(struct captured_screen* cs, + COORD cursor_position, + size_t length, + WORD attr) { + WORD* start; + start = cs->attributes + cs->si.width * (cursor_position.Y - 1) + + cursor_position.X - 1; + size_t remain_length = cs->si.length - (cs->attributes - start); + length = length > remain_length ? remain_length : length; + while (length) { + *start = attr; + start++; + length--; + } +} + +static BOOL compare_screen(uv_tty_t* tty_out, + struct captured_screen* actual, + struct captured_screen* expect) { + int line, col; + BOOL result = TRUE; + int current = 0; + ASSERT(actual->text); + ASSERT(actual->attributes); + ASSERT(expect->text); + ASSERT(expect->attributes); + if (actual->si.length != expect->si.length) { + return FALSE; + } + if (actual->si.width != expect->si.width) { + return FALSE; + } + if (actual->si.height != expect->si.height) { + return FALSE; + } + while (current < actual->si.length) { + if (*(actual->text + current) != *(expect->text + current)) { + line = current / actual->si.width + 1; + col = current - actual->si.width * (line - 1) + 1; + fprintf(stderr, + "line:%d col:%d expected character '%c' but found '%c'\n", + line, + col, + *(expect->text + current), + *(actual->text + current)); + result = FALSE; + } + if (*(actual->attributes + current) != *(expect->attributes + current)) { + line = current / actual->si.width + 1; + col = current - actual->si.width * (line - 1) + 1; + fprintf(stderr, + "line:%d col:%d expected attributes '%u' but found '%u'\n", + line, + col, + *(expect->attributes + current), + *(actual->attributes + current)); + result = FALSE; + } + current++; + } + clear_screen(tty_out, &expect->si); + free_screen(expect); + free_screen(actual); + return result; +} + +static void initialize_tty(uv_tty_t* tty_out) { + int r; + int ttyout_fd; + /* Make sure we have an FD that refers to a tty */ + HANDLE handle; + + uv_tty_set_vterm_state(UV_TTY_UNSUPPORTED); + + handle = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + CONSOLE_TEXTMODE_BUFFER, + NULL); + ASSERT(handle != INVALID_HANDLE_VALUE); + + ttyout_fd = _open_osfhandle((intptr_t) handle, 0); + ASSERT(ttyout_fd >= 0); + ASSERT(UV_TTY == uv_guess_handle(ttyout_fd)); + r = uv_tty_init(uv_default_loop(), tty_out, ttyout_fd, 0); /* Writable. */ + ASSERT(r == 0); +} + +static void terminate_tty(uv_tty_t* tty_out) { + set_cursor_to_home(tty_out); + uv_close((uv_handle_t*) tty_out, NULL); +} + +TEST_IMPL(tty_cursor_up) { + uv_tty_t tty_out; + uv_loop_t* loop; + COORD cursor_pos, cursor_pos_old; + char buffer[1024]; + struct screen_info si; + + loop = uv_default_loop(); + + initialize_tty(&tty_out); + get_screen_info(&tty_out, &si); + + cursor_pos_old.X = si.width / 2; + cursor_pos_old.Y = si.height / 2; + set_cursor_position(&tty_out, cursor_pos_old); + + /* cursor up one times if omitted arguments */ + snprintf(buffer, sizeof(buffer), "%sA", CSI); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(cursor_pos_old.Y - 1 == cursor_pos.Y); + ASSERT(cursor_pos_old.X == cursor_pos.X); + + /* cursor up nth times */ + cursor_pos_old = cursor_pos; + snprintf(buffer, sizeof(buffer), "%s%dA", CSI, si.height / 4); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(cursor_pos_old.Y - si.height / 4 == cursor_pos.Y); + ASSERT(cursor_pos_old.X == cursor_pos.X); + + /* cursor up from Window top does nothing */ + cursor_pos_old.X = 1; + cursor_pos_old.Y = 1; + set_cursor_position(&tty_out, cursor_pos_old); + snprintf(buffer, sizeof(buffer), "%sA", CSI); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(cursor_pos_old.Y == cursor_pos.Y); + ASSERT(cursor_pos_old.X == cursor_pos.X); + ASSERT(!is_scrolling(&tty_out, si)); + + terminate_tty(&tty_out); + + uv_run(loop, UV_RUN_DEFAULT); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + +TEST_IMPL(tty_cursor_down) { + uv_tty_t tty_out; + uv_loop_t* loop; + COORD cursor_pos, cursor_pos_old; + char buffer[1024]; + struct screen_info si; + + loop = uv_default_loop(); + + initialize_tty(&tty_out); + get_screen_info(&tty_out, &si); + + cursor_pos_old.X = si.width / 2; + cursor_pos_old.Y = si.height / 2; + set_cursor_position(&tty_out, cursor_pos_old); + + /* cursor down one times if omitted arguments */ + snprintf(buffer, sizeof(buffer), "%sB", CSI); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(cursor_pos_old.Y + 1 == cursor_pos.Y); + ASSERT(cursor_pos_old.X == cursor_pos.X); + + /* cursor down nth times */ + cursor_pos_old = cursor_pos; + snprintf(buffer, sizeof(buffer), "%s%dB", CSI, si.height / 4); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(cursor_pos_old.Y + si.height / 4 == cursor_pos.Y); + ASSERT(cursor_pos_old.X == cursor_pos.X); + + /* cursor down from bottom line does nothing */ + cursor_pos_old.X = si.width / 2; + cursor_pos_old.Y = si.height; + set_cursor_position(&tty_out, cursor_pos_old); + snprintf(buffer, sizeof(buffer), "%sB", CSI); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(cursor_pos_old.Y == cursor_pos.Y); + ASSERT(cursor_pos_old.X == cursor_pos.X); + ASSERT(!is_scrolling(&tty_out, si)); + + terminate_tty(&tty_out); + + uv_run(loop, UV_RUN_DEFAULT); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + +TEST_IMPL(tty_cursor_forward) { + uv_tty_t tty_out; + uv_loop_t* loop; + COORD cursor_pos, cursor_pos_old; + char buffer[1024]; + struct screen_info si; + + loop = uv_default_loop(); + + initialize_tty(&tty_out); + get_screen_info(&tty_out, &si); + + cursor_pos_old.X = si.width / 2; + cursor_pos_old.Y = si.height / 2; + set_cursor_position(&tty_out, cursor_pos_old); + + /* cursor forward one times if omitted arguments */ + snprintf(buffer, sizeof(buffer), "%sC", CSI); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(cursor_pos_old.Y == cursor_pos.Y); + ASSERT(cursor_pos_old.X + 1 == cursor_pos.X); + + /* cursor forward nth times */ + cursor_pos_old = cursor_pos; + snprintf(buffer, sizeof(buffer), "%s%dC", CSI, si.width / 4); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(cursor_pos_old.Y == cursor_pos.Y); + ASSERT(cursor_pos_old.X + si.width / 4 == cursor_pos.X); + + /* cursor forward from end of line does nothing*/ + cursor_pos_old.X = si.width; + cursor_pos_old.Y = si.height / 2; + set_cursor_position(&tty_out, cursor_pos_old); + snprintf(buffer, sizeof(buffer), "%sC", CSI); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(cursor_pos_old.Y == cursor_pos.Y); + ASSERT(cursor_pos_old.X == cursor_pos.X); + + /* cursor forward from end of screen does nothing */ + cursor_pos_old.X = si.width; + cursor_pos_old.Y = si.height; + set_cursor_position(&tty_out, cursor_pos_old); + snprintf(buffer, sizeof(buffer), "%sC", CSI); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(cursor_pos_old.Y == cursor_pos.Y); + ASSERT(cursor_pos_old.X == cursor_pos.X); + ASSERT(!is_scrolling(&tty_out, si)); + + terminate_tty(&tty_out); + + uv_run(loop, UV_RUN_DEFAULT); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + +TEST_IMPL(tty_cursor_back) { + uv_tty_t tty_out; + uv_loop_t* loop; + COORD cursor_pos, cursor_pos_old; + char buffer[1024]; + struct screen_info si; + + loop = uv_default_loop(); + + initialize_tty(&tty_out); + get_screen_info(&tty_out, &si); + + cursor_pos_old.X = si.width / 2; + cursor_pos_old.Y = si.height / 2; + set_cursor_position(&tty_out, cursor_pos_old); + + /* cursor back one times if omitted arguments */ + snprintf(buffer, sizeof(buffer), "%sD", CSI); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(cursor_pos_old.Y == cursor_pos.Y); + ASSERT(cursor_pos_old.X - 1 == cursor_pos.X); + + /* cursor back nth times */ + cursor_pos_old = cursor_pos; + snprintf(buffer, sizeof(buffer), "%s%dD", CSI, si.width / 4); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(cursor_pos_old.Y == cursor_pos.Y); + ASSERT(cursor_pos_old.X - si.width / 4 == cursor_pos.X); + + /* cursor back from beginning of line does nothing */ + cursor_pos_old.X = 1; + cursor_pos_old.Y = si.height / 2; + set_cursor_position(&tty_out, cursor_pos_old); + snprintf(buffer, sizeof(buffer), "%sD", CSI); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(cursor_pos_old.Y == cursor_pos.Y); + ASSERT(cursor_pos_old.X == cursor_pos.X); + + /* cursor back from top of screen does nothing */ + cursor_pos_old.X = 1; + cursor_pos_old.Y = 1; + set_cursor_position(&tty_out, cursor_pos_old); + snprintf(buffer, sizeof(buffer), "%sD", CSI); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(1 == cursor_pos.Y); + ASSERT(1 == cursor_pos.X); + ASSERT(!is_scrolling(&tty_out, si)); + + terminate_tty(&tty_out); + + uv_run(loop, UV_RUN_DEFAULT); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + +TEST_IMPL(tty_cursor_next_line) { + uv_tty_t tty_out; + uv_loop_t* loop; + COORD cursor_pos, cursor_pos_old; + char buffer[1024]; + struct screen_info si; + + loop = uv_default_loop(); + + initialize_tty(&tty_out); + get_screen_info(&tty_out, &si); + + cursor_pos_old.X = si.width / 2; + cursor_pos_old.Y = si.height / 2; + set_cursor_position(&tty_out, cursor_pos_old); + + /* cursor next line one times if omitted arguments */ + snprintf(buffer, sizeof(buffer), "%sE", CSI); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(cursor_pos_old.Y + 1 == cursor_pos.Y); + ASSERT(1 == cursor_pos.X); + + /* cursor next line nth times */ + cursor_pos_old = cursor_pos; + snprintf(buffer, sizeof(buffer), "%s%dE", CSI, si.height / 4); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(cursor_pos_old.Y + si.height / 4 == cursor_pos.Y); + ASSERT(1 == cursor_pos.X); + + /* cursor next line from buttom row moves beginning of line */ + cursor_pos_old.X = si.width / 2; + cursor_pos_old.Y = si.height; + set_cursor_position(&tty_out, cursor_pos_old); + snprintf(buffer, sizeof(buffer), "%sE", CSI); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(cursor_pos_old.Y == cursor_pos.Y); + ASSERT(1 == cursor_pos.X); + ASSERT(!is_scrolling(&tty_out, si)); + + terminate_tty(&tty_out); + + uv_run(loop, UV_RUN_DEFAULT); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + +TEST_IMPL(tty_cursor_previous_line) { + uv_tty_t tty_out; + uv_loop_t* loop; + COORD cursor_pos, cursor_pos_old; + char buffer[1024]; + struct screen_info si; + + loop = uv_default_loop(); + + initialize_tty(&tty_out); + get_screen_info(&tty_out, &si); + + cursor_pos_old.X = si.width / 2; + cursor_pos_old.Y = si.height / 2; + set_cursor_position(&tty_out, cursor_pos_old); + + /* cursor previous line one times if omitted arguments */ + snprintf(buffer, sizeof(buffer), "%sF", CSI); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(cursor_pos_old.Y - 1 == cursor_pos.Y); + ASSERT(1 == cursor_pos.X); + + /* cursor previous line nth times */ + cursor_pos_old = cursor_pos; + snprintf(buffer, sizeof(buffer), "%s%dF", CSI, si.height / 4); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(cursor_pos_old.Y - si.height / 4 == cursor_pos.Y); + ASSERT(1 == cursor_pos.X); + + /* cursor previous line from top of screen does nothing */ + cursor_pos_old.X = 1; + cursor_pos_old.Y = 1; + set_cursor_position(&tty_out, cursor_pos_old); + snprintf(buffer, sizeof(buffer), "%sD", CSI); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(1 == cursor_pos.Y); + ASSERT(1 == cursor_pos.X); + ASSERT(!is_scrolling(&tty_out, si)); + + terminate_tty(&tty_out); + + uv_run(loop, UV_RUN_DEFAULT); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + +TEST_IMPL(tty_cursor_horizontal_move_absolute) { + uv_tty_t tty_out; + uv_loop_t* loop; + COORD cursor_pos, cursor_pos_old; + char buffer[1024]; + struct screen_info si; + + loop = uv_default_loop(); + + initialize_tty(&tty_out); + get_screen_info(&tty_out, &si); + + cursor_pos_old.X = si.width / 2; + cursor_pos_old.Y = si.height / 2; + set_cursor_position(&tty_out, cursor_pos_old); + + /* Move to beginning of line if omitted argument */ + snprintf(buffer, sizeof(buffer), "%sG", CSI); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(1 == cursor_pos.X); + ASSERT(cursor_pos_old.Y == cursor_pos.Y); + + /* Move cursor to nth character */ + snprintf(buffer, sizeof(buffer), "%s%dG", CSI, si.width / 4); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(si.width / 4 == cursor_pos.X); + ASSERT(cursor_pos_old.Y == cursor_pos.Y); + + /* Moving out of screen will fit within screen */ + snprintf(buffer, sizeof(buffer), "%s%dG", CSI, si.width + 1); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(si.width == cursor_pos.X); + ASSERT(cursor_pos_old.Y == cursor_pos.Y); + + terminate_tty(&tty_out); + + uv_run(loop, UV_RUN_DEFAULT); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + +TEST_IMPL(tty_cursor_move_absolute) { + uv_tty_t tty_out; + uv_loop_t* loop; + COORD cursor_pos; + char buffer[1024]; + struct screen_info si; + + loop = uv_default_loop(); + + initialize_tty(&tty_out); + get_screen_info(&tty_out, &si); + + cursor_pos.X = si.width / 2; + cursor_pos.Y = si.height / 2; + set_cursor_position(&tty_out, cursor_pos); + + /* Move the cursor to home if omitted arguments */ + snprintf(buffer, sizeof(buffer), "%sH", CSI); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(1 == cursor_pos.X); + ASSERT(1 == cursor_pos.Y); + + /* Move the cursor to the middle of the screen */ + snprintf( + buffer, sizeof(buffer), "%s%d;%df", CSI, si.height / 2, si.width / 2); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(si.width / 2 == cursor_pos.X); + ASSERT(si.height / 2 == cursor_pos.Y); + + /* Moving out of screen will fit within screen */ + snprintf( + buffer, sizeof(buffer), "%s%d;%df", CSI, si.height / 2, si.width + 1); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(si.width == cursor_pos.X); + ASSERT(si.height / 2 == cursor_pos.Y); + + snprintf( + buffer, sizeof(buffer), "%s%d;%df", CSI, si.height + 1, si.width / 2); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(si.width / 2 == cursor_pos.X); + ASSERT(si.height == cursor_pos.Y); + ASSERT(!is_scrolling(&tty_out, si)); + + terminate_tty(&tty_out); + + uv_run(loop, UV_RUN_DEFAULT); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + +TEST_IMPL(tty_hide_show_cursor) { + uv_tty_t tty_out; + uv_loop_t* loop; + char buffer[1024]; + BOOL saved_cursor_visibility; + + loop = uv_default_loop(); + + initialize_tty(&tty_out); + + saved_cursor_visibility = get_cursor_visibility(&tty_out); + + /* Hide the cursor */ + set_cursor_visibility(&tty_out, TRUE); + snprintf(buffer, sizeof(buffer), "%s?25l", CSI); + write_console(&tty_out, buffer); + ASSERT(!get_cursor_visibility(&tty_out)); + + /* Show the cursor */ + set_cursor_visibility(&tty_out, FALSE); + snprintf(buffer, sizeof(buffer), "%s?25h", CSI); + write_console(&tty_out, buffer); + ASSERT(get_cursor_visibility(&tty_out)); + + set_cursor_visibility(&tty_out, saved_cursor_visibility); + terminate_tty(&tty_out); + + uv_run(loop, UV_RUN_DEFAULT); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + +TEST_IMPL(tty_erase) { + int dir; + uv_tty_t tty_out; + uv_loop_t* loop; + COORD cursor_pos; + char buffer[1024]; + struct captured_screen actual = {0}, expect = {0}; + + loop = uv_default_loop(); + + initialize_tty(&tty_out); + + /* Erase to below if omitted argument */ + dir = 0; + setup_screen(&tty_out); + capture_screen(&tty_out, &expect); + cursor_pos.X = expect.si.width / 2; + cursor_pos.Y = expect.si.height / 2; + make_expect_screen_erase(&expect, cursor_pos, dir, TRUE); + + set_cursor_position(&tty_out, cursor_pos); + snprintf(buffer, sizeof(buffer), "%sJ", CSI); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + + ASSERT(compare_screen(&tty_out, &actual, &expect)); + + /* Erase to below(dir = 0) */ + setup_screen(&tty_out); + capture_screen(&tty_out, &expect); + make_expect_screen_erase(&expect, cursor_pos, dir, TRUE); + + set_cursor_position(&tty_out, cursor_pos); + snprintf(buffer, sizeof(buffer), "%s%dJ", CSI, dir); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + + ASSERT(compare_screen(&tty_out, &actual, &expect)); + + /* Erase to above */ + dir = 1; + setup_screen(&tty_out); + capture_screen(&tty_out, &expect); + make_expect_screen_erase(&expect, cursor_pos, dir, TRUE); + + set_cursor_position(&tty_out, cursor_pos); + snprintf(buffer, sizeof(buffer), "%s%dJ", CSI, dir); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + + ASSERT(compare_screen(&tty_out, &actual, &expect)); + + /* Erase All */ + dir = 2; + setup_screen(&tty_out); + capture_screen(&tty_out, &expect); + make_expect_screen_erase(&expect, cursor_pos, dir, TRUE); + + set_cursor_position(&tty_out, cursor_pos); + snprintf(buffer, sizeof(buffer), "%s%dJ", CSI, dir); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + + ASSERT(compare_screen(&tty_out, &actual, &expect)); + + terminate_tty(&tty_out); + + uv_run(loop, UV_RUN_DEFAULT); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + +TEST_IMPL(tty_erase_line) { + int dir; + uv_tty_t tty_out; + uv_loop_t* loop; + COORD cursor_pos; + char buffer[1024]; + struct captured_screen actual = {0}, expect = {0}; + + loop = uv_default_loop(); + + initialize_tty(&tty_out); + + /* Erase to right if omitted arguments */ + dir = 0; + setup_screen(&tty_out); + capture_screen(&tty_out, &expect); + cursor_pos.X = expect.si.width / 2; + cursor_pos.Y = expect.si.height / 2; + make_expect_screen_erase(&expect, cursor_pos, dir, FALSE); + + set_cursor_position(&tty_out, cursor_pos); + snprintf(buffer, sizeof(buffer), "%sK", CSI); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + + ASSERT(compare_screen(&tty_out, &actual, &expect)); + + /* Erase to right(dir = 0) */ + setup_screen(&tty_out); + capture_screen(&tty_out, &expect); + make_expect_screen_erase(&expect, cursor_pos, dir, FALSE); + + set_cursor_position(&tty_out, cursor_pos); + snprintf(buffer, sizeof(buffer), "%s%dK", CSI, dir); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + + ASSERT(compare_screen(&tty_out, &actual, &expect)); + + /* Erase to Left */ + dir = 1; + setup_screen(&tty_out); + capture_screen(&tty_out, &expect); + make_expect_screen_erase(&expect, cursor_pos, dir, FALSE); + + set_cursor_position(&tty_out, cursor_pos); + snprintf(buffer, sizeof(buffer), "%s%dK", CSI, dir); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + + ASSERT(compare_screen(&tty_out, &actual, &expect)); + + /* Erase All */ + dir = 2; + setup_screen(&tty_out); + capture_screen(&tty_out, &expect); + make_expect_screen_erase(&expect, cursor_pos, dir, FALSE); + + set_cursor_position(&tty_out, cursor_pos); + snprintf(buffer, sizeof(buffer), "%s%dK", CSI, dir); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + + ASSERT(compare_screen(&tty_out, &actual, &expect)); + + terminate_tty(&tty_out); + + uv_run(loop, UV_RUN_DEFAULT); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + +TEST_IMPL(tty_set_cursor_shape) { + uv_tty_t tty_out; + uv_loop_t* loop; + DWORD saved_cursor_size; + char buffer[1024]; + + loop = uv_default_loop(); + + initialize_tty(&tty_out); + + saved_cursor_size = get_cursor_size(&tty_out); + + /* cursor size large if omitted arguments */ + set_cursor_size(&tty_out, CURSOR_SIZE_MIDDLE); + snprintf(buffer, sizeof(buffer), "%s q", CSI); + write_console(&tty_out, buffer); + ASSERT(get_cursor_size(&tty_out) == CURSOR_SIZE_LARGE); + + /* cursor size large */ + set_cursor_size(&tty_out, CURSOR_SIZE_MIDDLE); + snprintf(buffer, sizeof(buffer), "%s1 q", CSI); + write_console(&tty_out, buffer); + ASSERT(get_cursor_size(&tty_out) == CURSOR_SIZE_LARGE); + set_cursor_size(&tty_out, CURSOR_SIZE_MIDDLE); + snprintf(buffer, sizeof(buffer), "%s2 q", CSI); + write_console(&tty_out, buffer); + ASSERT(get_cursor_size(&tty_out) == CURSOR_SIZE_LARGE); + + /* cursor size small */ + set_cursor_size(&tty_out, CURSOR_SIZE_MIDDLE); + snprintf(buffer, sizeof(buffer), "%s3 q", CSI); + write_console(&tty_out, buffer); + ASSERT(get_cursor_size(&tty_out) == CURSOR_SIZE_SMALL); + set_cursor_size(&tty_out, CURSOR_SIZE_MIDDLE); + snprintf(buffer, sizeof(buffer), "%s6 q", CSI); + write_console(&tty_out, buffer); + ASSERT(get_cursor_size(&tty_out) == CURSOR_SIZE_SMALL); + + /* Nothing occurs with arguments outside valid range */ + set_cursor_size(&tty_out, CURSOR_SIZE_MIDDLE); + snprintf(buffer, sizeof(buffer), "%s7 q", CSI); + write_console(&tty_out, buffer); + ASSERT(get_cursor_size(&tty_out) == CURSOR_SIZE_MIDDLE); + + /* restore cursor size if arguments is zero */ + snprintf(buffer, sizeof(buffer), "%s0 q", CSI); + write_console(&tty_out, buffer); + ASSERT(get_cursor_size(&tty_out) == saved_cursor_size); + + terminate_tty(&tty_out); + + uv_run(loop, UV_RUN_DEFAULT); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + +TEST_IMPL(tty_set_style) { + uv_tty_t tty_out; + uv_loop_t* loop; + COORD cursor_pos; + char buffer[1024]; + struct captured_screen actual = {0}, expect = {0}; + WORD fg, bg; + WORD fg_attrs[9][2] = {{F_BLACK, FOREGROUND_BLACK}, + {F_RED, FOREGROUND_RED}, + {F_GREEN, FOREGROUND_GREEN}, + {F_YELLOW, FOREGROUND_YELLOW}, + {F_BLUE, FOREGROUND_BLUE}, + {F_MAGENTA, FOREGROUND_MAGENTA}, + {F_CYAN, FOREGROUND_CYAN}, + {F_WHITE, FOREGROUND_WHITE}, + {F_DEFAULT, 0}}; + WORD bg_attrs[9][2] = {{B_DEFAULT, 0}, + {B_BLACK, BACKGROUND_BLACK}, + {B_RED, BACKGROUND_RED}, + {B_GREEN, BACKGROUND_GREEN}, + {B_YELLOW, BACKGROUND_YELLOW}, + {B_BLUE, BACKGROUND_BLUE}, + {B_MAGENTA, BACKGROUND_MAGENTA}, + {B_CYAN, BACKGROUND_CYAN}, + {B_WHITE, BACKGROUND_WHITE}}; + WORD attr; + int i, length; + + loop = uv_default_loop(); + + initialize_tty(&tty_out); + + capture_screen(&tty_out, &expect); + fg_attrs[8][1] = expect.si.default_attr & FOREGROUND_WHITE; + bg_attrs[0][1] = expect.si.default_attr & BACKGROUND_WHITE; + + /* Set foreground color */ + length = ARRAY_SIZE(fg_attrs); + for (i = 0; i < length; i++) { + capture_screen(&tty_out, &expect); + cursor_pos.X = expect.si.width / 2; + cursor_pos.Y = expect.si.height / 2; + attr = (expect.si.default_attr & ~FOREGROUND_WHITE) | fg_attrs[i][1]; + make_expect_screen_write(&expect, cursor_pos, HELLO); + make_expect_screen_set_attr(&expect, cursor_pos, strlen(HELLO), attr); + + set_cursor_position(&tty_out, cursor_pos); + snprintf( + buffer, sizeof(buffer), "%s%dm%s%sm", CSI, fg_attrs[i][0], HELLO, CSI); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + + ASSERT(compare_screen(&tty_out, &actual, &expect)); + } + + /* Set background color */ + length = ARRAY_SIZE(bg_attrs); + for (i = 0; i < length; i++) { + capture_screen(&tty_out, &expect); + cursor_pos.X = expect.si.width / 2; + cursor_pos.Y = expect.si.height / 2; + attr = (expect.si.default_attr & ~BACKGROUND_WHITE) | bg_attrs[i][1]; + make_expect_screen_write(&expect, cursor_pos, HELLO); + make_expect_screen_set_attr(&expect, cursor_pos, strlen(HELLO), attr); + + set_cursor_position(&tty_out, cursor_pos); + snprintf( + buffer, sizeof(buffer), "%s%dm%s%sm", CSI, bg_attrs[i][0], HELLO, CSI); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + + ASSERT(compare_screen(&tty_out, &actual, &expect)); + } + + /* Set foregroud and background color */ + ASSERT(ARRAY_SIZE(fg_attrs) == ARRAY_SIZE(bg_attrs)); + length = ARRAY_SIZE(bg_attrs); + for (i = 0; i < length; i++) { + capture_screen(&tty_out, &expect); + cursor_pos.X = expect.si.width / 2; + cursor_pos.Y = expect.si.height / 2; + attr = expect.si.default_attr & ~FOREGROUND_WHITE & ~BACKGROUND_WHITE; + attr |= fg_attrs[i][1] | bg_attrs[i][1]; + make_expect_screen_write(&expect, cursor_pos, HELLO); + make_expect_screen_set_attr(&expect, cursor_pos, strlen(HELLO), attr); + + set_cursor_position(&tty_out, cursor_pos); + snprintf(buffer, + sizeof(buffer), + "%s%d;%dm%s%sm", + CSI, + bg_attrs[i][0], + fg_attrs[i][0], + HELLO, + CSI); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + + ASSERT(compare_screen(&tty_out, &actual, &expect)); + } + + /* Set foreground bright on */ + capture_screen(&tty_out, &expect); + cursor_pos.X = expect.si.width / 2; + cursor_pos.Y = expect.si.height / 2; + set_cursor_position(&tty_out, cursor_pos); + attr = expect.si.default_attr; + attr |= FOREGROUND_INTENSITY; + make_expect_screen_write(&expect, cursor_pos, HELLO); + make_expect_screen_set_attr(&expect, cursor_pos, strlen(HELLO), attr); + cursor_pos.X += strlen(HELLO); + make_expect_screen_write(&expect, cursor_pos, HELLO); + make_expect_screen_set_attr(&expect, cursor_pos, strlen(HELLO), attr); + + snprintf(buffer, + sizeof(buffer), + "%s%dm%s%s%dm%s%dm%s%s%dm", + CSI, + F_INTENSITY, + HELLO, + CSI, + F_INTENSITY_OFF1, + CSI, + F_INTENSITY, + HELLO, + CSI, + F_INTENSITY_OFF2); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + + ASSERT(compare_screen(&tty_out, &actual, &expect)); + + /* Set background bright on */ + capture_screen(&tty_out, &expect); + cursor_pos.X = expect.si.width / 2; + cursor_pos.Y = expect.si.height / 2; + set_cursor_position(&tty_out, cursor_pos); + attr = expect.si.default_attr; + attr |= BACKGROUND_INTENSITY; + make_expect_screen_write(&expect, cursor_pos, HELLO); + make_expect_screen_set_attr(&expect, cursor_pos, strlen(HELLO), attr); + + snprintf(buffer, + sizeof(buffer), + "%s%dm%s%s%dm", + CSI, + B_INTENSITY, + HELLO, + CSI, + B_INTENSITY_OFF); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + + ASSERT(compare_screen(&tty_out, &actual, &expect)); + + /* Inverse */ + capture_screen(&tty_out, &expect); + cursor_pos.X = expect.si.width / 2; + cursor_pos.Y = expect.si.height / 2; + set_cursor_position(&tty_out, cursor_pos); + attr = expect.si.default_attr; + fg = attr & FOREGROUND_WHITE; + bg = attr & BACKGROUND_WHITE; + attr &= (~FOREGROUND_WHITE & ~BACKGROUND_WHITE); + attr |= COMMON_LVB_REVERSE_VIDEO; + attr |= fg << 4; + attr |= bg >> 4; + make_expect_screen_write(&expect, cursor_pos, HELLO); + make_expect_screen_set_attr(&expect, cursor_pos, strlen(HELLO), attr); + cursor_pos.X += strlen(HELLO); + make_expect_screen_write(&expect, cursor_pos, HELLO); + + snprintf(buffer, + sizeof(buffer), + "%s%dm%s%s%dm%s", + CSI, + INVERSE, + HELLO, + CSI, + INVERSE_OFF, + HELLO); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + + ASSERT(compare_screen(&tty_out, &actual, &expect)); + + terminate_tty(&tty_out); + + uv_run(loop, UV_RUN_DEFAULT); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + +TEST_IMPL(tty_save_restore_cursor_position) { + uv_tty_t tty_out; + uv_loop_t* loop; + COORD cursor_pos, cursor_pos_old; + char buffer[1024]; + struct screen_info si; + + loop = uv_default_loop(); + + initialize_tty(&tty_out); + get_screen_info(&tty_out, &si); + + cursor_pos_old.X = si.width / 2; + cursor_pos_old.Y = si.height / 2; + set_cursor_position(&tty_out, cursor_pos_old); + + /* save the cursor position */ + snprintf(buffer, sizeof(buffer), "%ss", CSI); + write_console(&tty_out, buffer); + + cursor_pos.X = si.width / 4; + cursor_pos.Y = si.height / 4; + set_cursor_position(&tty_out, cursor_pos); + + /* restore the cursor postion */ + snprintf(buffer, sizeof(buffer), "%su", CSI); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(cursor_pos.X == cursor_pos_old.X); + ASSERT(cursor_pos.Y == cursor_pos_old.Y); + + cursor_pos_old.X = si.width / 2; + cursor_pos_old.Y = si.height / 2; + set_cursor_position(&tty_out, cursor_pos_old); + + /* save the cursor position */ + snprintf(buffer, sizeof(buffer), "%s7", ESC); + write_console(&tty_out, buffer); + + cursor_pos.X = si.width / 4; + cursor_pos.Y = si.height / 4; + set_cursor_position(&tty_out, cursor_pos); + + /* restore the cursor postion */ + snprintf(buffer, sizeof(buffer), "%s8", ESC); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(cursor_pos.X == cursor_pos_old.X); + ASSERT(cursor_pos.Y == cursor_pos_old.Y); + + terminate_tty(&tty_out); + + uv_run(loop, UV_RUN_DEFAULT); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + +TEST_IMPL(tty_full_reset) { + uv_tty_t tty_out; + uv_loop_t* loop; + char buffer[1024]; + struct captured_screen actual = {0}, expect = {0}; + COORD cursor_pos; + DWORD saved_cursor_size; + BOOL saved_cursor_visibility; + + loop = uv_default_loop(); + + initialize_tty(&tty_out); + + capture_screen(&tty_out, &expect); + setup_screen(&tty_out); + cursor_pos.X = expect.si.width; + cursor_pos.Y = expect.si.height; + set_cursor_position(&tty_out, cursor_pos); + snprintf(buffer, sizeof(buffer), "%s%d;%dm%s", CSI, F_CYAN, B_YELLOW, HELLO); + saved_cursor_size = get_cursor_size(&tty_out); + set_cursor_size(&tty_out, + saved_cursor_size == CURSOR_SIZE_LARGE ? CURSOR_SIZE_SMALL + : CURSOR_SIZE_LARGE); + saved_cursor_visibility = get_cursor_visibility(&tty_out); + set_cursor_visibility(&tty_out, saved_cursor_visibility ? FALSE : TRUE); + write_console(&tty_out, buffer); + snprintf(buffer, sizeof(buffer), "%sc", ESC); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + ASSERT(compare_screen(&tty_out, &actual, &expect)); + ASSERT(get_cursor_size(&tty_out) == saved_cursor_size); + ASSERT(get_cursor_visibility(&tty_out) == saved_cursor_visibility); + ASSERT(actual.si.csbi.srWindow.Top == 0); + + terminate_tty(&tty_out); + + uv_run(loop, UV_RUN_DEFAULT); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + +TEST_IMPL(tty_escape_sequence_processing) { + uv_tty_t tty_out; + uv_loop_t* loop; + COORD cursor_pos, cursor_pos_old; + DWORD saved_cursor_size; + char buffer[1024]; + struct captured_screen actual = {0}, expect = {0}; + int dir; + + loop = uv_default_loop(); + + initialize_tty(&tty_out); + + /* CSI + finaly byte does not output anything */ + cursor_pos.X = 1; + cursor_pos.Y = 1; + set_cursor_position(&tty_out, cursor_pos); + capture_screen(&tty_out, &expect); + make_expect_screen_write(&expect, cursor_pos, HELLO); + cursor_pos.X += strlen(HELLO); + make_expect_screen_write(&expect, cursor_pos, HELLO); + snprintf(buffer, sizeof(buffer), "%s@%s%s~%s", CSI, HELLO, CSI, HELLO); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + ASSERT(compare_screen(&tty_out, &actual, &expect)); + + /* CSI(C1) + finaly byte does not output anything */ + cursor_pos.X = 1; + cursor_pos.Y = 1; + set_cursor_position(&tty_out, cursor_pos); + capture_screen(&tty_out, &expect); + make_expect_screen_write(&expect, cursor_pos, HELLO); + cursor_pos.X += strlen(HELLO); + make_expect_screen_write(&expect, cursor_pos, HELLO); + snprintf(buffer, sizeof(buffer), "\xC2\x9B@%s\xC2\x9B~%s", HELLO, HELLO); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + ASSERT(compare_screen(&tty_out, &actual, &expect)); + + /* CSI + intermediate byte + finaly byte does not output anything */ + cursor_pos.X = 1; + cursor_pos.Y = 1; + set_cursor_position(&tty_out, cursor_pos); + capture_screen(&tty_out, &expect); + make_expect_screen_write(&expect, cursor_pos, HELLO); + cursor_pos.X += strlen(HELLO); + make_expect_screen_write(&expect, cursor_pos, HELLO); + snprintf(buffer, sizeof(buffer), "%s @%s%s/~%s", CSI, HELLO, CSI, HELLO); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + ASSERT(compare_screen(&tty_out, &actual, &expect)); + + /* CSI + parameter byte + finaly byte does not output anything */ + cursor_pos.X = 1; + cursor_pos.Y = 1; + set_cursor_position(&tty_out, cursor_pos); + capture_screen(&tty_out, &expect); + snprintf(buffer, + sizeof(buffer), + "%s0@%s%s>~%s%s?~%s", + CSI, + HELLO, + CSI, + HELLO, + CSI, + HELLO); + make_expect_screen_write(&expect, cursor_pos, HELLO); + cursor_pos.X += strlen(HELLO); + make_expect_screen_write(&expect, cursor_pos, HELLO); + cursor_pos.X += strlen(HELLO); + make_expect_screen_write(&expect, cursor_pos, HELLO); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + ASSERT(compare_screen(&tty_out, &actual, &expect)); + + /* ESC Single-char control does not output anyghing */ + cursor_pos.X = 1; + cursor_pos.Y = 1; + set_cursor_position(&tty_out, cursor_pos); + capture_screen(&tty_out, &expect); + make_expect_screen_write(&expect, cursor_pos, HELLO); + cursor_pos.X += strlen(HELLO); + make_expect_screen_write(&expect, cursor_pos, HELLO); + snprintf(buffer, sizeof(buffer), "%s @%s%s/~%s", CSI, HELLO, CSI, HELLO); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + ASSERT(compare_screen(&tty_out, &actual, &expect)); + + /* Nothing is output from ESC + ^, _, P, ] to BEL or ESC \ */ + /* Operaging System Command */ + cursor_pos.X = 1; + cursor_pos.Y = 1; + set_cursor_position(&tty_out, cursor_pos); + capture_screen(&tty_out, &expect); + make_expect_screen_write(&expect, cursor_pos, HELLO); + snprintf(buffer, sizeof(buffer), "%s]0;%s%s%s", ESC, HELLO, BEL, HELLO); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + ASSERT(compare_screen(&tty_out, &actual, &expect)); + /* Device Control Sequence */ + cursor_pos.X = 1; + cursor_pos.Y = 1; + set_cursor_position(&tty_out, cursor_pos); + capture_screen(&tty_out, &expect); + make_expect_screen_write(&expect, cursor_pos, HELLO); + snprintf(buffer, sizeof(buffer), "%sP$m%s%s", ESC, ST, HELLO); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + ASSERT(compare_screen(&tty_out, &actual, &expect)); + /* Privacy Message */ + cursor_pos.X = 1; + cursor_pos.Y = 1; + set_cursor_position(&tty_out, cursor_pos); + capture_screen(&tty_out, &expect); + make_expect_screen_write(&expect, cursor_pos, HELLO); + snprintf(buffer, + sizeof(buffer), + "%s^\"%s\\\"%s\"%s%s", + ESC, + HELLO, + HELLO, + ST, + HELLO); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + ASSERT(compare_screen(&tty_out, &actual, &expect)); + /* Application Program Command */ + cursor_pos.X = 1; + cursor_pos.Y = 1; + set_cursor_position(&tty_out, cursor_pos); + capture_screen(&tty_out, &expect); + make_expect_screen_write(&expect, cursor_pos, HELLO); + snprintf(buffer, + sizeof(buffer), + "%s_\"%s%s%s\"%s%s", + ESC, + HELLO, + ST, + HELLO, + BEL, + HELLO); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + ASSERT(compare_screen(&tty_out, &actual, &expect)); + + /* Ignore double escape */ + cursor_pos.X = 1; + cursor_pos.Y = 1; + set_cursor_position(&tty_out, cursor_pos); + capture_screen(&tty_out, &expect); + make_expect_screen_write(&expect, cursor_pos, HELLO); + cursor_pos.X += strlen(HELLO); + make_expect_screen_write(&expect, cursor_pos, HELLO); + snprintf(buffer, + sizeof(buffer), + "%s%s@%s%s%s~%s", + ESC, + CSI, + HELLO, + ESC, + CSI, + HELLO); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + ASSERT(compare_screen(&tty_out, &actual, &expect)); + + /* Ignored if argument overflow */ + set_cursor_to_home(&tty_out); + snprintf(buffer, sizeof(buffer), "%s1;%dH", CSI, UINT16_MAX + 1); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(cursor_pos.X == 1); + ASSERT(cursor_pos.Y == 1); + + /* Too many argument are ignored */ + cursor_pos.X = 1; + cursor_pos.Y = 1; + set_cursor_position(&tty_out, cursor_pos); + capture_screen(&tty_out, &expect); + make_expect_screen_write(&expect, cursor_pos, HELLO); + snprintf(buffer, + sizeof(buffer), + "%s%d;%d;%d;%d;%dm%s%sm", + CSI, + F_RED, + F_INTENSITY, + INVERSE, + B_CYAN, + B_INTENSITY_OFF, + HELLO, + CSI); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + ASSERT(compare_screen(&tty_out, &actual, &expect)); + + /* In the case of DECSCUSR, the others are ignored */ + set_cursor_to_home(&tty_out); + snprintf(buffer, + sizeof(buffer), + "%s%d;%d H", + CSI, + expect.si.height / 2, + expect.si.width / 2); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(cursor_pos.X == 1); + ASSERT(cursor_pos.Y == 1); + + /* Invalid sequence are ignored */ + saved_cursor_size = get_cursor_size(&tty_out); + set_cursor_size(&tty_out, CURSOR_SIZE_MIDDLE); + snprintf(buffer, sizeof(buffer), "%s 1q", CSI); + write_console(&tty_out, buffer); + ASSERT(get_cursor_size(&tty_out) == CURSOR_SIZE_MIDDLE); + snprintf(buffer, sizeof(buffer), "%s 1 q", CSI); + write_console(&tty_out, buffer); + ASSERT(get_cursor_size(&tty_out) == CURSOR_SIZE_MIDDLE); + set_cursor_size(&tty_out, saved_cursor_size); + + /* #1874 2. */ + snprintf(buffer, sizeof(buffer), "%s??25l", CSI); + write_console(&tty_out, buffer); + ASSERT(get_cursor_visibility(&tty_out)); + snprintf(buffer, sizeof(buffer), "%s25?l", CSI); + write_console(&tty_out, buffer); + ASSERT(get_cursor_visibility(&tty_out)); + cursor_pos_old.X = expect.si.width / 2; + cursor_pos_old.Y = expect.si.height / 2; + set_cursor_position(&tty_out, cursor_pos_old); + snprintf(buffer, + sizeof(buffer), + "%s??%d;%df", + CSI, + expect.si.height / 4, + expect.si.width / 4); + write_console(&tty_out, buffer); + get_cursor_position(&tty_out, &cursor_pos); + ASSERT(cursor_pos.X = cursor_pos_old.X); + ASSERT(cursor_pos.Y = cursor_pos_old.Y); + set_cursor_to_home(&tty_out); + + /* CSI 25 l does nothing (#1874 4.) */ + snprintf(buffer, sizeof(buffer), "%s25l", CSI); + write_console(&tty_out, buffer); + ASSERT(get_cursor_visibility(&tty_out)); + + /* Unsupported sequences are ignored(#1874 5.) */ + dir = 2; + setup_screen(&tty_out); + capture_screen(&tty_out, &expect); + set_cursor_position(&tty_out, cursor_pos); + snprintf(buffer, sizeof(buffer), "%s?%dJ", CSI, dir); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + ASSERT(compare_screen(&tty_out, &actual, &expect)); + + /* Finaly byte immedately after CSI [ are also output(#1874 1.) */ + cursor_pos.X = expect.si.width / 2; + cursor_pos.Y = expect.si.height / 2; + set_cursor_position(&tty_out, cursor_pos); + capture_screen(&tty_out, &expect); + make_expect_screen_write(&expect, cursor_pos, HELLO); + snprintf(buffer, sizeof(buffer), "%s[%s", CSI, HELLO); + write_console(&tty_out, buffer); + capture_screen(&tty_out, &actual); + ASSERT(compare_screen(&tty_out, &actual, &expect)); + + terminate_tty(&tty_out); + + uv_run(loop, UV_RUN_DEFAULT); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +#else + +typedef int file_has_no_tests; /* ISO C forbids an empty translation unit. */ + +#endif /* ifdef _WIN32 */ diff --git a/test/test-tty.c b/test/test-tty.c index 3bbdfad..0c6548f 100644 --- a/test/test-tty.c +++ b/test/test-tty.c @@ -310,6 +310,39 @@ TEST_IMPL(tty_large_write) { MAKE_VALGRIND_HAPPY(); return 0; } + +TEST_IMPL(tty_raw_cancel) { + int r; + int ttyin_fd; + uv_tty_t tty_in; + HANDLE handle; + + /* Make sure we have an FD that refers to a tty */ + handle = CreateFileA("conin$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + ASSERT(handle != INVALID_HANDLE_VALUE); + ttyin_fd = _open_osfhandle((intptr_t) handle, 0); + ASSERT(ttyin_fd >= 0); + ASSERT(UV_TTY == uv_guess_handle(ttyin_fd)); + + r = uv_tty_init(uv_default_loop(), &tty_in, ttyin_fd, 1); /* Readable. */ + ASSERT(r == 0); + r = uv_tty_set_mode(&tty_in, UV_TTY_MODE_RAW); + ASSERT(r == 0); + r = uv_read_start((uv_stream_t*)&tty_in, tty_raw_alloc, tty_raw_read); + ASSERT(r == 0); + + r = uv_read_stop((uv_stream_t*) &tty_in); + ASSERT(r == 0); + + MAKE_VALGRIND_HAPPY(); + return 0; +} #endif @@ -327,6 +360,8 @@ TEST_IMPL(tty_file) { if (fd != -1) { ASSERT(UV_EINVAL == uv_tty_init(&loop, &tty, fd, 1)); ASSERT(0 == close(fd)); + /* test EBADF handling */ + ASSERT(UV_EINVAL == uv_tty_init(&loop, &tty, fd, 1)); } /* Bug on AIX where '/dev/random' returns 1 from isatty() */ diff --git a/test/test-udp-connect.c b/test/test-udp-connect.c new file mode 100644 index 0000000..58cf947 --- /dev/null +++ b/test/test-udp-connect.c @@ -0,0 +1,186 @@ +/* Copyright libuv project and 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 +#include +#include + +#define CHECK_HANDLE(handle) \ + ASSERT((uv_udp_t*)(handle) == &server || (uv_udp_t*)(handle) == &client) + +static uv_udp_t server; +static uv_udp_t client; +static uv_buf_t buf; +static struct sockaddr_in lo_addr; + +static int cl_send_cb_called; +static int sv_recv_cb_called; + +static int close_cb_called; + + +static void alloc_cb(uv_handle_t* handle, + size_t suggested_size, + uv_buf_t* buf) { + static char slab[65536]; + CHECK_HANDLE(handle); + ASSERT(suggested_size <= sizeof(slab)); + buf->base = slab; + buf->len = sizeof(slab); +} + + +static void close_cb(uv_handle_t* handle) { + CHECK_HANDLE(handle); + ASSERT(uv_is_closing(handle)); + close_cb_called++; +} + + +static void cl_send_cb(uv_udp_send_t* req, int status) { + int r; + + ASSERT(req != NULL); + ASSERT(status == 0); + CHECK_HANDLE(req->handle); + if (++cl_send_cb_called == 1) { + uv_udp_connect(&client, NULL); + r = uv_udp_send(req, &client, &buf, 1, NULL, cl_send_cb); + ASSERT(r == UV_EDESTADDRREQ); + r = uv_udp_send(req, + &client, + &buf, + 1, + (const struct sockaddr*) &lo_addr, + cl_send_cb); + ASSERT(r == 0); + } + +} + + +static void sv_recv_cb(uv_udp_t* handle, + ssize_t nread, + const uv_buf_t* rcvbuf, + const struct sockaddr* addr, + unsigned flags) { + if (nread > 0) { + ASSERT(nread == 4); + ASSERT(addr != NULL); + ASSERT(memcmp("EXIT", rcvbuf->base, nread) == 0); + if (++sv_recv_cb_called == 4) { + uv_close((uv_handle_t*) &server, close_cb); + uv_close((uv_handle_t*) &client, close_cb); + } + } +} + + +TEST_IMPL(udp_connect) { +#if defined(__PASE__) + RETURN_SKIP( + "IBMi PASE's UDP connection can not be disconnected with AF_UNSPEC."); +#endif + uv_udp_send_t req; + struct sockaddr_in ext_addr; + struct sockaddr_in tmp_addr; + int r; + int addrlen; + + ASSERT(0 == uv_ip4_addr("0.0.0.0", TEST_PORT, &lo_addr)); + + r = uv_udp_init(uv_default_loop(), &server); + ASSERT(r == 0); + + r = uv_udp_bind(&server, (const struct sockaddr*) &lo_addr, 0); + ASSERT(r == 0); + + r = uv_udp_recv_start(&server, alloc_cb, sv_recv_cb); + ASSERT(r == 0); + + r = uv_udp_init(uv_default_loop(), &client); + ASSERT(r == 0); + + buf = uv_buf_init("EXIT", 4); + + ASSERT(0 == uv_ip4_addr("8.8.8.8", TEST_PORT, &ext_addr)); + ASSERT(0 == uv_ip4_addr("127.0.0.1", TEST_PORT, &lo_addr)); + + r = uv_udp_connect(&client, (const struct sockaddr*) &lo_addr); + ASSERT(r == 0); + r = uv_udp_connect(&client, (const struct sockaddr*) &ext_addr); + ASSERT(r == UV_EISCONN); + + addrlen = sizeof(tmp_addr); + r = uv_udp_getpeername(&client, (struct sockaddr*) &tmp_addr, &addrlen); + ASSERT(r == 0); + + /* To send messages in connected UDP sockets addr must be NULL */ + r = uv_udp_try_send(&client, &buf, 1, (const struct sockaddr*) &lo_addr); + ASSERT(r == UV_EISCONN); + r = uv_udp_try_send(&client, &buf, 1, NULL); + ASSERT(r == 4); + r = uv_udp_try_send(&client, &buf, 1, (const struct sockaddr*) &ext_addr); + ASSERT(r == UV_EISCONN); + + r = uv_udp_connect(&client, NULL); + ASSERT(r == 0); + r = uv_udp_connect(&client, NULL); + ASSERT(r == UV_ENOTCONN); + + addrlen = sizeof(tmp_addr); + r = uv_udp_getpeername(&client, (struct sockaddr*) &tmp_addr, &addrlen); + ASSERT(r == UV_ENOTCONN); + + /* To send messages in disconnected UDP sockets addr must be set */ + r = uv_udp_try_send(&client, &buf, 1, (const struct sockaddr*) &lo_addr); + ASSERT(r == 4); + r = uv_udp_try_send(&client, &buf, 1, NULL); + ASSERT(r == UV_EDESTADDRREQ); + + + r = uv_udp_connect(&client, (const struct sockaddr*) &lo_addr); + ASSERT(r == 0); + r = uv_udp_send(&req, + &client, + &buf, + 1, + (const struct sockaddr*) &lo_addr, + cl_send_cb); + ASSERT(r == UV_EISCONN); + r = uv_udp_send(&req, &client, &buf, 1, NULL, cl_send_cb); + ASSERT(r == 0); + + uv_run(uv_default_loop(), UV_RUN_DEFAULT); + + ASSERT(close_cb_called == 2); + ASSERT(sv_recv_cb_called == 4); + ASSERT(cl_send_cb_called == 2); + + ASSERT(client.send_queue_size == 0); + ASSERT(server.send_queue_size == 0); + + MAKE_VALGRIND_HAPPY(); + return 0; +} diff --git a/test/test-udp-ipv6.c b/test/test-udp-ipv6.c index 9d4db2b..7099953 100644 --- a/test/test-udp-ipv6.c +++ b/test/test-udp-ipv6.c @@ -41,11 +41,13 @@ static uv_udp_t client; static uv_udp_t server; static uv_udp_send_t req_; +static char data[10]; static uv_timer_t timeout; static int send_cb_called; static int recv_cb_called; static int close_cb_called; +static uint16_t client_port; #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__NetBSD__) static int can_ipv6_ipv4_dual(void) { @@ -83,12 +85,35 @@ static void send_cb(uv_udp_send_t* req, int status) { send_cb_called++; } +static int is_from_client(const struct sockaddr* addr) { + const struct sockaddr_in6* addr6; + char dst[256]; + int r; + + /* Debugging output, and filter out unwanted network traffic */ + if (addr != NULL) { + ASSERT(addr->sa_family == AF_INET6); + addr6 = (struct sockaddr_in6*) addr; + r = uv_inet_ntop(addr->sa_family, &addr6->sin6_addr, dst, sizeof(dst)); + if (r == 0) + printf("from [%.*s]:%d\n", (int) sizeof(dst), dst, addr6->sin6_port); + if (addr6->sin6_port != client_port) + return 0; + if (r != 0 || strcmp(dst, "::ffff:127.0.0.1")) + return 0; + } + return 1; +} + static void ipv6_recv_fail(uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags) { + printf("got %d %.*s\n", (int) nread, nread > 0 ? (int) nread : 0, buf->base); + if (!is_from_client(addr) || (nread == 0 && addr == NULL)) + return; ASSERT(0 && "this function should not have been called"); } @@ -99,10 +124,14 @@ static void ipv6_recv_ok(uv_udp_t* handle, const struct sockaddr* addr, unsigned flags) { CHECK_HANDLE(handle); - ASSERT(nread >= 0); - if (nread) - recv_cb_called++; + printf("got %d %.*s\n", (int) nread, nread > 0 ? (int) nread : 0, buf->base); + if (!is_from_client(addr) || (nread == 0 && addr == NULL)) + return; + + ASSERT(nread == 9); + ASSERT(!memcmp(buf->base, data, 9)); + recv_cb_called++; } @@ -116,7 +145,10 @@ static void timeout_cb(uv_timer_t* timer) { static void do_test(uv_udp_recv_cb recv_cb, int bind_flags) { struct sockaddr_in6 addr6; struct sockaddr_in addr; + int addr6_len; + int addr_len; uv_buf_t buf; + char dst[256]; int r; ASSERT(0 == uv_ip6_addr("::0", TEST_PORT, &addr6)); @@ -127,14 +159,25 @@ static void do_test(uv_udp_recv_cb recv_cb, int bind_flags) { r = uv_udp_bind(&server, (const struct sockaddr*) &addr6, bind_flags); ASSERT(r == 0); + addr6_len = sizeof(addr6); + ASSERT(uv_udp_getsockname(&server, (struct sockaddr*) &addr6, &addr6_len) == 0); + ASSERT(uv_inet_ntop(addr6.sin6_family, &addr6.sin6_addr, dst, sizeof(dst)) == 0); + printf("on [%.*s]:%d\n", (int) sizeof(dst), dst, addr6.sin6_port); + r = uv_udp_recv_start(&server, alloc_cb, recv_cb); ASSERT(r == 0); r = uv_udp_init(uv_default_loop(), &client); ASSERT(r == 0); - buf = uv_buf_init("PING", 4); ASSERT(0 == uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); + ASSERT(uv_inet_ntop(addr.sin_family, &addr.sin_addr, dst, sizeof(dst)) == 0); + printf("to [%.*s]:%d\n", (int) sizeof(dst), dst, addr.sin_port); + + /* Create some unique data to send */ + ASSERT(9 == snprintf(data, sizeof(data), "PING%5u", uv_os_getpid() & 0xFFFF)); + buf = uv_buf_init(data, 9); + printf("sending %s\n", data); r = uv_udp_send(&req_, &client, @@ -144,6 +187,12 @@ static void do_test(uv_udp_recv_cb recv_cb, int bind_flags) { send_cb); ASSERT(r == 0); + addr_len = sizeof(addr); + ASSERT(uv_udp_getsockname(&client, (struct sockaddr*) &addr, &addr_len) == 0); + ASSERT(uv_inet_ntop(addr.sin_family, &addr.sin_addr, dst, sizeof(dst)) == 0); + printf("from [%.*s]:%d\n", (int) sizeof(dst), dst, addr.sin_port); + client_port = addr.sin_port; + r = uv_timer_init(uv_default_loop(), &timeout); ASSERT(r == 0); @@ -180,6 +229,8 @@ TEST_IMPL(udp_dual_stack) { do_test(ipv6_recv_ok, 0); + printf("recv_cb_called %d\n", recv_cb_called); + printf("send_cb_called %d\n", send_cb_called); ASSERT(recv_cb_called == 1); ASSERT(send_cb_called == 1); diff --git a/test/test-udp-multicast-interface.c b/test/test-udp-multicast-interface.c index 0b3c0e6..9d36098 100644 --- a/test/test-udp-multicast-interface.c +++ b/test/test-udp-multicast-interface.c @@ -54,6 +54,11 @@ static void sv_send_cb(uv_udp_send_t* req, int status) { TEST_IMPL(udp_multicast_interface) { +/* TODO(gengjiawen): Fix test on QEMU. */ +#if defined(__QEMU__) + RETURN_SKIP("Test does not currently work in QEMU"); +#endif + int r; uv_udp_send_t req; uv_buf_t buf; diff --git a/test/test-udp-multicast-interface6.c b/test/test-udp-multicast-interface6.c index 40b0553..23a68a0 100644 --- a/test/test-udp-multicast-interface6.c +++ b/test/test-udp-multicast-interface6.c @@ -54,6 +54,11 @@ static void sv_send_cb(uv_udp_send_t* req, int status) { TEST_IMPL(udp_multicast_interface6) { +/* TODO(gengjiawen): Fix test on QEMU. */ +#if defined(__QEMU__) + RETURN_SKIP("Test does not currently work in QEMU"); +#endif + int r; uv_udp_send_t req; uv_buf_t buf; diff --git a/test/test-udp-multicast-join.c b/test/test-udp-multicast-join.c index 053d2f7..cb3ff87 100644 --- a/test/test-udp-multicast-join.c +++ b/test/test-udp-multicast-join.c @@ -29,8 +29,12 @@ #define CHECK_HANDLE(handle) \ ASSERT((uv_udp_t*)(handle) == &server || (uv_udp_t*)(handle) == &client) +#define MULTICAST_ADDR "239.255.0.1" + static uv_udp_t server; static uv_udp_t client; +static uv_udp_send_t req; +static uv_udp_send_t req_ss; static int cl_recv_cb_called; @@ -62,7 +66,26 @@ static void sv_send_cb(uv_udp_send_t* req, int status) { sv_send_cb_called++; - uv_close((uv_handle_t*) req->handle, close_cb); + if (sv_send_cb_called == 2) + uv_close((uv_handle_t*) req->handle, close_cb); +} + + +static int do_send(uv_udp_send_t* send_req) { + uv_buf_t buf; + struct sockaddr_in addr; + + buf = uv_buf_init("PING", 4); + + ASSERT(0 == uv_ip4_addr(MULTICAST_ADDR, TEST_PORT, &addr)); + + /* client sends "PING" */ + return uv_udp_send(send_req, + &client, + &buf, + 1, + (const struct sockaddr*) &addr, + sv_send_cb); } @@ -74,8 +97,6 @@ static void cl_recv_cb(uv_udp_t* handle, CHECK_HANDLE(handle); ASSERT(flags == 0); - cl_recv_cb_called++; - if (nread < 0) { ASSERT(0 && "unexpected error"); } @@ -90,18 +111,37 @@ static void cl_recv_cb(uv_udp_t* handle, ASSERT(nread == 4); ASSERT(!memcmp("PING", buf->base, nread)); - /* we are done with the client handle, we can close it */ - uv_close((uv_handle_t*) &client, close_cb); + cl_recv_cb_called++; + + if (cl_recv_cb_called == 2) { + /* we are done with the server handle, we can close it */ + uv_close((uv_handle_t*) &server, close_cb); + } else { + int r; + char source_addr[64]; + + r = uv_ip4_name((const struct sockaddr_in*)addr, source_addr, sizeof(source_addr)); + ASSERT(r == 0); + + r = uv_udp_set_membership(&server, MULTICAST_ADDR, NULL, UV_LEAVE_GROUP); + ASSERT(r == 0); + +#if !defined(__OpenBSD__) && !defined(__NetBSD__) + r = uv_udp_set_source_membership(&server, MULTICAST_ADDR, NULL, source_addr, UV_JOIN_GROUP); + ASSERT(r == 0); +#endif + + r = do_send(&req_ss); + ASSERT(r == 0); + } } TEST_IMPL(udp_multicast_join) { int r; - uv_udp_send_t req; - uv_buf_t buf; struct sockaddr_in addr; - ASSERT(0 == uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); + ASSERT(0 == uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); r = uv_udp_init(uv_default_loop(), &server); ASSERT(r == 0); @@ -110,27 +150,19 @@ TEST_IMPL(udp_multicast_join) { ASSERT(r == 0); /* bind to the desired port */ - r = uv_udp_bind(&client, (const struct sockaddr*) &addr, 0); + r = uv_udp_bind(&server, (const struct sockaddr*) &addr, 0); ASSERT(r == 0); /* join the multicast channel */ - r = uv_udp_set_membership(&client, "239.255.0.1", NULL, UV_JOIN_GROUP); + r = uv_udp_set_membership(&server, MULTICAST_ADDR, NULL, UV_JOIN_GROUP); if (r == UV_ENODEV) RETURN_SKIP("No multicast support."); ASSERT(r == 0); - r = uv_udp_recv_start(&client, alloc_cb, cl_recv_cb); + r = uv_udp_recv_start(&server, alloc_cb, cl_recv_cb); ASSERT(r == 0); - buf = uv_buf_init("PING", 4); - - /* server sends "PING" */ - r = uv_udp_send(&req, - &server, - &buf, - 1, - (const struct sockaddr*) &addr, - sv_send_cb); + r = do_send(&req); ASSERT(r == 0); ASSERT(close_cb_called == 0); @@ -140,8 +172,8 @@ TEST_IMPL(udp_multicast_join) { /* run the loop till all events are processed */ uv_run(uv_default_loop(), UV_RUN_DEFAULT); - ASSERT(cl_recv_cb_called == 1); - ASSERT(sv_send_cb_called == 1); + ASSERT(cl_recv_cb_called == 2); + ASSERT(sv_send_cb_called == 2); ASSERT(close_cb_called == 2); MAKE_VALGRIND_HAPPY(); diff --git a/test/test-udp-multicast-join6.c b/test/test-udp-multicast-join6.c index bda5e20..40aa577 100644 --- a/test/test-udp-multicast-join6.c +++ b/test/test-udp-multicast-join6.c @@ -30,8 +30,23 @@ #define CHECK_HANDLE(handle) \ ASSERT((uv_udp_t*)(handle) == &server || (uv_udp_t*)(handle) == &client) +#if defined(__APPLE__) || \ + defined(_AIX) || \ + defined(__MVS__) || \ + defined(__FreeBSD_kernel__) || \ + defined(__NetBSD__) || \ + defined(__OpenBSD__) + #define MULTICAST_ADDR "ff02::1%lo0" + #define INTERFACE_ADDR "::1%lo0" +#else + #define MULTICAST_ADDR "ff02::1" + #define INTERFACE_ADDR NULL +#endif + static uv_udp_t server; static uv_udp_t client; +static uv_udp_send_t req; +static uv_udp_send_t req_ss; static int cl_recv_cb_called; @@ -63,7 +78,26 @@ static void sv_send_cb(uv_udp_send_t* req, int status) { sv_send_cb_called++; - uv_close((uv_handle_t*) req->handle, close_cb); + if (sv_send_cb_called == 2) + uv_close((uv_handle_t*) req->handle, close_cb); +} + + +static int do_send(uv_udp_send_t* send_req) { + uv_buf_t buf; + struct sockaddr_in6 addr; + + buf = uv_buf_init("PING", 4); + + ASSERT(0 == uv_ip6_addr(MULTICAST_ADDR, TEST_PORT, &addr)); + + /* client sends "PING" */ + return uv_udp_send(send_req, + &client, + &buf, + 1, + (const struct sockaddr*) &addr, + sv_send_cb); } @@ -75,8 +109,6 @@ static void cl_recv_cb(uv_udp_t* handle, CHECK_HANDLE(handle); ASSERT(flags == 0); - cl_recv_cb_called++; - if (nread < 0) { ASSERT(0 && "unexpected error"); } @@ -91,21 +123,57 @@ static void cl_recv_cb(uv_udp_t* handle, ASSERT(nread == 4); ASSERT(!memcmp("PING", buf->base, nread)); - /* we are done with the client handle, we can close it */ - uv_close((uv_handle_t*) &client, close_cb); + cl_recv_cb_called++; + + if (cl_recv_cb_called == 2) { + /* we are done with the server handle, we can close it */ + uv_close((uv_handle_t*) &server, close_cb); + } else { + int r; + char source_addr[64]; + + r = uv_ip6_name((const struct sockaddr_in6*)addr, source_addr, sizeof(source_addr)); + ASSERT(r == 0); + + r = uv_udp_set_membership(&server, MULTICAST_ADDR, INTERFACE_ADDR, UV_LEAVE_GROUP); + ASSERT(r == 0); + + r = uv_udp_set_source_membership(&server, MULTICAST_ADDR, INTERFACE_ADDR, source_addr, UV_JOIN_GROUP); + ASSERT(r == 0); + + r = do_send(&req_ss); + ASSERT(r == 0); + } +} + + +static int can_ipv6_external(void) { + uv_interface_address_t* addr; + int supported; + int count; + int i; + + if (uv_interface_addresses(&addr, &count)) + return 0; /* Assume no IPv6 support on failure. */ + + supported = 0; + for (i = 0; supported == 0 && i < count; i += 1) + supported = (AF_INET6 == addr[i].address.address6.sin6_family && + !addr[i].is_internal); + + uv_free_interface_addresses(addr, count); + return supported; } TEST_IMPL(udp_multicast_join6) { int r; - uv_udp_send_t req; - uv_buf_t buf; struct sockaddr_in6 addr; - if (!can_ipv6()) - RETURN_SKIP("IPv6 not supported"); + if (!can_ipv6_external()) + RETURN_SKIP("No external IPv6 interface available"); - ASSERT(0 == uv_ip6_addr("::1", TEST_PORT, &addr)); + ASSERT(0 == uv_ip6_addr("::", TEST_PORT, &addr)); r = uv_udp_init(uv_default_loop(), &server); ASSERT(r == 0); @@ -114,20 +182,10 @@ TEST_IMPL(udp_multicast_join6) { ASSERT(r == 0); /* bind to the desired port */ - r = uv_udp_bind(&client, (const struct sockaddr*) &addr, 0); + r = uv_udp_bind(&server, (const struct sockaddr*) &addr, 0); ASSERT(r == 0); - /* join the multicast channel */ -#if defined(__APPLE__) || \ - defined(_AIX) || \ - defined(__MVS__) || \ - defined(__FreeBSD_kernel__) || \ - defined(__NetBSD__) || \ - defined(__OpenBSD__) - r = uv_udp_set_membership(&client, "ff02::1", "::1%lo0", UV_JOIN_GROUP); -#else - r = uv_udp_set_membership(&client, "ff02::1", NULL, UV_JOIN_GROUP); -#endif + r = uv_udp_set_membership(&server, MULTICAST_ADDR, INTERFACE_ADDR, UV_JOIN_GROUP); if (r == UV_ENODEV) { MAKE_VALGRIND_HAPPY(); RETURN_SKIP("No ipv6 multicast route"); @@ -135,18 +193,14 @@ TEST_IMPL(udp_multicast_join6) { ASSERT(r == 0); - r = uv_udp_recv_start(&client, alloc_cb, cl_recv_cb); +/* TODO(gengjiawen): Fix test on QEMU. */ +#if defined(__QEMU__) + RETURN_SKIP("Test does not currently work in QEMU"); +#endif + r = uv_udp_recv_start(&server, alloc_cb, cl_recv_cb); ASSERT(r == 0); - - buf = uv_buf_init("PING", 4); - - /* server sends "PING" */ - r = uv_udp_send(&req, - &server, - &buf, - 1, - (const struct sockaddr*) &addr, - sv_send_cb); + + r = do_send(&req); ASSERT(r == 0); ASSERT(close_cb_called == 0); @@ -156,8 +210,8 @@ TEST_IMPL(udp_multicast_join6) { /* run the loop till all events are processed */ uv_run(uv_default_loop(), UV_RUN_DEFAULT); - ASSERT(cl_recv_cb_called == 1); - ASSERT(sv_send_cb_called == 1); + ASSERT(cl_recv_cb_called == 2); + ASSERT(sv_send_cb_called == 2); ASSERT(close_cb_called == 2); MAKE_VALGRIND_HAPPY(); diff --git a/test/test-udp-open.c b/test/test-udp-open.c index ee04c99..dee408b 100644 --- a/test/test-udp-open.c +++ b/test/test-udp-open.c @@ -27,6 +27,8 @@ #ifndef _WIN32 # include +# include +# include #endif static int send_cb_called = 0; @@ -129,6 +131,7 @@ static void send_cb(uv_udp_send_t* req, int status) { ASSERT(status == 0); send_cb_called++; + uv_close((uv_handle_t*)req->handle, close_cb); } @@ -215,3 +218,133 @@ TEST_IMPL(udp_open_twice) { MAKE_VALGRIND_HAPPY(); return 0; } + +TEST_IMPL(udp_open_bound) { + struct sockaddr_in addr; + uv_udp_t client; + uv_os_sock_t sock; + int r; + + ASSERT(0 == uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); + + startup(); + sock = create_udp_socket(); + + r = bind(sock, (struct sockaddr*) &addr, sizeof(addr)); + ASSERT(r == 0); + + r = uv_udp_init(uv_default_loop(), &client); + ASSERT(r == 0); + + r = uv_udp_open(&client, sock); + ASSERT(r == 0); + + r = uv_udp_recv_start(&client, alloc_cb, recv_cb); + ASSERT(r == 0); + + uv_close((uv_handle_t*) &client, NULL); + uv_run(uv_default_loop(), UV_RUN_DEFAULT); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +TEST_IMPL(udp_open_connect) { + struct sockaddr_in addr; + uv_buf_t buf = uv_buf_init("PING", 4); + uv_udp_t client; + uv_udp_t server; + uv_os_sock_t sock; + int r; + + ASSERT(0 == uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); + + startup(); + sock = create_udp_socket(); + + r = uv_udp_init(uv_default_loop(), &client); + ASSERT(r == 0); + + r = connect(sock, (const struct sockaddr*) &addr, sizeof(addr)); + ASSERT(r == 0); + + r = uv_udp_open(&client, sock); + ASSERT(r == 0); + + r = uv_udp_init(uv_default_loop(), &server); + ASSERT(r == 0); + + r = uv_udp_bind(&server, (const struct sockaddr*) &addr, 0); + ASSERT(r == 0); + + r = uv_udp_recv_start(&server, alloc_cb, recv_cb); + ASSERT(r == 0); + + r = uv_udp_send(&send_req, + &client, + &buf, + 1, + NULL, + send_cb); + ASSERT(r == 0); + + uv_run(uv_default_loop(), UV_RUN_DEFAULT); + + ASSERT(send_cb_called == 1); + ASSERT(close_cb_called == 2); + + ASSERT(client.send_queue_size == 0); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +#ifndef _WIN32 +TEST_IMPL(udp_send_unix) { + /* Test that "uv_udp_send()" supports sending over + a "sockaddr_un" address. */ + struct sockaddr_un addr; + uv_udp_t handle; + uv_udp_send_t req; + uv_loop_t* loop; + uv_buf_t buf = uv_buf_init("PING", 4); + int fd; + int r; + + loop = uv_default_loop(); + + memset(&addr, 0, sizeof addr); + addr.sun_family = AF_UNIX; + ASSERT(strlen(TEST_PIPENAME) < sizeof(addr.sun_path)); + memcpy(addr.sun_path, TEST_PIPENAME, strlen(TEST_PIPENAME)); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + ASSERT(fd >= 0); + + unlink(TEST_PIPENAME); + ASSERT(0 == bind(fd, (const struct sockaddr*)&addr, sizeof addr)); + ASSERT(0 == listen(fd, 1)); + + r = uv_udp_init(loop, &handle); + ASSERT(r == 0); + r = uv_udp_open(&handle, fd); + ASSERT(r == 0); + uv_run(loop, UV_RUN_DEFAULT); + + r = uv_udp_send(&req, + &handle, + &buf, + 1, + (const struct sockaddr*) &addr, + NULL); + ASSERT(r == 0); + + uv_close((uv_handle_t*)&handle, NULL); + uv_run(loop, UV_RUN_DEFAULT); + close(fd); + unlink(TEST_PIPENAME); + + MAKE_VALGRIND_HAPPY(); + return 0; +} +#endif diff --git a/test/test-udp-options.c b/test/test-udp-options.c index d8c9d68..3ea51ba 100644 --- a/test/test-udp-options.c +++ b/test/test-udp-options.c @@ -128,12 +128,16 @@ TEST_IMPL(udp_no_autobind) { ASSERT(UV_EBADF == uv_udp_set_ttl(&h, 1)); #endif ASSERT(UV_EBADF == uv_udp_set_multicast_loop(&h, 1)); +/* TODO(gengjiawen): Fix test on QEMU. */ +#if defined(__QEMU__) + RETURN_SKIP("Test does not currently work in QEMU"); +#endif ASSERT(UV_EBADF == uv_udp_set_multicast_interface(&h, "0.0.0.0")); uv_close((uv_handle_t*) &h, NULL); /* Test a non-lazily initialized socket. */ - ASSERT(0 == uv_udp_init_ex(loop, &h2, AF_INET)); + ASSERT(0 == uv_udp_init_ex(loop, &h2, AF_INET | UV_UDP_RECVMMSG)); ASSERT(0 == uv_udp_set_multicast_ttl(&h2, 32)); ASSERT(0 == uv_udp_set_broadcast(&h2, 1)); diff --git a/test/test-uname.c b/test/test-uname.c new file mode 100644 index 0000000..105a17f --- /dev/null +++ b/test/test-uname.c @@ -0,0 +1,69 @@ +/* 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 + +#ifndef _WIN32 +# include +#endif + +TEST_IMPL(uname) { +#ifndef _WIN32 + struct utsname buf; +#endif +#ifdef _AIX + char temp[256]; +#endif + uv_utsname_t buffer; + int r; + + /* Verify that NULL is handled properly. */ + r = uv_os_uname(NULL); + ASSERT(r == UV_EINVAL); + + /* Verify the happy path. */ + r = uv_os_uname(&buffer); + ASSERT(r == 0); + +#ifndef _WIN32 + ASSERT(uname(&buf) != -1); + ASSERT(strcmp(buffer.sysname, buf.sysname) == 0); + ASSERT(strcmp(buffer.version, buf.version) == 0); + +# ifdef _AIX + snprintf(temp, sizeof(temp), "%s.%s", buf.version, buf.release); + ASSERT(strcmp(buffer.release, temp) == 0); +# else + ASSERT(strcmp(buffer.release, buf.release) == 0); +# endif /* _AIX */ + +# if defined(_AIX) || defined(__PASE__) + ASSERT(strcmp(buffer.machine, "ppc64") == 0); +# else + ASSERT(strcmp(buffer.machine, buf.machine) == 0); +# endif /* defined(_AIX) || defined(__PASE__) */ + +#endif /* _WIN32 */ + + return 0; +} diff --git a/test/test-watcher-cross-stop.c b/test/test-watcher-cross-stop.c index 29a82a5..b26deb8 100644 --- a/test/test-watcher-cross-stop.c +++ b/test/test-watcher-cross-stop.c @@ -26,8 +26,9 @@ #include /* NOTE: Number should be big enough to trigger this problem */ -#if defined(__CYGWIN__) || defined(__MSYS__) +#if defined(__CYGWIN__) || defined(__MSYS__) || defined(__PASE__) /* Cygwin crashes or hangs in socket() with too many AF_INET sockets. */ +/* IBMi PASE timeout with too many AF_INET sockets. */ static uv_udp_t sockets[1250]; #else static uv_udp_t sockets[2500]; diff --git a/uv_win_longpath.manifest b/uv_win_longpath.manifest new file mode 100644 index 0000000..8976e6d --- /dev/null +++ b/uv_win_longpath.manifest @@ -0,0 +1,8 @@ + + + + + true + + +