diff --git a/.clang-format-ignore b/.clang-format-ignore index 4ed9bc6c3..495d36387 100644 --- a/.clang-format-ignore +++ b/.clang-format-ignore @@ -12,3 +12,6 @@ src/common/pist_check.cc include/pistache/pist_check.h src/common/pist_syslog.cc include/pistache/pist_syslog.h +src/common/ps_strl.cc +include/pistache/ps_strl.h +include/pistache/winornix.h diff --git a/.github/workflows/linux.yaml b/.github/workflows/linux.yaml index 7d0695302..0040a1e0c 100644 --- a/.github/workflows/linux.yaml +++ b/.github/workflows/linux.yaml @@ -57,7 +57,7 @@ jobs: run: | if [ ${{ matrix.compiler }} = gcc ]; then compiler=g++; else compiler="clang lld ?exact-name(libclang-rt-dev)"; fi apt -y update - apt -y install $compiler meson pkg-config cmake rapidjson-dev libssl-dev netbase '?exact-name(libhowardhinnant-date-dev)' '?exact-name(libgmock-dev) (?version([1-9]\.[1-9][1-9]) | ?version([1-9]\.[2-9][0-9]))' '?exact-name(libcpp-httplib-dev)' libcurl4-openssl-dev git ca-certificates curl gpg gpgv gpg-agent lcov llvm-dev --no-install-recommends + apt -y install $compiler meson pkg-config cmake brotli libbrotli-dev zstd libzstd-dev rapidjson-dev libssl-dev netbase '?exact-name(libhowardhinnant-date-dev)' '?exact-name(libgmock-dev) (?version([1-9]\.[1-9][1-9]) | ?version([1-9]\.[2-9][0-9]))' '?exact-name(libcpp-httplib-dev)' libcurl4-openssl-dev git ca-certificates curl gpg gpgv gpg-agent lcov llvm-dev --no-install-recommends # Periodically, debian:testing fails with clang, saying that # libstdc++ cannot be found. In debian:testing/clang, normally @@ -79,7 +79,7 @@ jobs: if: contains(matrix.os, 'redhat') run: | if [ ${{ matrix.compiler }} = gcc ]; then compiler=gcc-c++; else compiler=llvm-toolset; fi - microdnf -y install $compiler lld pkgconf cmake openssl-devel zlib-devel libcurl-devel git python3-pip unzip + microdnf -y install $compiler lld pkgconf cmake brotli brotli-devel openssl-devel zlib-devel libcurl-devel git python3-pip unzip curl -LO https://github.com/ninja-build/ninja/releases/latest/download/ninja-linux.zip unzip ninja-linux.zip mv ninja /usr/local/bin @@ -94,8 +94,9 @@ jobs: run: | if [ ${{ matrix.compiler }} = gcc ]; then CXX=g++; else CXX=clang++ CXX_LD=lld; fi export CXX CXX_LD + if [[ ${{ matrix.os }} == *redhat* ]]; then PST_USE_ZSTD=false; else PST_USE_ZSTD=true; fi meson setup build \ - -DPISTACHE_BUILD_TESTS=true -DPISTACHE_USE_SSL=${{ matrix.tls }} -DPISTACHE_USE_CONTENT_ENCODING_DEFLATE=true \ + -DPISTACHE_BUILD_TESTS=true -DPISTACHE_USE_SSL=${{ matrix.tls }} -DPISTACHE_USE_CONTENT_ENCODING_DEFLATE=true -DPISTACHE_USE_CONTENT_ENCODING_BROTLI=true -DPISTACHE_USE_CONTENT_ENCODING_ZSTD=$PST_USE_ZSTD \ --buildtype=debug -Db_coverage=true -Db_sanitize=${{ matrix.sanitizer }} -Db_lundef=false \ || (cat build/meson-logs/meson-log.txt ; false) env: diff --git a/.github/workflows/linuxflibev.yaml b/.github/workflows/linuxflibev.yaml index 1bfaf952d..8b4046cf1 100644 --- a/.github/workflows/linuxflibev.yaml +++ b/.github/workflows/linuxflibev.yaml @@ -46,13 +46,13 @@ jobs: run: | if [ ${{ matrix.compiler }} = gcc ]; then compiler=g++; else compiler="clang lld ?exact-name(libclang-rt-dev)"; fi apt -y update - apt -y install $compiler meson pkg-config cmake rapidjson-dev libssl-dev libevent-dev netbase '?exact-name(libhowardhinnant-date-dev)' '?exact-name(libgmock-dev) (?version([1-9]\.[1-9][1-9]) | ?version([1-9]\.[2-9][0-9]))' '?exact-name(libcpp-httplib-dev)' libcurl4-openssl-dev git ca-certificates curl gpg gpgv gpg-agent lcov llvm-dev --no-install-recommends + apt -y install $compiler meson pkg-config cmake brotli libbrotli-dev zstd libzstd-dev rapidjson-dev libssl-dev libevent-dev netbase '?exact-name(libhowardhinnant-date-dev)' '?exact-name(libgmock-dev) (?version([1-9]\.[1-9][1-9]) | ?version([1-9]\.[2-9][0-9]))' '?exact-name(libcpp-httplib-dev)' libcurl4-openssl-dev git ca-certificates curl gpg gpgv gpg-agent lcov llvm-dev --no-install-recommends - name: Install dependencies (Red Hat) if: contains(matrix.os, 'redhat') run: | if [ ${{ matrix.compiler }} = gcc ]; then compiler=gcc-c++; else compiler=llvm-toolset; fi - microdnf -y install $compiler pkgconf cmake openssl-devel zlib-devel libcurl-devel git python3-pip unzip + microdnf -y install $compiler pkgconf cmake brotli libbrotli zstd libzstd openssl-devel zlib-devel libcurl-devel git python3-pip unzip curl -LO https://github.com/ninja-build/ninja/releases/latest/download/ninja-linux.zip unzip ninja-linux.zip mv ninja /usr/local/bin @@ -68,7 +68,7 @@ jobs: if [ ${{ matrix.compiler }} = gcc ]; then CXX=g++; else CXX=clang++ CXX_LD=lld; fi export CXX CXX_LD meson setup build \ - -DPISTACHE_BUILD_TESTS=true -DPISTACHE_DEBUG=${{ matrix.def_debug }} -DPISTACHE_USE_SSL=${{ matrix.tls }} -DPISTACHE_FORCE_LIBEVENT=${{ matrix.flibev }} -DPISTACHE_USE_CONTENT_ENCODING_DEFLATE=true \ + -DPISTACHE_BUILD_TESTS=true -DPISTACHE_DEBUG=${{ matrix.def_debug }} -DPISTACHE_USE_SSL=${{ matrix.tls }} -DPISTACHE_FORCE_LIBEVENT=${{ matrix.flibev }} -DPISTACHE_USE_CONTENT_ENCODING_DEFLATE=true -DPISTACHE_USE_CONTENT_ENCODING_BROTLI=true -DPISTACHE_USE_CONTENT_ENCODING_ZSTD=true \ --buildtype=debug -Db_coverage=true -Db_sanitize=${{ matrix.sanitizer }} -Db_lundef=false \ || (cat build/meson-logs/meson-log.txt ; false) env: diff --git a/.github/workflows/macos.yaml b/.github/workflows/macos.yaml index 02cb135fb..1f84acbd5 100644 --- a/.github/workflows/macos.yaml +++ b/.github/workflows/macos.yaml @@ -75,6 +75,8 @@ jobs: brew install rapidjson # brew install libevent # Already installed in base image brew install howard-hinnant-date + if ! brew list brotli &>/dev/null; then brew install brotli; fi + if ! brew list zstd &>/dev/null; then brew install zstd; fi - uses: actions/checkout@v4 with: @@ -91,7 +93,7 @@ jobs: echo "Using CXX $CXX, and CC $CC" meson setup build \ - -DPISTACHE_BUILD_TESTS=true -DPISTACHE_DEBUG=${{ matrix.def_debug }} -DPISTACHE_USE_SSL=${{ matrix.tls }} -DPISTACHE_BUILD_EXAMPLES=true -DPISTACHE_BUILD_DOCS=false -DPISTACHE_USE_CONTENT_ENCODING_DEFLATE=true \ + -DPISTACHE_BUILD_TESTS=true -DPISTACHE_DEBUG=${{ matrix.def_debug }} -DPISTACHE_USE_SSL=${{ matrix.tls }} -DPISTACHE_BUILD_EXAMPLES=true -DPISTACHE_BUILD_DOCS=false -DPISTACHE_USE_CONTENT_ENCODING_DEFLATE=true -DPISTACHE_USE_CONTENT_ENCODING_BROTLI=true -DPISTACHE_USE_CONTENT_ENCODING_ZSTD=true \ --buildtype=debug -Db_coverage=true -Db_sanitize=${{ matrix.sanitizer }} -Db_lundef=false \ || (cat build/meson-logs/meson-log.txt ; false) env: diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml new file mode 100644 index 000000000..59fda8a1d --- /dev/null +++ b/.github/workflows/windows.yaml @@ -0,0 +1,407 @@ +# SPDX-FileCopyrightText: 2024 Duncan Greatwood +# +# SPDX-License-Identifier: Apache-2.0 + +# See: +# https://docs.github.com/en/actions/writing-workflows/choosing-where-your-workflow-runs/choosing-the-runner-for-a-job + +name: Windows + +on: + push: + branches: + - master + pull_request: + branches: + - master + +defaults: + run: + shell: powershell + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + windows: + name: Windows with Libevent + + strategy: + fail-fast: false + matrix: + os: [ 'windows-2019', 'windows-latest' ] # Oct/2024, latest = 2022 + compiler: [ 'msvc', 'gcc' ] # Could also add clang + sanitizer: [ 'address', 'none' ] + tls: [ 'true', 'false' ] + def_debug: [ 'true', 'false' ] + exclude: + # VS only supports address sanitizer as of Oct/2024 + # mingw-gcc does not support any sanitizer as of Oct/2024 + - compiler: gcc + sanitizer: address + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + # For Windows, we check out first, before installing + # dependencies, so we can use the Pistache scripts + # msvcsetup.ps1 and gccsetup.ps1 for dependency installation + + - name: Install dependencies (Windows) + env: + mtrx_compiler: ${{ matrix.compiler }} + if: contains(matrix.os, 'windows') + run: > + $savedpwd=$pwd + + # Save variables that we store to a file to be restored on next step + + $csvarray = @() + + if ($env:mtrx_compiler -eq "msvc") { + + . winscripts\msvcsetup.ps1 + + cd ~ + + $myobj = "" | Select "name", "val"; + $myobj.name = "launch_vs_dev_shell_dir"; + $myobj.val = $launch_vs_dev_shell_dir; + $csvarray += $myobj + + if ($vs_instance_id) { + $myobj = "" | Select "name", "val"; + $myobj.name = "vs_instance_id"; + $myobj.val = $vs_instance_id; + $csvarray += $myobj + } + + if ($dev_shell_path) { + $myobj = "" | Select "name", "val"; + $myobj.name = "dev_shell_path"; + $myobj.val = $dev_shell_path; + $csvarray += $myobj + } + + if ($env:VSCMD_DEBUG) { + $myobj = "" | Select "name", "val"; + $myobj.name = "env:VSCMD_DEBUG"; + $myobj.val = $env:VSCMD_DEBUG; + $csvarray += $myobj + } + } # end 'if ($env:mtrx_compiler -eq "msvc") {...}' + + elseif ($env:mtrx_compiler -eq "gcc") { + $env:force_msys_gcc = "Y"; + . winscripts\gccsetup.ps1 + } + else { # Use clang with MSVC (MSVC's stdlib etc.) + + $env:CXX="clang-cl"; + $env:CXX_LD="lld"; + $env:CC=$env:CXX + } + + if ($env:CXX) { + $myobj = "" | Select "name", "val"; + $myobj.name = "env:CXX"; + $myobj.val = $env:CXX; + $csvarray += $myobj + } + + if ($env:CC) { + $myobj = "" | Select "name", "val"; + $myobj.name = "env:CC"; + $myobj.val = $env:CC; + $csvarray += $myobj + } + + if ($env:CXX_LD) { + $myobj = "" | Select "name", "val"; + $myobj.name = "env:CXX_LD"; + $myobj.val = $env:CXX_LD; + $csvarray += $myobj + } + + if (Test-Path -Path "$env:USERPROFILE\doxygen.bin") { + $env:Path="$env:Path;$env:USERPROFILE\doxygen.bin" + } + + if ($env:PKG_CONFIG_PATH) { + $myobj = "" | Select "name", "val"; + $myobj.name = "env:PKG_CONFIG_PATH"; + $myobj.val = $env:PKG_CONFIG_PATH; + $csvarray += $myobj + } + + if ($env:Path) { + $myobj = "" | Select "name", "val"; + $myobj.name = "env:Path"; + $myobj.val = $env:Path; + $csvarray += $myobj + } + + # Save vars for use in later steps + + cd ~; + $csvarray | export-csv "tmpvars.csv" + + cat "tmpvars.csv" + + - name: Configure Meson + env: + mtrx_compiler: ${{ matrix.compiler }} + mtrx_def_debug: ${{ matrix.def_debug }} + mtrx_tls: ${{ matrix.tls }} + mtrx_sanitizer: ${{ matrix.sanitizer }} + run: > + $savedpwd=$pwd + + cd ~; + $impcsv = import-csv -Path "tmpvars.csv"; + foreach ($impobj in $impcsv) { + $impobj_name=$impobj.name; + $impobj_val=$impobj.val; + $assign_cmd=-join('$', "$impobj_name", '="', "$impobj_val", '"'); + echo "assign_cmd is $assign_cmd"; + Invoke-Expression "$assign_cmd" + } + + if ($env:mtrx_compiler -eq "msvc") { + # Start a Visual Studio Developer Prompt, for access to compiler "cl" + + if (($dev_shell_path) -and (Test-Path -Path $dev_shell_path)) { + Write-Host "Doing Import-Module and Enter-VsDevShell"; + Import-Module "$dev_shell_path"; + $env:VSCMD_DEBUG=1; + Enter-VsDevShell -VsInstanceId $vs_instance_id -SkipAutomaticLocation -DevCmdDebugLevel Basic -DevCmdArguments '-arch=x64' + } + elseif (($launch_vs_dev_shell_dir) -and (Test-Path -Path $launch_vs_dev_shell_dir)) { + cd "$launch_vs_dev_shell_dir"; + ./Launch-VsDevShell.ps1 -Arch amd64 -HostArch amd64 + } + else { + Write-Error + "ERROR: Failed to start Visual Studio Developer Prompt" + } + + # Import vars again in case starting dev prompt overwrote anything + + cd ~; + $impcsv = import-csv -Path "tmpvars.csv"; + foreach ($impobj in $impcsv) { + $impobj_name=$impobj.name; + $impobj_val=$impobj.val; + $assign_cmd=-join('$', "$impobj_name", '="', "$impobj_val", '"'); + echo "assign_cmd is $assign_cmd"; + Invoke-Expression "$assign_cmd" + } + } + + cd "$savedpwd" + + meson setup build -DPISTACHE_BUILD_TESTS=true -DPISTACHE_DEBUG="$env:mtrx_def_debug" -DPISTACHE_USE_SSL="$env:mtrx_tls" -DPISTACHE_BUILD_EXAMPLES=true -DPISTACHE_BUILD_DOCS=false -DPISTACHE_USE_CONTENT_ENCODING_DEFLATE=true -DPISTACHE_USE_CONTENT_ENCODING_BROTLI=true -DPISTACHE_USE_CONTENT_ENCODING_ZSTD=true --buildtype=debug -Db_coverage=true -Db_sanitize="$env:mtrx_sanitizer" -Db_lundef=false --prefix="$env:ProgramFiles\pistache_distribution" + + if (Test-Path -Path 'build/meson-logs/meson-log.txt') { + cat build/meson-logs/meson-log.txt + } + else { + Write-Error "ERROR: No meson output log found" + } + + - name: Build + env: + mtrx_compiler: ${{ matrix.compiler }} + run: > + $savedpwd=$pwd + + cd ~; + $impcsv = import-csv -Path "tmpvars.csv"; + foreach ($impobj in $impcsv) { + $impobj_name=$impobj.name; + $impobj_val=$impobj.val; + $assign_cmd=-join('$', "$impobj_name", '="', "$impobj_val", '"'); + echo "assign_cmd is $assign_cmd"; + Invoke-Expression "$assign_cmd" + } + + if ($env:mtrx_compiler -eq "msvc") { + # Start a Visual Studio Developer Prompt, for access to compiler "cl" + + if (($dev_shell_path) -and (Test-Path -Path $dev_shell_path)) { + Write-Host "Doing Import-Module and Enter-VsDevShell"; + Import-Module "$dev_shell_path"; + $env:VSCMD_DEBUG=1; + Enter-VsDevShell -VsInstanceId $vs_instance_id -SkipAutomaticLocation -DevCmdDebugLevel Basic -DevCmdArguments '-arch=x64' + } + elseif (($launch_vs_dev_shell_dir) -and (Test-Path -Path $launch_vs_dev_shell_dir)) { + cd "$launch_vs_dev_shell_dir"; + ./Launch-VsDevShell.ps1 -Arch amd64 -HostArch amd64 + } + else { + Write-Error + "ERROR: Failed to start Visual Studio Developer Prompt" + } + + # Import vars again in case starting dev prompt overwrote anything + + cd ~; + $impcsv = import-csv -Path "tmpvars.csv"; + foreach ($impobj in $impcsv) { + $impobj_name=$impobj.name; + $impobj_val=$impobj.val; + $assign_cmd=-join('$', "$impobj_name", '="', "$impobj_val", '"'); + echo "assign_cmd is $assign_cmd"; + Invoke-Expression "$assign_cmd" + } + } + cd "$savedpwd" + + ninja -C build + + - name: Test + env: + mtrx_compiler: ${{ matrix.compiler }} + mtrx_def_debug: ${{ matrix.def_debug }} + run: > + $savedpwd=$pwd + + cd ~; + $impcsv = import-csv -Path "tmpvars.csv"; + foreach ($impobj in $impcsv) { + $impobj_name=$impobj.name; + $impobj_val=$impobj.val; + $assign_cmd=-join('$', "$impobj_name", '="', "$impobj_val", '"'); + echo "assign_cmd is $assign_cmd"; + Invoke-Expression "$assign_cmd" + } + + # To make log messages for the test executables go to stdout, + # we can set the registry value psLogToStdoutAsWell to 1, as below. + + #if (($env:mtrx_compiler -eq "gcc") -and + # ($env:mtrx_def_debug -eq "false")) { + # $registryPath = "HKCU:\Software\pistacheio\pistache"; + # $valName = "psLogToStdoutAsWell"; + # if (! (Test-Path $registryPath)) { + # Write-Error "pistache registry entry missing?" + # } + # else { + # New-ItemProperty -Path $registryPath -Name $valName + # -Value 1 -PropertyType DWORD -Force + # } + # } + + # Alternatively, you could write the logging to a csv file. Do: + # logman start -f csv -ets Pistache -p "Pistache-Provider" 0 0 -o pistachelog.csv + # Then after "meson test" has run: + # logman stop Pistache -ets + # cat pistachelog.csv + # or + # logman stop Pistache -ets + # $plogcsv = import-csv -Path "pistachelog.csv" + # Write-Host $plogcsv + + if ($env:mtrx_compiler -eq "msvc") { + # Start a Visual Studio Developer Prompt + + if (($dev_shell_path) -and (Test-Path -Path $dev_shell_path)) { + Write-Host "Doing Import-Module and Enter-VsDevShell"; + Import-Module "$dev_shell_path"; + $env:VSCMD_DEBUG=1; + Enter-VsDevShell -VsInstanceId $vs_instance_id -SkipAutomaticLocation -DevCmdDebugLevel Basic -DevCmdArguments '-arch=x64' + } + elseif (($launch_vs_dev_shell_dir) -and (Test-Path -Path $launch_vs_dev_shell_dir)) { + cd "$launch_vs_dev_shell_dir"; + ./Launch-VsDevShell.ps1 -Arch amd64 -HostArch amd64 + } + else { + Write-Error + "ERROR: Failed to start Visual Studio Developer Prompt" + } + + # Import vars again in case starting dev prompt overwrote anything + + cd ~; + $impcsv = import-csv -Path "tmpvars.csv"; + foreach ($impobj in $impcsv) { + $impobj_name=$impobj.name; + $impobj_val=$impobj.val; + $assign_cmd=-join('$', "$impobj_name", '="', "$impobj_val", '"'); + echo "assign_cmd is $assign_cmd"; + Invoke-Expression "$assign_cmd" + } + } + + cd "$savedpwd" + + meson test --no-rebuild -C build --verbose + # Use the following to run just a single test (e.g. http_server_test) + # build/tests/run_http_server_test + + - name: Install + env: + mtrx_compiler: ${{ matrix.compiler }} + mtrx_def_debug: ${{ matrix.def_debug }} + run: > + $savedpwd=$pwd + + cd ~; + $impcsv = import-csv -Path "tmpvars.csv"; + foreach ($impobj in $impcsv) { + $impobj_name=$impobj.name; + $impobj_val=$impobj.val; + $assign_cmd=-join('$', "$impobj_name", '="', "$impobj_val", '"'); + echo "assign_cmd is $assign_cmd"; + Invoke-Expression "$assign_cmd" + } + + if ($env:mtrx_compiler -eq "msvc") { + # Start a Visual Studio Developer Prompt + + if (($dev_shell_path) -and (Test-Path -Path $dev_shell_path)) { + Write-Host "Doing Import-Module and Enter-VsDevShell"; + Import-Module "$dev_shell_path"; + $env:VSCMD_DEBUG=1; + Enter-VsDevShell -VsInstanceId $vs_instance_id -SkipAutomaticLocation -DevCmdDebugLevel Basic -DevCmdArguments '-arch=x64' + } + elseif (($launch_vs_dev_shell_dir) -and (Test-Path -Path $launch_vs_dev_shell_dir)) { + cd "$launch_vs_dev_shell_dir"; + ./Launch-VsDevShell.ps1 -Arch amd64 -HostArch amd64 + } + else { + Write-Error + "ERROR: Failed to start Visual Studio Developer Prompt" + } + + # Import vars again in case starting dev prompt overwrote anything + + cd ~; + $impcsv = import-csv -Path "tmpvars.csv"; + foreach ($impobj in $impcsv) { + $impobj_name=$impobj.name; + $impobj_val=$impobj.val; + $assign_cmd=-join('$', "$impobj_name", '="', "$impobj_val", '"'); + echo "assign_cmd is $assign_cmd"; + Invoke-Expression "$assign_cmd" + } + } + + cd "$savedpwd" + + if (Test-Path -Path "$env:ProgramFiles\pistache_distribution\bin\pistachelog.dll") { + rm "$env:ProgramFiles\pistache_distribution\bin\pistachelog.dll" + } + + meson install --no-rebuild -C build + + if (! (Test-Path -Path "$env:ProgramFiles\pistache_distribution\bin\pistachelog.dll")) { + throw "pistachelog.dll not installed as expected" + } + + # No coverage analysis for Windows diff --git a/.gitignore b/.gitignore index af8ccb74a..5e2fc9505 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ # Soft links to favorite build convenience files /build.sh /builddebug.sh +/builddebug.ps1 /copyall.sh # Compiled Object files diff --git a/Building on BSD - FreeBSD, OpenBSD and NetBSD.txt b/Building on BSD - FreeBSD, OpenBSD and NetBSD.txt index f6643b122..03a39a5a7 100644 --- a/Building on BSD - FreeBSD, OpenBSD and NetBSD.txt +++ b/Building on BSD - FreeBSD, OpenBSD and NetBSD.txt @@ -26,6 +26,8 @@ You will need the following Pistache-dependencies installed: rapidjson (*) howard-hinnant-date (*) libevent + brotli libbrotli-dev (presuming you want to include) + zstd libzstd-dev (presuming you want to include) See BSD-type-specific notes below regarding installing these dependencies. diff --git a/Building on Windows.txt b/Building on Windows.txt new file mode 100644 index 000000000..82ec0bbbf --- /dev/null +++ b/Building on Windows.txt @@ -0,0 +1,247 @@ +# SPDX-FileCopyrightText: 2024 Duncan Greatwood +# +# SPDX-License-Identifier: Apache-2.0 + +Building Pistache on Windows +============================ + +Pistache has been built and tested on Windows 11, Windows Server 2022 +and Windows Server 2019. It could perhaps be made to work on older +Windows versions too, though going back before Windows 8 would +be more difficult due to Windows API changes. + +Convenience scripts are provided to configure Pistache to be built +either with GCC (MinGW-w64) or with Visual Studio. These scripts will +download and install the needed dependencies on your Windows machine, +if not already present. + +The following instructions assume you are at a PowerShell terminal +prompt. The terminal prompt can be at the console of the target +Windows machine, or via a remote SSH session. Just a regular +PowerShell prompt is needed - you do NOT need to use a preconfigured +"Developer PowerShell" prompt, and in fact the Pistache configuration +scripts will configure a Developer prompt for you if required. + +To begin - clone, fork or download and expand the zip of Pistache to +fetch Pistache to your Windows machine: + To get Pistache using "git clone": + If you don't already have git, but do have winget on your machine: + Invoke-WebRequest -Uri "https://git.io/JXGyd" -OutFile "git.inf" + winget install Git.Git --override '/SILENT /LOADINF="git.inf"' + Or to install git without using winget: + $mingit_latest_url=(Invoke-WebRequest -Uri "https://raw.githubusercontent.com/git-for-windows/git-for-windows.github.io/refs/heads/main/latest-64-bit-mingit.url").Content + Invoke-WebRequest -Uri "$mingit_latest_url" -Outfile "mingit_latest.zip" + Expand-Archive -Path "mingit_latest.zip" -DestinationPath "$env:ProgramFiles/Git" + $git_dir = "$env:ProgramFiles/Git" + $env:Path="$git_dir\cmd;$git_dir\mingw64\bin;$git_dir\usr\bin;$env:Path" + You can configure your newly installed git: + git config --global user.email "you@somedomain.com" + git config --global user.name "Your Name" + etc. + Then, once git is installed: + git clone https://github.com/pistacheio/pistache.git + OR to get Pistache using zip download: + Invoke-WebRequest -Uri "https://github.com/pistacheio/pistache/archive/refs/heads/master.zip" -OutFile "pistache.zip" + Expand-Archive -Path "pistache.zip" -DestinationPath "pistache" + (Or you can download and expand the zip using a web browser in the GUI) + +Then configure for either GCC or Visual Studio: + cd pistache +and EITHER: + winscripts\msvcsetup.ps1 +OR: + winscripts\gccsetup.ps1 + +Note: You should run msvcsetup.ps1 or gccsetup.ps1 each time you start +a new PowerShell prompt that you will use for building Pistache. + +Then run: + winscripts\mesbuild.ps1 + winscripts\mestest.ps1 + winscripts\mesinstall.ps1 + +Pistache is installed to the pistache_distribution subdirectory of the +"Program Files" directory, most commonly: + C:\Program Files\pistache_distribution +You can link your own executables to Pistache via +\Program Files\pistache_distribution. Note that you will link your +executable to an import library (.lib file) and, to run your program, +you will ensure that your $env:path includes the location of the +Pistache DLL; Windows then loads the Pistache DLL when your executable +runs, based on the information provided in the .lib file. + +Note also that you do not need to link to or load pistachelog.dll in +any way. pistachelog.dll's only purpose is to provide a place to keep +certain strings that are needed when registering the Pistache logging +manifest with Windows logging (see "Logging" below). + +If you prefer not to use msvcsetup.ps1 or gccsetup.ps1, dependencies +can be installed manually and the environment can be configured +manually. See the later "Dependencies and Configuration" section +below. + + +Logging +======= + +In Windows, Pistache uses the ETW ("Event Tracing") system for logging. + +For events above level DEBUG, the event is sent to the Windows +Application channel. This makes events appear in Windows Event +Viewer. You can see the Pistache events in Event Viewer under Windows +Logs / Application, looking for Source type Pistache. + +DEBUG events (in Pistache debug builds) are sent to the Pistache debug +channel. + +Since DEBUG events are not consumed automatically by EventViewer via the +Application channel, you will need another event consumer to record the +events. You can use the Windows utility logman.exe. + +To use logman, you can do: + logman start -ets Pistache -p "Pistache-Provider" 0 0 -o pistache.etl + Then run your program, which you want to log. + Once your program is complete, you can do: + logman stop Pistache -ets +This causes the log information to be written out to pistache.etl + +You can view the etl file: + 1/ Use Event Viewer. (In Event Viewer, Action -> Open Saved Log, then + choose pistache.etl). + 2/ Convert the to XML: + tracerpt -y pistache.etl + Then view dumpfile.xml in a text editor or XML viewer +Alternatively, you can have logman generate a CSV file instead of an .etl +by adding the "-f csv" option to the "logman start..." command. + +logman has many other options, do "logman -?" to see the list. + +To use logging, you must also: + 1/ First, copy pistachelog.dll to the predefined location + "$env:ProgramFiles\pistache_distribution\bin" + 2/ Install the Pistache manifest file by doing: + wevtutil im "pist_winlog.man" +The Pistache build process will do both 1/ and 2/ automatically upon +running "meson build" (e.g. when doing winscripts\mesbuild.ps1). + +If needed or preferred you can also cause Pistache log messages to be +written to the console i.e. to "stdout". This is controlled by a +registry entry: + HKCU:\Software\pistacheio\pistache\psLogToStdoutAsWell +A value of zero means, don't send to stdout; 1 means do send to +stdout; and 10 means don't send to stdout even if the Pistache binary +has been preconfigured and built to send every log message to stdout. +The pistache build process creates the psLogToStdoutAsWell property +automatically, giving it an initial value of zero. You can change its +value via the WIndows GUI using the Windows Registry Editor, or at +PowerShell command line: + $psre_path = "HKCU:\Software\pistacheio\pistache" + Set-ItemProperty -Path $psre_path -Name "psLogToStdoutAsWell" -Value 1 +Pistache will respond dynamically while a program is running to a +change in psLogToStdoutAsWell's value, but typically you will set +psLogToStdoutAsWell's value before you run your program. + +Note: To run mesinstall.ps1 or mesinstalldebug.ps1, you will typically +have to close Windows Event Viewer if it's open. Event Viewer holds +open the prior pistachelog.dll, preventing it being overwritten by the +meson install methods. + + +Debug Builds +============ + +To create and install a debug version of Pistache, do the following: + winscripts\mesbuilddebug.ps1 + winscripts\mestestdebug.ps1 + winscripts\mesinstalldebug.ps1 + +The debug build can generate verbose debug log output if desired. + + +Dependencies and Configuration +============================== + +If installing dependencies by hand, install the following: + git + Either GCC or Visual Studio, or both + Windows Kits (aka Windows SDK) + vcpkg + pkg-config (aka pkgconf) + Ninja (comes automatically with Visual Studio) + Python3 (with PIP) + meson + doxygen + googletest + howard-hinnant-date + libevent + curl[openssl] + brotli (library) + zstd (library) + +Add the locations of the above utilities and libraries to +$env:path. Note that $env:path is used by Windows to look for +executables, but also to look for the locations of DLLs. + +Set $env:PKG_CONFIG_PATH to point to the location(s) of your +pkg-config information. If you have installed some components using +vcpkg, you will likely want to include: + \installed\x64-windows\lib\pkgconfig +in your $env:PKG_CONFIG_PATH. + +For GCC builds: + $env:CXX="g++" + $env:CC="gcc" +For Visual Studio builds, set: + $env:CXX="cl" + $env:CC=$env:CXX + +Once you have built your executable that links to Pistache, if you +find that the executable exits silently when you try to run it, it +probably means that the executable is failing to find a needed DLL on +$env:path, or it is finding the wrong version of a DLL. Try going to +the Windows GUI, open Windows Explorer, and double click on your +executable - when the executable tries and fails to run, Windows may +show you an error message that tells you what is wrong. + + +Using A Source-Code Debugger +============================ + +A debugger such as Visual Studio Code can be used to debug binaries +linked to a Pistache debug build, locally or remotely. + +For Visual Studio Code - + +For GCC debug builds, include in your launch.json entry: + "type": "cppdbg", + "MIMode": "gdb", + "miDebuggerPath": "C:\\msys64\\ucrt64\\bin\\gdb.exe", + "environment": [ + {"name": "Path", "value": + "C:\\msys64\\ucrt64\\bin;${env:Path};\\src;;C:\\vcpkg\\installed\\x64-windows\\bin;C:\\Program Files (x86)\\Windows Kits\\\\bin\\\\x64"}, + ], + (Substituting in your , + and ) + +For Visual Studio debug builds, include in your launch.json entry: + "type": "cppvsdbg", + "environment": [ + {"name": "Path", "value": + "C:\\msys64\\ucrt64\\bin;${env:Path};\\src;;C:\\vcpkg\\installed\\x64-windows\\bin;C:\\Program Files (x86)\\Windows Kits\\\\bin\\\\x64"}, + ], + (Substituting in your , + and ) + +You can also use the native debugggers, i.e. the Visual Studio +debugger for Visual Studio debug builds, and gdb for GCC debug builds +(note: gccsetup.ps1 configures gdb on the path if not already +installed). + + +How It Works +============ +Pistache on Windows works very much as it does on macOS, i.e. by using +the libevent library to provide the core event loop. Additionally, it +uses the libevent mechanisms to emulate Linux-style or macOS-style +edge-triggered events to the extent needed by Pistache, even though +Windows native events are level-triggered not edge-triggered. diff --git a/Building on macOS.txt b/Building on macOS.txt index ab066cb70..7c70da812 100644 --- a/Building on macOS.txt +++ b/Building on macOS.txt @@ -20,6 +20,9 @@ Then, install the necessary brew packages via terminal command line: brew install openssl brew install rapidjson brew install howard-hinnant-date + brew install libevent + brew install brotli + brew install zstd (You may be able to skip howard-hinnant-date) Convenience shell scripts are provided to make the build. At terminal, diff --git a/README.md b/README.md index 35c208840..91f0cddf5 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,9 @@ SPDX-License-Identifier: Apache-2.0 Pistache is a modern and elegant HTTP and REST framework for C++. It is entirely written in pure-C++17[\*](#linux-only) and provides a clear and pleasant API. -Pistache supports Linux and macOS. To use in macOS, see the file: *Building on macOS.txt* +Pistache supports Linux, macOS, Windows and BSD (FreeBSD, OpenBSD, and +NetBSD). To use in macOS, Windows, or BSD, see the respective files: +*Building on macOS.txt*, *Building on Windows.txt* or *Building on BSD.txt*. ## Documentation @@ -39,6 +41,9 @@ Pistache has the following third party dependencies - [OpenSSL](https://www.openssl.org/) - [RapidJSON](https://rapidjson.org/) - [Hinnant Date](https://github.com/HowardHinnant/date) +- [brotli](https://www.brotli.org/) +- [zstd](https://github.com/facebook/zstd) +- [libevent](https://libevent.org/) ## Contributing @@ -272,4 +277,4 @@ int main() { Pistache hasn't yet hit the 1.0 release. This means that the project is _unstable_ but not _unusable_. In fact, most of the code is production ready; you can use Pistache to develop a RESTful API without issues, but the HTTP client has a few issues in it that make it buggy. -\* While most code uses modern C++, Pistache makes use of some Linux-specific APIs where the standard library doesn't provide alternatives, and works only on that OS. See [#6](https://github.com/pistacheio/pistache/issues/6#issuecomment-242398225) for details. If you know how to help, please contribute a PR to add support for your desired platform :) +\* While most code uses modern C++, Pistache makes use of some platform-specific APIs where the standard library doesn't provide alternatives. If you know how to help, please contribute a PR to add support for your desired platform :) diff --git a/examples/http_server.cc b/examples/http_server.cc index 837988d05..dc3739879 100644 --- a/examples/http_server.cc +++ b/examples/http_server.cc @@ -159,7 +159,7 @@ class MyHandler : public Http::Handler { if (req.method() == Http::Method::Get) { - Http::serveFile(response, "README.md").then([](ssize_t bytes) { + Http::serveFile(response, "README.md").then([](PST_SSIZE_T bytes) { std::cout << "Sent " << bytes << " bytes" << std::endl; }, Async::NoExcept); @@ -177,7 +177,7 @@ class MyHandler : public Http::Handler { response .send(Http::Code::Request_Timeout, "Timeout") - .then([=](ssize_t) {}, PrintException()); + .then([=](PST_SSIZE_T) {}, PrintException()); } }; diff --git a/examples/http_server_shutdown.cc b/examples/http_server_shutdown.cc index 90d375a89..00abdc703 100644 --- a/examples/http_server_shutdown.cc +++ b/examples/http_server_shutdown.cc @@ -7,6 +7,12 @@ #include "pistache/endpoint.h" #include +#ifdef _WIN32 +#include +#include +// #include // included in WinCon.h; needs Kernel32.lib +#endif + using namespace Pistache; class HelloHandler : public Http::Handler @@ -20,8 +26,46 @@ class HelloHandler : public Http::Handler } }; +#ifdef _WIN32 + +static std::atomic lSigBool = false; +static std::mutex lMutex; +static std::condition_variable cv; + +#define CTRL_TYPE_EMPTY 0xDEADDEAD +static DWORD lCtrlType = CTRL_TYPE_EMPTY; +static BOOL consoleCtrlHandler(DWORD dwCtrlType) +{ + std::unique_lock lock(lMutex); + if (lCtrlType == 0) + { + lCtrlType = dwCtrlType; + + lSigBool = true; + cv.notify_one(); + } + + return(TRUE);// We have handled the ctrl signal +} + +#endif // of ifdef _WIN32 + int main() { +#ifdef _WIN32 + // Note: SetConsoleCtrlHandler can be used for console apps or GUI apps; + // for GUI apps, the notification from WM_QUERYENDSESSION _may_ arrive + // before the call to consoleCtrlHandler + + BOOL set_cch_res = SetConsoleCtrlHandler(consoleCtrlHandler, + true /*Add*/); + if (!set_cch_res) + { + perror("install ctrl-c-handler failed"); + return 1; + } + +#else sigset_t signals; if (sigemptyset(&signals) != 0 || sigaddset(&signals, SIGTERM) != 0 @@ -32,6 +76,7 @@ int main() perror("install signal handler failed"); return 1; } +#endif Pistache::Address addr(Pistache::Ipv4::any(), Pistache::Port(9080)); auto opts = Pistache::Http::Endpoint::options() @@ -42,6 +87,48 @@ int main() server.setHandler(Http::make_handler()); server.serveThreaded(); +#ifdef _WIN32 + std::unique_lock lock(lMutex); + cv.wait(lock, [&] {return(lSigBool.load());}); + + switch(lCtrlType) + { + case CTRL_TYPE_EMPTY: + perror("ctrl-type not set"); + break; + + case CTRL_C_EVENT: + std::cout << + "ctrl-c received from keyboard or GenerateConsoleCtrlEvent" << + std::endl; + break; + + case CTRL_BREAK_EVENT: + std::cout << + "ctrl-break received from keyboard or GenerateConsoleCtrlEvent" << + std::endl; + break; + + case CTRL_CLOSE_EVENT: + std::cout << + "Attached console closed" << std::endl; + break; + + case CTRL_LOGOFF_EVENT: + std::cout << + "User logging off" << std::endl; + break; + + case CTRL_SHUTDOWN_EVENT: + std::cout << + "System shutting down" << std::endl; + break; + + default: + perror("ctrl-type unknown"); + break; + } +#else // not ifdef _WIN32 int signal = 0; int status = sigwait(&signals, &signal); if (status == 0) @@ -52,6 +139,7 @@ int main() { std::cerr << "sigwait returns " << status << std::endl; } +#endif // of ifdef _WIN32... else... server.shutdown(); } diff --git a/examples/meson.build b/examples/meson.build index 8ed5e45bc..e346ef6a1 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -12,8 +12,25 @@ pistache_example_files = [ 'rest_description' ] +test_link_args = [] +if host_machine.system() == 'windows' and compiler.get_id() == 'gcc' + # If we don't make libstdc++ static, we leave it to the Windows OS + # to find the dynamic libstdc++-6.dll. Normally, that would be OK, + # since Windows checks the Path for DLL locations and we could + # could have the gcc libstdc++-6.dll appear on the path before + # Visual Studio's libstdc++-6.dll. However, Windows actually + # treats libstdc++-6.dll as a "Known DLL", which means it fetches + # it from Visual Studio regardless of what we have on the + # Path. Which is not OK if we're building with gcc. + # Ref: https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order + # The alternative might be do it using DLL redirection (see + # https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-redirection) + + test_link_args += '-static-libgcc' + test_link_args += '-static-libstdc++' +endif threads_dep = dependency('threads') foreach example_name : pistache_example_files - executable('run'+example_name, example_name+'.cc', dependencies: [pistache_dep, threads_dep]) + executable('run'+example_name, example_name+'.cc', link_args: test_link_args, dependencies: [pistache_dep, threads_dep]) endforeach diff --git a/include/pistache/.gitignore b/include/pistache/.gitignore new file mode 100644 index 000000000..1c532473c --- /dev/null +++ b/include/pistache/.gitignore @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2015 Mathieu Stefani +# +# SPDX-License-Identifier: CC0-1.0 + +# File generated by Windows Logging +MSG*.bin +pist_winlog.h +pist_winlog.rc +pist_winlogTEMP.BIN diff --git a/include/pistache/async.h b/include/pistache/async.h index c6e984287..3b0c68bc6 100644 --- a/include/pistache/async.h +++ b/include/pistache/async.h @@ -185,7 +185,7 @@ namespace Pistache::Async struct Throw { - void operator()(std::exception_ptr exc) const + [[noreturn]] void operator()(std::exception_ptr exc) const { throw InternalRethrow(std::move(exc)); } @@ -352,7 +352,8 @@ namespace Pistache::Async chain_->state = State::Rejected; for (const auto& req : chain_->requests) { - req->reject(chain_); + if (req) + req->reject(chain_); } } } @@ -433,10 +434,16 @@ namespace Pistache::Async void doReject(const std::shared_ptr>& core) override { reject_(core->exc); + /* + * reject_ is guaranteed to throw ("[[noreturn]]") so + * doing this "for" loop is pointless + * for (const auto& req : this->chain_->requests) { - req->reject(this->chain_); + if (req) + req->reject(this->chain_); } + */ } template @@ -446,7 +453,8 @@ namespace Pistache::Async this->chain_->template construct(std::forward(ret)); for (const auto& req : this->chain_->requests) { - req->resolve(this->chain_); + if (req) + req->resolve(this->chain_); } } @@ -480,7 +488,8 @@ namespace Pistache::Async reject_(core->exc); for (const auto& req : this->chain_->requests) { - req->reject(this->chain_); + if (req) + req->reject(this->chain_); } } @@ -491,7 +500,8 @@ namespace Pistache::Async this->chain_->template construct(std::forward(ret)); for (const auto& req : this->chain_->requests) { - req->resolve(this->chain_); + if (req) + req->resolve(this->chain_); } } @@ -594,10 +604,16 @@ namespace Pistache::Async void doReject(const std::shared_ptr>& core) override { reject_(core->exc); + /* + * reject_ is guaranteed to throw ("[[noreturn]]") so + * doing this "for" loop is pointless + * for (const auto& req : core->requests) { - req->reject(core); + if (req) + req->reject(core); } + */ } template @@ -612,7 +628,8 @@ namespace Pistache::Async chainCore->construct(val); for (const auto& req : chainCore->requests) { - req->resolve(chainCore); + if (req) + req->resolve(chainCore); } } @@ -639,7 +656,8 @@ namespace Pistache::Async for (const auto& req : core->requests) { - req->reject(core); + if (req) + req->reject(core); } } }); @@ -676,7 +694,8 @@ namespace Pistache::Async reject_(core->exc); for (const auto& req : core->requests) { - req->reject(core); + if (req) + req->reject(core); } } @@ -692,7 +711,8 @@ namespace Pistache::Async chainCore->construct(val); for (const auto& req : chainCore->requests) { - req->resolve(chainCore); + if (req) + req->resolve(chainCore); } } @@ -712,7 +732,8 @@ namespace Pistache::Async for (const auto& req : chainCore->requests) { - req->resolve(chainCore); + if (req) + req->resolve(chainCore); } } @@ -737,7 +758,8 @@ namespace Pistache::Async for (const auto& req : core->requests) { - req->reject(core); + if (req) + req->reject(core); } }); } @@ -853,7 +875,8 @@ namespace Pistache::Async for (const auto& req : core_->requests) { - req->resolve(core_); + if (req) + req->resolve(core_); } return true; @@ -874,7 +897,8 @@ namespace Pistache::Async core_->state = State::Fulfilled; for (const auto& req : core_->requests) { - req->resolve(core_); + if (req) + req->resolve(core_); } return true; @@ -915,7 +939,8 @@ namespace Pistache::Async core_->state = State::Rejected; for (const auto& req : core_->requests) { - req->reject(core_); + if (req) + req->reject(core_); } return true; @@ -1120,11 +1145,13 @@ namespace Pistache::Async std::unique_lock guard(core_->mtx); if (isFulfilled()) { - req->resolve(core_); + if (req) + req->resolve(core_); } else if (isRejected()) { - req->reject(core_); + if (req) + req->reject(core_); } core_->requests.push_back(req); diff --git a/include/pistache/common.h b/include/pistache/common.h index 68a6eca87..7a88f2d28 100644 --- a/include/pistache/common.h +++ b/include/pistache/common.h @@ -18,8 +18,13 @@ #include -#include -#include +#include + +#include PIST_QUOTE(PST_STRERROR_R_HDR) + +#include PIST_QUOTE(PST_NETDB_HDR) +#include PIST_QUOTE(PST_SOCKET_HDR) + #include #include @@ -28,6 +33,7 @@ #define TRY(...) \ do \ { \ + PST_SOCK_STARTUP_CHECK; \ auto ret = __VA_ARGS__; \ if (ret < 0) \ { \ @@ -40,7 +46,8 @@ } \ else \ { \ - oss << strerror(errno); \ + PST_DECL_SE_ERR_P_EXTRA; \ + oss << PST_STRERROR_R_ERRNO; \ } \ PS_LOG_INFO_ARGS("TRY ret %d errno %d throw %s", \ ret, errno, oss.str().c_str()); \ @@ -52,12 +59,14 @@ #define TRY_RET(...) \ [&]() { \ + PST_SOCK_STARTUP_CHECK; \ auto ret = __VA_ARGS__; \ if (ret < 0) \ { \ const char* str = #__VA_ARGS__; \ std::ostringstream oss; \ - oss << str << ": " << strerror(errno); \ + PST_DECL_SE_ERR_P_EXTRA; \ + oss << str << ": " << PST_STRERROR_R_ERRNO; \ PS_LOG_INFO_ARGS("TRY ret %d errno %d throw %s", \ ret, errno, oss.str().c_str()); \ PS_LOGDBG_STACK_TRACE; \ @@ -70,12 +79,14 @@ #define TRY_NULL_RET(...) \ [&]() { \ + PST_SOCK_STARTUP_CHECK; \ auto ret = __VA_ARGS__; \ - if (ret == NULL) \ + if (ret == nullptr) \ { \ const char* str = #__VA_ARGS__; \ std::ostringstream oss; \ - oss << str << ": " << strerror(errno); \ + PST_DECL_SE_ERR_P_EXTRA; \ + oss << str << ": " << PST_STRERROR_R_ERRNO; \ PS_LOG_INFO_ARGS("TRY_NULL_RET throw errno %d %s", \ errno, oss.str().c_str()); \ PS_LOGDBG_STACK_TRACE; \ @@ -100,5 +111,3 @@ struct PrintException } } }; - -#define unreachable() __builtin_unreachable() diff --git a/include/pistache/em_socket_t.h b/include/pistache/em_socket_t.h new file mode 100644 index 000000000..5c7bb4af2 --- /dev/null +++ b/include/pistache/em_socket_t.h @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// #include + +#ifndef _EM_SOCKET_T_H_ +#define _EM_SOCKET_T_H_ + +#include + +/* ------------------------------------------------------------------------- */ + +// A type wide enough to hold the output of "socket()" or "accept()". On +// Windows, this is an intptr_t; elsewhere, it is an int. +// Note: Mapped directly from evutil_socket_t in libevent's event2/util.h +#ifdef _IS_WINDOWS +#define em_socket_t intptr_t +#else +#define em_socket_t int +#endif + +/* ------------------------------------------------------------------------- */ + +#endif // ifndef _EM_SOCKET_T_H_ diff --git a/include/pistache/emosandlibevdefs.h b/include/pistache/emosandlibevdefs.h index be5f2ce5a..ce12d4675 100644 --- a/include/pistache/emosandlibevdefs.h +++ b/include/pistache/emosandlibevdefs.h @@ -14,6 +14,8 @@ #ifndef _EMOSANDLIBEVDEFS_H_ #define _EMOSANDLIBEVDEFS_H_ +#include + /* ------------------------------------------------------------------------- */ #ifdef PISTACHE_FORCE_LIBEVENT @@ -44,8 +46,11 @@ #ifndef _USE_LIBEVENT_LIKE_APPLE #define _USE_LIBEVENT_LIKE_APPLE 1 #endif -#elif defined(_WIN32) // Defined for both 32-bit and 64-bit environments +#elif defined(_IS_WINDOWS) #define _USE_LIBEVENT 1 + #ifndef _USE_LIBEVENT_LIKE_APPLE + #define _USE_LIBEVENT_LIKE_APPLE 1 + #endif #elif defined(__unix__) || !defined(__APPLE__) && defined(__MACH__) #include #if defined(BSD) diff --git a/include/pistache/endpoint.h b/include/pistache/endpoint.h index f50566cd3..b63a7a49b 100644 --- a/include/pistache/endpoint.h +++ b/include/pistache/endpoint.h @@ -145,7 +145,7 @@ namespace Pistache::Http * [2] https://en.wikipedia.org/wiki/CRIME */ void useSSL(const std::string& cert, const std::string& key, - bool use_compression = false, int (*cb_password)(char*, int, int, void*) = NULL); + bool use_compression = false, int (*cb_password)(char*, int, int, void*) = nullptr); /*! * \brief Use SSL certificate authentication on this endpoint diff --git a/include/pistache/eventmeth.h b/include/pistache/eventmeth.h index 604edba82..6c96476c4 100644 --- a/include/pistache/eventmeth.h +++ b/include/pistache/eventmeth.h @@ -10,6 +10,8 @@ #ifndef _EVENTMETH_H_ #define _EVENTMETH_H_ +#include + /* ------------------------------------------------------------------------- */ #include @@ -20,14 +22,7 @@ /* ------------------------------------------------------------------------- */ -// A type wide enough to hold the output of "socket()" or "accept()". On -// Windows, this is an intptr_t; elsewhere, it is an int. -// Note: Mapped directly from evutil_socket_t in libevent's event2/util.h -#ifdef _WIN32 -#define em_socket_t SOCKET -#else -#define em_socket_t int -#endif +#include #ifndef _USE_LIBEVENT #include @@ -53,7 +48,7 @@ // WRITE_EFD and READ_EFD are for eventfd/EmEventFd Fds // They return 0 on success, and -1 on fail - + #ifdef _USE_LIBEVENT // Returns -1 if __fd__ not EmEventFd #define WRITE_EFD(__fd__, __val__) \ @@ -92,6 +87,42 @@ #define PS_FD_PRNTFCD d #endif +#ifdef _USE_LIBEVENT +// PS_FD_CAST_TO_UNUM / PS_FD_CAST_TO_SNUM are used to cast Fd to numeric type +// E.g.: +// Fd fd; +// ... +// uint64_t n = PS_FD_CAST_TO_UNUM(uint64_t, fd); +#define PS_FD_CAST_TO_UNUM(__T, __N) \ + (static_cast<__T>(reinterpret_cast(__N))) +#define PS_FD_CAST_TO_SNUM(__T, __N) \ + (static_cast<__T>(reinterpret_cast(__N))) +#else +#define PS_FD_CAST_TO_UNUM(__T, __N) \ + (static_cast<__T>(__N)) +#define PS_FD_CAST_TO_SNUM(__T, __N) \ + (static_cast<__T>(__N)) +#endif + +#ifdef _USE_LIBEVENT +// PS_NUM_CAST_TO_FD/PS_NUM_CAST_TO_FDCONST casts a numeric type to Fd/FdConst +// PS_CAST_AWAY_CONST_FD casts an FdConst to type Fd +#define PS_NUM_CAST_TO_FD(__N) \ + (reinterpret_cast(static_cast(__N))) +#define PS_NUM_CAST_TO_FDCONST(__N) \ + (reinterpret_cast(static_cast(__N))) +#define PS_CAST_AWAY_CONST_FD(__fdconst) \ + (const_cast(__fdconst)) +#else +#define PS_NUM_CAST_TO_FD(__N) \ + (static_cast(__N)) +#define PS_NUM_CAST_TO_FDCONST(__N) \ + PS_NUM_CAST_TO_FD(__N) +#define PS_CAST_AWAY_CONST_FD(__fdconst) \ + (__fdconst) +#endif + + #ifdef _USE_LIBEVENT // Note - closeEvent calls delete __ev__; #define CLOSE_FD(__ev__) \ @@ -145,7 +176,7 @@ namespace Pistache class EmEvent; class EmEventFd; class EmEventTmrFd; - + using Fd = EmEvent *; using FdConst = const EmEvent *; @@ -178,7 +209,7 @@ namespace Pistache #include #include -#include // For FD_CLOEXEC and O_NONBLOCK +#include PIST_QUOTE(PST_FCNTL_HDR) // For FD_CLOEXEC and O_NONBLOCK /* ------------------------------------------------------------------------- */ @@ -224,7 +255,7 @@ namespace Pistache std::shared_ptr create(int size); - #define F_SETFDL_NOTHING ((int)((unsigned) 0x8A82)) + #define F_SETFDL_NOTHING (static_cast(0x8A82u)) Fd em_event_new(em_socket_t actual_fd,//file desc, signal, or -1 short flags, // EVM_... flags // For setfd and setfl arg: @@ -241,7 +272,7 @@ namespace Pistache // If emee is NULL here, it will need to be supplied when settime is // called - Fd em_timer_new(clockid_t clock_id, + Fd em_timer_new(PST_CLOCK_ID_T clock_id, // For setfd and setfl arg: // F_SETFDL_NOTHING - change nothing // Zero or pos number that is not @@ -277,15 +308,15 @@ namespace Pistache // otherwise. emee_cptr_set_mutex_ is locked inside the function. EventMethEpollEquiv * getEventMethEpollEquivFromEmeeSet( EventMethEpollEquiv * emee); - - int getActualFd(const EmEvent * em_event); + + em_socket_t getActualFd(const EmEvent * em_event); // efd should be a pointer to EmEventFd - does dynamic cast - ssize_t writeEfd(EmEvent * efd, const uint64_t val); - ssize_t readEfd(EmEvent * efd, uint64_t * val_out_ptr); + PST_SSIZE_T writeEfd(EmEvent * efd, const uint64_t val); + PST_SSIZE_T readEfd(EmEvent * efd, uint64_t * val_out_ptr); - ssize_t read(EmEvent * fd, void * buf, size_t count); - ssize_t write(EmEvent * fd, const void * buf, size_t count); + PST_SSIZE_T read(EmEvent * fd, void * buf, size_t count); + PST_SSIZE_T write(EmEvent * fd, const void * buf, size_t count); EmEvent * getAsEmEvent(EmEventFd * efd); @@ -295,21 +326,21 @@ namespace Pistache void setEmEventUserData(EmEvent * fd, Fd user_data); // For EmEventTmrFd, settime is analagous to timerfd_settime in linux - // + // // The linux flags TFD_TIMER_ABSTIME and TFD_TIMER_CANCEL_ON_SET are // not supported // // Since pistache doesn't use the "struct itimerspec * old_value" // feature of timerfd_settime, we haven't implemented that feature. - // + // // If the EventMethEpollEquiv was not specified already (e.g. at // make_new), the it must be specified here - // + // // Note: settime is in EmEvent rather than solely in EmEventTmrFd since // any kind of event may have a timeout set, not only timer events int setEmEventTime(EmEvent * fd, - const std::chrono::milliseconds * new_timeval_cptr, - EventMethEpollEquiv * emee = NULL/*may be NULL*/); + const std::chrono::milliseconds * new_timeval_cptr, + EventMethEpollEquiv * emee = nullptr); EmEventType getEmEventType(EmEvent * fd); @@ -319,7 +350,7 @@ namespace Pistache EventMethEpollEquiv * emee/*may be NULL*/); #ifdef DEBUG - std::string getActFdAndFdlFlagsAsStr(int actual_fd); + std::string getActFdAndFdlFlagsAsStr(em_socket_t actual_fd); // See also macro LOG_DEBUG_ACT_FD_AND_FDL_FLAGS(__ACTUAL_FD__) #endif @@ -336,9 +367,9 @@ namespace Pistache class EventMethEpollEquiv { // See man epoll, epoll_create, epoll_ctl, epl_wait - + public: - + // Add to interest list // Returns 0 for success, on error -1 with errno set int ctl(EvCtlAction op, // add, mod, or del @@ -355,7 +386,7 @@ namespace Pistache // "timeout" is in milliseconds, or -1 means wait indefinitely // Returns number of ready events being returned; or 0 if timed-out // without an event becoming ready; or -1, with errno set, on error - // + // // NOTE: Caller must call unlockInterestMutexIfLocked after // getReadyEmEvents has returned and after the caller has finished // processing any Fds in ready_evm_events_out. getReadyEmEvents returns @@ -376,12 +407,12 @@ namespace Pistache private: // Allow create to call the constructor friend std::shared_ptr EventMethFns::create(int); - + EventMethEpollEquiv(int size); friend EventMethEpollEquivImpl * EventMethFns::getEMEEImpl( EventMethEpollEquiv *); - + std::unique_ptr impl_; }; diff --git a/include/pistache/flags.h b/include/pistache/flags.h index 6adcc74bc..2d815f3d7 100644 --- a/include/pistache/flags.h +++ b/include/pistache/flags.h @@ -17,6 +17,8 @@ #include #include +#include + namespace Pistache { @@ -161,7 +163,7 @@ std::ostream& operator<<(std::ostream& os, Pistache::Flags flags) typedef typename Pistache::detail::UnderlyingType::Type UnderlyingType; auto val = static_cast(static_cast(flags)); - for (ssize_t i = (sizeof(UnderlyingType) * CHAR_BIT) - 1; i >= 0; --i) + for (PST_SSIZE_T i = (sizeof(UnderlyingType) * CHAR_BIT) - 1; i >= 0; --i) { os << ((val >> i) & 0x1); } diff --git a/include/pistache/http.h b/include/pistache/http.h index 49c5803d7..97e9f6ba9 100644 --- a/include/pistache/http.h +++ b/include/pistache/http.h @@ -312,11 +312,11 @@ namespace Pistache "event_meth_epoll_equiv null"); timerFd = TRY_NULL_RET(EventMethFns::em_timer_new( - CLOCK_MONOTONIC, - F_SETFDL_NOTHING, O_NONBLOCK, + PST_CLOCK_MONOTONIC, + F_SETFDL_NOTHING, PST_O_NONBLOCK, event_meth_epoll_equiv.get())); #else - timerFd = TRY_RET(timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK)); + timerFd = TRY_RET(timerfd_create(PST_CLOCK_MONOTONIC, TFD_NONBLOCK)); #endif transport->armTimer(timerFd, duration, std::move(deferred)); }); @@ -435,7 +435,7 @@ namespace Pistache public: static constexpr size_t DefaultStreamSize = 512; - friend Async::Promise + friend Async::Promise serveFile(ResponseWriter&, const std::string&, const Mime::MediaType&); friend class Handler; @@ -464,22 +464,22 @@ namespace Pistache * - movedPermantly -> 301 * - moved() -> 302 */ - Async::Promise + Async::Promise sendMethodNotAllowed(const std::vector& supportedMethods); - Async::Promise send(Code code, const std::string& body = "", - const Mime::MediaType& mime = Mime::MediaType()); + Async::Promise send(Code code, const std::string& body = "", + const Mime::MediaType& mime = Mime::MediaType()); template - Async::Promise + Async::Promise send(Code code, const char (&arr)[N], const Mime::MediaType& mime = Mime::MediaType()) { return sendImpl(code, arr, N - 1, mime); } - Async::Promise send(Code code, const char* data, const size_t size, - const Mime::MediaType& mime = Mime::MediaType()); + Async::Promise send(Code code, const char* data, const size_t size, + const Mime::MediaType& mime = Mime::MediaType()); ResponseStream stream(Code code, size_t streamSize = DefaultStreamSize); @@ -501,7 +501,7 @@ namespace Pistache // Returns total count of HTTP bytes (headers, cookies, body) written when // sending the response. Result valid AFTER ResponseWriter.send() is called. - ssize_t getResponseSize() const { return sent_bytes_; } + PST_SSIZE_T getResponseSize() const { return sent_bytes_; } // Returns HTTP result code that was sent with the response. Code getResponseCode() const { return response_.code(); } @@ -521,8 +521,6 @@ namespace Pistache return nullptr; } - - // Compress using the requested content encoding, if supported, // before sending bits to client. Content-Encoding header will be // automatically set to the requested encoding, if supported... @@ -558,18 +556,18 @@ namespace Pistache private: ResponseWriter(const ResponseWriter& other); - Async::Promise sendImpl(Code code, const char* data, - const size_t size, - const Mime::MediaType& mime); + Async::Promise sendImpl(Code code, const char* data, + const size_t size, + const Mime::MediaType& mime); - Async::Promise putOnWire(const char* data, size_t len); + Async::Promise putOnWire(const char* data, size_t len); Response response_; std::weak_ptr peer_; DynamicStreamBuf buf_; Tcp::Transport* transport_ = nullptr; Timeout timeout_; - ssize_t sent_bytes_ = 0; + PST_SSIZE_T sent_bytes_ = 0; Http::Header::Encoding contentEncoding_ = Http::Header::Encoding::Identity; @@ -588,7 +586,7 @@ namespace Pistache #endif }; - Async::Promise + Async::Promise serveFile(ResponseWriter& writer, const std::string& fileName, const Mime::MediaType& contentType = Mime::MediaType()); @@ -609,7 +607,7 @@ namespace Pistache virtual StepId id() const = 0; virtual State apply(StreamCursor& cursor) = 0; - static void raise(const char* msg, Code code = Code::Bad_Request); + [[noreturn]] static void raise(const char* msg, Code code = Code::Bad_Request); protected: Message* message; @@ -692,8 +690,8 @@ namespace Pistache private: Message* message; size_t bytesRead; - ssize_t size; - ssize_t alreadyAppendedChunkBytes; + PST_SSIZE_T size; + PST_SSIZE_T alreadyAppendedChunkBytes; }; State parseContentLength(StreamCursor& cursor, diff --git a/include/pistache/http_headers.h b/include/pistache/http_headers.h index 578a0aa08..f0e9ed571 100644 --- a/include/pistache/http_headers.h +++ b/include/pistache/http_headers.h @@ -199,9 +199,9 @@ namespace Pistache::Http::Header #define CAT(a, b) CAT_I(a, b) #define CAT_I(a, b) a##b -#define UNIQUE_NAME(base) CAT(base, __LINE__) +#define PST_UNIQUE_NAME(base) CAT(base, __LINE__) #define RegisterHeader(Header) \ - Registrar
UNIQUE_NAME(CAT(CAT_I(__, Header), __)) + Registrar
PST_UNIQUE_NAME(CAT(CAT_I(__, Header), __)) } // namespace Pistache::Http::Header diff --git a/include/pistache/listener.h b/include/pistache/listener.h index 42c39606f..3a9c8280f 100644 --- a/include/pistache/listener.h +++ b/include/pistache/listener.h @@ -12,6 +12,8 @@ #pragma once +#include + #include #include #include @@ -22,7 +24,7 @@ #include #include -#include +#include PIST_QUOTE(PST_SYS_RESOURCE_HDR) #include #include @@ -49,7 +51,7 @@ namespace Pistache::Tcp double global; std::vector workers; - std::vector raw; + std::vector raw; TimePoint tick; }; @@ -117,9 +119,12 @@ namespace Pistache::Tcp bool bindListener(const struct addrinfo* addr); void handleNewConnection(); - int acceptConnection(struct sockaddr_storage& peer_addr) const; + em_socket_t acceptConnection(struct sockaddr_storage& peer_addr) const; void dispatchPeer(const std::shared_ptr& peer); +#ifdef _IS_WINDOWS + std::atomic idxCtr_ = 1; +#endif bool useSSL_ = false; ssl::SSLCtxPtr ssl_ctx_ = nullptr; diff --git a/include/pistache/mailbox.h b/include/pistache/mailbox.h index 074e65f27..8e262fcbc 100644 --- a/include/pistache/mailbox.h +++ b/include/pistache/mailbox.h @@ -11,9 +11,15 @@ #include #include + +#include + +#include PIST_QUOTE(PST_STRERROR_R_HDR) + #include #include -#include + +#include PIST_QUOTE(PIST_SOCKFNS_HDR) #include #include @@ -92,7 +98,7 @@ namespace Pistache #ifdef _USE_LIBEVENT - FdEventFd emefd = TRY_NULL_RET(Epoll::em_eventfd_new(0, 0, O_NONBLOCK)); + FdEventFd emefd = TRY_NULL_RET(Epoll::em_eventfd_new(0, 0, PST_O_NONBLOCK)); event_fd = EventMethFns::getAsEmEvent(emefd); #else @@ -308,7 +314,7 @@ namespace Pistache } #ifdef _USE_LIBEVENT - FdEventFd emefd = TRY_NULL_RET(Epoll::em_eventfd_new(0, 0, O_NONBLOCK)); + FdEventFd emefd = TRY_NULL_RET(Epoll::em_eventfd_new(0, 0, PST_O_NONBLOCK)); event_fd = EventMethFns::getAsEmEvent(emefd); @@ -414,8 +420,10 @@ namespace Pistache } else { + PST_DBG_DECL_SE_ERR_P_EXTRA; PS_LOG_DEBUG_ARGS("Unimplemented errno %d %s", - errno, strerror(errno)); + errno, + PST_STRERROR_R_ERRNO); // TODO } } diff --git a/include/pistache/meson.build b/include/pistache/meson.build index 7331070e5..d150b843d 100644 --- a/include/pistache/meson.build +++ b/include/pistache/meson.build @@ -12,6 +12,7 @@ install_headers( 'config.h', 'cookie.h', 'description.h', + 'em_socket_t.h', 'emosandlibevdefs.h', 'endpoint.h', 'eventmeth.h', @@ -30,11 +31,22 @@ install_headers( 'net.h', 'os.h', 'peer.h', - 'pist_quote.h', 'pist_check.h', + 'pist_clock_gettime.h', + 'pist_fcntl.h', + 'pist_filefns.h', + 'pist_ifaddrs.h', + 'pist_quote.h', + 'pist_resource.h', + 'pist_sockfns.h', + 'pist_strerror_r.h', 'pist_syslog.h', - 'prototype.h', 'pist_timelog.h', + 'prototype.h', + 'ps_basename.h', + 'ps_sendfile.h', + 'ps_strl.h', + 'pst_errno.h', 'reactor.h', 'route_bind.h', 'router.h', @@ -48,6 +60,7 @@ install_headers( 'typeid.h', 'utils.h', 'view.h', + 'winornix.h', subdir: 'pistache') if get_option('PISTACHE_USE_RAPIDJSON') diff --git a/include/pistache/net.h b/include/pistache/net.h index b9e2319aa..3bea8d179 100644 --- a/include/pistache/net.h +++ b/include/pistache/net.h @@ -1,4 +1,4 @@ -/* + /* * SPDX-FileCopyrightText: 2015 Mathieu Stefani * * SPDX-License-Identifier: Apache-2.0 @@ -17,10 +17,18 @@ #include #include -#include -#include -#include -#include +#include + +#include PIST_QUOTE(PST_NETDB_HDR) + +// netinet/in.h defines in_port_t, in_addr_t, in_addr, sockaddr_in, +// sockaddr_in6, IPPROTO_IP, INADDR_ANY, etc. +#include PIST_QUOTE(PST_NETINET_IN_HDR) + +#include PIST_QUOTE(PST_SOCKET_HDR) +#include PIST_QUOTE(PST_SYS_UN_HDR) + +#include PIST_QUOTE(PIST_SOCKFNS_HDR) #ifndef _KERNEL_FASTOPEN #define _KERNEL_FASTOPEN @@ -49,6 +57,7 @@ namespace Pistache { if (addrs) { + PST_SOCK_STARTUP_CHECK; ::freeaddrinfo(addrs); } } @@ -58,6 +67,8 @@ namespace Pistache int invoke(const char* node, const char* service, const struct addrinfo* hints) { + PST_SOCK_STARTUP_CHECK; + if (addrs) { ::freeaddrinfo(addrs); @@ -87,7 +98,8 @@ namespace Pistache static constexpr uint16_t min() { - return std::numeric_limits::min(); + // return std::numeric_limits::min(); + return std::numeric_limits::min(); } static constexpr uint16_t max() { @@ -116,7 +128,7 @@ namespace Pistache int getFamily() const; uint16_t getPort() const; std::string toString() const; - void toNetwork(in_addr_t*) const; + void toNetwork(PST_IN_ADDR_T*) const; void toNetwork(struct in6_addr*) const; // Returns 'true' if the system has IPV6 support, false if not. static bool supported(); diff --git a/include/pistache/os.h b/include/pistache/os.h index 21e9836b6..888a800cf 100644 --- a/include/pistache/os.h +++ b/include/pistache/os.h @@ -12,6 +12,8 @@ #pragma once +#include + #include #include #include @@ -23,14 +25,12 @@ #include #include -#include - namespace Pistache { // Note: Fd is defined in eventmeth.h - uint hardware_concurrency(); - bool make_non_blocking(int fd); + unsigned int hardware_concurrency(); + bool make_non_blocking(em_socket_t fd); class CpuSet { @@ -96,7 +96,8 @@ namespace Pistache { } constexpr TagValue value() const { return value_; } - uint64_t valueU64() const { return ((uint64_t)value_); } + uint64_t valueU64() const { return (static_cast + (reinterpret_cast(value_))); } #ifndef _USE_LIBEVENT constexpr #endif @@ -104,10 +105,10 @@ namespace Pistache actualFdU64Value() const { #ifdef _USE_LIBEVENT - if (value_ == NULL) - return ((uint64_t)((int)-1)); + if (value_ == nullptr) + return (static_cast(-1)); em_socket_t actual_fd = GET_ACTUAL_FD(value_); - return ((uint64_t)actual_fd); + return (static_cast(actual_fd)); #else return (value_); #endif @@ -138,13 +139,14 @@ namespace Pistache Epoll(); ~Epoll(); - void addFd(Fd fd, Flags interest, Tag tag, Mode mode = Mode::Level); + void addFd(Fd fd, Flags interest, Tag tag, + [[maybe_unused]] Mode mode = Mode::Level); void addFdOneShot(Fd fd, Flags interest, Tag tag, Mode mode = Mode::Level); void removeFd(Fd fd); void rearmFd(Fd fd, Flags interest, Tag tag, - Mode mode = Mode::Level); + [[maybe_unused]] Mode mode = Mode::Level); int poll(std::vector& events, const std::chrono::milliseconds timeout = std::chrono::milliseconds(-1)) const; @@ -171,7 +173,7 @@ namespace Pistache int f_setfl_flags // e.g. O_NONBLOCK ); - Fd em_timer_new(clockid_t clock_id, + Fd em_timer_new(PST_CLOCK_ID_T clock_id, // For setfd and setfl arg: // F_SETFDL_NOTHING - change nothing // Zero or pos number that is not diff --git a/include/pistache/peer.h b/include/pistache/peer.h index 477999bb7..d3026257e 100644 --- a/include/pistache/peer.h +++ b/include/pistache/peer.h @@ -53,7 +53,7 @@ namespace Pistache::Tcp const Address& address() const; const std::string& hostname(); Fd fd() const; // can return PS_FD_EMPTY - int actualFd() const; // can return -1 + em_socket_t actualFd() const; // can return -1 void closeFd(); @@ -63,7 +63,8 @@ namespace Pistache::Tcp std::shared_ptr getData(std::string name) const; std::shared_ptr tryGetData(std::string name) const; - Async::Promise send(const RawBuffer& buffer, int flags = 0); + Async::Promise send(const RawBuffer& buffer, + int flags = 0); size_t getID() const; protected: diff --git a/include/pistache/pist_clock_gettime.h b/include/pistache/pist_clock_gettime.h new file mode 100644 index 000000000..e1bc38868 --- /dev/null +++ b/include/pistache/pist_clock_gettime.h @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Defines a pist_clock_gettime when in an OS that does not provide +// clock_gettime natively, notably windows +// +// #include + +#ifndef _PIST_CLOCK_GETTIME_H_ +#define _PIST_CLOCK_GETTIME_H_ + +#include + +/* ------------------------------------------------------------------------- */ + +#ifdef _IS_WINDOWS + +// PST_CLOCK_REALTIME, PST_CLOCK_MONOTONIC etc. defined in winornix.h + +#include // for time_t +#include // for struct tm + +/* ------------------------------------------------------------------------- */ + +extern "C" int PST_CLOCK_GETTIME(PST_CLOCK_ID_T clockid, + struct PST_TIMESPEC *tp); + +extern "C" struct tm *PST_GMTIME_R(const time_t *timep, struct tm *result); + +extern "C" char *PST_ASCTIME_R(const struct tm *tm, char *buf); + +extern "C" struct tm *PST_LOCALTIME_R(const time_t *timep, struct tm *result); + +/* ------------------------------------------------------------------------- */ + +#endif // of ifdef _IS_WINDOWS + +/* ------------------------------------------------------------------------- */ +#endif // of ifndef _PIST_CLOCK_GETTIME_H_ diff --git a/include/pistache/pist_fcntl.h b/include/pistache/pist_fcntl.h new file mode 100644 index 000000000..15795ac1f --- /dev/null +++ b/include/pistache/pist_fcntl.h @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Defines a pist_fcntl for Windows +// +// #include + +#ifndef _PIST_FCNTL_H_ +#define _PIST_FCNTL_H_ + +#include + +/* ------------------------------------------------------------------------- */ + +#ifdef _IS_WINDOWS + +#include + +/* ------------------------------------------------------------------------- */ + +extern "C" int PST_FCNTL(em_socket_t fd, int cmd, ... /* arg */ ); + +/* ------------------------------------------------------------------------- */ + +#endif // of ifdef _IS_WINDOWS + +/* ------------------------------------------------------------------------- */ +#endif // of ifndef _PIST_FCNTL_H_ diff --git a/include/pistache/pist_filefns.h b/include/pistache/pist_filefns.h new file mode 100644 index 000000000..50e2026ff --- /dev/null +++ b/include/pistache/pist_filefns.h @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Defines certain file functions (operations on an 'int' file descriptor) that +// exist in macOS/Linux/BSD but which need to be defined in Windows +// +// #include + +#ifndef _PIST_FILEFNS_H_ +#define _PIST_FILEFNS_H_ + +#include + +/* ------------------------------------------------------------------------- */ + +#ifdef _IS_WINDOWS + +#include // off_t, size_t + +/* ------------------------------------------------------------------------- */ + +extern "C" PST_SSIZE_T pist_pread(int fd, void *buf, + size_t count, off_t offset); + +int pist_open(const char *pathname, int flags); + +int pist_open(const char *pathname, int flags, PST_FILE_MODE_T mode); + +/* ------------------------------------------------------------------------- */ + +#endif // of ifdef _IS_WINDOWS + +/* ------------------------------------------------------------------------- */ +#endif // of ifndef _PIST_FILEFNS_H_ diff --git a/include/pistache/pist_ifaddrs.h b/include/pistache/pist_ifaddrs.h new file mode 100644 index 000000000..ce2051b4b --- /dev/null +++ b/include/pistache/pist_ifaddrs.h @@ -0,0 +1,74 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Defines "struct pist_ifaddrs", and the functions pist_getifaddrs and +// pist_freeifaddrs for Windows +// +// #include + +#ifndef _PIST_IFADDRS_H_ +#define _PIST_IFADDRS_H_ + +#include + +/* ------------------------------------------------------------------------- */ + +#ifdef _IS_WINDOWS + +// #include // for sockaddr + +/* ------------------------------------------------------------------------- */ + +// Per Linux "man getifaddrs" +struct PST_IFADDRS { + struct PST_IFADDRS *ifa_next; /* Next item in list */ + char *ifa_name; /* Name of interface */ + unsigned int ifa_flags; /* Flags from SIOCGIFFLAGS */ + struct sockaddr *ifa_addr; /* Address of interface */ + struct sockaddr *ifa_netmask; /* Netmask of interface */ + union { + struct sockaddr *ifu_broadaddr; + /* Broadcast address of interface */ + struct sockaddr *ifu_dstaddr; + /* Point-to-point destination address */ + } ifa_ifu; + #define pist_ifa_broadaddr ifa_ifu.ifu_broadaddr + #define pist_ifa_dstaddr ifa_ifu.ifu_dstaddr + void *ifa_data; /* Address-specific data */ + }; + +// Defines for ifa_flags +// As per /usr/include/net/if.h in Ubuntu: +#define PST_IFF_UP 0x1 /* Interface is up. */ +#define PST_IFF_BROADCAST 0x2 /* Broadcast address valid. */ +#define PST_IFF_DEBUG 0x4 /* Turn on debugging. */ +#define PST_IFF_LOOPBACK 0x8 /* Is a loopback net. */ +#define PST_IFF_POINTOPOINT 0x10 /* Interface is point-to-point link. */ +#define PST_IFF_NOTRAILERS 0x20 /* Avoid use of trailers. */ +#define PST_IFF_RUNNING 0x40 /* Resources allocated. */ +#define PST_IFF_NOARP 0x80 /* No address resolution protocol. */ +#define PST_IFF_PROMISC 0x100 /* Receive all packets. */ + +// The following flags are not supported in Linux, again as per if.h: +#define PST_IFF_ALLMULTI 0x200 /* Receive all multicast packets. */ +#define PST_IFF_MASTER 0x400 /* Master of a load balancer. */ +#define PST_IFF_SLAVE 0x800 /* Slave of a load balancer. */ +#define PST_IFF_MULTICAST 0x1000 /* Supports multicast. */ +#define PST_IFF_PORTSEL 0x2000 /* Can set media type. */ +#define PST_IFF_AUTOMEDIA 0x4000 /* Auto media select active. */ +#define PST_IFF_DYNAMIC 0x8000 /* Dialup device with changing addresses*/ + + +extern "C" int PST_GETIFADDRS(struct PST_IFADDRS **ifap); + +extern "C" void PST_FREEIFADDRS(struct PST_IFADDRS *ifa); + +/* ------------------------------------------------------------------------- */ + +#endif // of ifdef _IS_WINDOWS + +/* ------------------------------------------------------------------------- */ +#endif // of ifndef _PIST_IFADDRS_H_ diff --git a/include/pistache/pist_resource.h b/include/pistache/pist_resource.h new file mode 100644 index 000000000..6cea1e81a --- /dev/null +++ b/include/pistache/pist_resource.h @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Defines features of , notably struct rusage and getrusage() +// +// #include + +#ifndef _PIST_PIST_RESOURCE_H_ +#define _PIST_PIST_RESOURCE_H_ + +#include + +/* ------------------------------------------------------------------------- */ + +#ifdef _IS_WINDOWS + +#include // for struct timeval + +/* ------------------------------------------------------------------------- */ + +// Note PST_RUSAGE_SELF and PST_RUSAGE_CHILDREN defined in winornix.h + +struct PST_RUSAGE +{ + struct timeval ru_utime; /* user time used */ + struct timeval ru_stime; /* system time used */ +}; + +extern "C" int pist_getrusage(int who, struct PST_RUSAGE * rusage); + +/* ------------------------------------------------------------------------- */ + +#endif // of ifdef _IS_WINDOWS + +/* ------------------------------------------------------------------------- */ +#endif // of ifndef _PIST_PIST_RESOURCE_H_ diff --git a/include/pistache/pist_sockfns.h b/include/pistache/pist_sockfns.h new file mode 100644 index 000000000..ab3b7007e --- /dev/null +++ b/include/pistache/pist_sockfns.h @@ -0,0 +1,102 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Defines certain socket functions (operations on an em_socket_t) that we +// implement using the corresponding winsock2 methods. +// +// #include + +#ifndef _PIST_SOCKFNS_H_ +#define _PIST_SOCKFNS_H_ + +#include + +/* ------------------------------------------------------------------------- */ + +#ifdef _IS_WINDOWS + +#include // size_t +#include + +/* ------------------------------------------------------------------------- */ + +// pist_sock_startup_check must be called before any winsock2 function. It can +// be called as many times as you like, it does nothing after the first time it +// is called, and it is threadsafe. All the pist_sock_xxx functions in this +// file call it themselves, so you don't need to call pist_sock_startup_check +// before calling pist_sock_socket for instance. However, if code outside of +// this file is calling winsock functions, that code must call +// pist_sock_startup_check, using the macro provided in winornix.h. +// +// Returns 0 on success, or -1 on failure with errno set. +int pist_sock_startup_check(); + +// Returns 0 for success. On fail, rets -1, and errno is set +int pist_sock_getsockname(em_socket_t em_sock, + struct sockaddr *addr, PST_SOCKLEN_T *addrlen); + +// pist_sock_close rets 0 for success. On fail, ret -1, and errno is set +int pist_sock_close(em_socket_t em_sock); + +// On success, returns number of bytes read (zero meaning the connection has +// gracefully closed). On failure, -1 is returned and errno is set +PST_SSIZE_T pist_sock_read(em_socket_t em_sock, void *buf, size_t count); + +// On success, returns number of bytes written. On failure, -1 is returned and +// errno is set. Note that, even on success, bytes written may be fewer than +// count. +PST_SSIZE_T pist_sock_write(em_socket_t em_sock, const void *buf,size_t count); + +// On success, returns em_socket_t. On failure, -1 is returned and errno is +// set. +em_socket_t pist_sock_socket(int domain, int type, int protocol); + +// On success, returns 0. On failure, -1 is returned and errno is set. +int pist_sock_bind(em_socket_t em_sock, const struct sockaddr *addr, + PST_SOCKLEN_T addrlen); + +// On success returns an em_socket_t for the accepted socket. On failure, -1 is +// returned and errno is set. +em_socket_t pist_sock_accept(em_socket_t em_sock, struct sockaddr *addr, + PST_SOCKLEN_T *addrlen); + +// On success, returns 0. On failure, -1 is returned and errno is set. +int pist_sock_connect(em_socket_t em_sock, const struct sockaddr *addr, + PST_SOCKLEN_T addrlen); + +// On success, returns 0. On failure, -1 is returned and errno is set. +int pist_sock_listen(em_socket_t em_sock, int backlog); + +PST_SSIZE_T pist_sock_send(em_socket_t em_sock, const void *buf, + size_t len, int flags); + +// On success, returns the number of bytes received. On error, -1 is +// returned and errno is set. Returns 0 if connection closed gracefully. +PST_SSIZE_T pist_sock_recv(em_socket_t em_sock, void * buf, size_t len, + int flags); + +typedef struct PST_POLLFD +{ + em_socket_t fd; + short events; /* requested events */ + short revents; /* returned events */ +} PST_POLLFD_T; + +typedef unsigned long PST_NFDS_T; + +// On success, returns a nonnegative value which is the number of elements in +// the pollfds whose revents fields have been set to a non‐ zero value +// (indicating an event or an error). A return value of zero indicates that +// the system call timed out before any file descriptors became readable. +// On error, -1 is returned, and errno is set. +int pist_sock_poll(PST_POLLFD_T * fds, PST_NFDS_T nfds, int timeout); + +/* ------------------------------------------------------------------------- */ + +#endif // of ifdef _IS_WINDOWS + +/* ------------------------------------------------------------------------- */ +#endif // of ifndef _PIST_SOCKFNS_H_ diff --git a/include/pistache/pist_strerror_r.h b/include/pistache/pist_strerror_r.h new file mode 100644 index 000000000..78c84b0bc --- /dev/null +++ b/include/pistache/pist_strerror_r.h @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Defines a pist_strerror_r for use in Windows +// +// #include + +#ifndef _PIST_STRERROR_R_H_ +#define _PIST_STRERROR_R_H_ + +#include + +/* ------------------------------------------------------------------------- */ + +#if !defined(__linux__) && ((!defined(__GNUC__)) || (defined(__MINGW32__)) \ + || (defined(__clang__)) || (defined(__NetBSD__)) || (defined(__APPLE__))) + +/* ------------------------------------------------------------------------- */ +// Note: We provide the GNU-specific/POSIX style (which returns char *), not +// the XSI-compliant definition (which returns int) even in the non-GNU case. + +extern "C" char * pist_strerror_r(int errnum, char *buf, size_t buflen); + +/* ------------------------------------------------------------------------- */ + +#endif // of ifdef _IS_WINDOWS + +/* ------------------------------------------------------------------------- */ +#endif // of ifndef _PIST_STRERROR_R_H_ diff --git a/include/pistache/pist_syslog.h b/include/pistache/pist_syslog.h index d55243fe6..9a3ef2bab 100644 --- a/include/pistache/pist_syslog.h +++ b/include/pistache/pist_syslog.h @@ -5,10 +5,12 @@ */ /****************************************************************************** - * PistSysLog.h + * pist_syslog.h * * Logging Facilities * + * #include + * */ @@ -17,9 +19,54 @@ /*****************************************************************************/ +#include + +/*****************************************************************************/ + +#include + +#ifndef _IS_WINDOWS + #include -#include +#else // This is for Windows + +// As per /usr/include/sys/syslog.h + +#ifndef LOG_EMERG +#define LOG_EMERG 0 /* system is unusable */ +#endif + +#ifndef LOG_ALERT +#define LOG_ALERT 1 /* action must be taken immediately */ +#endif + +#ifndef LOG_CRIT +#define LOG_CRIT 2 /* critical conditions */ +#endif + +#ifndef LOG_ERR +#define LOG_ERR 3 /* error conditions */ +#endif + +#ifndef LOG_WARNING +#define LOG_WARNING 4 /* warning conditions */ +#endif + +#ifndef LOG_NOTICE +#define LOG_NOTICE 5 /* normal but significant condition */ +#endif + +#ifndef LOG_INFO +#define LOG_INFO 6 /* informational */ +#endif + +#ifndef LOG_DEBUG +#define LOG_DEBUG 7 /* debug-level messages */ +#endif + + +#endif /*****************************************************************************/ // Following macros do a log message with current file location. If you want to @@ -32,10 +79,24 @@ #define PS_LOG_DEBUG(__str) PS_LOG_DEBUG_ARGS("%s", __str) // If PS_LOG_AND_STDOUT is true, all logging is sent to stdout in addition to -// being sent to log file -// +// being sent to log file (for Windows, note the additional comment below). +// // You can define PS_LOG_AND_STDOUT to true using the meson build option -// "PISTACHE_LOG_AND_STDOUT", or simple #define it here +// "PISTACHE_LOG_AND_STDOUT", or simply comment in the #define below. +// +// Note that, in the Windows case, sending of log messages to stdout is +// intended to be controlled principally not by the #define PS_LOG_AND_STDOUT +// but by the Registry key HKCU:\Software\pistacheio\pistache property +// psLogToStdoutAsWell; the Registry key property value can be set to 0 (off +// unless PS_LOG_AND_STDOUT is #defined to be true in which case on), 1 (on) or +// 10 (turn off even if PS_LOG_AND_STDOUT is #defined to be true). It defaults +// to 0 (i.e. off unless overridden by PS_LOG_AND_STDOUT). Any Registry key +// property value other than 0, 1 or 10 is treated like 1. Deleting the +// property or key is treated like 0. If the property value is changed while +// pistache.dll is running, the log output behavior will update dynamically. +// +// #define PS_LOG_AND_STDOUT true + #ifndef PS_LOG_AND_STDOUT #define PS_LOG_AND_STDOUT false #endif @@ -107,16 +168,6 @@ extern "C" void setPsLogCategory(const char * _category); // --------------------------------------------------------------------------- -#ifdef __APPLE__ -#define my_basename_r basename_r -#include // for basename_r -#else -#define my_basename_r ps_basename_r -extern "C" char * ps_basename_r(const char * path, char * bname); -#endif - -// --------------------------------------------------------------------------- - // You can use these PSLG_... just like std::cout, except they go to the log // E.g.: // PSLG_DEBUG_OS << "fd_actual value: " << fd_actual; diff --git a/include/pistache/pist_timelog.h b/include/pistache/pist_timelog.h index 71a98ce60..2d7395779 100644 --- a/include/pistache/pist_timelog.h +++ b/include/pistache/pist_timelog.h @@ -16,41 +16,41 @@ #ifndef INCLUDED_PS_TIMELOG_H #define INCLUDED_PS_TIMELOG_H -#include // for clock_gettime and asctime +#include +#include // For _IS_BSD + +#include PIST_QUOTE(PST_CLOCK_GETTIME_HDR) // for clock_gettime and asctime #include // snprintf #include // for vsnprintf #include // strlen -#include // for abi::__cxa_demangle - #include #include - // --------------------------------------------------------------------------- -#include // For _IS_BSD #include +#include + +#ifdef DEBUG +#define PS_TIMINGS_DBG 1 +#endif -#ifdef _IS_BSD -#define PS_STRLCPY(__dest, __src, __n) strlcpy(__dest, __src, __n) -#define PS_STRLCAT(__dest, __src, __size) strlcat(__dest, __src, __size) -#elif defined(__linux__) -#define PS_STRLCPY(__dest, __src, __n) strncpy(__dest, __src, __n) -#define PS_STRLCAT(__dest, __src, __size) strncat(__dest, __src, __size) -#elif defined(__APPLE__) -#define PS_STRLCPY(__dest, __src, __n) strlcpy(__dest, __src, __n) -#define PS_STRLCAT(__dest, __src, __size) strlcat(__dest, __src, __size) +#ifdef PS_TIMINGS_DBG +// For C++ name demangling: +#ifdef _IS_WINDOWS +// From dbghelp.lib/dll +extern "C" char *__unDName(char*, const char*, int, void*, void*, int); #else -#define PS_STRLCPY(__dest, __src, __n) strcpy(__dest, __src) -#define PS_STRLCAT(__dest, __src, __size) strcat(__dest, __src) +#include // for abi::__cxa_demangle #endif -#ifdef DEBUG -#define PS_TIMINGS_DBG 1 + #endif +// --------------------------------------------------------------------------- + // Pointy delimiters (<...>) are default, others can be used // Delimiters are repeated to indicate nesting (eg <<...>>) // Note, in note DEBUG case, all these PS_TIMEDBG_START_xxx macros evaluate to @@ -68,7 +68,7 @@ #define PS_TIMEDBG_START_W_DELIMIT_CH(PSDBG_DELIMIT_CH) \ __PS_TIMEDBG __ps_timedbg(PSDBG_DELIMIT_CH, \ - __FILE__, __LINE__, __FUNCTION__, NULL); + __FILE__, __LINE__, __FUNCTION__, nullptr); #define PS_TIMEDBG_START_ARGS(__fmt, ...) \ char ps_timedbg_buff[2048]; \ @@ -78,105 +78,111 @@ __PS_TIMEDBG \ __ps_timedbg('<', __FILE__, __LINE__, __FUNCTION__, ps_timedbg_inf) +#ifdef _IS_WINDOWS +// Note on __unDName from Wine: +// Demangle a C++ identifier. +// +// PARAMS +// buffer [O] If not NULL, the place to put the demangled string +// mangled [I] Mangled name of the function +// buflen [I] Length of buffer +// memget [I] Function to allocate memory with +// memfree [I] Function to free memory with +// unknown [?] Unknown, possibly a call back +// flags [I] Flags determining demangled format +// +// RETURNS +// Success: A string pointing to the unmangled name, allocated with memget. +// (Pistache note - memget used solely if buffer null or buflen zero) +// Failure: NULL. + + +#define GET__PTST_DEMANGLED \ + char ptst_undecorated_name[2048+16]; \ + ptst_undecorated_name[0] = 0; \ + char * __ptst_demangled = __unDName(&(ptst_undecorated_name[0]), \ + typeid(*this).name(), 2048, reinterpret_cast(malloc), \ + reinterpret_cast(free), 0x2800); + // Note: __ptst_demangled will point to ptst_undecorated_name; + // Do not free +#else +#define GET__PTST_DEMANGLED \ + char * __ptst_demangled = abi::__cxa_demangle( \ + typeid(*this).name(), nullptr, nullptr, &__ptst_dem_status); \ + __ptst_demangled_to_free = __ptst_demangled; +#endif // of ifdef _IS_WINDOWS ... else ... + // Same as PS_TIMEDBG_START but logs class name and "this" value #define PS_TIMEDBG_START_THIS \ int __ptst_dem_status = 0; \ - char * __ptst_demangled = abi::__cxa_demangle( \ - typeid(*this).name(), NULL, NULL, &__ptst_dem_status); \ - \ + char * __ptst_demangled_to_free = nullptr; \ + GET__PTST_DEMANGLED; \ char ps_timedbg_this_buff[2048]; \ if ((__ptst_demangled) && (__ptst_dem_status == 0)) \ + { \ + if (PST_STRCASECMP(__ptst_demangled, "class ") == 0) \ + __ptst_demangled += 6; \ + else if (PST_STRCASECMP(__ptst_demangled, "struct ") == 0) \ + __ptst_demangled += 7; \ snprintf(&(ps_timedbg_this_buff[0]), \ sizeof(ps_timedbg_this_buff), \ - "%s (this) %p", __ptst_demangled, (void *) this); \ + "%s (this) %p", __ptst_demangled, \ + reinterpret_cast(this)); \ + } \ else \ + { \ snprintf(&(ps_timedbg_this_buff[0]), \ sizeof(ps_timedbg_this_buff), \ - "this %p", (void *) this); \ - if ((__ptst_demangled) && (__ptst_dem_status == 0)) \ - free(__ptst_demangled); \ + "this %p", reinterpret_cast(this)); \ + } \ + if ((__ptst_demangled_to_free) && (__ptst_dem_status == 0)) \ + free(__ptst_demangled_to_free); \ PS_TIMEDBG_START_ARGS("%s", &(ps_timedbg_this_buff[0])); - + #define PS_TIMEDBG_START_STR(__str) \ PS_TIMEDBG_START_ARGS("%s", __str) #define PS_TIMEDBG_GET_CTR \ (__ps_timedbg.getCounter()) - + // --------------------------------------------------------------------------- class __PS_TIMEDBG { private: const char mMarkerChar; - + const char * mFileName; int mLineNum; const char * mFnName; - - struct timespec mPsTimedbg; - struct timespec mPsTimeCPUdbg; - - static unsigned mUniCounter; // universal (static) counter - unsigned mCounter; // individual counter for this __PS_TIMEDBG - static std::map mThreadMap; - static std::mutex mThreadMapMutex; + struct PST_TIMESPEC mPsTimedbg; + struct PST_TIMESPEC mPsTimeCPUdbg; - - - unsigned getThreadNextDepth() // returns depth value after increment - { - std::lock_guard l_guard(mThreadMapMutex); - pthread_t pthread_id = pthread_self(); - - std::map::iterator it = - mThreadMap.find(pthread_id); - if (it == mThreadMap.end()) - { - std::pair pr(pthread_id, 1); - mThreadMap.insert(pr); - return(1); - } + unsigned mCounter; // individual counter for this __PS_TIMEDBG + unsigned getNextUniCounter(); - return(++(it->second)); - } - unsigned decrementThreadDepth() // returns depth value before decrement - { - std::lock_guard l_guard(mThreadMapMutex); - pthread_t pthread_id = pthread_self(); - - std::map::iterator it = - mThreadMap.find(pthread_id); - - unsigned old_depth = 1; - if (it->second) // else something went wrong - old_depth = ((it->second)--); - - if (old_depth <= 1) - mThreadMap.erase(it); // arguably optional, but avoids any risk - // of leaks - return(old_depth); - } + unsigned getThreadNextDepth(); // returns depth value after increment + unsigned decrementThreadDepth(); // returns depth value before decrement void setMarkerChars(char * marker_chars, char the_marker, unsigned call_depth) { if (call_depth > 20) { - for(unsigned int i=0; i<10; i++) + for(unsigned int i=0; i<10; ++i) marker_chars[i] = the_marker; - PS_STRLCPY(&(marker_chars[10]), "...", 4); + PS_STRLCPY(&(marker_chars[10]), "...", 4); marker_chars += (10 + 3); - for(unsigned int i=0; i<10; i++) + for(unsigned int i=0; i<10; ++i) marker_chars[i] = the_marker; - marker_chars[10 + 3 + 10] = 0; + marker_chars[10] = 0; } else { - for(unsigned int i=0; i= ((int) sizeof_buf)) + if (ln >= (static_cast(sizeof_buf))) PS_STRLCAT(buf_ptr, "...", 5); return(buf_ptr); } - + public: __PS_TIMEDBG(char marker_ch, const char * f, int l, const char * m, const char * _inf) : mMarkerChar(marker_ch), - mFileName(f), mLineNum(l), mFnName(m), mCounter(++mUniCounter) + mFileName(f), + mLineNum(l), + mFnName(m), + mCounter(getNextUniCounter()) { const char * ps_time_str = "No-Time"; - char pschbuff[40]; + char pschbuff[80]; - int res = clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &mPsTimeCPUdbg); + int res = PST_CLOCK_GETTIME(PST_CLOCK_PROCESS_CPUTIME_ID, + &mPsTimeCPUdbg); if (res != 0) memset(&mPsTimeCPUdbg, 0, sizeof(mPsTimeCPUdbg)); - res = clock_gettime(CLOCK_REALTIME, &mPsTimedbg); + res = PST_CLOCK_GETTIME(PST_CLOCK_REALTIME, &mPsTimedbg); if (res == 0) { struct tm pstm; - if (gmtime_r(&mPsTimedbg.tv_sec, &pstm) != NULL) + const time_t ps_timedbg_sec = mPsTimedbg.tv_sec; + if (PST_GMTIME_R(&ps_timedbg_sec, &pstm)!= nullptr) { - if (asctime_r(&pstm, pschbuff) != NULL) + if (PST_ASCTIME_R(&pstm, pschbuff) != nullptr) { size_t pschbuff_len = strlen(pschbuff); while((pschbuff_len > 0) && @@ -280,10 +291,10 @@ class __PS_TIMEDBG } } - char marker_chars[32]; + char marker_chars[64]; unsigned call_depth = getThreadNextDepth(); setMarkerChars(&(marker_chars[0]), mMarkerChar, call_depth); - + if (_inf) PSLogFn(LOG_DEBUG, PS_LOG_AND_STDOUT, mFileName, mLineNum, mFnName, @@ -302,22 +313,22 @@ class __PS_TIMEDBG const char * ps_diff_time_str = "No-Time"; char pschbuff[40]; char ps_diff_chbuff[40]; - struct timespec latest_ps_timedbg; - struct timespec latest_ps_time_cpu_dbg; - int res = clock_gettime(CLOCK_PROCESS_CPUTIME_ID, - &latest_ps_time_cpu_dbg); + struct PST_TIMESPEC latest_ps_timedbg; + struct PST_TIMESPEC latest_ps_time_cpu_dbg; + int res = PST_CLOCK_GETTIME(PST_CLOCK_PROCESS_CPUTIME_ID, + &latest_ps_time_cpu_dbg); if (res != 0) memset(&latest_ps_time_cpu_dbg, 0, sizeof(latest_ps_time_cpu_dbg)); - res = clock_gettime(CLOCK_REALTIME, &latest_ps_timedbg); + res = PST_CLOCK_GETTIME(PST_CLOCK_REALTIME, &latest_ps_timedbg); if (res == 0) { struct tm pstm; - if (gmtime_r(&latest_ps_timedbg.tv_sec, &pstm) != NULL) + const time_t latest_ps_timedbg_sec = latest_ps_timedbg.tv_sec; + if (PST_GMTIME_R(&latest_ps_timedbg_sec, &pstm) != nullptr) { - - if (asctime_r(&pstm, pschbuff) != NULL) + if (PST_ASCTIME_R(&pstm, pschbuff) != nullptr) { size_t pschbuff_len = strlen(pschbuff); while((pschbuff_len > 0) && @@ -331,14 +342,14 @@ class __PS_TIMEDBG } } - int diff_sec = - (int)(latest_ps_timedbg.tv_sec - mPsTimedbg.tv_sec); + int diff_sec = static_cast + (latest_ps_timedbg.tv_sec - mPsTimedbg.tv_sec); if (diff_sec < 31536000) { long diff_nsec = latest_ps_timedbg.tv_nsec - mPsTimedbg.tv_nsec; - long diff_msec = (((long)diff_sec) * 1000) + + long diff_msec = ((static_cast(diff_sec)) * 1000) + (diff_nsec / 1000000); if ((diff_msec < 10) && @@ -346,19 +357,21 @@ class __PS_TIMEDBG ((latest_ps_time_cpu_dbg.tv_nsec) || (latest_ps_time_cpu_dbg.tv_sec))) { - diff_sec = (int)(latest_ps_time_cpu_dbg.tv_sec - + diff_sec = static_cast + (latest_ps_time_cpu_dbg.tv_sec - mPsTimeCPUdbg.tv_sec); diff_nsec = latest_ps_time_cpu_dbg.tv_nsec - mPsTimeCPUdbg.tv_nsec; - long diff_usec = (((long)diff_sec) * 1000000) + + long diff_usec = + ((static_cast(diff_sec)) * 1000000) + (diff_nsec / 1000); diff_msec = diff_usec / 1000; long diff_ms_thousandths = diff_usec % 1000; - + snprintf(&(ps_diff_chbuff[0]), sizeof(ps_diff_chbuff) - 1, "%ld.%03ldms", diff_msec,diff_ms_thousandths); - + } else { @@ -366,7 +379,7 @@ class __PS_TIMEDBG sizeof(ps_diff_chbuff) - 1, "%ldms", diff_msec); } - + ps_diff_time_str = &(ps_diff_chbuff[0]); } } @@ -375,7 +388,7 @@ class __PS_TIMEDBG unsigned call_depth = decrementThreadDepth(); setMarkerChars(&(marker_chars[0]), reverseMarkerChar(mMarkerChar), call_depth); - + PSLogFn(LOG_DEBUG, PS_LOG_AND_STDOUT, mFileName, mLineNum, mFnName, "%s diff=%s ctr:%u%s", ps_time_str, ps_diff_time_str, mCounter, @@ -391,7 +404,7 @@ class __PS_TIMEDBG #define PS_TIMEDBG_START_W_DELIMIT_CH(PSDBG_DELIMIT_CH) -#define PS_TIMEDBG_START_ARGS(__fmt, ...) +#define PS_TIMEDBG_START_ARGS(__fmt, ...) #define PS_TIMEDBG_START_THIS @@ -402,4 +415,3 @@ class __PS_TIMEDBG #endif // of ifndef INCLUDED_PS_TIMELOG_H // --------------------------------------------------------------------------- - diff --git a/include/pistache/ps_basename.h b/include/pistache/ps_basename.h new file mode 100644 index 000000000..9bd659b77 --- /dev/null +++ b/include/pistache/ps_basename.h @@ -0,0 +1,43 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Defines a ps_basename_r for OS that do not have basename_r natively +// +// #include + +#ifndef _PS_BASENAME_H_ +#define _PS_BASENAME_H_ + +#include + +#include PIST_QUOTE(PST_MAXPATH_HDR) // for PST_MAXPATHLEN +/* ------------------------------------------------------------------------- */ + +#ifdef __APPLE__ + +/* ------------------------------------------------------------------------- */ + +#define PS_BASENAME_R basename_r +#include // for basename_r + +/* ------------------------------------------------------------------------- */ + +#else + +/* ------------------------------------------------------------------------- */ +// Linux, BSD or Windows + +#define PS_BASENAME_R ps_basename_r + +extern "C" char * ps_basename_r(const char * path, char * bname); + +/* ------------------------------------------------------------------------- */ + +#endif // of ifdef __APPLE__... else... + + +/* ------------------------------------------------------------------------- */ +#endif // of ifndef _PS_BASENAME_H_ diff --git a/include/pistache/ps_sendfile.h b/include/pistache/ps_sendfile.h new file mode 100644 index 000000000..ebd2be339 --- /dev/null +++ b/include/pistache/ps_sendfile.h @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Defines a Linux-style ps_sendfile when in an OS that does not provide one +// natively (BSD) or with a different interface (Windows) +// +// +// +// #include + +#ifndef _PIST_PS_SENDFILE_H_ +#define _PIST_PS_SENDFILE_H_ + +#include +#include + +/* ------------------------------------------------------------------------- */ + +#if defined(_IS_WINDOWS) || defined(_IS_BSD) + +#include // off_t, size_t +#include + +/* ------------------------------------------------------------------------- */ + +// Linux style sendfile +extern "C" PST_SSIZE_T ps_sendfile(em_socket_t out_fd, int in_fd, + off_t *offset, size_t count); + +#define PS_SENDFILE ps_sendfile + +/* ------------------------------------------------------------------------- */ + +#else // of if defined(_IS_WINDOWS) || defined(_IS_BSD) + +/* ------------------------------------------------------------------------- */ + +// Linux or macOS + +#ifdef __linux__ +#include +#elif defined __APPLE__ +#include +#include +#include +#endif + +#define PS_SENDFILE ::sendfile + +#endif // of if defined(_IS_WINDOWS) || defined(_IS_BSD)... else... + +/* ------------------------------------------------------------------------- */ +#endif // of ifndef _PIST_PS_SENDFILE_H_ diff --git a/include/pistache/ps_strl.h b/include/pistache/ps_strl.h new file mode 100644 index 000000000..5ecfe4bc1 --- /dev/null +++ b/include/pistache/ps_strl.h @@ -0,0 +1,61 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Defines a ps_strlcpy and ps_strlcat for OS that do not have strlcpy/strlcat +// natively, including Windows and (some e.g. Ubuntu LTS 22.04) Linux +// +// #include + +#ifndef _PS_STRL_H_ +#define _PS_STRL_H_ + +#include + +/* ------------------------------------------------------------------------- */ + +#if defined(_IS_WINDOWS) || defined(__linux__) + +/* ------------------------------------------------------------------------- */ + +extern "C" size_t ps_strlcpy(char *dst, const char *src, size_t size); +extern "C" size_t ps_strlcat(char *dst, const char *src, size_t size); + +#define PS_STRLCPY(__dest, __src, __n) ps_strlcpy(__dest, __src, __n) +#define PS_STRLCAT(__dest, __src, __size) ps_strlcat(__dest, __src, __size) + +/* ------------------------------------------------------------------------- */ + +#else // of #if defined(_IS_WINDOWS) || defined(__linux__) + +/* ------------------------------------------------------------------------- */ + +// Assuming this is for a _IS_BSD or __APPLE__ case +#define PS_STRLCPY(__dest, __src, __n) strlcpy(__dest, __src, __n) +#define PS_STRLCAT(__dest, __src, __size) strlcat(__dest, __src, __size) + +/* ------------------------------------------------------------------------- */ + +#endif // of #if defined(_IS_WINDOWS) || defined(__linux__)... else... + +/* ------------------------------------------------------------------------- */ + +// ps_strncpy_s returns 0 for success, -1 on failure with errno set. NB: This +// is different from C++ standard (Annex K) strncpy_s which returns an errno_t +// on failure; we diverge because errno_t is often not defined on non-Windows +// systems. If the copy would result in a truncation, errno is set to +// PS_ESTRUNCATE. +extern "C" int ps_strncpy_s(char *strDest, size_t numberOfElements, + const char *strSource, size_t count); + +#define PS_STRNCPY_S ps_strncpy_s +#ifdef _IS_WINDOWS +#define PS_ESTRUNCATE STRUNCATE +#else +#define PS_ESTRUNCATE E2BIG +#endif + +/* ------------------------------------------------------------------------- */ +#endif // of ifndef _PS_STRL_H_ diff --git a/include/pistache/pst_errno.h b/include/pistache/pst_errno.h new file mode 100644 index 000000000..9f08900ec --- /dev/null +++ b/include/pistache/pst_errno.h @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes an appropriate errno.h. +// +// Note that we use this intermediate include, rather than just relying solely +// on defining PST_ERRNO_HDR to be 'errno.h' for Windows, because of mingw +// gcc's treatment of errno as a macro - 'include PIST_QUOTE(PST_ERRNO_HDR)', +// with PST_ERRNO_HDR defined as 'errno.h', can translate to "(*_errno()).h", +// which is not what we want. +// +// #include + +#ifndef _PST_ERRNO_H_ +#define _PST_ERRNO_H_ + +#include + +/* ------------------------------------------------------------------------- */ + +#ifdef _IS_WINDOWS +#include +#else +#include +#endif + +/* ------------------------------------------------------------------------- */ + +#endif // of ifndef _PST_ERRNO_H_ diff --git a/include/pistache/reactor.h b/include/pistache/reactor.h index 22c93ce8c..03254fe5d 100644 --- a/include/pistache/reactor.h +++ b/include/pistache/reactor.h @@ -16,14 +16,13 @@ #pragma once +#include + #include #include #include #include -#include -#include - #include #include #include @@ -243,7 +242,7 @@ namespace Pistache::Aio // Remove this line from reactor.cc: // Reactor::~Reactor() = default; // Replace with this line: - // Reactor::~Reactor() {impl_ = NULL;}//impl_ is member of Reactor_ + // Reactor::~Reactor() {impl_ = nullptr;} //impl_ is in Reactor_ // With this change, the test program run_http_server_test was creating // the same exception in macOS and Linux // diff --git a/include/pistache/serializer/rapidjson.h b/include/pistache/serializer/rapidjson.h index fe4496115..691ecb911 100644 --- a/include/pistache/serializer/rapidjson.h +++ b/include/pistache/serializer/rapidjson.h @@ -110,7 +110,27 @@ namespace Pistache::Rest::Serializer std::string methodStr(methodString(path.method)); // So it looks like Swagger requires method to be in lowercase std::transform(std::begin(methodStr), std::end(methodStr), - std::begin(methodStr), ::tolower); + std::begin(methodStr), + [](const char ch) + { + const unsigned char uch = + static_cast(ch); + auto ires = ::tolower(uch); + return(static_cast(ires)); + }); + // Note re: the lambda function being used for std::transform + // above. Previously (before 10/2024), std::transform was simply being + // passed ::tolower/::toupper for the transformer function, but in fact + // we need to make two changes to that: i) we need to cast the input + // parm to "unsigned char" to avoid errors due to sign extension as + // explained on the Linux man page + // (e.g. https://www.man7.org/linux/man-pages/man3/toupper.3.html, + // Notes section); and ii) since the result of the transformer function + // is written into std::string, i.e. to a char, the transformer + // function needs to return a char, not (as ::tolower/::toupper does) + // an int, otherwise the compiler may complain about loss of integer + // size in writing the int to a char (and in fact MSVC was complaining + // in exactly this way). writer.String(methodStr.c_str()); writer.StartObject(); diff --git a/include/pistache/stream.h b/include/pistache/stream.h index 2394882c9..9f7d61beb 100644 --- a/include/pistache/stream.h +++ b/include/pistache/stream.h @@ -36,6 +36,23 @@ namespace Pistache typedef std::basic_streambuf Base; typedef typename Base::traits_type traits_type; +#ifdef __MINGW32__ + // As of Oct/2024, if we do not explicitly set the locale, as below, + // the test http_parsing_test.parser_reset generates an exception in + // the locale destructor when the Http::RequestParser (which inherits + // from StreamBuf) that the test uses goes out of scope. The locale + // instance that crashes on destruction is a component of + // std::basic_streambuf, inheried by StreamBuf and thence by + // Http::RequestParser. This may be a bug in the mingw standard + // library; setting the std::basic_streambuf's locale explicitly works + // around the issue. + StreamBuf() + { + std::locale dummy_loc("C"); + Base::pubimbue(dummy_loc); + } +#endif + void setArea(char* begin, char* current, char* end) { this->setg(begin, current, end); diff --git a/include/pistache/timer_pool.h b/include/pistache/timer_pool.h index 623e73455..2cb262930 100644 --- a/include/pistache/timer_pool.h +++ b/include/pistache/timer_pool.h @@ -25,7 +25,7 @@ #include #include -#include +#include PIST_QUOTE(PST_MISC_IO_HDR) // e.g. unistd.h namespace Pistache { diff --git a/include/pistache/transport.h b/include/pistache/transport.h index a26009651..6cffa617a 100644 --- a/include/pistache/transport.h +++ b/include/pistache/transport.h @@ -12,6 +12,12 @@ #pragma once +#include + +#include PIST_QUOTE(PST_SYS_RESOURCE_HDR) // for PST_RUSAGE + PST_GETRUSAGE + +#include +#include #include #include #include @@ -51,7 +57,7 @@ namespace Pistache::Tcp void onReady(const Aio::FdSet& fds) override; template - Async::Promise asyncWrite(Fd fd, const Buf& buffer, + Async::Promise asyncWrite(Fd fd, const Buf& buffer, int flags = 0 #ifdef _USE_LIBEVENT_LIKE_APPLE , @@ -64,8 +70,8 @@ namespace Pistache::Tcp // order. // // Note: fd could be PS_FD_EMPTY - return Async::Promise( - [=](Async::Deferred deferred) mutable { + return Async::Promise( + [=](Async::Deferred deferred) mutable { BufferHolder holder { buffer }; WriteEntry write(std::move(deferred), std::move(holder), fd, flags @@ -78,9 +84,9 @@ namespace Pistache::Tcp }); } - Async::Promise load() + Async::Promise load() { - return Async::Promise([=](Async::Deferred deferred) { + return Async::Promise([=](Async::Deferred deferred) { PS_TIMEDBG_START_CURLY; loadRequest_ = std::move(deferred); @@ -160,7 +166,7 @@ namespace Pistache::Tcp return _raw; } - BufferHolder detach(size_t offset = 0) + BufferHolder detach(off_t offset = 0) { if (!isRaw()) return BufferHolder(_fd, size_, offset); @@ -188,7 +194,7 @@ namespace Pistache::Tcp struct WriteEntry { - WriteEntry(Async::Deferred deferred_, BufferHolder buffer_, + WriteEntry(Async::Deferred deferred_, BufferHolder buffer_, Fd peerFd_, int flags_ = 0 #ifdef _USE_LIBEVENT_LIKE_APPLE , @@ -204,7 +210,7 @@ namespace Pistache::Tcp , peerFd(peerFd_) { } - Async::Deferred deferred; + Async::Deferred deferred; BufferHolder buffer; int flags = 0; #ifdef _USE_LIBEVENT_LIKE_APPLE @@ -266,7 +272,7 @@ namespace Pistache::Tcp PollableQueue peersQueue; - Async::Deferred loadRequest_; + Async::Deferred loadRequest_; NotifyFd notifier; std::shared_ptr handler_; @@ -285,7 +291,7 @@ namespace Pistache::Tcp // Peer per request; it fails when two of the requests are using the // same Peer. Which appears to happen when the peers_ unordered_map // gets messed up due to a threading issue. - std::mutex peers_mutex_; + mutable std::mutex peers_mutex_; std::unordered_map> peers_; private: @@ -310,13 +316,13 @@ namespace Pistache::Tcp void configureMsgMoreStyle(Fd fd, bool msg_more_style); #endif - ssize_t sendRawBuffer(Fd fd, const char* buffer, size_t len, int flags + PST_SSIZE_T sendRawBuffer(Fd fd, const char* buffer, size_t len, int flags #ifdef _USE_LIBEVENT_LIKE_APPLE , bool msg_more_style #endif ); - ssize_t sendFile(Fd fd, int file, off_t offset, size_t len); + PST_SSIZE_T sendFile(Fd fd, int file, off_t offset, size_t len); void handlePeerDisconnection(const std::shared_ptr& peer); void handleIncoming(const std::shared_ptr& peer); diff --git a/include/pistache/utils.h b/include/pistache/utils.h index 7e8843755..ebd32aba7 100644 --- a/include/pistache/utils.h +++ b/include/pistache/utils.h @@ -40,6 +40,6 @@ * * [1] https://www.openssl.org/docs/manmaster/man3/SSL_sendfile.html */ -ssize_t SSL_sendfile(SSL* out, int in, off_t* offset, size_t count); +PST_SSIZE_T SSL_sendfile(SSL* out, int in, off_t* offset, size_t count); #endif /* PISTACHE_USE_SSL */ diff --git a/include/pistache/winornix.h b/include/pistache/winornix.h new file mode 100644 index 000000000..b1151d156 --- /dev/null +++ b/include/pistache/winornix.h @@ -0,0 +1,609 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Defines to distinguish or abstract the differences between Windows and +// non-Windows OSes +// +// #include + +#ifndef _WINORNIX_H_ +#define _WINORNIX_H_ + +#include + +// DO NOT include emosandlibevdefs.h here +// emosandlibevdefs.h includes winornix.h, and depends on it + +/* ------------------------------------------------------------------------- */ + +// _WIN32 Defined for both 32-bit and 64-bit environments +// https:// +// learn.microsoft.com/en-us/windows-hardware/drivers/kernel/64-bit-compiler +// https://sourceforge.net/p/predef/wiki/OperatingSystems/ + +#if defined(_WIN16) || defined(_WIN32) || defined(_WIN64) || \ + defined(__WIN32__) || defined(__TOS_WIN__) || defined(__WINDOWS__) + +#define _IS_WINDOWS 1 + +#endif + +#ifdef _IS_WINDOWS +// Need to do this FIRST before including any windows headers, otherwise those +// headers may redefine min/max such that valid expressions like +// "std::numeric_limits::max()" no longer work +#ifndef NOMINMAX +#define NOMINMAX +#endif +#endif + +#ifdef _IS_WINDOWS +#include +#include +#endif + +#ifdef _IS_WINDOWS +#define PST_SSIZE_T SSIZE_T +#else +#define PST_SSIZE_T ssize_t +#endif + +#ifdef _IS_WINDOWS +#define PST_RUSAGE_SELF 0 +#define PST_RUSAGE_CHILDREN (-1) + +#define PST_RUSAGE pst_rusage +#define PST_GETRUSAGE pist_getrusage + +#else +#define PST_RUSAGE_SELF RUSAGE_SELF +#define PST_RUSAGE_CHILDREN RUSAGE_CHILDREN + +#define PST_RUSAGE rusage +#define PST_GETRUSAGE getrusage +#endif + +// Use #include PIST_QUOTE(PST_SYS_RESOURCE_HDR) +#ifdef _IS_WINDOWS +#define PST_SYS_RESOURCE_HDR pistache/pist_resource.h +#else +#define PST_SYS_RESOURCE_HDR sys/resource.h +#endif + +#ifdef _IS_WINDOWS +typedef int pst_clock_id_t; +#define PST_CLOCK_ID_T pst_clock_id_t + +// see https:// +// learn.microsoft.com/en-us/windows/win32/api/winsock/ns-winsock-timeval +typedef long pst_suseconds_t; +#define PST_SUSECONDS_T pst_suseconds_t +typedef long pst_timeval_s_t; +#define PST_TIMEVAL_S_T pst_timeval_s_t + +struct pst_timespec { long tv_sec; long tv_nsec; }; +#define PST_TIMESPEC pst_timespec + +#define PST_CLOCK_GETTIME pist_clock_gettime + +#define PST_GMTIME_R pist_gmtime_r + +#define PST_ASCTIME_R pist_asctime_r + +#define PST_LOCALTIME_R pist_localtime_r + +// Note: clock_gettime doesn't exist on Windows, we'll implement our own +// version. See pist_clock_gettime.h/.cc +#else +#define PST_CLOCK_ID_T clockid_t +#define PST_SUSECONDS_T suseconds_t +#define PST_TIMEVAL_S_T time_t +#define PST_TIMESPEC timespec +#define PST_CLOCK_GETTIME clock_gettime +#define PST_GMTIME_R gmtime_r +#define PST_ASCTIME_R asctime_r +#define PST_LOCALTIME_R localtime_r +#endif + +#ifdef _IS_WINDOWS +typedef char PST_SOCK_OPT_VAL_T; + +// defined in ws2tcpip.h; defined here to avoid need to include big header +// files (winsock2.h and ws2tcpip.h) in our headers just for this type +typedef int PST_SOCKLEN_T; +#else +typedef int PST_SOCK_OPT_VAL_T; +#define PST_SOCKLEN_T socklen_t +#endif + + +#ifdef _IS_WINDOWS +// As per /usr/include/linux/time.h + +// If additional PST_CLOCK_... constants are defined (commented in), then they +// must be supported in the PST_CLOCK_GETTIME function implementation (see +// pist_clock_gettime.h/.cc). +#define PST_CLOCK_REALTIME 0 +#define PST_CLOCK_MONOTONIC 1 +#define PST_CLOCK_PROCESS_CPUTIME_ID 2 +#define PST_CLOCK_THREAD_CPUTIME_ID 3 +#define PST_CLOCK_MONOTONIC_RAW 4 +#define PST_CLOCK_REALTIME_COARSE 5 +#define PST_CLOCK_MONOTONIC_COARSE 6 +// #define PST_CLOCK_BOOTTIME 7 +// #define PST_CLOCK_REALTIME_ALARM 8 +// #define PST_CLOCK_BOOTTIME_ALARM 9 + +#else +#define PST_CLOCK_REALTIME CLOCK_REALTIME +#define PST_CLOCK_MONOTONIC CLOCK_MONOTONIC +#define PST_CLOCK_PROCESS_CPUTIME_ID CLOCK_PROCESS_CPUTIME_ID +#define PST_CLOCK_THREAD_CPUTIME_ID CLOCK_THREAD_CPUTIME_ID +#define PST_CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC_RAW +#define PST_CLOCK_REALTIME_COARSE CLOCK_REALTIME_COARSE +#define PST_CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC_COARSE +// #define PST_CLOCK_BOOTTIME CLOCK_BOOTTIME +// #define PST_CLOCK_REALTIME_ALARM CLOCK_REALTIME_ALARM +// #define PST_CLOCK_BOOTTIME_ALARM CLOCK_BOOTTIME_ALARM +#endif + +// Use #include PIST_QUOTE(PST_CLOCK_GETTIME_HDR) +#ifdef _IS_WINDOWS +#define PST_CLOCK_GETTIME_HDR pistache/pist_clock_gettime.h +#else +#define PST_CLOCK_GETTIME_HDR time.h +#endif + +// Use #include PIST_QUOTE(PST_IFADDRS_HDR) +#ifdef _IS_WINDOWS +#define PST_IFADDRS_HDR pistache/pist_ifaddrs.h +#else +#define PST_IFADDRS_HDR ifaddrs.h +#endif + +// Use #include PIST_QUOTE(PST_MAXPATH_HDR) +#ifdef _IS_WINDOWS +#define PST_MAXPATH_HDR Stdlib.h +#define PST_MAXPATHLEN _MAX_PATH +#elif defined __APPLE__ +#define PST_MAXPATH_HDR sys/syslimits.h +#define PST_MAXPATHLEN PATH_MAX +#else +#define PST_MAXPATH_HDR sys/param.h +#define PST_MAXPATHLEN MAXPATHLEN +#endif + +// Do this so the PST_DECL_SE_ERR_P_EXTRA / PST_STRERROR_R_ERRNO macros have +// PST_MAXPATHLEN fully defined +#include PIST_QUOTE(PST_MAXPATH_HDR) + +// Use #include PIST_QUOTE(PST_STRERROR_R_HDR) +// mingw gcc doesn't define strerror_r (Oct/2024) +// gcc on macOS does define strerror_r, but the XSI version not the POSIX one +#if defined(__linux__) || (defined(__GNUC__) && (!defined(__MINGW32__)) && \ + (!defined(__clang__)) && (!defined(__NetBSD__)) && (!defined(__APPLE__))) +#define PST_STRERROR_R_HDR string.h +#define PST_STRERROR_R strerror_r // returns char * +#else +#define PST_STRERROR_R_HDR pistache/pist_strerror_r.h +#define PST_STRERROR_R pist_strerror_r // returns char * +#endif + +// Convenience macros to declare se_err for PST_STRERROR_R/PST_STRERROR_R_ERRNO +#define PST_DECL_SE_ERR_P_EXTRA \ + char se_err[PST_MAXPATHLEN+16]; se_err[0] = 0 + +#define PST_STRERROR_R_ERRNO \ + PST_STRERROR_R(errno, &se_err[0], PST_MAXPATHLEN) +#ifdef DEBUG +#define PST_DBG_DECL_SE_ERR_P_EXTRA PST_DECL_SE_ERR_P_EXTRA +#else +#define PST_DBG_DECL_SE_ERR_P_EXTRA +#endif + +// Use #include PIST_QUOTE(PST_FCNTL_HDR) +#ifdef _IS_WINDOWS +#define PST_FCNTL_HDR pistache/pist_fcntl.h +#define PST_FCNTL pist_fcntl + +// As per Linux /usr/include/x86_64-linux-gnu/bits/fcntl-linux.h +#define PST_F_GETFD 1 /* Get file descriptor flags. */ +#define PST_F_SETFD 2 /* Set file descriptor flags. */ +#define PST_F_GETFL 3 /* Get file status flags. */ +#define PST_F_SETFL 4 /* Set file status flags. */ + +#else +#define PST_FCNTL_HDR fcntl.h +#define PST_FCNTL fcntl + +#define PST_F_GETFD F_GETFD +#define PST_F_SETFD F_SETFD +#define PST_F_GETFL F_GETFL +#define PST_F_SETFL F_SETFL +#endif +// In Windows, we don't support doing F_GETFL; return this magic num instead +#define PST_FCNTL_GETFL_UNKNOWN \ + (static_cast((static_cast((-1)/2))) - (0xded - 97)) + + +// Use #include PIST_QUOTE(PST_NETDB_HDR) +#ifdef _IS_WINDOWS +#define PST_NETDB_HDR ws2tcpip.h +#else +#define PST_NETDB_HDR netdb.h +#endif + +// Use #include PIST_QUOTE(PST_SOCKET_HDR) +#ifdef _IS_WINDOWS +#define PST_SOCKET_HDR winsock2.h +#else +#define PST_SOCKET_HDR sys/socket.h +#endif + +// Use #include PIST_QUOTE(PST_ARPA_INET_HDR) +#ifdef _IS_WINDOWS +#define PST_ARPA_INET_HDR winsock2.h +#else +#define PST_ARPA_INET_HDR arpa/inet.h +#endif + +// Use #include PIST_QUOTE(PST_NETINET_IN_HDR) +#ifdef _IS_WINDOWS +#define PST_NETINET_IN_HDR ws2def.h +#else +#define PST_NETINET_IN_HDR netinet/in.h +#endif + +// Use #include PIST_QUOTE(PST_NETINET_TCP_HDR) +#ifdef _IS_WINDOWS +#define PST_NETINET_TCP_HDR winsock2.h +#else +#define PST_NETINET_TCP_HDR netinet/tcp.h +#endif + + +// Use #include PIST_QUOTE(PST_IFADDRS_HDR) +#ifdef _IS_WINDOWS +#define PST_IFADDRS_HDR pistache/pist_ifaddrs.h +#define PST_IFADDRS pist_ifaddrs +#define PST_GETIFADDRS pist_getifaddrs +#define PST_FREEIFADDRS pist_freeifaddrs +#else +#define PST_IFADDRS_HDR ifaddrs.h +#define PST_IFADDRS ifaddrs +#define PST_GETIFADDRS getifaddrs +#define PST_FREEIFADDRS freeifaddrs +#endif + +// Use #include PIST_QUOTE(PST_SYS_UN_HDR) +#ifdef _IS_WINDOWS +#define PST_SYS_UN_HDR afunix.h +#else +#define PST_SYS_UN_HDR sys/un.h +#endif + + +#ifdef _IS_WINDOWS +struct in_addr; +typedef struct in_addr PST_IN_ADDR_T; +// in_addr is defined in winsock2.h +// https:// +// learn.microsoft.com/en-us/windows/win32/api/winsock2/ns-winsock2-in_addr +// Note that it is a union - effectively a "u_long" that can be accessed as 4 +// u_char, 2 u_short, or 1 u_long +#else +#define PST_IN_ADDR_T in_addr_t +#endif + + + +// Use #include PIST_QUOTE(PST_THREAD_HDR) +#ifdef _IS_WINDOWS +// Note: processthreadsapi.h appears to require the prior inclusion of +// windows.h as well. +// So make sure to include PST_THREAD_HDR only in C/C++ files, not in a header +// file where it could end up including the massive windows.h all over the +// place. +#define PST_THREAD_HDR processthreadsapi.h +#else +#define PST_THREAD_HDR pthread.h +#endif + +// Use #include PIST_QUOTE(PST_ERRNO_HDR) +#ifndef __linux__ +// pistache/pst_errno.h prevents mingw gcc's bad macro substitution on errno +// Same issue with clang on macOS and gcc on OpenBSD +#define PST_ERRNO_HDR pistache/pst_errno.h +#else +#define PST_ERRNO_HDR sys/errno.h +#endif + +// Use #include PIST_QUOTE(PST_MISC_IO_HDR) +#ifdef _IS_WINDOWS +#define PST_MISC_IO_HDR io.h +// For _close etc. +#else +#define PST_MISC_IO_HDR unistd.h +#endif + +// Use #include PIST_QUOTE(PIST_FILEFNS_HDR) +#ifdef _IS_WINDOWS +#define PIST_FILEFNS_HDR pistache/pist_filefns.h +#else +// unistd.h defines pread +#define PIST_FILEFNS_HDR unistd.h +#endif + +// Use #include PIST_QUOTE(PIST_POLL_HDR) +#ifdef _IS_WINDOWS +#define PIST_POLL_HDR pistache/pist_sockfns.h +#else +#define PIST_POLL_HDR poll.h +#endif + +// Use #include PIST_QUOTE(PIST_SOCKFNS_HDR) +#ifdef _IS_WINDOWS +#define PIST_SOCKFNS_HDR pistache/pist_sockfns.h +#else +// unistd.h defines pread +#define PIST_SOCKFNS_HDR unistd.h // has close, read and write in Linux +#endif + +// PST_SOCK_xxx macros are for sockets. For files, use PST_FILE_xxx +#ifdef _IS_WINDOWS + +// PST_SOCK_STARTUP_CHECK must be invoked before any winsock2 function. It can +// be invoked as many times as you like, it does nothing after the first time +// it is invoked, and it is threadsafe. All the PST_SOCK_xxx functions call it +// themselves, so you don't need to invoke PST_SOCK_STARTUP_CHECK before +// calling PST_SOCK_SOCKET for instance. However, if code outside of the +// PST_SOCK_xxx functions is calling winsock, that code must invoke +// PST_SOCK_STARTUP_CHECK before making the winsock call (e.g. before calling +// getaddrinfo()). +// +// Returns 0 on success, or -1 on failure with errno set. +#define PST_SOCK_STARTUP_CHECK pist_sock_startup_check() + +#define PST_SOCK_GETSOCKNAME pist_sock_getsockname +#define PST_SOCKLEN_T int + +#define PST_SOCK_CLOSE pist_sock_close +// Note - Windows use "unsigned int" for count, whereas Linux uses size_t. In +// general we use size_t for count in Pistache, hence why we cast here +#define PST_SOCK_READ(__fd, __buf, __count) \ + pist_sock_read(__fd, __buf, static_cast(__count)) +#define PST_SOCK_WRITE(__fd, __buf, __count) \ + pist_sock_write(__fd, __buf, static_cast(__count)) +#define PST_SOCK_SOCKET pist_sock_socket +// Note - Windows uses "int" for socklen_t, whereas Linux uses size_t. In +// general we use size_t for addresses' lengths in Pistache (e.g. in struct +// ifaddr), hence why we cast here +#define PST_SOCK_BIND(__sockfd, __addr, __addrlen) \ + pist_sock_bind(__sockfd, __addr, static_cast(__addrlen)) +#define PST_SOCK_ACCEPT pist_sock_accept +#define PST_SOCK_CONNECT pist_sock_connect +#define PST_SOCK_LISTEN pist_sock_listen +#define PST_SOCK_SEND pist_sock_send +#define PST_SOCK_RECV pist_sock_recv + +// PST_POLLFD, PST_POLLFD_T + PST_NFDS_T defined in pist_sockfns.h for Windows +#define PST_SOCK_POLL pist_sock_poll + +#else +#define PST_SOCK_GETSOCKNAME ::getsockname +#define PST_SOCKLEN_T socklen_t + +#define PST_SOCK_CLOSE ::close +#define PST_SOCK_READ ::read +#define PST_SOCK_WRITE ::write +#define PST_SOCK_SOCKET ::socket +#define PST_SOCK_BIND ::bind +#define PST_SOCK_ACCEPT ::accept +#define PST_SOCK_CONNECT ::connect +#define PST_SOCK_LISTEN ::listen +#define PST_SOCK_SEND ::send +#define PST_SOCK_RECV ::recv + +#define PST_SOCK_POLL ::poll +#define PST_POLLFD pollfd // Note - this is a type, not a function +typedef struct PST_POLLFD PST_POLLFD_T; +#define PST_NFDS_T nfds_t + +#define PST_SOCK_STARTUP_CHECK +#endif + + +// PST_FILE_CLOSE, PST_FILE_OPEN, PST_FILE_READ, PST_FILE_WRITE and +// PST_FILE_PREAD are for *files* +// For sockets, make sure to use PST_SOCK_xxx macros (above) +#ifdef _IS_WINDOWS +// See: +// https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/close + +#define PST_FILE_CLOSE ::_close +#define PST_FILE_OPEN pist_open + +// Note - Windows use "unsigned int" for count, whereas Linux uses size_t. In +// general we use size_t for count in Pistache, hence why we cast here +#define PST_FILE_READ(__fd, __buf, __count) \ + ::_read(__fd, __buf, static_cast(__count)) +#define PST_FILE_WRITE(__fd, __buf, __count) \ + ::_write(__fd, __buf, static_cast(__count)) +#define PST_FILE_PREAD pist_pread + +#define PST_UNLINK ::_unlink +#define PST_RMDIR ::_rmdir + +typedef int pst_mode_t; +#define PST_FILE_MODE_T pst_mode_t +#else +#define PST_FILE_CLOSE ::close +#define PST_FILE_OPEN ::open +#define PST_FILE_READ ::read +#define PST_FILE_WRITE ::write +#define PST_FILE_PREAD ::pread +#define PST_UNLINK ::unlink +#define PST_RMDIR ::rmdir + +#define PST_FILE_MODE_T mode_t +#endif + +// Open flags +// In Linux, see man(2) open +// In Windows, see https:// +// learn.microsoft.com/en-us/cpp/c-runtime-library/reference/open-wopen +#ifdef _IS_WINDOWS +// Following flags are defined in Linux +// If commented out, it means there is no equivalent flag in Windows (though +// sometimes a similar effect can be achieved in Windows by different means) +// Note: File constants are declared in fcntl.h in Windows +// (https://learn.microsoft.com/en-us/cpp/c-runtime-library/file-constants) +#define PST_O_RDONLY _O_RDONLY +#define PST_O_WRONLY _O_WRONLY +#define PST_O_RDWR _O_RDWR +#define PST_O_APPEND _O_APPEND +// #define PST_O_ASYNC _O_ASYNC +// #define PST_O_CLOEXEC _O_CLOEXEC +#define PST_O_CREAT _O_CREAT +// #define PST_O_DIRECT _O_DIRECT +// #define PST_O_DIRECTORY _O_DIRECTORY +// #define PST_O_DSYNC _O_DSYNC +#define PST_O_EXCL _O_EXCL +// #define PST_O_LARGEFILE _O_LARGEFILE +// #define PST_O_NOATIME _O_NOATIME +// #define PST_O_NOCTTY _O_NOCTTY +// #define PST_O_NOFOLLOW _O_NOFOLLOW +// #define PST_O_NONBLOCK _O_NONBLOCK +// #define PST_O_NDELAY _O_NDELAY // same as O_NONBLOCK +// #define PST_O_PATH _O_PATH +// #define PST_O_SYNC _O_SYNC +#define PST_O_TMPFILE _O_TEMPORARY +#define PST_O_TRUNC _O_TRUNC + +// Defined in Windows, not Linux (though might be achieved with pmode): +// _O_BINARY +// _O_SHORT_LIVED +// _O_NOINHERIT +// _O_RANDOM +// _O_SEQUENTIAL +// _O_TEXT, _O_U16TEXT, _O_U8TEXT, _O_WTEXT + +#else +#define PST_O_RDONLY O_RDONLY +#define PST_O_WRONLY O_WRONLY +#define PST_O_RDWR O_RDWR +#define PST_O_APPEND O_APPEND +// #define PST_O_ASYNC O_ASYNC +// #define PST_O_CLOEXEC O_CLOEXEC +#define PST_O_CREAT O_CREAT +// #define PST_O_DIRECT O_DIRECT +// #define PST_O_DIRECTORY O_DIRECTORY +// #define PST_O_DSYNC O_DSYNC +#define PST_O_EXCL O_EXCL +// #define PST_O_LARGEFILE O_LARGEFILE +// #define PST_O_NOATIME O_NOATIME +// #define PST_O_NOCTTY O_NOCTTY +// #define PST_O_NOFOLLOW O_NOFOLLOW +// #define PST_O_NONBLOCK O_NONBLOCK +// #define PST_O_NDELAY O_NDELAY // same as O_NONBLOCK +// #define PST_O_PATH O_PATH +// #define PST_O_SYNC O_SYNC +#define PST_O_TMPFILE O_TMPFILE +#define PST_O_TRUNC O_TRUNC +#endif + +#ifdef _IS_WINDOWS +// See GetCurrentThreadId +// Note: The PST_THREAD_ID is the unique system-wide thread id. A Windows +// thread HANDLE can be derived from the id if needed by calling OpenThread +typedef uint32_t pst_thread_id; // DWORD := uint32_t +#define PST_THREAD_ID pst_thread_id + +#define PST_THREAD_ID_SELF GetCurrentThreadId +#else +#include // to define pthread_t even without PST_THREAD_HDR +#define PST_THREAD_ID pthread_t + +#define PST_THREAD_ID_SELF pthread_self +#endif + +#ifdef _IS_WINDOWS +#define PST_STRNCASECMP _strnicmp +#else +#define PST_STRNCASECMP strncasecmp +#endif + +#ifdef _IS_WINDOWS +#define PST_STRCASECMP _stricmp +#else +#define PST_STRCASECMP strcasecmp +#endif + +// Note: PS_STRLCPY, PS_STRLCAT, PS_STRNCPY_S and PS_ESTRUNCATE are defined in +// ps_strl.h. + +#ifdef _IS_WINDOWS +// Reference /usr/include/asm-generic/fcntl.h +// #define PST_O_ACCMODE 00000003 +// #define PST_O_RDONLY 00000000 +// #define PST_O_WRONLY 00000001 +// #define PST_O_RDWR 00000002 +// #define PST_O_CREAT 00000100 /* not fcntl */ +// #define PST_O_EXCL 00000200 /* not fcntl */ +// #define PST_O_NOCTTY 00000400 /* not fcntl */ +// #define PST_O_TRUNC 00001000 /* not fcntl */ +// #define PST_O_APPEND 00002000 +#define PST_O_NONBLOCK 00004000 +// #define PST_O_DSYNC 00010000 /* used to be O_SYNC, see below */ +// #define PST_FASYNC 00020000 /* fcntl, for BSD compatibility */ +// #define PST_O_DIRECT 00040000 /* direct disk access hint */ +// #define PST_O_LARGEFILE 00100000 +// #define PST_O_DIRECTORY 00200000 /* must be a directory */ +// #define PST_O_NOFOLLOW 00400000 /* don't follow links */ +// #define PST_O_NOATIME 01000000 +#define PST_O_CLOEXEC 02000000 /* set close_on_exec - does nothing in Win */ +#else +#include // for O_NONBLOCK +// #define PST_O_ACCMODE O_ACCMODE +// #define PST_O_RDONLY O_RDONLY +// #define PST_O_WRONLY O_WRONLY +// #define PST_O_RDWR O_RDWR +// #define PST_O_CREAT O_CREAT +// #define PST_O_EXCL O_EXCL +// #define PST_O_NOCTTY O_NOCTTY +// #define PST_O_TRUNC O_TRUNC +// #define PST_O_APPEND O_APPEND +#define PST_O_NONBLOCK O_NONBLOCK +// #define PST_O_DSYNC O_DSYNC +// #define PST_FASYNC FASYNC +// #define PST_O_DIRECT O_DIRECT +// #define PST_O_LARGEFILE O_LARGEFILE +// #define PST_O_DIRECTORY O_DIRECTORY +// #define PST_O_NOFOLLOW O_NOFOLLOW +// #define PST_O_NOATIME O_NOATIME +#define PST_O_CLOEXEC O_CLOEXEC +#endif + +#ifdef _IS_WINDOWS +// As per include/asm-generic/fcntl.h in Linux +#define PST_FD_CLOEXEC 1 +#else +#define PST_FD_CLOEXEC FD_CLOEXEC +#endif + +#ifdef __GNUC__ +// GCC 4.8+, Clang, Intel and other compilers compatible with GCC +#define unreachable() __builtin_unreachable() +// [[noreturn]] inline __attribute__((always_inline)) void unreachable() {__builtin_unreachable();} +#elif defined(_MSC_VER) // MSVC +[[noreturn]] __forceinline void unreachable() {__assume(false);} +#else // ??? +inline void unreachable() {} +#endif +/// Note: Could also use std::unreachable for C++23 + +/* ------------------------------------------------------------------------- */ +#endif // of ifndef _WINORNIX_H_ diff --git a/meson.build b/meson.build index b33a11fa4..273f734e4 100644 --- a/meson.build +++ b/meson.build @@ -21,8 +21,8 @@ fs = import('fs') #macOS host_machine.system() is 'darwin' -if host_machine.system() != 'linux' and host_machine.system() != 'darwin' and not host_machine.system().endswith('bsd') - error('Pistache currenly only supports Linux, macOS and Free/Open/NetBSD. See https://github.com/pistacheio/pistache/issues/6#issuecomment-242398225 for more information') +if host_machine.system() != 'linux' and host_machine.system() != 'darwin' and host_machine.system() != 'windows' and not host_machine.system().endswith('bsd') + error('Pistache currenly only supports Linux, macOS, Free/Open/NetBSD and Windows.') endif compiler = meson.get_compiler('cpp') @@ -89,6 +89,27 @@ if get_option('PISTACHE_USE_CONTENT_ENCODING_DEFLATE') public_deps += zlib_dep endif +if host_machine.system() == 'windows' + # We have to use find_library unless the library is one of the + # special ones documented here: + # https://mesonbuild.com/Dependencies.html#dependencies-with-custom-lookup-functionality + # If the library is one of the "special ones", we can simply do + # deps_libpistache += as we do for zlib above for + # instance + + ws2_32_dep = compiler.find_library('Ws2_32', required: true) + deps_libpistache += ws2_32_dep + + dbghelp_dep = compiler.find_library('dbghelp', required: true) + deps_libpistache += dbghelp_dep # for __unDName + + iphlpapi_dep = compiler.find_library('Iphlpapi', required: true) + deps_libpistache += iphlpapi_dep # for GetAdaptersAddresses + + mswsock_dep = compiler.find_library('Mswsock', required: true) + deps_libpistache += mswsock_dep # for TransmitFile +endif + # Check if -latomic is needed - https://github.com/llvm/llvm-project/blob/main/llvm/cmake/modules/CheckAtomic.cmake compiler_id = compiler.get_id() @@ -146,40 +167,48 @@ if get_option('PISTACHE_USE_SSL') public_deps += openssl_dep endif -if host_machine.system() == 'darwin' or host_machine.system().endswith('bsd') or get_option('PISTACHE_FORCE_LIBEVENT') +if host_machine.system() == 'darwin' or host_machine.system() == 'windows' or host_machine.system().endswith('bsd') or get_option('PISTACHE_FORCE_LIBEVENT') deps_libpistache += dependency('libevent') - deps_libpistache += dependency('libevent_pthreads') + if (not (host_machine.system() == 'windows')) + # It looks like libevent assumes windows threads in + # windows, at least by default, and libevent_pthreads + # is not built on windows + deps_libpistache += dependency('libevent_pthreads') + endif endif if host_machine.system().endswith('bsd') # libexecinfo is included for the 'backtrace' function libexecinfo_dep = compiler.find_library('execinfo') + deps_libpistache += libexecinfo_dep endif -# libdl may be required for function dladdr, used in logStackTrace, -# which is called by PS_LogWoBreak, used in turn by the stack-trace -# logging macro PS_LOG_WO_BREAK_LIMITED and its derivatives. Issue -# #1230. -# Note: If 'dl' is not available, per Meson it suggests that the -# functionality is provided by libc - -# It would be nice to use compiler.has_function('dladdr') to test if -# we need to add libdl, but unfortunately that approach seems to break -# certain Redhat builds, specifically the Redhat builds that use -# ubi-8. In such cases, it appears meson creates a test file that -# includes the comment: -# With some toolchains ... the compiler provides various builtins -# which are not really implemented... [If] the user provides a -# header, including the header didn't lead to the function being -# defined, and the function we are checking isn't a builtin itself, -# we assume the builtin is not functional and error out -# To avoid generating such an error, we take the simpler approach of -# trying to add libdl but making it optional (i.e. not required). -if meson.version().version_compare('>=0.62.0') - deps_libpistache += dependency('dl', required: false) -else - deps_libpistache += compiler.find_library('dl', required: false) +if host_machine.system() != 'windows' + # libdl may be required for function dladdr, used in logStackTrace, + # which is called by PS_LogWoBreak, used in turn by the stack-trace + # logging macro PS_LOG_WO_BREAK_LIMITED and its derivatives. Issue + # #1230. + # Note: If 'dl' is not available, per Meson it suggests that the + # functionality is provided by libc + + # It would be nice to use compiler.has_function('dladdr') to test if + # we need to add libdl, but unfortunately that approach seems to break + # certain Redhat builds, specifically the Redhat builds that use + # ubi-8. In such cases, it appears meson creates a test file that + # includes the comment: + # With some toolchains ... the compiler provides various builtins + # which are not really implemented... [If] the user provides a + # header, including the header didn't lead to the function being + # defined, and the function we are checking isn't a builtin itself, + # we assume the builtin is not functional and error out + # To avoid generating such an error, we take the simpler approach of + # trying to add libdl but making it optional (i.e. not required). + if meson.version().version_compare('>=0.62.0') + deps_libpistache += dependency('dl', required: false) + else + deps_libpistache += compiler.find_library('dl', required: false) + endif endif version_array = [] diff --git a/pistache.io/docs/quickstart.md b/pistache.io/docs/quickstart.md index 79202b2a3..7fc8eda48 100644 --- a/pistache.io/docs/quickstart.md +++ b/pistache.io/docs/quickstart.md @@ -37,7 +37,9 @@ meson setup build meson install -C build ``` -Also, Pistache does not support Windows yet, but should work fine under [WSL](https://docs.microsoft.com/windows/wsl/about). +Pistache can run natively under Windows (see "Building on Windows.txt") or should work fine under [WSL](https://docs.microsoft.com/windows/wsl/about). + +For macOS, see "Building on macOS.txt". For BSD, see "Building on BSD - FreeBSD, OpenBSD and NetBSD.txt". ## Serving requests diff --git a/src/client/client.cc b/src/client/client.cc index 06a27968b..94283005a 100644 --- a/src/client/client.cc +++ b/src/client/client.cc @@ -10,6 +10,8 @@ Implementation of the Http client */ +#include + #include #include #include @@ -17,24 +19,22 @@ #include #include -#include +#include PIST_QUOTE(PST_NETDB_HDR) +#include PIST_QUOTE(PST_SOCKET_HDR) +#include PIST_QUOTE(PIST_SOCKFNS_HDR) -#ifdef _USE_LIBEVENT_LIKE_APPLE -// For sendfile(...) function -#include -#include -#include -#else -#include -#endif +// ps_sendfile.h includes sys/uio.h in macOS, and sys/sendfile.h in Linux +#include + +#include PIST_QUOTE(PST_STRERROR_R_HDR) -#include #include #include #include #include #include +#include // for std::memcpy namespace Pistache::Http::Experimental { @@ -51,7 +51,7 @@ namespace Pistache::Http::Experimental // if https_out is non null, then *https_out is set to true if URL // starts with "https://" and false otherwise std::pair splitUrl( - const std::string& url, bool* https_out = NULL) + const std::string& url, bool* https_out = nullptr) { RawStreamBuf buf(const_cast(url.data()), url.size()); StreamCursor cursor(&buf); @@ -188,9 +188,9 @@ namespace Pistache::Http::Experimental Async::Promise asyncConnect(std::shared_ptr connection, const struct sockaddr* address, - socklen_t addr_len); + PST_SOCKLEN_T addr_len); - Async::Promise + Async::Promise asyncSendRequest(std::shared_ptr connection, std::shared_ptr timer, std::string buffer); @@ -218,7 +218,7 @@ namespace Pistache::Http::Experimental , connection(connection) , addr_len(_addr_len) { - memcpy(&addr, _addr, addr_len); + std::memcpy(&addr, _addr, addr_len); } const sockaddr* getAddr() const @@ -341,7 +341,7 @@ namespace Pistache::Http::Experimental void Transport::unregisterPoller(Polling::Epoll& poller) { #ifdef _USE_LIBEVENT - epoll_fd = NULL; + epoll_fd = nullptr; #endif connectionsQueue.unbind(poller); @@ -350,7 +350,8 @@ namespace Pistache::Http::Experimental Async::Promise Transport::asyncConnect(std::shared_ptr connection, - const struct sockaddr* address, socklen_t addr_len) + const struct sockaddr* address, + PST_SOCKLEN_T addr_len) { PS_TIMEDBG_START_THIS; @@ -364,14 +365,14 @@ namespace Pistache::Http::Experimental }); } - Async::Promise + Async::Promise Transport::asyncSendRequest(std::shared_ptr connection, std::shared_ptr timer, std::string buffer) { PS_TIMEDBG_START_THIS; - return Async::Promise( + return Async::Promise( [&](Async::Resolver& resolve, Async::Rejection& reject) { PS_TIMEDBG_START; auto ctx = context(); @@ -407,13 +408,13 @@ namespace Pistache::Http::Experimental return; } - ssize_t totalWritten = 0; + PST_SSIZE_T totalWritten = 0; for (;;) { - const char* data = buffer.data() + totalWritten; - const ssize_t len = buffer.size() - totalWritten; - const ssize_t bytesWritten = ::send(GET_ACTUAL_FD(fd), data, - len, 0); + const char* data = buffer.data() + totalWritten; + const PST_SSIZE_T len = buffer.size() - totalWritten; + const PST_SSIZE_T bytesWritten = PST_SOCK_SEND(GET_ACTUAL_FD(fd), + data, len, 0); if (bytesWritten < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) @@ -490,12 +491,24 @@ namespace Pistache::Http::Experimental PS_LOG_DEBUG_ARGS("Calling ::connect fs %d", GET_ACTUAL_FD(fd)); - int res = ::connect(GET_ACTUAL_FD(fd), data->getAddr(), data->addr_len); + int res = PST_SOCK_CONNECT(GET_ACTUAL_FD(fd), data->getAddr(), data->addr_len); + PST_DBG_DECL_SE_ERR_P_EXTRA; PS_LOG_DEBUG_ARGS("::connect res %d, errno on fail %d (%s)", res, (res < 0) ? errno : 0, - (res < 0) ? strerror(errno) : "success"); - - if ((res == 0) || ((res == -1) && (errno == EINPROGRESS))) + (res < 0) ? + PST_STRERROR_R_ERRNO : "success"); + + if ((res == 0) || ((res == -1) && (errno == EINPROGRESS)) + #ifdef _IS_WINDOWS + || ((res == -1) && (errno == EWOULDBLOCK)) + // In Linux, EWOULDBLOCK can be set by ::connect, but only for + // Unix domain sockets (i.e. sockets being used for + // inter-process communication) which is not our situation + // + // In Windows, EWOULDBLOCK is typically set here for + // non-blocking sockets + #endif + ) { reactor()->registerFdOneShot(key(), fd, NotifyOn::Write | NotifyOn::Hangup | NotifyOn::Shutdown); @@ -526,7 +539,7 @@ namespace Pistache::Http::Experimental // Note: We only use the second element of *connIt (which is // "connection"); fd is the first element (the map key). Since *fd is // not in fact changed, it is OK to cast away the const of Fd here. - Fd fd_for_find = ((Fd)fd); + Fd fd_for_find = PS_CAST_AWAY_CONST_FD(fd); auto connIt = connections.find(fd_for_find); if (connIt != std::end(connections)) @@ -570,7 +583,7 @@ namespace Pistache::Http::Experimental // Note: We only use the second element of *connIt (which is // "connection"); fd is the first element (the map key). Since *fd is // not in fact changed, it is OK to cast away the const of Fd here. - Fd fd = ((Fd)fd_const); + Fd fd = PS_CAST_AWAY_CONST_FD(fd_const); auto connIt = connections.find(fd); if (connIt != std::end(connections)) @@ -616,7 +629,7 @@ namespace Pistache::Http::Experimental // Note: We only use the second element of *connIt (which is // "connection"); fd is the first element (the map key). Since *fd is // not in fact changed, it is OK to cast away the const of Fd here. - Fd fd = ((Fd)fd_const); + Fd fd = PS_CAST_AWAY_CONST_FD(fd_const); auto connIt = connections.find(fd); if (connIt != std::end(connections)) @@ -634,25 +647,47 @@ namespace Pistache::Http::Experimental { PS_TIMEDBG_START_THIS; - ssize_t totalBytes = 0; - - for (;;) - { - char buffer[Const::MaxBuffer] = { + PST_SSIZE_T totalBytes = 0; + unsigned int max_buffer = Const::MaxBuffer; + char stack_buffer[Const::MaxBuffer+16] = { 0, }; + char * buffer = &(stack_buffer[0]); + std::unique_ptr buffer_uptr; + for (;;) + { Fd conn_fd = connection->fd(); if (conn_fd == PS_FD_EMPTY) break; // can happen if fd was closed meanwhile - const ssize_t bytes = recv(GET_ACTUAL_FD(conn_fd), - buffer, Const::MaxBuffer, 0); + const PST_SSIZE_T bytes = PST_SOCK_RECV( + GET_ACTUAL_FD(conn_fd), buffer+totalBytes, + max_buffer - totalBytes, 0); if (bytes == -1) { - if (errno != EAGAIN && errno != EWOULDBLOCK) + if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { - connection->handleError(strerror(errno)); + if (totalBytes) + { + PS_LOG_DEBUG_ARGS("Passing %d totalBytes to " + "handleResponsePacket", + totalBytes); + + connection->handleResponsePacket(buffer, totalBytes); + } + else + { + PS_LOG_DEBUG("totalBytes is zero"); + } + } + else + { + PST_DECL_SE_ERR_P_EXTRA; + const char * err_msg = PST_STRERROR_R_ERRNO; + PS_LOG_DEBUG_ARGS("recv err, errno %d %s", errno, err_msg); + + connection->handleError(err_msg); } break; } @@ -668,8 +703,29 @@ namespace Pistache::Http::Experimental } else { + PS_LOG_DEBUG_ARGS("Rxed %d bytes", bytes); totalBytes += bytes; - connection->handleResponsePacket(buffer, bytes); + } + if (totalBytes >= max_buffer) + { + auto new_max_buffer = (max_buffer * 2); + char * new_buffer = 0; + if ((new_max_buffer > (8*1024*1024)) ||//new_max_buffer > 8MB ? + (0 == (new_buffer = new char[new_max_buffer+16]))) + { + if (new_max_buffer > 268435456) + PS_LOG_WARNING("Receive buffer would be too big"); + else + PS_LOG_WARNING_ARGS("Failed to alloc %d bytes memory", + new_max_buffer+16); + + connection->handleResponsePacket(buffer, totalBytes); + break; + } + std::memcpy(new_buffer, buffer, max_buffer); + buffer_uptr = std::unique_ptr(new_buffer); + buffer = new_buffer; + max_buffer = new_max_buffer; } } } @@ -699,11 +755,13 @@ namespace Pistache::Http::Experimental TRY(addressInfo.invoke(host.c_str(), port.c_str(), &hints)); const addrinfo* addrs = addressInfo.get_info_ptr(); - int sfd = -1; + em_socket_t sfd = -1; - for (const addrinfo* addr = addrs; addr; addr = addr->ai_next) + for (const addrinfo* an_addr = addrs; an_addr; + an_addr = an_addr->ai_next) { - sfd = ::socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + sfd = PST_SOCK_SOCKET(an_addr->ai_family, an_addr->ai_socktype, + an_addr->ai_protocol); PS_LOG_DEBUG_ARGS("::socket actual_fd %d", sfd); if (sfd < 0) continue; @@ -718,20 +776,26 @@ namespace Pistache::Http::Experimental fd_ = TRY_NULL_RET( EventMethFns::em_event_new( sfd, // pre-allocated file desc - EVM_READ | EVM_WRITE | EVM_PERSIST, + EVM_READ | EVM_WRITE | EVM_PERSIST | EVM_ET, F_SETFDL_NOTHING, // setfd - O_NONBLOCK // setfl + PST_O_NONBLOCK // setfl )); #else fd_ = sfd; #endif transport_ - ->asyncConnect(shared_from_this(), addr->ai_addr, addr->ai_addrlen) + ->asyncConnect(shared_from_this(), an_addr->ai_addr, + static_cast(an_addr->ai_addrlen)) + // Note: We cast to PST_SOCKLEN_T for Windows because Windows + // uses "int" for PST_SOCKLEN_T, whereas Linux uses size_t. In + // general, even for Windows we use size_t for addresses' + // lengths in Pistache (e.g. in struct ifaddr), hence why we + // cast here .then( [=]() { socklen_t len = sizeof(saddr); - getsockname(sfd, reinterpret_cast(&saddr), &len); + PST_SOCK_GETSOCKNAME(sfd, reinterpret_cast(&saddr), &len); connectionState_.store(Connected); processRequestQueue(); }, @@ -886,6 +950,8 @@ namespace Pistache::Http::Experimental { PS_TIMEDBG_START_THIS; + PS_LOG_DEBUG_ARGS("Error string %s", error); + if (requestEntry) { if (requestEntry->timer) @@ -993,11 +1059,11 @@ namespace Pistache::Http::Experimental } } - void ConnectionPool::init(size_t maxConnectionsPerHost, - size_t maxResponseSize) + void ConnectionPool::init(size_t maxConnectionsPerHostParm, + size_t maxResponseSizeParm) { - this->maxConnectionsPerHost = maxConnectionsPerHost; - this->maxResponseSize = maxResponseSize; + this->maxConnectionsPerHost = maxConnectionsPerHostParm; + this->maxResponseSize = maxResponseSizeParm; } std::shared_ptr @@ -1355,9 +1421,9 @@ namespace Pistache::Http::Experimental PS_LOG_DEBUG("No transport yet on connection"); auto transports = reactor_->handlers(transportKey); - auto index = ioIndex.fetch_add(1) % transports.size(); + auto index = ioIndex.fetch_add(1) % transports.size(); - auto transport = std::static_pointer_cast(transports[index]); + auto transport = std::static_pointer_cast(transports[static_cast(index)]); PS_LOG_DEBUG_ARGS("Associating transport %p on connection %p", transport.get(), conn.get()); conn->associateTransport(transport); diff --git a/src/common/base64.cc b/src/common/base64.cc index f67daeca8..445756e53 100644 --- a/src/common/base64.cc +++ b/src/common/base64.cc @@ -46,7 +46,11 @@ vector::size_type Base64Decoder::CalculateDecodedSize() const // without storing them, until we hit the first character we cannot decode. // This should be the first padding character or end of string... while (DecodeCharacter(*EndIterator) < static_cast(64)) + { ++EndIterator; + if (EndIterator == m_Base64EncodedString.end()) + break; + } // The length of the encoded string is the distance from the beginning to // the first non-decodable character, such as padding... diff --git a/src/common/cookie.cc b/src/common/cookie.cc index 0bd0d8a51..91ed1f371 100644 --- a/src/common/cookie.cc +++ b/src/common/cookie.cc @@ -208,28 +208,28 @@ namespace Pistache::Http os << name << "=" << value; if (path.has_value()) { - const std::string& value = *path; + const std::string& val = *path; os << "; "; - os << "Path=" << value; + os << "Path=" << val; } if (domain.has_value()) { - const std::string& value = *domain; + const std::string& val = *domain; os << "; "; - os << "Domain=" << value; + os << "Domain=" << val; } if (maxAge.has_value()) { - int value = *maxAge; + int val = *maxAge; os << "; "; - os << "Max-Age=" << value; + os << "Max-Age=" << val; } if (expires.has_value()) { - const FullDate& value = *expires; + const FullDate& val = *expires; os << "; "; os << "Expires="; - value.write(os); + val.write(os); } if (secure) os << "; Secure"; diff --git a/src/common/description.cc b/src/common/description.cc index 178eda95f..7f53b53ef 100644 --- a/src/common/description.cc +++ b/src/common/description.cc @@ -236,9 +236,9 @@ namespace Pistache::Rest std::move(description)); } - SubPath SubPath::path(const std::string& prefix) const + SubPath SubPath::path(const std::string& pth_prefix) const { - return SubPath(this->prefix + prefix, paths); + return SubPath(this->prefix + pth_prefix, paths); } Parameter::Parameter(std::string name, std::string description) @@ -466,13 +466,13 @@ namespace Pistache::Rest return trailingSlashValue; } - std::string join(const std::string& value) const + std::string join(const std::string& value_parm) const { std::string val; - if (value[0] == '/') - val = value.substr(1); + if (value_parm[0] == '/') + val = value_parm.substr(1); else - val = value; + val = value_parm; return trailingSlashValue + val; } diff --git a/src/common/eventmeth.cc b/src/common/eventmeth.cc index e49fd54da..e67c2ec40 100644 --- a/src/common/eventmeth.cc +++ b/src/common/eventmeth.cc @@ -4,6 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include +#include PIST_QUOTE(PST_ERRNO_HDR) + #include #include @@ -12,6 +15,8 @@ #ifdef _USE_LIBEVENT #include +#include // for evutil_socket_t + /* ------------------------------------------------------------------------- */ /* @@ -25,7 +30,7 @@ namespace Pistache { class EventMethBase; class EmEventCtr; - + class EventMethEpollEquivImpl { public: @@ -38,8 +43,7 @@ namespace Pistache short events, // bitmask of EVM_... events std::chrono::milliseconds * timeval_cptr); - #define F_SETFDL_NOTHING ((int)((unsigned) 0x8A82)) - static Fd em_event_new(em_socket_t actual_fd,//file desc, signal, or -1 + static Fd em_event_new(evutil_socket_t actual_fd,//file desc, signal, or -1 short flags, // EVM_... flags // For setfd and setfl arg: // F_SETFDL_NOTHING - change nothing @@ -55,7 +59,7 @@ namespace Pistache // If emee is NULL here, it will need to be supplied when settime is // called - static Fd em_timer_new(clockid_t clock_id, + static Fd em_timer_new(PST_CLOCK_ID_T clock_id, // For setfd and setfl arg: // F_SETFDL_NOTHING - change nothing // Zero or pos number that is not @@ -97,7 +101,7 @@ namespace Pistache // EvEvents are some combination of EVM_TIMEOUT, EVM_READ, EVM_WRITE, // EVM_SIGNAL, EVM_PERSIST, EVM_ET, EVM_CLOSED - + int toEvEvents(const Flags& interest); Flags toNotifyOn(Fd fd); // uses fd->ready_flags @@ -122,22 +126,22 @@ namespace Pistache // otherwise. emee_cptr_set_mutex_ is locked inside the function. static EventMethEpollEquivImpl * getEventMethEpollEquivImplFromEmeeSet( EventMethEpollEquivImpl * emee); - + static int getTcpProtNum(); // As per getprotobyname("tcp") void handleEventCallback(void * cb_arg, - em_socket_t cb_actual_fd, + evutil_socket_t cb_actual_fd, short ev_flags); // One or more EVM_* flags public: - static int getActualFd(const EmEvent * em_event); + static evutil_socket_t getActualFd(const EmEvent * em_event); // efd should be a pointer to EmEventFd - does dynamic cast - static ssize_t writeEfd(EmEvent * efd, const uint64_t val); - static ssize_t readEfd(EmEvent * efd, uint64_t * val_out_ptr); + static PST_SSIZE_T writeEfd(EmEvent * efd, const uint64_t val); + static PST_SSIZE_T readEfd(EmEvent * efd, uint64_t * val_out_ptr); - static ssize_t read(EmEvent * fd, void * buf, size_t count); - static ssize_t write(EmEvent * fd, const void * buf, size_t count); + static PST_SSIZE_T read(EmEvent * fd, void * buf, size_t count); + static PST_SSIZE_T write(EmEvent * fd, const void * buf, size_t count); static EmEvent * getAsEmEvent(EmEventFd * efd); @@ -147,21 +151,21 @@ namespace Pistache static void setEmEventUserData(EmEvent * fd, Fd user_data); // For EmEventTmrFd, settime is analagous to timerfd_settime in linux - // + // // The linux flags TFD_TIMER_ABSTIME and TFD_TIMER_CANCEL_ON_SET are // not supported // // Since pistache doesn't use the "struct itimerspec * old_value" // feature of timerfd_settime, we haven't implemented that feature. - // + // // If the EventMethEpollEquiv was not specified already (e.g. at // make_new), the it must be specified here - // + // // Note: settime is in EmEvent rather than solely in EmEventTmrFd since // any kind of event may have a timeout set, not only timer events static int setEmEventTime(EmEvent * fd, const std::chrono::milliseconds * new_timeval_cptr, - EventMethEpollEquivImpl * emee = NULL/*may be NULL*/); + EventMethEpollEquivImpl * emee = nullptr); static EmEventType getEmEventType(EmEvent * fd); @@ -180,9 +184,13 @@ namespace Pistache // Note there is a different EventMethBase for each EventMethEpollEquiv EventMethBase * getEventMethBase() {return(event_meth_base_.get());} + // Adjusts the em_event flags to be exactly what we should pass to the + // libevent event_new function + short getFlagsToActuallyUseWithLibEvEventNew(short candidate_flags); + #ifdef DEBUG public: - static std::string getActFdAndFdlFlagsAsStr(int actual_fd); + static std::string getActFdAndFdlFlagsAsStr(em_socket_t actual_fd); // See also macro LOG_DEBUG_ACT_FD_AND_FDL_FLAGS(__ACTUAL_FD__) #endif @@ -205,7 +213,7 @@ namespace Pistache int getReadyEmEventsHelper(int timeout, std::set & ready_evm_events_out); - + // returns PS_FD_EMPTY if not found, returns fd if found Fd findFdInInterest(Fd fd); void addEventToReadyInterestAlrdyLocked(Fd fd, @@ -235,7 +243,7 @@ namespace Pistache // Note there is a different EventMethBase for each EventMethEpollEquiv std::unique_ptr event_meth_base_; - + // interest list - events that process has asked to be monitored std::set interest_; // ready - members of interest list ready for IO @@ -260,13 +268,13 @@ namespace Pistache // Note: If emee_cptr_set_mutex_ is to be locked together with // interest_mutex_, emee_cptr_set_mutex_ must be locked first }; - - + + class EmEvent { public: - - static EmEvent * make_new(int actual_fd, short flags, + + static EmEvent * make_new(evutil_socket_t actual_fd, short flags, // For setfd and setfl arg: // F_SETFDL_NOTHING - change nothing // Zero or pos number that is not @@ -284,33 +292,45 @@ namespace Pistache int set_timeout(std::chrono::milliseconds * timeval_cptr); // For EmEventTmrFd, settime is analagous to timerfd_settime in linux - // + // // The linux flags TFD_TIMER_ABSTIME and TFD_TIMER_CANCEL_ON_SET are // not supported // // Since pistache doesn't use the "struct itimerspec * old_value" // feature of timerfd_settime, we haven't implemented that feature. - // + // // If new_timeval_cptr is NULL or *new_timeval_cptr is all zero, the // settime call will reset the timer (again as per timerfd_settime) // // If the EventMethEpollEquiv was not specified already (e.g. at // make_new), the it must be specified here - // + // // Note: settime is in EmEvent rather than solely in EmEventTmrFd since // any kind of event may have a timeout set, not only timer events virtual int settime(const std::chrono::milliseconds* new_timeval_cptr, - EventMethEpollEquivImpl * emee = NULL/*may be NULL*/); + EventMethEpollEquivImpl * emee = nullptr); int disarm(); int close(); // disarms and closes + // In level-triggered systems, "deactivate" is used for a triggered + // event, calling libevent's event_del and then event_add. event_del + // causes the event to be inactive and non-pending, while event_add + // causes it to be pending (i.e. waiting for events). So after + // deactivate is called, the event is inactive and pending. + int deactivate(); + + // Call only on OS where we don't have edge-trigger support (i.e. where + // libevent does not have EV_FEATURE_ET) + int deactivateAfterTriggerIfNeeded(); + + // Return -1 if there is no actual file descriptor - static int getActualFd(const EmEvent * em_ev); - virtual int getActualFd() const; + static evutil_socket_t getActualFd(const EmEvent * em_ev); + virtual evutil_socket_t getActualFd() const; - virtual ssize_t read(void * buf, size_t count); - virtual ssize_t write(const void * buf, size_t count); + virtual PST_SSIZE_T read(void * buf, size_t count); + virtual PST_SSIZE_T write(const void * buf, size_t count); virtual int ctl( EvCtlAction op, //add,mod,del @@ -339,7 +359,8 @@ namespace Pistache void resetReadyFlags() {ready_flags_ = 0;} uint64_t getUserDataUi64() const {return(user_data_);} - Fd getUserData() const {return(((Fd)user_data_));} + Fd getUserData() const {return((reinterpret_cast + (static_cast(user_data_))));} void setUserData(uint64_t user_data) { user_data_ = user_data; } virtual EmEventType getEmEventType() const { return(EmEvReg); } @@ -347,7 +368,7 @@ namespace Pistache // checks emee_cptr_set_ EventMethEpollEquivImpl * getEventMethEpollEquivImpl(); - void detachEventMethEpollEquiv(); + void detachEventMethEpollEquivImmedFree(); // made virtual since can't call destructor on non-final type with // virtual function(s) but non-virtual destructor @@ -371,26 +392,26 @@ namespace Pistache // To be called only from EventMethEpollEquiv::ctlEx() and callbacks - // almost private bool addWasArtificial() { return(add_was_artificial_); } - - + + protected: EmEvent(); // init is clled from make_new, or from a construction function of a // derived class - EmEvent * init(int actual_fd, - short flags, - // For setfd and setfl arg: - // F_SETFDL_NOTHING - change nothing - // Zero or pos number that is not - // F_SETFDL_NOTHING - set flags to value of - // arg, and clear any other flags - // Neg number that is not F_SETFDL_NOTHING - // - set flags that are set in (0 - arg), - // but don't clear any flags - int f_setfd_flags, // e.g. FD_CLOEXEC - int f_setfl_flags); // e.g. O_NONBLOCK + EmEvent * init(evutil_socket_t actual_fd, + short flags, + // For setfd and setfl arg: + // F_SETFDL_NOTHING - change nothing + // Zero or pos number that is not + // F_SETFDL_NOTHING - set flags to value of + // arg, and clear any other flags + // Neg number that is not F_SETFDL_NOTHING + // - set flags that are set in (0 - arg), + // but don't clear any flags + int f_setfd_flags, // e.g. FD_CLOEXEC + int f_setfl_flags); // e.g. O_NONBLOCK void setPriorTv(const std::chrono::milliseconds * timeval_cptr); @@ -409,7 +430,7 @@ namespace Pistache // then add_was_artificial_ is reset. EventMethEpollEquivImpl * event_meth_epoll_equiv_impl_; - + private: short ready_flags_; // set when event becomes ready @@ -419,16 +440,16 @@ namespace Pistache int requested_f_setfd_flags_; int requested_f_setfl_flags_; - int requested_actual_fd_; + evutil_socket_t requested_actual_fd_; struct timeval * prior_tv_cptr_;//either points to prior_tv_ or is null struct timeval prior_tv_; // for timeout - static void setFdlFlagsHelper(int actual_fd, + static void setFdlFlagsHelper(evutil_socket_t actual_fd, int get_cmd, // F_GETFD or F_GETFL int set_cmd, // F_SETFD or F_SETFL int f_setfdl_flags); - void setFdlFlagsIfNeededAndActualFd(int actual_fd); + void setFdlFlagsIfNeededAndActualFd(evutil_socket_t actual_fd); // The main getActualFd() will throw rather than return an actual-fd // value for the derived class EmEventFd, whereas getActualFdPrv will @@ -439,12 +460,12 @@ namespace Pistache #ifdef DEBUG public:// getActualFdPrv public so debug fn logPendingOrNot can call it #endif - int getActualFdPrv() const; + evutil_socket_t getActualFdPrv() const; #ifdef DEBUG private: #endif // Make eventCallbackFn a friend so it can call getActualFdPrv - friend void eventCallbackFn(em_socket_t, short, void *); + friend void eventCallbackFn(evutil_socket_t, short, void *); }; @@ -456,16 +477,16 @@ namespace Pistache Fd getAsFd() {return(getAsFd(this));} // getActualFd must not be called for a EmEventFd - int getActualFd() const override; // overriding EmEvent version + evutil_socket_t getActualFd() const override;//override EmEvent version // For read, write, poll rules, see definition of eventfd in Linux: // e.g. https://www.man7.org/linux/man-pages/man2/eventfd.2.html - ssize_t read(uint64_t * val_out_ptr); + PST_SSIZE_T read(uint64_t * val_out_ptr); // buf must be at least 8 bytes long // read copies an 8-byte integer into buf - ssize_t read(void * buf, size_t count) override; + PST_SSIZE_T read(void * buf, size_t count) override; // Sets counter_val_ to zero. If counter_val_ already zero, does // nothing. Returns old counter_val. @@ -485,10 +506,10 @@ namespace Pistache virtual EmEventType getEmEventType() const override {return(EmEvNone);} protected: - ssize_t writeProt(const uint64_t val); + PST_SSIZE_T writeProt(const uint64_t val); // write copies an integer of length up to 8 from buf - ssize_t writeProt(const void * buf, size_t count); + PST_SSIZE_T writeProt(const void * buf, size_t count); public: // renewEv is public solely so it can be a friend of @@ -497,9 +518,9 @@ namespace Pistache // solely from EmEventFd::read and EmEventFd::write. void renewEv(); // pseudo private - protected: + protected: EmEventCtr(uint64_t initval); - + private: std::mutex cv_read_mutex_; // used in wait std::mutex cv_write_mutex_; // used in wait @@ -528,13 +549,13 @@ namespace Pistache // - EmEventFd is writable if 1 can be written without blocking // (Note - we ignore the possibility of an overflow caused by 2^64 eventfd // "signal posts" - won't happen) - // + // // Where appropriate, we can use libevent's event_active to make the event // active class EmEventFd final : public EmEventCtr // public so we can dynamic_cast { public: - + // Will return NULL if not an EmEventFd static FdEventFd getFromEmEventCPtr(Fd eme_cptr); static FdEventFd getFromEmEventCPtrNoLogIfNull(Fd eme_cptr); @@ -552,16 +573,16 @@ namespace Pistache int f_setfd_flags, // e.g. FD_CLOEXEC int f_setfl_flags); // e.g. O_NONBLOCK - ssize_t write(const uint64_t val) {return(writeProt(val));} + PST_SSIZE_T write(const uint64_t val) {return(writeProt(val));} // write copies an integer of length up to 8 from buf - ssize_t write(const void * buf, size_t count) override + PST_SSIZE_T write(const void * buf, size_t count) override {return(writeProt(buf, count));} EmEventType getEmEventType() const override { return(EmEvEventFd); } - - + + private: EmEventFd(uint64_t initval); }; @@ -572,7 +593,7 @@ namespace Pistache // The EmEventTmrFd count holds the number of times the timer has expired // since the most recent call to read or settime, either of which sets the // count to zero. Read returns the value of count prior to the reset. - // + // // If read is attempted while count is already zero, the thread will block // if the EmEventTmrFd is blocking; or, if the EmEventTmrFd is nonblocking, // -1 is returned and errno set to EAGAIN. @@ -582,13 +603,13 @@ namespace Pistache // - EmEventTmrFd is writable if 1 can be written without blocking // (Note - we ignore the possibility of an overflow caused by 2^64 eventfd // "signal posts" - won't happen) - // + // // Where appropriate, we can use libevent's event_active to make the event // active class EmEventTmrFd final : public EmEventCtr//public so we can dynamic_cast { public: - + // Will return NULL if not an EmEventTmrFd static FdEventTmrFd getFromEmEventCPtr(Fd eme_cptr); static FdEventTmrFd getFromEmEventCPtrNoLogIfNull(Fd eme_cptr); @@ -605,7 +626,7 @@ namespace Pistache // changes in the clock value // If emee is NULL here, it will need to be supplied when settime is // called - static EmEventTmrFd * make_new(clockid_t clock_id, + static EmEventTmrFd * make_new(PST_CLOCK_ID_T clock_id, // For setfd and setfl arg: // F_SETFDL_NOTHING - change nothing // Zero or pos number that is not @@ -617,23 +638,23 @@ namespace Pistache int f_setfd_flags, // e.g. FD_CLOEXEC int f_setfl_flags, // e.g. O_NONBLOCK EventMethEpollEquivImpl * emee/*may be NULL*/); - + // settime is analagous to timerfd_settime in linux - // + // // The linux flags TFD_TIMER_ABSTIME and TFD_TIMER_CANCEL_ON_SET are // not supported // // Since pistache doesn't use the "struct itimerspec * old_value" // feature of timerfd_settime, we haven't implemented that feature. - // + // // If new_timeval_cptr is NULL or *new_timeval_cptr is all zero, the // settime call will reset the timer (again as per timerfd_settime) - // + // // Note: settime is in EmEvent rather than solely in EmEventTmrFd since // any kind of event may have a timeout set, not only timer events int settime(const std::chrono::milliseconds* new_timeval_cptr, - EventMethEpollEquivImpl * emee = NULL/*may be NULL*/) override; + EventMethEpollEquivImpl * emee = nullptr) override; EmEventType getEmEventType() const override { return(EmEvTimer); } @@ -642,11 +663,11 @@ namespace Pistache void handleEventCallback(short & ev_flags_in_out) override; private: - // NO ssize_t write(const uint64_t val); - ssize_t write(const void * buf, size_t count) override;// always fails + // NO PST_SSIZE_T write(const uint64_t val); + PST_SSIZE_T write(const void * buf, size_t count) override;// always fails private: - EmEventTmrFd(clockid_t clock_id, + EmEventTmrFd(PST_CLOCK_ID_T clock_id, EventMethEpollEquivImpl * emee/*may be NULL*/); }; @@ -675,18 +696,18 @@ namespace Pistache std::lock_guard l_guard(dbg_emv_set_mutex); PS_LOG_DEBUG_ARGS("Full set of %u EmEvent * follows:", dbg_emv_set.size()); - - for(auto it = dbg_emv_set.begin(); it != dbg_emv_set.end(); it++) + + for(auto it = dbg_emv_set.begin(); it != dbg_emv_set.end(); ++it) { bool break_out = false; std::stringstream sss; sss << " EmEvents: "; - - for(unsigned int i = 0; i<6; i++) + + for(unsigned int i = 0; i<6; ++i) { if (i != 0) { - it++; + ++it; if (it == dbg_emv_set.end()) { break_out = true; @@ -698,7 +719,7 @@ namespace Pistache const EmEvent * eme = *it; sss << eme; } - + const std::string s = sss.str(); PS_LOG_DEBUG_ARGS("%s", s.c_str()); @@ -720,8 +741,6 @@ namespace Pistache /* ------------------------------------------------------------------------- */ -#include - #include #include @@ -729,13 +748,13 @@ namespace Pistache #include #include -#include // for close -#include // for fcntl +#include PIST_QUOTE(PST_MISC_IO_HDR) // unistd.h, for close +#include PIST_QUOTE(PST_FCNTL_HDR) +#include PIST_QUOTE(PIST_SOCKFNS_HDR) // socket read, write and close + #include #ifdef DEBUG -#include // for basename_r -#include // for MAXPATHLEN #include // for std::atomic_int #endif @@ -760,10 +779,9 @@ namespace Pistache return(std::shared_ptr(emee_cptr)); } - - #define F_SETFDL_NOTHING ((int)((unsigned) 0x8A82)) + Fd EventMethFns::em_event_new( - em_socket_t actual_fd,//file desc, signal, or -1 + evutil_socket_t actual_fd,//file desc, signal, or -1 short flags, // EVM_... flags // For setfd and setfl arg: // F_SETFDL_NOTHING - change nothing @@ -780,11 +798,11 @@ namespace Pistache return(EventMethEpollEquivImpl::em_event_new(actual_fd, flags, f_setfd_flags, f_setfl_flags)); } - + // If emee is NULL here, it will need to be supplied when settime is // called - Fd EventMethFns::em_timer_new(clockid_t clock_id, + Fd EventMethFns::em_timer_new(PST_CLOCK_ID_T clock_id, // For setfd and setfl arg: // F_SETFDL_NOTHING - change nothing // Zero or pos number that is not @@ -801,7 +819,7 @@ namespace Pistache f_setfd_flags, f_setfl_flags, getEMEEImpl(emee))); } - + // For "eventfd-style" descriptors // Note that FdEventFd does not have an "actual fd" that the caller can @@ -827,7 +845,7 @@ namespace Pistache return(EventMethEpollEquivImpl::ctl(op, getEMEEImpl(epoll_equiv), event, events, timeval_cptr)); } - + // rets 0 on success, -1 error int EventMethFns::closeEvent(EmEvent * em_event) @@ -836,74 +854,74 @@ namespace Pistache } // See also CLOSE_FD macro - int EventMethFns::getActualFd(const EmEvent * em_event) + evutil_socket_t EventMethFns::getActualFd(const EmEvent * em_event) { return(EventMethEpollEquivImpl::getActualFd(em_event)); } // efd should be a pointer to EmEventFd - does dynamic cast - ssize_t EventMethFns::writeEfd(EmEvent * efd, const uint64_t val) + PST_SSIZE_T EventMethFns::writeEfd(EmEvent * efd, const uint64_t val) { return(EventMethEpollEquivImpl::writeEfd(efd, val)); } - - ssize_t EventMethFns::readEfd(EmEvent * efd, uint64_t * val_out_ptr) + + PST_SSIZE_T EventMethFns::readEfd(EmEvent * efd, uint64_t * val_out_ptr) { return(EventMethEpollEquivImpl::readEfd(efd, val_out_ptr)); } - - ssize_t EventMethFns::read(EmEvent * fd, void * buf, size_t count) + + PST_SSIZE_T EventMethFns::read(EmEvent * fd, void * buf, size_t count) { return(EventMethEpollEquivImpl::read(fd, buf, count)); } - - ssize_t EventMethFns::write(EmEvent * fd, + + PST_SSIZE_T EventMethFns::write(EmEvent * fd, const void * buf, size_t count) { return(EventMethEpollEquivImpl::write(fd, buf, count)); } - + EmEvent * EventMethFns::getAsEmEvent(EmEventFd * efd) { return(EventMethEpollEquivImpl::getAsEmEvent(efd)); } - + uint64_t EventMethFns::getEmEventUserDataUi64(const EmEvent * fd) { return(EventMethEpollEquivImpl::getEmEventUserDataUi64(fd)); } - + Fd EventMethFns::getEmEventUserData(const EmEvent * fd) { return(EventMethEpollEquivImpl::getEmEventUserData(fd)); } - + void EventMethFns::setEmEventUserData(EmEvent * fd, uint64_t user_data) { EventMethEpollEquivImpl::setEmEventUserData(fd, user_data); } - + void EventMethFns::setEmEventUserData(EmEvent * fd, Fd user_data) { EventMethEpollEquivImpl::setEmEventUserData(fd, user_data); } - + // For EmEventTmrFd, settime is analagous to timerfd_settime in linux - // + // // The linux flags TFD_TIMER_ABSTIME and TFD_TIMER_CANCEL_ON_SET are // not supported // // Since pistache doesn't use the "struct itimerspec * old_value" // feature of timerfd_settime, we haven't implemented that feature. - // + // // If the EventMethEpollEquiv was not specified already (e.g. at // make_new), the it must be specified here - // + // // Note: settime is in EmEvent rather than solely in EmEventTmrFd since // any kind of event may have a timeout set, not only timer events int EventMethFns::setEmEventTime(EmEvent * fd, @@ -913,7 +931,7 @@ namespace Pistache return(EventMethEpollEquivImpl::setEmEventTime(fd, new_timeval_cptr, getEMEEImpl(emee))); } - + EmEventType EventMethFns::getEmEventType(EmEvent * fd) { @@ -928,11 +946,11 @@ namespace Pistache EventMethEpollEquivImpl * EventMethFns::getEMEEImpl( EventMethEpollEquiv * emee/*may be NULL*/) { - return(emee ? emee->impl_.get() : NULL); + return(emee ? emee->impl_.get() : nullptr); } - + #ifdef DEBUG - std::string EventMethFns::getActFdAndFdlFlagsAsStr(int actual_fd) + std::string EventMethFns::getActFdAndFdlFlagsAsStr(em_socket_t actual_fd) { return(EventMethEpollEquivImpl::getActFdAndFdlFlagsAsStr(actual_fd)); } @@ -943,22 +961,22 @@ namespace Pistache { return(EventMethEpollEquivImpl::getEmEventCount()); } - + int EventMethFns::getLibeventEventCount() { return(EventMethEpollEquivImpl::getLibeventEventCount()); } - + int EventMethFns::getEventMethEpollEquivCount() { return(EventMethEpollEquivImpl::getEventMethEpollEquivCount()); } - + int EventMethFns::getEventMethBaseCount() { return(EventMethEpollEquivImpl::getEventMethBaseCount()); } - + int EventMethFns::getWaitThenGetAndEmptyReadyEvsCount() { return(EventMethEpollEquivImpl::getWaitThenGetAndEmptyReadyEvsCount()); @@ -1017,9 +1035,9 @@ namespace Pistache { return(impl_->toEvEvents(interest)); } - - Flags EventMethEpollEquiv::toNotifyOn(Fd fd) + + Flags EventMethEpollEquiv::toNotifyOn(Fd fd) { // uses fd->ready_flags return(impl_->toNotifyOn(fd)); } @@ -1028,10 +1046,10 @@ namespace Pistache { PS_TIMEDBG_START_THIS; - impl_ = NULL; + impl_ = nullptr; } - + } // namespace Pistache /* ------------------------------------------------------------------------- */ @@ -1053,7 +1071,7 @@ static std::atomic_int wait_then_get_count__ = 0; // by EventMethEpollEquiv /* ------------------------------------------------------------------------- */ #ifdef DEBUG - + #define NAME_EVM_FLAG(CAPS_NAME, TITLE_CASE_NAME) \ if (flags & EVM_##CAPS_NAME) \ { \ @@ -1079,7 +1097,7 @@ static std::atomic_int wait_then_get_count__ = 0; // by EventMethEpollEquiv { (*res) += " "; bool fst_flag = true; - + NAME_EVM_FLAG(TIMEOUT, Timeout); NAME_EVM_FLAG(READ, Read); NAME_EVM_FLAG(WRITE, Write); @@ -1105,7 +1123,7 @@ static std::atomic_int wait_then_get_count__ = 0; // by EventMethEpollEquiv /* ------------------------------------------------------------------------- */ -extern "C" void eventCallbackFn(em_socket_t cb_actual_fd, +extern "C" void eventCallbackFn(evutil_socket_t cb_actual_fd, short ev_flags, // One or more EV_* flags void * cb_arg) // caller-supplied arg { @@ -1122,7 +1140,7 @@ extern "C" void eventCallbackFn(em_socket_t cb_actual_fd, PS_LOG_WARNING("arg null"); return; } - + if (cb_arg == ((void *) -1)) { PS_LOG_WARNING("arg -1"); @@ -1133,7 +1151,7 @@ extern "C" void eventCallbackFn(em_socket_t cb_actual_fd, // another thread at any time. You can't use it safely until additional // mutex(es) are claimed in epoll_equiv->handleEventCallback(...). - Pistache::EventMethEpollEquivImpl * epoll_equiv = NULL; + Pistache::EventMethEpollEquivImpl * epoll_equiv = nullptr; if (!Pistache::EventMethEpollEquivImpl::findEmEventInAnInterestSet( cb_arg, &epoll_equiv)) { @@ -1203,7 +1221,7 @@ namespace Pistache public: EventMethBase(); ~EventMethBase(); - + struct event_base * getEventBase() {return(event_base_);} static int getEventBaseFeatures() {return(event_base_features_);} @@ -1212,7 +1230,7 @@ namespace Pistache private: struct event_base * event_base_; - + static bool event_meth_base_inited_previously; static std::mutex event_meth_base_inited_previously_mutex; @@ -1227,7 +1245,7 @@ namespace Pistache std::mutex EventMethBase::event_meth_base_inited_previously_mutex; EventMethBase::EventMethBase() : - event_base_(NULL) + event_base_(nullptr) { PS_TIMEDBG_START; @@ -1246,9 +1264,12 @@ namespace Pistache INC_DEBUG_CTR(event_meth_base); return; } - + event_meth_base_inited_previously = true; #ifdef _WIN32 // Defined for both 32-bit and 64-bit environments + + PST_SOCK_STARTUP_CHECK; + evthread_use_windows_threads(); #else evthread_use_pthreads(); @@ -1259,10 +1280,11 @@ namespace Pistache event_base_features_ = event_base_get_features(event_base_); if (!(event_base_features_ & EV_FEATURE_ET)) { - PS_LOG_WARNING("No edge trigger"); - throw std::system_error(EOPNOTSUPP, std::generic_category(), - "No edge trigger"); - // Because EV_ET is used, e.g. see Epoll::addFd + #ifdef _IS_WINDOWS + PS_LOG_DEBUG("No edge trigger, as expected in Windows"); + #else + PS_LOG_INFO("No edge trigger"); + #endif } INC_DEBUG_CTR(event_meth_base); @@ -1280,11 +1302,11 @@ namespace Pistache event_base_ = 0; } } - + /* ------------------------------------------------------------------------- */ - int EmEventCtr::getActualFd() const // overridden from EmEvent + evutil_socket_t EmEventCtr::getActualFd() const // overridden from EmEvent { PS_LOG_WARNING_ARGS("EmEventCtr (EmEvent) %p has no actual-fd", this); PS_LOGDBG_STACK_TRACE; @@ -1305,15 +1327,15 @@ namespace Pistache GUARD_AND_DBG_LOG(counter_val_mutex_); return(resetCounterValMutexAlreadyLocked()); } - - + + // Sets counter_val_ to zero. If counter_val_ already zero, does // nothing. Returns old counter_val. // counter_val_mutex_ must be locked prior to calling uint64_t EmEventCtr::resetCounterValMutexAlreadyLocked() { uint64_t old_counter_val = counter_val_; - + if (counter_val_ != 0) { counter_val_ = 0; @@ -1321,7 +1343,7 @@ namespace Pistache this, old_counter_val); if (ev_) - { + { if (flags_ & EVM_READ) { // counter_val_ was readable, but no longer is renewEv(); @@ -1344,7 +1366,7 @@ namespace Pistache "EmEventCtr %p waking up any blocked writes", this); { // encapsulate cv_write_mutex_ lock - // + // // Per spec, must claim and release the mutex before // doing a notify_all // https://en.cppreference.com/w/cpp/thread/ @@ -1352,15 +1374,15 @@ namespace Pistache // (See example) GUARD_AND_DBG_LOG(cv_write_mutex_); } - + tmp_cv_sptr->notify_all(); // does nothing if none waiting } } return(old_counter_val); } - - ssize_t EmEventCtr::read(uint64_t * val_out_ptr) + + PST_SSIZE_T EmEventCtr::read(uint64_t * val_out_ptr) { PS_TIMEDBG_START_ARGS("Read EmEventCtr %p", this); @@ -1373,7 +1395,7 @@ namespace Pistache { // encapsulate counter_val_mutex_ GUARD_AND_DBG_LOG(counter_val_mutex_); - + uint64_t old_counter_val = resetCounterValMutexAlreadyLocked(); if (old_counter_val) { @@ -1397,12 +1419,12 @@ namespace Pistache std::unique_lock lk(cv_read_mutex_); cv_read_sptr_->wait(lk); } - + PS_LOG_DEBUG_ARGS("EmEventCtr %p unblocked after read", this); return(this->read(val_out_ptr)); } - ssize_t EmEventCtr::writeProt(const uint64_t val) + PST_SSIZE_T EmEventCtr::writeProt(const uint64_t val) { PS_TIMEDBG_START_ARGS("Write EmEventCtr %p with val %u", this, val); @@ -1455,7 +1477,7 @@ namespace Pistache this); short flags = EV_READ; - + if ((getEmEventType() != EmEvTimer) && (flags_ & EVM_WRITE) && (counter_val_ < 0xfffffffffffffffe)) @@ -1463,14 +1485,14 @@ namespace Pistache PS_LOG_DEBUG_ARGS( "EmEventCtr %p also being activated for write", this); - + flags |= EV_WRITE; } event_active(ev_, flags, 0 /* obsolete parm*/); } } - + std::shared_ptr tmp_cv_sptr( /* Use tmp variable in case set to zero */ cv_read_sptr_); @@ -1480,7 +1502,7 @@ namespace Pistache "EmEventCtr %p waking up any blocked reads", this); { // encapsulate cv_read_mutex_) lock - // + // // Per spec, must claim and release the mutex before // doing a notify_all // https://en.cppreference.com/w/cpp/thread/ @@ -1488,7 +1510,7 @@ namespace Pistache // (See example) GUARD_AND_DBG_LOG(cv_read_mutex_); } - + tmp_cv_sptr->notify_all();// does nothing if none waiting } @@ -1502,7 +1524,7 @@ namespace Pistache // buf must be at least 8 bytes long // read copies an 8-byte integer into buf - ssize_t EmEventCtr::read(void * buf, size_t count) + PST_SSIZE_T EmEventCtr::read(void * buf, size_t count) { if (!buf) { @@ -1517,7 +1539,7 @@ namespace Pistache PS_LOG_INFO("count too small"); return(-1); } - + if (count > 8) { @@ -1525,11 +1547,11 @@ namespace Pistache memset(buf, 0, count); } - return(this->read((uint64_t *)buf)); + return(this->read(reinterpret_cast(buf))); } // write copies an integer of length up to 8 from buf - ssize_t EmEventCtr::writeProt(const void * buf, size_t count) + PST_SSIZE_T EmEventCtr::writeProt(const void * buf, size_t count) { if (!buf) { @@ -1539,12 +1561,13 @@ namespace Pistache } if (count == 8) - return(this->writeProt(*((uint64_t *)buf))); + return(this->writeProt( + *(reinterpret_cast(buf)))); PS_LOG_DEBUG_ARGS("EmEventCtr::write count is not 8 but %u", count); - + uint64_t val = 0; - memcpy(&val, buf, std::min(count, sizeof(val))); + std::memcpy(&val, buf, std::min(count, sizeof(val))); return(this->writeProt(val)); } @@ -1553,18 +1576,18 @@ namespace Pistache PS_TIMEDBG_START_THIS; GUARD_AND_DBG_LOG(block_nonblock_mutex_); - + if (cv_read_sptr_) { PS_LOG_DEBUG_ARGS("EmEventCtr %p already blocking", this); return; // already blocking, nothing to do } - + cv_read_sptr_ = std::make_shared(); if (getEmEventType() != EmEvTimer) // Timer not writable cv_write_sptr_ = std::make_shared(); } - + void EmEventCtr::makeNonBlocking() { PS_TIMEDBG_START_ARGS("EmEventCtr %p", this); @@ -1575,9 +1598,9 @@ namespace Pistache PS_LOG_DEBUG_ARGS("EmEventCtr %p already nonblocking", this); return; // already nonblocking, nothing to do } - - cv_read_sptr_ = NULL; - cv_write_sptr_ = NULL; + + cv_read_sptr_ = nullptr; + cv_write_sptr_ = nullptr; } bool EmEventCtr::isBlocking() @@ -1592,7 +1615,7 @@ namespace Pistache std::chrono::milliseconds * timeval_cptr) // override { PS_TIMEDBG_START_ARGS("EmEventCtr %p", this); - + struct event * old_ev = ev_; short old_flags = flags_; @@ -1607,11 +1630,11 @@ namespace Pistache int evfd_flags = 0; // flags that should be active int chgd_evfd_flags = 0; // Should-be-active flags that were // changed by EmEvent::ctl - + if ((flags_ & EVM_READ) && (counter_val_ > 0)) { evfd_flags |= EV_READ; - + if ((!old_ev) || (!(old_flags & EVM_READ))) chgd_evfd_flags |= EV_READ; } @@ -1620,7 +1643,7 @@ namespace Pistache (flags_ & EVM_WRITE) && (counter_val_ < 0xfffffffffffffffe)) { evfd_flags |= EV_WRITE; - + if ((!old_ev) || (!(old_flags & EVM_WRITE))) chgd_evfd_flags |= EV_WRITE; } @@ -1644,7 +1667,7 @@ namespace Pistache void EmEventCtr::renewEv() { PS_TIMEDBG_START_ARGS("EmEventCtr %p", this); - + short old_flags = flags_; EventMethEpollEquivImpl * emee = getEventMethEpollEquivImpl(); @@ -1653,14 +1676,14 @@ namespace Pistache { if (emee) { - ev_in_emee = (emee->findFdInInterest(this) != NULL); + ev_in_emee = (emee->findFdInInterest(this) != nullptr); if (ev_in_emee) { - + int ctl_res = emee->ctlEx(EvCtlAction::Del, this, 0 /* events*/, - NULL /*timeval_cptr*/, + nullptr /*timeval_cptr*/, true/*forceEmEventCtlOnly*/); if (ctl_res != 0) { @@ -1671,11 +1694,11 @@ namespace Pistache } } } - + if (ev_) { event_free(ev_); - ev_ = NULL; + ev_ = nullptr; DEC_DEBUG_CTR(libevent_event); } @@ -1686,10 +1709,10 @@ namespace Pistache if (ev_in_emee) { // Have to add back the (new) ev_ int ctl_res = emee->ctlEx(EvCtlAction::Add, - this, - old_flags /* events*/, - NULL /* timeval_cptr - use prior_tv_ if available*/, - true/*forceEmEventCtlOnly*/); + this, + old_flags /* events*/, + nullptr /* timeval_cptr - use prior_tv_ if available*/, + true/*forceEmEventCtlOnly*/); if (ctl_res != 0) { PS_LOG_INFO_ARGS( @@ -1704,7 +1727,7 @@ namespace Pistache } } } - + short emefd_flags = 0; if ((flags_ & EVM_READ) && (counter_val_ > 0)) { @@ -1743,6 +1766,9 @@ namespace Pistache if (fdl_flags == F_SETFDL_NOTHING) return(std::string("set nothing")); + if (fdl_flags == PST_FCNTL_GETFL_UNKNOWN) + return(std::string("unknown")); + std::string res("set 0x"); std::stringstream ss; @@ -1756,28 +1782,28 @@ namespace Pistache } std::string EventMethEpollEquivImpl::getActFdAndFdlFlagsAsStr( - int actual_fd) + em_socket_t actual_fd) { // static method std::string res("actual-fd "); res += std::to_string(actual_fd); if (actual_fd < 0) return(res); - + res += ", fd_flags "; - int getfd_flags = fcntl(actual_fd, F_GETFD, (int) 0); + int getfd_flags = PST_FCNTL(actual_fd, PST_F_GETFD, 0); res += fdlFlagsToStr(getfd_flags); res += ", fl_flags "; - int getfl_flags = fcntl(actual_fd, F_GETFL, (int) 0); + int getfl_flags = PST_FCNTL(actual_fd, PST_F_GETFL, 0); res += fdlFlagsToStr(getfl_flags); return(res); } - - + + #endif // of ifdef DEBUG - + EmEventFd * EmEventFd::getFromEmEventCPtr(EmEvent * eme_cptr) { EmEventFd * res = dynamic_cast(eme_cptr); @@ -1794,7 +1820,7 @@ namespace Pistache return(res); } - + EmEventFd::EmEventFd(uint64_t initval) : EmEventCtr(initval) { @@ -1813,16 +1839,16 @@ namespace Pistache int f_setfl_flags) // e.g. O_NONBLOCK { PS_TIMEDBG_START_ARGS("initval %u, fd_flags %s, fl_flags %s", - initval, + initval, fdlFlagsToStr(f_setfd_flags).c_str(), fdlFlagsToStr(f_setfl_flags).c_str()); EmEventFd * emefd = new EmEventFd(initval); if (!emefd) - return(NULL); + return(nullptr); DBG_NEW_EMV(emefd); - if (!(f_setfl_flags & O_NONBLOCK)) + if (!(f_setfl_flags & PST_O_NONBLOCK)) { emefd->makeBlocking(); PS_LOG_DEBUG_ARGS("EmEventFd %p blocking", emefd); @@ -1831,7 +1857,7 @@ namespace Pistache { PS_LOG_DEBUG_ARGS("EmEventFd %p nonblocking", emefd); } - + PS_LOG_DEBUG_ARGS("EmEventFd created %p, %s, initval %u", emefd, @@ -1845,12 +1871,12 @@ namespace Pistache { delete emefd; DBG_DELETE_EMV(emefd); - emefd = NULL; + emefd = nullptr; } - + return(emefd); } - + /* ------------------------------------------------------------------------- */ @@ -1871,13 +1897,13 @@ namespace Pistache return(res); } -EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, +EmEventTmrFd::EmEventTmrFd(PST_CLOCK_ID_T clock_id, EventMethEpollEquivImpl * emee/*may be NULL*/) : EmEventCtr(0 /*initval*/) { switch(clock_id) { - case CLOCK_REALTIME: + case PST_CLOCK_REALTIME: #ifdef __linux__ case CLOCK_REALTIME_ALARM: case CLOCK_REALTIME_COARSE: @@ -1889,10 +1915,10 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, "clock_id realtime clock not supported"); break; - case CLOCK_MONOTONIC: + case PST_CLOCK_MONOTONIC: #ifndef _IS_BSD // CLOCK_MONOTONIC_RAW not defined on FreeBSD13.3 and OpenBSD 7.3 - case CLOCK_MONOTONIC_RAW: + case PST_CLOCK_MONOTONIC_RAW: #endif #ifdef __APPLE__ case CLOCK_MONOTONIC_RAW_APPROX: @@ -1907,13 +1933,13 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, // We treat all these as CLOCK_MONOTONIC break; - case CLOCK_PROCESS_CPUTIME_ID: + case PST_CLOCK_PROCESS_CPUTIME_ID: PS_LOG_WARNING("CLOCK_PROCESS_CPUTIME_ID not supported"); throw std::invalid_argument( "clock_id = CLOCK_PROCESS_CPUTIME_ID not supported"); break; - - case CLOCK_THREAD_CPUTIME_ID: + + case PST_CLOCK_THREAD_CPUTIME_ID: PS_LOG_WARNING("CLOCK_THREAD_CPUTIME_ID not supported"); throw std::invalid_argument( "clock_id = CLOCK_THREAD_CPUTIME_ID not supported"); @@ -1933,14 +1959,14 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, // now we accept any of the monotonic system-wide clocks, and reject any // others - in particular, we reject CLOCK_REALTIME (which can change // value) as well as the thread and process time clocks. - // + // // Consistent with not supporting CLOCK_REALTIME, we do not support // TFD_TIMER_CANCEL_ON_SET nor do we have to support discontinuous changes // in the clock value // // If emee is NULL here, it will need to be supplied when settime is // called - EmEventTmrFd * EmEventTmrFd::make_new(clockid_t clock_id,// static function + EmEventTmrFd * EmEventTmrFd::make_new(PST_CLOCK_ID_T clock_id,// static function // For setfd and setfl arg: // F_SETFDL_NOTHING - change nothing // Zero or pos number that is not @@ -1960,10 +1986,10 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, EmEventTmrFd * emefd = new EmEventTmrFd(clock_id, emee); if (!emefd) - return(NULL); + return(nullptr); DBG_NEW_EMV(emefd); - if (!(f_setfl_flags & O_NONBLOCK)) + if (!(f_setfl_flags & PST_O_NONBLOCK)) { emefd->makeBlocking(); PS_LOG_DEBUG_ARGS("EmEventTmrFd %p blocking", emefd); @@ -1972,7 +1998,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, { PS_LOG_DEBUG_ARGS("EmEventTmrFd %p nonblocking", emefd); } - + PS_LOG_DEBUG_ARGS("EmEventTmrFd created %p, emee %p, %s, clock_id %u", emefd, emee, @@ -1986,23 +2012,23 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, { delete emefd; DBG_DELETE_EMV(emefd); - emefd = NULL; + emefd = nullptr; } - + return(emefd); } // settime is analagous to timerfd_settime in linux - // + // // The linux flags TFD_TIMER_ABSTIME and TFD_TIMER_CANCEL_ON_SET are not // supported // // Since pistache doesn't use the "struct itimerspec * old_value" feature // of timerfd_settime, we haven't implemented that feature. - // + // // If new_timeval_cptr is NULL or *new_timeval_cptr is all zero, the // settime call will reset the timer (again as per timerfd_settime) - // + // // Note: settime is in EmEvent rather than solely in EmEventTmrFd since any // kind of event may have a timeout set, not only timer events int EmEventTmrFd::settime( @@ -2045,7 +2071,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, { // We HAVE to add to an EMEE now, since that's how the timer starts // running - + if (!emee) { PS_LOG_INFO_ARGS( @@ -2056,7 +2082,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, // connects each of them to an EMEE via a call to // TimerPool::Entry::registerReactor (the reactor owns the // EMEE). - + add_was_artificial_ = false; return(0); } @@ -2064,7 +2090,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, int ctl_res = emee->ctlEx(EvCtlAction::Add, this, flags_ /* events*/, - NULL /* timeval_cptr - use prior_tv_*/, + nullptr /* timeval_cptr - use prior_tv_*/, true/*forceEmEventCtlOnly*/); if (ctl_res != 0) { @@ -2081,7 +2107,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, int ctl_res = emee->ctlEx(EvCtlAction::Del, this, 0 /* events*/, - NULL /*timeval_cptr*/, + nullptr /*timeval_cptr*/, true/*forceEmEventCtlOnly*/); if (ctl_res != 0) { @@ -2106,7 +2132,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, { if (ev_flags_in_out & EV_TIMEOUT) { - PS_LOG_DEBUG_ARGS("EmEventTmrFd %p increment expiry counter", + PS_LOG_DEBUG_ARGS("EmEventTmrFd %p increment expiry counter", this); writeProt(1); } @@ -2114,20 +2140,20 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, if (flags_ & EVM_READ) ev_flags_in_out |= EV_READ; } - - ssize_t EmEventTmrFd::write([[maybe_unused]] const void * buf, + + PST_SSIZE_T EmEventTmrFd::write([[maybe_unused]] const void * buf, [[maybe_unused]] size_t count) { PS_LOG_DEBUG("Cannot write to an EmEventTmrFd"); - + errno = EBADF; // "not open for writing" return(-1); } - - + + /* ------------------------------------------------------------------------- */ - + // Returns true if "pending" (i.e. has been added in libevent) // events is any of EV_TIMEOUT|EV_READ|EV_WRITE|EV_SIGNAL // If tv is non-null and event has time out, *tv is set to it @@ -2136,7 +2162,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, { if (!ev_) return(false); - + return(event_pending(ev_, events, tv) != 0); } @@ -2151,18 +2177,18 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, } - EmEvent * EmEvent::make_new(int actual_fd, - short flags, - // For setfd and setfl arg: - // F_SETFDL_NOTHING - change nothing - // Zero or pos number that is not - // F_SETFDL_NOTHING - set flags to value of - // arg, and clear any other flags - // Neg number that is not F_SETFDL_NOTHING - // - set flags that are set in (0 - arg), - // but don't clear any flags - int f_setfd_flags, // e.g. FD_CLOEXEC - int f_setfl_flags // e.g. O_NONBLOCK + EmEvent * EmEvent::make_new(evutil_socket_t actual_fd, + short flags, + // For setfd and setfl arg: + // F_SETFDL_NOTHING - change nothing + // Zero or pos number that is not + // F_SETFDL_NOTHING - set flags to value of + // arg, and clear any other flags + // Neg number that is not F_SETFDL_NOTHING + // - set flags that are set in (0 - arg), + // but don't clear any flags + int f_setfd_flags, // e.g. FD_CLOEXEC + int f_setfl_flags // e.g. O_NONBLOCK ) { // static method PS_TIMEDBG_START_ARGS("actual_fd %d, evm_flags %s, " @@ -2173,31 +2199,31 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, fdlFlagsToStr(f_setfl_flags).c_str()); EmEvent * eme = new EmEvent(); - if (!eme) - return(NULL); + if (!eme) + return(nullptr); DBG_NEW_EMV(eme); PS_LOG_DEBUG_ARGS("EmEvent created %p", eme); // NB: don't pass EVM_TIMEOUT as a flag; the presence of a timeout is // indicate solely by the presence of non-zero timeout period - + return(eme->init(actual_fd, flags, f_setfd_flags, f_setfl_flags)); } - - - EmEvent * EmEvent::init(int actual_fd, - short flags, - // For setfd and setfl arg: - // F_SETFDL_NOTHING - change nothing - // Zero or pos number that is not - // F_SETFDL_NOTHING - set flags to value of - // arg, and clear any other flags - // Neg number that is not F_SETFDL_NOTHING - // - set flags that are set in (0 - arg), - // but don't clear any flags - int f_setfd_flags, // e.g. FD_CLOEXEC - int f_setfl_flags // e.g. O_NONBLOCK + + + EmEvent * EmEvent::init(evutil_socket_t actual_fd, + short flags, + // For setfd and setfl arg: + // F_SETFDL_NOTHING - change nothing + // Zero or pos number that is not + // F_SETFDL_NOTHING - set flags to value of + // arg, and clear any other flags + // Neg number that is not F_SETFDL_NOTHING + // - set flags that are set in (0 - arg), + // but don't clear any flags + int f_setfd_flags, // e.g. FD_CLOEXEC + int f_setfl_flags // e.g. O_NONBLOCK ) { PS_TIMEDBG_START; @@ -2247,46 +2273,46 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, return(this); } - EmEvent::EmEvent() : ev_(NULL), flags_(0), + EmEvent::EmEvent() : ev_(nullptr), flags_(0), add_was_artificial_(false), - event_meth_epoll_equiv_impl_(NULL), // parent + event_meth_epoll_equiv_impl_(nullptr), // parent ready_flags_(0), user_data_(0), requested_f_setfd_flags_(F_SETFDL_NOTHING), requested_f_setfl_flags_(F_SETFDL_NOTHING), requested_actual_fd_(-1), - prior_tv_cptr_(NULL) // ptr to timeout value + prior_tv_cptr_(nullptr) // ptr to timeout value { memset(&prior_tv_, 0, sizeof(prior_tv_)); - + INC_DEBUG_CTR(em_event); } void EmEvent::setPriorTv(const std::chrono::milliseconds * timeval_cptr) { memset(&prior_tv_, 0, sizeof(prior_tv_)); - prior_tv_cptr_ = NULL; + prior_tv_cptr_ = nullptr; if (timeval_cptr) { if (timeval_cptr->count() < 1000) - prior_tv_.tv_usec = (suseconds_t) std::chrono:: - duration_cast(*timeval_cptr).count(); + prior_tv_.tv_usec = static_cast(std::chrono:: + duration_cast(*timeval_cptr).count()); else - prior_tv_.tv_sec = (time_t) (std::chrono:: - duration_cast(*timeval_cptr).count()); + prior_tv_.tv_sec = static_cast((std::chrono:: + duration_cast(*timeval_cptr).count())); prior_tv_cptr_ = &prior_tv_; } } - + // settime can be used to configure the timeout prior to calling ctl/Add // // For EmEventTmrFd, settime is analagous to timerfd_settime in linux - // + // // The linux flags TFD_TIMER_ABSTIME and TFD_TIMER_CANCEL_ON_SET are not // supported - // + // // Since pistache doesn't use the "struct itimerspec * old_value" feature // of timerfd_settime, we haven't implemented that feature. // @@ -2323,6 +2349,8 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, throw std::invalid_argument( "EmEventTmrFd EMEE cannot be changed"); } + PS_LOG_DEBUG_ARGS("Setting EMEE %p for EmEvent %p", + emee, this); event_meth_epoll_equiv_impl_ = emee; } } @@ -2330,35 +2358,38 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, { emee = event_meth_epoll_equiv_impl_; } - + setPriorTv(new_timeval_cptr); return(0); // success } - + // Always use this, don't set flags_ directly void EmEvent::setFlags(short flgs) { PS_TIMEDBG_START; - + // Mask out EVM_TIMEOUT - a flag that may be set in ready_flags_ if a // timeout occurs, but which is not needed to get a timeout. flgs &= ~EVM_TIMEOUT; - if ((event_meth_epoll_equiv_impl_) && (flgs & (EVM_CLOSED | EVM_ET))) + // Note - if edge-triggered is requested ("flgs & EVM_ET" is non-zero) + // but the underlying OS does not support edge triggered + // ("base_features & EV_FEATURE_ET" is zero) as is the case in Windows, + // then we will simulate edge triggered + #ifdef DEBUG + PS_LOG_DEBUG_ARGS("EVM_ET %s", (flgs & EVM_ET) ? + "requested" : "not requested"); + if ((!(flgs & EVM_ET)) && (flgs & EVM_PERSIST)) + PS_LOG_DEBUG("flgs_ has EVM_PERSIST, but does not have EVM_ET"); + #endif + + if ((event_meth_epoll_equiv_impl_) && (flgs & EVM_CLOSED)) { int base_features = event_meth_epoll_equiv_impl_->getEventBaseFeatures(); - - if ((flgs & EVM_ET) && (!(base_features & EV_FEATURE_ET))) - { - PS_LOG_INFO("No edge trigger"); - throw std::system_error(EOPNOTSUPP, - std::generic_category(), "No edge trigger"); - } - if ((flgs & EVM_CLOSED) && - (!(base_features & EV_FEATURE_EARLY_CLOSE))) + if (!(base_features & EV_FEATURE_EARLY_CLOSE)) { PS_LOG_INFO("No early close"); throw std::system_error(EOPNOTSUPP, @@ -2375,8 +2406,8 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, int EmEvent::disarm() { PS_TIMEDBG_START; - - if (ev_ == NULL) + + if (ev_ == nullptr) return(0); // nothing to do // Note. If the event has already executed or has never been added, @@ -2386,20 +2417,86 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, return(event_del_res); } + // In level-triggered systems, "deactivate" is used for a triggered event, + // calling libevent's event_del and then event_add. event_del causes the + // event to be inactive and non-pending, while event_add causes it to be + // pending (i.e. waiting for events). So after deactivate is called, the + // event is inactive and pending. + int EmEvent::deactivate() + { + PS_TIMEDBG_START; + + if (ev_ == nullptr) + return(0); // nothing to do + + // Note. If the event has already executed or has never been added, + // event_del will have no effect (i.e. is harmless). + int event_del_res = TRY_RET(event_del(ev_)); + if (event_del_res != 0) + { + PS_LOG_DEBUG_ARGS("event_del failed for EmEvent %p, ev_ %p", + this, ev_); + return(event_del_res); + } + + // Re: prior_tv_cptr_, per libevent, timeout pointer may be null. "If a + // timeout is NULL, no timeout occurs and the function will only be + // called if a matching event occurs." + int event_add_res = event_add(ev_, prior_tv_cptr_); + if (event_add_res != 0) + { + PS_LOG_DEBUG_ARGS("event_del failed for EmEvent %p, ev_ %p", + this, ev_); + } + return(event_add_res); + } + + // Call only on OS where we don't have edge-trigger support (i.e. where + // libevent does not have EV_FEATURE_ET) + int EmEvent::deactivateAfterTriggerIfNeeded() + { + PS_TIMEDBG_START; + + if (!(flags_ & EVM_ET)) + { + if (!(flags_ & EVM_PERSIST)) + { + PS_LOG_DEBUG("Deactivate for non-persistence and " + "make not pending"); + return(disarm()); + } + + return(0); + } + + if (flags_ & EVM_PERSIST) + { + PS_LOG_DEBUG("Deactivate event"); + + // deactivates the libevent event and makes it pending + return(deactivate()); + } + + PS_LOG_DEBUG("Deactivate event and make not pending"); + return(disarm()); + } + + int EmEvent::close() // disarms as well as closes { PS_TIMEDBG_START_THIS; - em_socket_t actual_fd = -1; // em_socket_t is type int + evutil_socket_t actual_fd = -1; int finalize_res = 0; - if (ev_ == NULL) + if (ev_ == nullptr) { actual_fd = requested_actual_fd_; } else { - actual_fd = event_get_fd(ev_); + evutil_socket_t ev_fd = event_get_fd(ev_); + actual_fd = (ev_fd < 0) ? (-1) : (static_cast(ev_fd)); // See earlier comment: Why and how we use libevent's finalize PS_LOG_DEBUG_ARGS("About to finalize+free ev_ %p of EmEvent %p", @@ -2425,7 +2522,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, if (actual_fd > 0) { PS_LOG_DEBUG_ARGS("::close actual_fd %d", actual_fd); - actual_fd_close_res = ::close(actual_fd); + actual_fd_close_res = PST_SOCK_CLOSE(actual_fd); } if (finalize_res < 0) @@ -2447,11 +2544,11 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, EventMethEpollEquivImpl * tmp_event_meth_epoll_equiv = event_meth_epoll_equiv_impl_; if (!tmp_event_meth_epoll_equiv) - return(NULL); + return(nullptr); EventMethEpollEquivImpl * found_emee = EventMethEpollEquivImpl:: getEventMethEpollEquivImplFromEmeeSet(tmp_event_meth_epoll_equiv); - + if (!found_emee) { PS_LOG_DEBUG_ARGS("EmEvent %p has " @@ -2459,19 +2556,21 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, "unexpectedly not in emee_cptr_set, " "nulling out event_meth_epoll_equiv_impl_", this, tmp_event_meth_epoll_equiv); - event_meth_epoll_equiv_impl_ = NULL; - return(NULL); + + PS_LOG_DEBUG_ARGS("Setting EMEE null for EmEvent %p", this); + event_meth_epoll_equiv_impl_ = nullptr; + return(nullptr); } if (found_emee != tmp_event_meth_epoll_equiv) { PS_LOG_DEBUG_ARGS("found_emee %p != tmp_event_meth_epoll_equiv %p", found_emee, tmp_event_meth_epoll_equiv); - + assert(found_emee == tmp_event_meth_epoll_equiv); - return(NULL); + return(nullptr); } - + return(tmp_event_meth_epoll_equiv); } @@ -2479,35 +2578,32 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, // ev_ (the libevent event) which effectively holds a reference to the // libevent event_base class, which is in EventMethBase, which in turn is // in EventMethEpollEquiv. So if EventMethEpollEquiv goes out of scope, - // then ev_ needs to be freed - void EmEvent::detachEventMethEpollEquiv() + // then ev_ needs to be freed. + // + // Note that this uses event_free, not event_free_finalize; as such, it can + // be safely called only after event_base_loopbreak or event_base_loopexit, + // which guarantee that there will be no more event callbacks on the event + // and that any in-process event callback on the event has completed. + void EmEvent::detachEventMethEpollEquivImmedFree() { if (ev_) { // See earlier comment: Why and how we use libevent's finalize - PS_LOG_DEBUG_ARGS("About to finalize+free ev_ %p of EmEvent %p", - ev_, this); + PS_LOG_DEBUG_ARGS("About to free ev_ %p of EmEvent %p", ev_, this); auto old_ev = ev_; ev_ = 0; - #ifdef DEBUG - int ev_free_finalize_initial_res = - #endif - event_free_finalize(0,//reserved - old_ev, libevEventFinalizeAndFreeCallback); - - PS_LOG_DEBUG_ARGS("ev_free_finalize_initial_res %d, ev_ %p", - ev_free_finalize_initial_res, old_ev); - - // Note: libevEventFinalizeAndFreeCallback does - // DEC_DEBUG_CTR(libevent_event) + // event_free makes the event non-pending and non-active before + // freeing + event_free(old_ev); + DEC_DEBUG_CTR(libevent_event); } - event_meth_epoll_equiv_impl_ = NULL; + PS_LOG_DEBUG_ARGS("Setting EMEE (was %p) null for EmEvent %p", + event_meth_epoll_equiv_impl_, this); + event_meth_epoll_equiv_impl_ = nullptr; } - - EmEvent::~EmEvent() { @@ -2516,33 +2612,38 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, close(); } - int EmEvent::getActualFd() const // virtual + evutil_socket_t EmEvent::getActualFd() const // virtual { - int actual_fd = getActualFdPrv(); + evutil_socket_t actual_fd = getActualFdPrv(); #ifdef DEBUG if (actual_fd < 0) { PS_LOG_INFO_ARGS("EmEvent %p has negative actual_fd?", this); - PS_LOGDBG_STACK_TRACE; + PS_LOGDBG_STACK_TRACE; } #endif - + return(actual_fd); } - ssize_t EmEvent::read(void * buf, size_t count) // virtual + PST_SSIZE_T EmEvent::read(void * buf, size_t count) // virtual { - return(::read(getActualFd(), buf, count)); + return(PST_SOCK_READ(getActualFd(), buf, count)); } - - ssize_t EmEvent::write(const void * buf, size_t count) // virtual + + PST_SSIZE_T EmEvent::write(const void * buf, size_t count) // virtual { - return(::write(getActualFd(), buf, count)); + return(PST_SOCK_WRITE(getActualFd(), buf, count)); } - int EmEvent::getActualFdPrv() const + evutil_socket_t EmEvent::getActualFdPrv() const { - int actual_fd = ((ev_) ? event_get_fd(ev_) : requested_actual_fd_); + evutil_socket_t actual_fd = requested_actual_fd_; + if (ev_) + { + evutil_socket_t ev_fd = event_get_fd(ev_); + actual_fd = (ev_fd < 0) ? (-1) : (static_cast(ev_fd)); + } #ifdef DEBUG if (actual_fd >= 0) @@ -2561,17 +2662,18 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, "Non negative actual_fd for eventfd EmEvent"); } } - + #endif return(actual_fd); } - - int EmEvent::getActualFd(const EmEvent * em_ev) // static version + + // static version + evutil_socket_t EmEvent::getActualFd(const EmEvent * em_ev) { - if (em_ev == NULL) + if (em_ev == nullptr) return(-1); - + return(em_ev->getActualFd()); } @@ -2604,13 +2706,13 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, } #endif // of ifdef DEBUG - void EmEvent::setFdlFlagsHelper(int actual_fd, + void EmEvent::setFdlFlagsHelper(evutil_socket_t actual_fd, int get_cmd, // F_GETFD or F_GETFL int set_cmd, // F_SETFD or F_SETFL int f_setfdl_flags) { PS_TIMEDBG_START; - + if (f_setfdl_flags == F_SETFDL_NOTHING) return; @@ -2628,7 +2730,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, throw std::invalid_argument("actual_fd not set"); } - int fcntl_res = fcntl(actual_fd, set_cmd, f_setfdl_flags); + int fcntl_res = PST_FCNTL(actual_fd, set_cmd, f_setfdl_flags); if (fcntl_res == -1) { PS_LOG_INFO("fcntl set failed"); @@ -2644,12 +2746,13 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, throw std::invalid_argument("actual_fd not set"); } - int old_setfdl_flags = fcntl(actual_fd, get_cmd, (int) 0); + int old_setfdl_flags = PST_FCNTL(actual_fd, get_cmd, 0); f_setfdl_flags = (0 - f_setfdl_flags); if (old_setfdl_flags != f_setfdl_flags) { - f_setfdl_flags |= old_setfdl_flags; - int fcntl_res = fcntl(actual_fd, set_cmd, f_setfdl_flags); + if (old_setfdl_flags != PST_FCNTL_GETFL_UNKNOWN) + f_setfdl_flags |= old_setfdl_flags; + int fcntl_res = PST_FCNTL(actual_fd, set_cmd, f_setfdl_flags); if (fcntl_res == -1) { PS_LOG_INFO("fcntl set failed"); @@ -2660,7 +2763,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, } - void EmEvent::setFdlFlagsIfNeededAndActualFd(int actual_fd) + void EmEvent::setFdlFlagsIfNeededAndActualFd(evutil_socket_t actual_fd) { if (actual_fd < 0) return; @@ -2673,14 +2776,14 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, if (requested_f_setfd_flags_ != F_SETFDL_NOTHING) { - setFdlFlagsHelper(actual_fd, F_GETFD, F_SETFD, + setFdlFlagsHelper(actual_fd, PST_F_GETFD, PST_F_SETFD, requested_f_setfd_flags_); requested_f_setfd_flags_ = F_SETFDL_NOTHING; } if (requested_f_setfl_flags_ != F_SETFDL_NOTHING) { - setFdlFlagsHelper(actual_fd, F_GETFL, F_SETFL, + setFdlFlagsHelper(actual_fd, PST_F_GETFL, PST_F_SETFL, requested_f_setfl_flags_); requested_f_setfl_flags_ = F_SETFDL_NOTHING; } @@ -2699,7 +2802,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, case EmEvEventFd: return("eventfd"); - + case EmEvTimer: return("Timer"); @@ -2746,7 +2849,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, { // Check to se if the EmEvent is already in a different // interest_ list - Pistache::EventMethEpollEquivImpl * owning_emee = NULL; + Pistache::EventMethEpollEquivImpl * owning_emee = nullptr; EmEvent * dummy_em_event = EventMethEpollEquivImpl::findEmEventInAnInterestSet( this, &owning_emee); @@ -2760,13 +2863,15 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, throw std::runtime_error("Unsupported emee change"); } } - + + PS_LOG_DEBUG_ARGS("Setting EMEE %p for EmEvent %p", + emee, this); event_meth_epoll_equiv_impl_ = emee; } } else { - Pistache::EventMethEpollEquivImpl * owning_emee = NULL; + Pistache::EventMethEpollEquivImpl * owning_emee = nullptr; #ifdef DEBUG [[maybe_unused]] #endif @@ -2786,21 +2891,22 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, throw std::invalid_argument("emee null and owning_emee null"); } + PS_LOG_DEBUG_ARGS("Setting EMEE %p for EmEvent %p", emee, this); event_meth_epoll_equiv_impl_ = emee; } - - int actual_fd = getActualFdPrv(); + + evutil_socket_t actual_fd = getActualFdPrv(); struct timeval tv; memset(&tv, 0, sizeof(tv)); - struct timeval * tv_cptr = NULL; + struct timeval * tv_cptr = nullptr; if (timeval_cptr) { if (timeval_cptr->count() < 1000) - tv.tv_usec = (suseconds_t) std::chrono:: - duration_cast(*timeval_cptr).count(); + tv.tv_usec = static_cast(std::chrono:: + duration_cast(*timeval_cptr).count()); else - tv.tv_sec = (time_t) (std::chrono:: + tv.tv_sec = static_cast(std::chrono:: duration_cast(*timeval_cptr).count()); tv_cptr = &tv; } @@ -2833,7 +2939,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, { PS_LOG_INFO_ARGS("EmEvent %p, no actual fd (ctl error)", this); - + errno = EBADF; return(-1); } @@ -2843,10 +2949,10 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, event_meth_epoll_equiv_impl_->getEventMethBase()-> getEventBase(), actual_fd, - flags_ | EV_FINALIZE, // See earlier comment: Why and - // how we use libevent's finalize + event_meth_epoll_equiv_impl_-> + getFlagsToActuallyUseWithLibEvEventNew(flags_), eventCallbackFn, - (void *)this + reinterpret_cast(this) /*final arg here is passed to callback as "arg"*/)); if (!ev_) @@ -2860,7 +2966,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, PS_LOG_DEBUG_ARGS("EmEvent %p libevent ev_ %p via event_new, " "actual_fd %d", this, ev_, actual_fd); - + } else if ((events) && (events != flags_)) { @@ -2882,7 +2988,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, ev_, this); struct event * old_ev = ev_; - ev_ = NULL; + ev_ = nullptr; #ifdef DEBUG int ev_free_finalize_initial_res = @@ -2899,10 +3005,10 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, event_meth_epoll_equiv_impl_->getEventMethBase()-> getEventBase(), actual_fd, // keep same actual fd, if any - events | EV_FINALIZE, // See earlier comment: Why and how - // we use libevent's finalize + event_meth_epoll_equiv_impl_-> + getFlagsToActuallyUseWithLibEvEventNew(events), eventCallbackFn, - (void *)this/*passed to callback as "arg"*/); + reinterpret_cast(this)/*passed as callback arg*/); if (!replacement_ev) { PS_LOG_INFO("new replacement_ev is NULL"); @@ -2948,7 +3054,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, // whether a timeout occured on the event. Rather, the second parm // to event_add (which is const struct timeval *) indicates whether // timeout is needed - // + // // Note: In some SSL-code cases, pistache code calls setsockopt to // set a timer on a file descriptor directly. However, this doesn't // appear to happen for a file-desc that has an associated EmEvent; @@ -2961,7 +3067,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, case EvCtlAction::Mod: // rearm // For a deactivated event, reactivated by adding again - // + // // Per libevent documentation, if the event is already active, it // remains active (aka pending); in that case, if tv_cptr is // non-null, the prior timeout (if any) is replaced by the new @@ -2986,38 +3092,38 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, return(ctl_res); } - + /* ------------------------------------------------------------------------- */ int EventMethEpollEquivImpl::tcp_prot_num = -1; std::mutex EventMethEpollEquivImpl::tcp_prot_num_mutex; - int EventMethEpollEquivImpl::getActualFd(const EmEvent * em_event) + evutil_socket_t EventMethEpollEquivImpl::getActualFd(const EmEvent * em_event) { // static // Returns -1 if no actual Fd return(EmEvent::getActualFd(em_event)); } - ssize_t EventMethEpollEquivImpl::writeEfd(EmEvent* efd, const uint64_t val) + PST_SSIZE_T EventMethEpollEquivImpl::writeEfd(EmEvent* efd, const uint64_t val) { // static EmEventFd * this_efd = EmEventFd::getFromEmEventCPtrNoLogIfNull(efd); if (!this_efd) return(-1); - + return(this_efd->write(val)); } - ssize_t EventMethEpollEquivImpl::readEfd(EmEvent * efd, + PST_SSIZE_T EventMethEpollEquivImpl::readEfd(EmEvent * efd, uint64_t * val_out_ptr) { // static EmEventFd * this_efd = EmEventFd::getFromEmEventCPtrNoLogIfNull(efd); if (!this_efd) return(-1); - + return(this_efd->read(val_out_ptr)); } - ssize_t EventMethEpollEquivImpl::read(EmEvent* fd, void* buf, size_t count) + PST_SSIZE_T EventMethEpollEquivImpl::read(EmEvent* fd, void* buf, size_t count) { // static if (!fd) { @@ -3025,11 +3131,11 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, errno = EINVAL; return(-1); } - + return(fd->read(buf, count)); } - - ssize_t EventMethEpollEquivImpl::write(EmEvent * fd, + + PST_SSIZE_T EventMethEpollEquivImpl::write(EmEvent * fd, const void * buf, size_t count) { // static if (!fd) @@ -3038,18 +3144,18 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, errno = EINVAL; return(-1); } - + return(fd->write(buf, count)); } EmEvent * EventMethEpollEquivImpl::getAsEmEvent(EmEventFd * efd) { // static if (!efd) - return(NULL); + return(nullptr); return(efd->getAsFd()); } - + uint64_t EventMethEpollEquivImpl::getEmEventUserDataUi64(const EmEvent* fd) { // static if (!fd) @@ -3060,7 +3166,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, return(fd->getUserDataUi64()); } - + Fd EventMethEpollEquivImpl::getEmEventUserData(const EmEvent * fd) { // static if (!fd) @@ -3068,10 +3174,10 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, PS_LOG_WARNING("Null fd"); throw std::runtime_error("Null fd"); } - + return(fd->getUserData()); } - + void EventMethEpollEquivImpl::setEmEventUserData(EmEvent * fd, uint64_t user_data) { // static @@ -3080,7 +3186,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, PS_LOG_WARNING("Null fd"); throw std::runtime_error("Null fd"); } - + fd->setUserData(user_data); } @@ -3092,8 +3198,9 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, PS_LOG_WARNING("Null fd"); throw std::runtime_error("Null fd"); } - - fd->setUserData((uint64_t)user_data); + + fd->setUserData(static_cast( + reinterpret_cast(user_data))); } void EventMethEpollEquivImpl::resetEmEventReadyFlags(EmEvent * fd) @@ -3103,21 +3210,21 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, PS_LOG_WARNING("Null fd"); throw std::runtime_error("Null fd"); } - + fd->resetReadyFlags(); } // For EmEventTmrFd, settime is analagous to timerfd_settime in linux - // + // // The linux flags TFD_TIMER_ABSTIME and TFD_TIMER_CANCEL_ON_SET are // not supported // // Since pistache doesn't use the "struct itimerspec * old_value" // feature of timerfd_settime, we haven't implemented that feature. - // + // // If the EventMethEpollEquiv was not specified already (e.g. at // make_new), the it must be specified here - // + // // Note: settime is in EmEvent rather than solely in EmEventTmrFd since // any kind of event may have a timeout set, not only timer events int EventMethEpollEquivImpl::setEmEventTime(EmEvent * fd, @@ -3145,7 +3252,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, int EventMethEpollEquivImpl::getTcpProtNum() { PS_TIMEDBG_START; - + if (tcp_prot_num != -1) return(tcp_prot_num); @@ -3159,8 +3266,8 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, return(tcp_prot_num); } - - + + std::set EventMethEpollEquivImpl::emee_cptr_set_; std::mutex EventMethEpollEquivImpl::emee_cptr_set_mutex_; @@ -3174,13 +3281,13 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, void * arg, EventMethEpollEquivImpl * * epoll_equiv_cptr_out) { PS_TIMEDBG_START; - + if (!epoll_equiv_cptr_out) { PS_LOG_WARNING("epoll_equiv_cptr_out null"); throw std::invalid_argument("epoll_equiv_cptr_out null"); } - *epoll_equiv_cptr_out = NULL; + *epoll_equiv_cptr_out = nullptr; if (!arg) { @@ -3188,13 +3295,13 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, throw std::invalid_argument("arg null"); } - EmEvent * em_event = NULL; + EmEvent * em_event = nullptr; GUARD_AND_DBG_LOG(emee_cptr_set_mutex_); for(std::set::iterator it = emee_cptr_set_.begin(); - it != emee_cptr_set_.end(); it++) + it != emee_cptr_set_.end(); ++it) { EventMethEpollEquivImpl * epoll_equiv = *it; if (!epoll_equiv) @@ -3203,7 +3310,8 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, throw std::runtime_error("epoll_equiv null"); } - Fd this_fd = epoll_equiv->findFdInInterest((Fd) arg); + Fd this_fd = epoll_equiv->findFdInInterest( + reinterpret_cast(arg)); if (this_fd) { em_event = this_fd; @@ -3214,28 +3322,49 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, return(em_event); } + // Adjusts the em_event flags to be exactly what we should pass to the + // libevent event_new function + short EventMethEpollEquivImpl::getFlagsToActuallyUseWithLibEvEventNew( + short candidate_flags) + { + candidate_flags |= EV_FINALIZE; // See earlier comment: Why and how we + // use libevent's finalize + + // Don't request edge-triggered from libevent if edge-triggered is not + // supported + if (!(getEventBaseFeatures() & EV_FEATURE_ET)) + candidate_flags &= (~EV_ET); + + // Mask out EV_TIMEOUT - a flag that may be set in ready_flags_ if a + // timeout occurs, but which is not needed to get a timeout. + candidate_flags &= (~EV_TIMEOUT); + + return(candidate_flags); + } + + #ifdef DEBUG int EventMethEpollEquivImpl::getEmEventCount() { return(em_event_count__); } - + int EventMethEpollEquivImpl::getLibeventEventCount() { return(libevent_event_count__); } - + int EventMethEpollEquivImpl::getEventMethEpollEquivCount() { return(event_meth_epoll_equiv_count__); } - + int EventMethEpollEquivImpl::getEventMethBaseCount() { return(event_meth_base_count__); } int EventMethEpollEquivImpl::getWaitThenGetAndEmptyReadyEvsCount() { return(wait_then_get_count__); } #endif - + EventMethEpollEquivImpl::EventMethEpollEquivImpl(int size) : event_meth_base_(std::make_unique()), int_mut_locked_by_get_ready_em_events_(false) { // size is a hint as to how many FDs to be monitored - + PS_TIMEDBG_START; if (size <= 0) @@ -3253,7 +3382,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, GUARD_AND_DBG_LOG(emee_cptr_set_mutex_); emee_cptr_set_.insert(this); - + INC_DEBUG_CTR(event_meth_epoll_equiv); } @@ -3263,6 +3392,16 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, PS_TIMEDBG_START_THIS; + // exit libevent loop + // + // Note we call event_base_loopbreak not event_base_loopexit. loopbreak + // returns as soon as any of our event callbacks that have been called + // return to libevent, and doesn't call any more callbacks after that; + // whereas loopexit keeps calling event callbacks for all activated + // events previously added to the base. We don't need those extra + // callbacks. + event_meth_base_->eMBaseLoopbreak(); + // When emee_cptr_set_mutex_ is to be locked together with // interest_mutex_, emee_cptr_set_mutex_ must be locked first GUARD_AND_DBG_LOG(emee_cptr_set_mutex_); @@ -3270,11 +3409,11 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, GUARD_AND_DBG_LOG(interest_mutex_); for(std::set::iterator it = interest_.begin(); - it != interest_.end(); it++) + it != interest_.end(); ++it) { Fd fd = *it; if (fd) // forget this EventMethEpollEquiv which is being destroyed - fd->detachEventMethEpollEquiv(); + fd->detachEventMethEpollEquivImmedFree(); } interest_.clear(); @@ -3282,21 +3421,15 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, // interest FIRST GUARD_AND_DBG_LOG(ready_mutex_); for(std::set::iterator it = ready_.begin(); - it != ready_.end(); it++) + it != ready_.end(); ++it) { Fd fd = *it; if (fd) // forget this EventMethEpollEquiv which is being destroyed - fd->detachEventMethEpollEquiv(); + fd->detachEventMethEpollEquivImmedFree(); } ready_.clear(); - // exit libevent loop - // - // Note we call event_base_loopbreak not event_base_loopexit; the later - // exits the loop after a time out, while the former exits immediately - event_meth_base_->eMBaseLoopbreak(); - - event_meth_base_ = NULL; + event_meth_base_ = nullptr; } // Returns emee if emee is in emee_cptr_set_, or NULL @@ -3305,18 +3438,18 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, getEventMethEpollEquivImplFromEmeeSet(EventMethEpollEquivImpl * emee) { GUARD_AND_DBG_LOG(emee_cptr_set_mutex_); - + std::set::iterator it( emee_cptr_set_.find(emee)); if (it == emee_cptr_set_.end()) - return(NULL); + return(nullptr); return(emee); } - int EventMethEpollEquivImpl::getEventBaseFeatures() + int EventMethEpollEquivImpl::getEventBaseFeatures() { PS_TIMEDBG_START; - + return(event_meth_base_->getEventBaseFeatures()); } @@ -3324,7 +3457,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, const Flags& interest) { PS_TIMEDBG_START; - + int events = 0; if (interest.hasFlag(Polling::NotifyOn::Read)) @@ -3357,15 +3490,15 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, Flags EventMethEpollEquivImpl::toNotifyOn(Fd fd) { PS_TIMEDBG_START; - + if (!fd) { PS_LOG_WARNING("fd is NULL"); throw std::runtime_error("fd is NULL"); } - + int evm_events = fd->getReadyFlags(); - + Flags flags; // Note: There is no Polling::NotifyOn::Timeout @@ -3377,9 +3510,30 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, flags.setFlag(Polling::NotifyOn::Hangup); if (evm_events & EVM_SIGNAL) { + // Signals in Windows vs. Linux: + // + // In Linux, signal constants can be found, for instance, in + // include/asm-generic/signal.h. In Windows, e.g., in "C:\Program + // Files (x86)\Windows Kits\10\Include\10.0.22621.0\ucrt\signal.h" + // + // SIGURG, SIGCONT, SIGCHLD, SIGIO, SIGWINCH are defined in the + // Linux signal.h (as you'd expect) but not in Windows. The only + // Windows signal not defined in Linux is SIGBREAK "Ctrl-Break + // sequence" which is generated when CTRL+BREAK is pressed on a + // console app. The signals that are defined in Windows (and in + // Linux) are SIGINT, SIGILL, SIGFPE, SIGSEGV, SIGTERM, and + // SIGABRT. All of these (and SIGBREAK) suggest or require a + // shutdown. + // + // Therefore, in the unlikely event we get a signal in Windows, we + // treat it as a shutdown. + + #ifdef _IS_WINDOWS + flags.setFlag(Polling::NotifyOn::Shutdown); + #else // Since this is a signal event, it cannot be a FdEventFd or timer - - int actual_fd_num = fd->getActualFd(); + + int actual_fd_num = static_cast(fd->getActualFd()); // Per libevent documentation, this is the signal number being // monitored by the libevent event. // Most, but not all, signals require shutdown. Do "man signal" to @@ -3390,7 +3544,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, case SIGURG: // urgent condition present on socket flags.setFlag(Polling::NotifyOn::Hangup); break; - + case SIGCONT: // continue after stop case SIGCHLD: // child status has changed case SIGIO: // I/O is possible on a descriptor (see fcntl(2)) @@ -3401,12 +3555,12 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, #endif // Above conditions should be ignored... we set no flag break; - + default: flags.setFlag(Polling::NotifyOn::Shutdown); break; } - + #endif // of ifdef _IS_WINDOWS... else... } return flags; @@ -3431,7 +3585,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, #ifndef DEBUG [[maybe_unused]] #endif - em_socket_t cb_actual_fd, + evutil_socket_t cb_actual_fd, short ev_flags) { PS_TIMEDBG_START_SQUARE; @@ -3441,10 +3595,10 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, // We check again that cb_arg is in interest_ to make sure em_event // hasn't been closed/deleted since we called // findEmEventInAnInterestSet in eventCallbackFn - // + // // Note: So long as em_event has not already been closed/deleted, it // can't be deleted now so long as we have interest_mutex_ locked - EmEvent * em_event = ((EmEvent *)cb_arg); + EmEvent * em_event = (reinterpret_cast(cb_arg)); if (interest_.find(em_event) == interest_.end()) { PS_LOG_DEBUG_ARGS("cb_arg %p is not in interest_ of EMEEI %p", @@ -3454,7 +3608,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, #ifdef DEBUG // There is no actual-fd for EmEventFd or EmEventTmrFd - em_socket_t em_events_actual_fd = -1; + evutil_socket_t em_events_actual_fd = -1; if (em_event->getEmEventType() == Pistache::EmEvReg) em_events_actual_fd = em_event->getActualFdPrv(); @@ -3467,13 +3621,16 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, } #endif - // handleEventCallback may update ev_flags and/or em_event - em_event->handleEventCallback(ev_flags); + // handleEventCallback may update ev_flags and/or em_event + em_event->handleEventCallback(ev_flags); + + if (!(getEventBaseFeatures() & EV_FEATURE_ET)) + em_event->deactivateAfterTriggerIfNeeded(); addEventToReadyInterestAlrdyLocked(em_event, ev_flags); } - - + + void EventMethEpollEquivImpl::addEventToReadyInterestAlrdyLocked(Fd fd, short ev_flags) { @@ -3497,7 +3654,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, // or has already been, deleted return; } - + GUARD_AND_DBG_LOG(ready_mutex_); #ifdef DEBUG @@ -3548,7 +3705,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, void EventMethEpollEquivImpl::lockInterestMutex() { GUARD_AND_DBG_LOG(int_mut_locked_by_get_ready_em_events_mutex_); - + #ifdef DEBUG if (int_mut_locked_by_get_ready_em_events_) PS_LOG_WARNING_ARGS("interest_mutex_ (at %p) already locked?", @@ -3559,8 +3716,8 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, PS_LOG_DEBUG_ARGS("Locking interest_mutex_ (at %p)", &interest_mutex_); interest_mutex_.lock(); } - - + + int EventMethEpollEquivImpl::getReadyEmEvents(int timeout, std::set & ready_evm_events_out) @@ -3571,9 +3728,9 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, // scope WaitThenGetCountHelper wait_then_get_count_helper; #endif - + PS_TIMEDBG_START; - + int num_ready_out = 0; // Note: It's possible in thoery (perhaps not in practice) for @@ -3582,10 +3739,10 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, // have null EmEvent. In that case, getReadyEmEventsHelper // returns -1, and we try again do { - num_ready_out = + num_ready_out = getReadyEmEventsHelper(timeout, ready_evm_events_out); } while (num_ready_out < 0); - + return(num_ready_out); } @@ -3601,10 +3758,10 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, unsigned int i = 0; for(std::set::iterator it = interest_.begin(); - it != interest_.end(); it++, i++) + it != interest_.end(); ++it, ++i) { Fd fd = *it; - + if (fd) { std::string pends(""); @@ -3636,10 +3793,10 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, if (readys.empty()) readys += " none"; - int actual_fd = -1; + evutil_socket_t actual_fd = -1; if (fd->getEmEventType() == EmEvReg) actual_fd = fd->getActualFdPrv(); - + PS_LOG_DEBUG_ARGS("#%u EmEvent %p of EMEE %p pending, " "type %s, " "actual fd %d, pending events%s, " @@ -3685,7 +3842,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, return(-1); } - + // Waits (if needed) until events are ready, then sets the _out set to be // equal to ready events, and empties the list of ready events @@ -3704,12 +3861,12 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, int timeout, std::set & ready_evm_events_out) { PS_TIMEDBG_START_ARGS("EMEE %p", this); - + ready_evm_events_out.clear(); { // encapsulate for loop_timer_eme - - std::shared_ptr loop_timer_eme(NULL); + + std::shared_ptr loop_timer_eme(nullptr); #ifdef DEBUG PS_LOG_DEBUG("Listing interest_ before wait(event_base_dispatch)"); @@ -3752,7 +3909,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, // event_base_loop with EVLOOP_ONCE: block until we have an active // event, then exit once all active events have had their callbacks // run. - // + // // We can also break out of the loop by calling // event_base_loopbreak e.g. from a callback, though right now we // don't do so @@ -3764,13 +3921,13 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, PS_LOG_DEBUG("event_base_dispatch error"); return(dispatch_res); } - + if (dispatch_res == 1) { PS_LOG_DEBUG("No pending or active events"); return(0); } - + PS_LOG_DEBUG("event_base dispatch/loopexit success"); #ifdef DEBUG logPendingOrNot(); @@ -3779,7 +3936,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, if (loop_timer_eme) { std::size_t remaining_ready_size = 0; - + int remove_loop_timer_res = removeSpecialTimerFromInterestAndReady( loop_timer_eme.get(), &remaining_ready_size); @@ -3788,10 +3945,10 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, return(0); // the only ready event was the loop timeout } - - loop_timer_eme = NULL; // Not needed since about to go out of scope + + loop_timer_eme = nullptr; // Not needed since about to go out of scope // anyway, but to be clear... - + // loop_timer_eme gets deleted here, causing it to be removed from // loop as needed } @@ -3800,23 +3957,23 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, // ready_, to ensure that no event (Fd) we get from ready_ can be // closed (noting that close of an Fd requires the interest_mutex_) // until processing of said event/Fd has completed - + lockInterestMutex(); // interest_mutex_ shall remain locked on return // from this function { // encapsulate ready_mutex_ lock GUARD_AND_DBG_LOG(ready_mutex_); - + if (ready_.empty()) { PS_LOG_DEBUG("ready_ empty despite dispatch completion"); return(0); } - + PS_LOG_DEBUG_ARGS("ready_ events ready. Number: %d", ready_.size()); - + ready_evm_events_out = std::move(ready_); ready_.clear(); // probably unneeded because using std::move, but // just in case... @@ -3827,7 +3984,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, PS_LOG_DEBUG_ARGS("ready_evm_events_out_initial_size = %d", ready_evm_events_out_initial_size); - + if (ready_evm_events_out_initial_size) { bool repeat_for = false; @@ -3835,24 +3992,24 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, do { repeat_for = false; for(std::set::iterator it = ready_evm_events_out.begin(); - it != ready_evm_events_out.end(); it++) + it != ready_evm_events_out.end(); ++it) { Fd em_event(*it); if (!em_event) { PS_LOG_WARNING("ready em_event is NULL"); - + ready_evm_events_out.erase(it); // remove from ready - + repeat_for = true; break; // because erase invalidates iteratator it } PS_LOG_DEBUG("Event not null"); - + if (!(em_event->getFlags() & EVM_PERSIST)) { // remove from interest_ - // + // // Note: A non-persistent event becomes non-pending (aka // not active) as soon as it is triggered, so should be // removed from interest_ @@ -3889,15 +4046,15 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, (ready_evm_events_out.size() <= 0)) res = -1; else - res = (int)(ready_evm_events_out.size()); + res = static_cast(ready_evm_events_out.size()); PS_LOG_DEBUG_ARGS("Returning %d", res); - + return(res); } Fd EventMethEpollEquivImpl::em_event_new( // static method - em_socket_t actual_fd, // file desc, signal, or -1 + evutil_socket_t actual_fd,//fl desc, signal, or -1 short flags, // EVM_... flags // For setfd and setfl arg: // F_SETFDL_NOTHING - change nothing @@ -3911,11 +4068,11 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, int f_setfl_flags // e.g. O_NONBLOCK ) { - return(EmEvent::make_new(actual_fd, flags, + return(EmEvent::make_new(actual_fd, flags, f_setfd_flags, f_setfl_flags)); } - Fd EventMethEpollEquivImpl::em_timer_new(clockid_t clock_id, + Fd EventMethEpollEquivImpl::em_timer_new(PST_CLOCK_ID_T clock_id, // For setfd and setfl arg: // F_SETFDL_NOTHING - change nothing // Zero or pos number that is not @@ -3933,7 +4090,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, } // For "eventfd-style" descriptors - // + // // Note that FdEventFd does not have an "actual fd" that the caller can // access; the caller must use FdEventFd's member functions instead FdEventFd EventMethEpollEquivImpl::em_eventfd_new(unsigned int initval, @@ -3973,16 +4130,16 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, std::chrono::milliseconds * timeval_cptr) { // static version PS_TIMEDBG_START; - + if(!epoll_equiv) { PS_LOG_WARNING("epoll_equiv null"); throw std::invalid_argument("epoll_equiv null"); } - + return(epoll_equiv->ctl(op, epoll_equiv, event, events, timeval_cptr)); } - + // Add to interest list // Returns 0 for success, on error -1 with errno set @@ -4015,7 +4172,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, std::set::iterator eme_interest_it(interest_.find(em_event)); eme_found_in_interest = (eme_interest_it != interest_.end()); - + if ((op == EvCtlAction::Add) && (eme_found_in_interest) && (!(em_event->addWasArtificial()))) { @@ -4024,12 +4181,12 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, "and em_event->ctl(EvCtlAction::Add...) not called; " "em_event is already in interest_", em_event, this); - + errno = EEXIST; return(-1); } } - + int ctl_res = (forceEmEventCtlOnly ? em_event->EmEvent::ctl(op, this, events, timeval_cptr) : @@ -4038,7 +4195,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, if (ctl_res == 0) { GUARD_AND_DBG_LOG(interest_mutex_); - + switch(op) { case EvCtlAction::Add: @@ -4088,7 +4245,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, // added is permitted provided EPOLLEXCLUSIVE is not set. // Pistache doesn't use EPOLLEXCLUSIVE, and we don't // support it at present - + PS_LOG_DEBUG_ARGS( "em_event %p in interest_ for Mod of EMEE %p", em_event, this); @@ -4134,7 +4291,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, int EventMethEpollEquivImpl::closeEvent(EmEvent * em_event) { PS_TIMEDBG_START; - + if (!em_event) { PS_LOG_INFO("em_event null"); @@ -4166,9 +4323,9 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, auto interest_it = emeei->interest_.find(em_event); if (interest_it != emeei->interest_.end()) { - + std::mutex & red_mut(emeei->ready_mutex_); - + GUARD_AND_DBG_LOG(red_mut); int close_res = em_event->close(); @@ -4191,7 +4348,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, // while we perform the delete, to make sure we're not // adding em_event to ready in another thread that's // doing polling - // + // // Also so that if another thread is processing // em_event (e.g. after polling) it can, by claiming // interest_mutex_, avoid having em_event stamped on @@ -4205,7 +4362,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, PS_LOG_DEBUG_ARGS( "em_event->close() failed for %p", em_event); #endif - + return(close_res); } } @@ -4225,30 +4382,29 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, PS_LOG_DEBUG_ARGS("em_event->close() failed for %p", em_event); } #endif - + return(close_res); } /* --------------------------------------------------------------------- */ - // Calls event_base_loopbreak for this base + // Calls event_base_loopbreak for this base int EventMethBase::eMBaseLoopbreak() { PS_TIMEDBG_START; - + return(event_base_loopbreak(getEventBase())); } // To enable to_string of an Fd std::string to_string(const EmEvent * eme) - {return(std::to_string((unsigned long) eme));} - + {return(std::to_string(reinterpret_cast(eme)));} + /* ------------------------------------------------------------------------- */ - - + + } // of namespace Pistache /* ------------------------------------------------------------------------- */ #endif // ifdef _USE_LIBEVENT - diff --git a/src/common/http.cc b/src/common/http.cc index b998c58f1..d9e5dd07d 100644 --- a/src/common/http.cc +++ b/src/common/http.cc @@ -10,14 +10,18 @@ Http layer implementation */ -#include +#include + #include #include #include +#include #include #include #include +#include PIST_QUOTE(PST_STRERROR_R_HDR) + #include #include #include @@ -28,10 +32,14 @@ #include #include -#include +#include // for file-constants (_O_RDONLY etc.) in Windows +#include PIST_QUOTE(PST_FCNTL_HDR) // for function fcntl() + +#include PIST_QUOTE(PST_MISC_IO_HDR) // for _close (io.h / unistd.h) +#include PIST_QUOTE(PIST_FILEFNS_HDR) // for "open" + #include #include -#include namespace Pistache::Http { @@ -54,7 +62,7 @@ namespace Pistache::Http { bool writeStatusLine(Version version, Code code, DynamicStreamBuf& buf) { -#define OUT(...) \ +#define PST_OUT(...) \ do \ { \ __VA_ARGS__; \ @@ -64,20 +72,20 @@ namespace Pistache::Http std::ostream os(&buf); - OUT(os << version << " "); - OUT(os << static_cast(code)); - OUT(os << ' '); - OUT(os << code); - OUT(os << crlf); + PST_OUT(os << version << " "); + PST_OUT(os << static_cast(code)); + PST_OUT(os << ' '); + PST_OUT(os << code); + PST_OUT(os << crlf); return true; -#undef OUT +#undef PST_OUT } bool writeHeaders(const Header::Collection& headers, DynamicStreamBuf& buf) { -#define OUT(...) \ +#define PST_OUT(...) \ do \ { \ __VA_ARGS__; \ @@ -89,19 +97,19 @@ namespace Pistache::Http for (const auto& header : headers.list()) { - OUT(os << header->name() << ": "); - OUT(header->write(os)); - OUT(os << crlf); + PST_OUT(os << header->name() << ": "); + PST_OUT(header->write(os)); + PST_OUT(os << crlf); } return true; -#undef OUT +#undef PST_OUT } bool writeCookies(const CookieJar& cookies, DynamicStreamBuf& buf) { -#define OUT(...) \ +#define PST_OUT(...) \ do \ { \ __VA_ARGS__; \ @@ -112,14 +120,14 @@ namespace Pistache::Http std::ostream os(&buf); for (const auto& cookie : cookies) { - OUT(os << "Set-Cookie: "); - OUT(os << cookie); - OUT(os << crlf); + PST_OUT(os << "Set-Cookie: "); + PST_OUT(os << cookie); + PST_OUT(os << crlf); } return true; -#undef OUT +#undef PST_OUT } using HttpMethods = std::unordered_map; @@ -434,15 +442,17 @@ namespace Pistache::Http if (bytesRead > 0) { // How many bytes do we still need to read ? - const size_t remaining = contentLength - bytesRead; + const size_t remaining = static_cast( + contentLength - bytesRead); if (!readBody(remaining)) return State::Again; } // This is the first time we are reading the payload else { - message->body_.reserve(contentLength); - if (!readBody(contentLength)) + message->body_.reserve( + static_cast(contentLength)); + if (!readBody(static_cast(contentLength))) return State::Again; } @@ -485,7 +495,7 @@ namespace Pistache::Http message->body_.reserve(size); StreamCursor::Token chunkData(cursor); - const ssize_t available = cursor.remaining(); + const PST_SSIZE_T available = cursor.remaining(); if (available + alreadyAppendedChunkBytes < size + 2) { @@ -537,7 +547,8 @@ namespace Pistache::Http { raise("Unsupported Transfer-Encoding", Code::Not_Implemented); } - return State::Done; + // raise defined with [[noreturn]], so compiler knows we cannot + // reach here } ParserBase::ParserBase(size_t maxDataSize) @@ -833,7 +844,7 @@ namespace Pistache::Http } } - Async::Promise ResponseWriter::sendMethodNotAllowed( + Async::Promise ResponseWriter::sendMethodNotAllowed( const std::vector& supportedMethods) { response_.code_ = Http::Code::Method_Not_Allowed; @@ -843,22 +854,22 @@ namespace Pistache::Http return putOnWire(body.c_str(), body.size()); } - Async::Promise ResponseWriter::send(Code code, const std::string& body, - const Mime::MediaType& mime) + Async::Promise ResponseWriter::send(Code code, const std::string& body, + const Mime::MediaType& mime) { return sendImpl(code, body.c_str(), body.size(), mime); } - Async::Promise ResponseWriter::send(Code code, const char* data, - const size_t size, - const Mime::MediaType& mime) + Async::Promise ResponseWriter::send(Code code, const char* data, + const size_t size, + const Mime::MediaType& mime) { return sendImpl(code, data, size, mime); } - Async::Promise ResponseWriter::sendImpl(Code code, const char* data, - const size_t size, - const Mime::MediaType& mime) + Async::Promise ResponseWriter::sendImpl(Code code, const char* data, + const size_t size, + const Mime::MediaType& mime) { if (!peer_.expired()) { @@ -961,7 +972,7 @@ namespace Pistache::Http // Compute upper bound on size of expected compressed data. This // will be updated by compress2()... - uLongf compressedSize = ::compressBound(size); + uLongf compressedSize = static_cast(::compressBound(static_cast(size))); // Allocate a smart buffer to contain compressed data... std::unique_ptr compressedData = std::make_unique(compressedSize); @@ -971,7 +982,7 @@ namespace Pistache::Http reinterpret_cast(compressedData.get()), &compressedSize, reinterpret_cast(data), - size, + static_cast(size), contentEncodingDeflateLevel_); // Failed... @@ -1039,41 +1050,41 @@ namespace Pistache::Http ResponseWriter ResponseWriter::clone() const { return ResponseWriter(*this); } - Async::Promise ResponseWriter::putOnWire(const char* data, - size_t len) + Async::Promise ResponseWriter::putOnWire(const char* data, + size_t len) { try { std::ostream os(&buf_); -#define OUT(...) \ - do \ - { \ - __VA_ARGS__; \ - if (!os) \ - { \ - return Async::Promise::rejected( \ - Error("Response exceeded buffer size")); \ - } \ +#define PST_OUT(...) \ + do \ + { \ + __VA_ARGS__; \ + if (!os) \ + { \ + return Async::Promise::rejected( \ + Error("Response exceeded buffer size")); \ + } \ } while (0); - OUT(writeStatusLine(response_.version(), response_.code(), buf_)); - OUT(writeHeaders(response_.headers(), buf_)); - OUT(writeCookies(response_.cookies(), buf_)); + PST_OUT(writeStatusLine(response_.version(), response_.code(), buf_)); + PST_OUT(writeHeaders(response_.headers(), buf_)); + PST_OUT(writeCookies(response_.cookies(), buf_)); /* @Todo @Major: * Correctly handle non-keep alive requests * Do not put Keep-Alive if version == Http::11 and request.keepAlive == * true */ - // OUT(writeHeader(os, ConnectionControl::KeepAlive)); - OUT(writeHeader(os, len)); + // PST_OUT(writeHeader(os, ConnectionControl::KeepAlive)); + PST_OUT(writeHeader(os, len)); - OUT(os << crlf); + PST_OUT(os << crlf); if (len > 0) { - OUT(os.write(data, len)); + PST_OUT(os.write(data, len)); } auto buffer = buf_.buffer(); @@ -1081,24 +1092,24 @@ namespace Pistache::Http timeout_.disarm(); -#undef OUT +#undef PST_OUT auto fd = peer()->fd(); return transport_->asyncWrite(fd, buffer) - .then(ssize_t)>, + .then(PST_SSIZE_T)>, std::function>( - [=](ssize_t data) { - return Async::Promise::resolved(data); + [=](PST_SSIZE_T data) { + return Async::Promise::resolved(data); }, [=](std::exception_ptr& eptr) { - return Async::Promise::rejected(eptr); + return Async::Promise::rejected(eptr); }); } catch (const std::runtime_error& e) { - return Async::Promise::rejected(e); + return Async::Promise::rejected(e); } } @@ -1141,16 +1152,17 @@ namespace Pistache::Http } } - Async::Promise serveFile(ResponseWriter& writer, - const std::string& fileName, - const Mime::MediaType& contentType) + Async::Promise serveFile(ResponseWriter& writer, + const std::string& fileName, + const Mime::MediaType& contentType) { struct stat sb; - int fd = open(fileName.c_str(), O_RDONLY); + int fd = PST_FILE_OPEN(fileName.c_str(), PST_O_RDONLY); if (fd == -1) { - std::string str_error(strerror(errno)); + PST_DECL_SE_ERR_P_EXTRA; + std::string str_error(PST_STRERROR_R_ERRNO); if (errno == ENOENT) { throw HttpError(Http::Code::Not_Found, std::move(str_error)); @@ -1167,7 +1179,7 @@ namespace Pistache::Http int res = ::fstat(fd, &sb); - close(fd); // Done with fd, close before error can be thrown + PST_FILE_CLOSE(fd); // Done with fd, close before error can be thrown if (res == -1) { throw HttpError(Code::Internal_Server_Error, ""); @@ -1177,15 +1189,15 @@ namespace Pistache::Http std::ostream os(buf); -#define OUT(...) \ - do \ - { \ - __VA_ARGS__; \ - if (!os) \ - { \ - return Async::Promise::rejected( \ - Error("Response exceeded buffer size")); \ - } \ +#define PST_OUT(...) \ + do \ + { \ + __VA_ARGS__; \ + if (!os) \ + { \ + return Async::Promise::rejected( \ + Error("Response exceeded buffer size")); \ + } \ } while (0); auto setContentType = [&](const Mime::MediaType& contentType) { @@ -1197,7 +1209,7 @@ namespace Pistache::Http headers.add(contentType); }; - OUT(writeStatusLine(writer.response_.version(), Http::Code::Ok, *buf)); + PST_OUT(writeStatusLine(writer.response_.version(), Http::Code::Ok, *buf)); if (contentType.isValid()) { setContentType(contentType); @@ -1209,13 +1221,13 @@ namespace Pistache::Http setContentType(mime); } - OUT(writeHeaders(writer.headers(), *buf)); + PST_OUT(writeHeaders(writer.headers(), *buf)); const size_t len = sb.st_size; - OUT(writeHeader(os, len)); + PST_OUT(writeHeader(os, len)); - OUT(os << crlf); + PST_OUT(os << crlf); auto* transport = writer.transport_; auto peer = writer.peer(); @@ -1233,12 +1245,12 @@ namespace Pistache::Http #endif ) .then( - [=](ssize_t) { + [=](PST_SSIZE_T) { return transport->asyncWrite(sockFd, FileBuffer(fileName)); }, Async::Throw); -#undef OUT +#undef PST_OUT } Private::ParserImpl::ParserImpl(size_t maxDataSize) diff --git a/src/common/http_defs.cc b/src/common/http_defs.cc index f91742b3a..a31dbc7fb 100644 --- a/src/common/http_defs.cc +++ b/src/common/http_defs.cc @@ -24,6 +24,8 @@ #endif #include +#include PIST_QUOTE(PST_CLOCK_GETTIME_HDR) + namespace Pistache::Http { @@ -135,7 +137,10 @@ namespace Pistache::Http // isn't guaranteed to have a tm_zone field - it only does on // POSIX.1-2024 systems; this issue seen on NetBSD 10.0). time_t t = std::chrono::system_clock::to_time_t(date_); - os << std::put_time(std::gmtime(&t), "%a, %d %b %Y %T GMT"); + + struct tm gmtm; + PST_GMTIME_R(&t, &gmtm); + os << std::put_time(&gmtm, "%a, %d %b %Y %T GMT"); } break; case Type::RFC850: diff --git a/src/common/http_header.cc b/src/common/http_header.cc index 2452a604f..78f00a1e8 100644 --- a/src/common/http_header.cc +++ b/src/common/http_header.cc @@ -10,6 +10,8 @@ Implementation of common HTTP headers described by the RFC */ +#include + #include #include #include @@ -26,7 +28,8 @@ #include #include #include -#include + +#include PIST_QUOTE(PST_SOCKET_HDR) namespace Pistache::Http::Header { @@ -76,32 +79,31 @@ namespace Pistache::Http::Header return Encoding::Unknown; } - if (!strncasecmp(str.data(), "zstd", str.length())) + if (!PST_STRNCASECMP(str.data(), "zstd", str.length())) { return Encoding::Zstd; } - - if (!strncasecmp(str.data(), "gzip", str.length())) + else if (!PST_STRNCASECMP(str.data(), "gzip", str.length())) { return Encoding::Gzip; } - else if (!strncasecmp(str.data(), "br", str.length())) + else if (!PST_STRNCASECMP(str.data(), "br", str.length())) { return Encoding::Br; } - else if (!strncasecmp(str.data(), "deflate", str.length())) + else if (!PST_STRNCASECMP(str.data(), "deflate", str.length())) { return Encoding::Deflate; } - else if (!strncasecmp(str.data(), "compress", str.length())) + else if (!PST_STRNCASECMP(str.data(), "compress", str.length())) { return Encoding::Compress; } - else if (!strncasecmp(str.data(), "identity", str.length())) + else if (!PST_STRNCASECMP(str.data(), "identity", str.length())) { return Encoding::Identity; } - else if (!strncasecmp(str.data(), "chunked", str.length())) + else if (!PST_STRNCASECMP(str.data(), "chunked", str.length())) { return Encoding::Chunked; } @@ -308,7 +310,7 @@ namespace Pistache::Http::Header case CacheDirective::Ext: return ""; default: - return ""; + break; } return ""; }; diff --git a/src/common/http_headers.cc b/src/common/http_headers.cc index 331a5982f..c0e7b2ee4 100644 --- a/src/common/http_headers.cc +++ b/src/common/http_headers.cc @@ -115,7 +115,27 @@ namespace Pistache::Http::Header std::string toLowercase(std::string str) { - std::transform(str.begin(), str.end(), str.begin(), ::tolower); + std::transform(str.begin(), str.end(), str.begin(), + [](const char ch) + { + const unsigned char uch = + static_cast(ch); + auto ires = ::tolower(uch); + return(static_cast(ires)); + }); + // Note re: the lambda function being used for std::transform + // above. Previously (before 10/2024), std::transform was simply being + // passed ::tolower/::toupper for the transformer function, but in fact + // we need to make two changes to that: i) we need to cast the input + // parm to "unsigned char" to avoid errors due to sign extension as + // explained on the Linux man page + // (e.g. https://www.man7.org/linux/man-pages/man3/toupper.3.html, + // Notes section); and ii) since the result of the transformer function + // is written into std::string, i.e. to a char, the transformer + // function needs to return a char, not (as ::tolower/::toupper does) + // an int, otherwise the compiler may complain about loss of integer + // size in writing the int to a char (and in fact MSVC was complaining + // in exactly this way). return str; } diff --git a/src/common/net.cc b/src/common/net.cc index 9a81fa616..a5f46c0ca 100644 --- a/src/common/net.cc +++ b/src/common/net.cc @@ -9,6 +9,8 @@ * Mathieu Stefani, 12 August 2015 */ +#include + #include #include #include @@ -22,10 +24,15 @@ #include #include -#include -#include -#include -#include +#include + +#include PIST_QUOTE(PST_ARPA_INET_HDR) + +#include PIST_QUOTE(PST_IFADDRS_HDR) + +#include PIST_QUOTE(PST_NETDB_HDR) +#include PIST_QUOTE(PST_SOCKET_HDR) + #include namespace Pistache @@ -37,7 +44,7 @@ namespace Pistache return(httpAddr(view, 0/*default port*/)); } } // namespace helpers - + Port::Port(uint16_t port) : port(port) { } @@ -58,7 +65,6 @@ namespace Pistache bool Port::isUsed() const { throw std::runtime_error("Unimplemented"); - return false; } std::string Port::toString() const { return std::to_string(port); } @@ -70,12 +76,17 @@ namespace Pistache IP::IP(uint8_t a, uint8_t b, uint8_t c, uint8_t d) { - addr_.ss_family = AF_INET; - const uint8_t buff[] = { a, b, c, d }; - in_addr_t* in_addr = &reinterpret_cast(&addr_)->sin_addr.s_addr; + addr_.ss_family = AF_INET; + const uint8_t buff[] = { a, b, c, d }; + // Note that in Windows in_addr is a union - effectively a "u_long" + // that can be accessed as 4 u_char, 2 u_short, or 1 u_long. + // Meanwhile, s_addr on the right-hand side is type ULONG. So both + // sides are effectively "ulong *" and the cast is valid + PST_IN_ADDR_T* in_addr = reinterpret_cast + (&reinterpret_cast(&addr_)->sin_addr.s_addr); static_assert(sizeof(buff) == sizeof(*in_addr)); - memcpy(in_addr, buff, sizeof(*in_addr)); + std::memcpy(in_addr, buff, sizeof(*in_addr)); } IP::IP(uint16_t a, uint16_t b, uint16_t c, uint16_t d, uint16_t e, uint16_t f, @@ -94,12 +105,12 @@ namespace Pistache } else { - memcpy(remap, buff, sizeof(remap)); + std::memcpy(remap, buff, sizeof(remap)); } auto& in6_addr = reinterpret_cast(&addr_)->sin6_addr.s6_addr; static_assert(sizeof(in6_addr) == sizeof(remap)); - memcpy(in6_addr, remap, sizeof(in6_addr)); + std::memcpy(in6_addr, remap, sizeof(in6_addr)); } IP::IP(const struct sockaddr* addr) @@ -123,7 +134,7 @@ namespace Pistache ss_in_addr->sin6_family = in_addr->sin6_family; ss_in_addr->sin6_port = in_addr->sin6_port; ss_in_addr->sin6_flowinfo = in_addr->sin6_flowinfo; /* Should be 0 per RFC 3493 */ - memcpy(ss_in_addr->sin6_addr.s6_addr, in_addr->sin6_addr.s6_addr, sizeof(ss_in_addr->sin6_addr.s6_addr)); + std::memcpy(ss_in_addr->sin6_addr.s6_addr, in_addr->sin6_addr.s6_addr, sizeof(ss_in_addr->sin6_addr.s6_addr)); } else if (addr->sa_family == AF_UNIX) { @@ -131,7 +142,7 @@ namespace Pistache struct sockaddr_un* ss_un_addr = reinterpret_cast(&addr_); ss_un_addr->sun_family = un_addr->sun_family; - memcpy(ss_un_addr->sun_path, un_addr->sun_path, sizeof(ss_un_addr->sun_path)); + std::memcpy(ss_un_addr->sun_path, un_addr->sun_path, sizeof(ss_un_addr->sun_path)); } else { @@ -214,7 +225,7 @@ namespace Pistache char buff[INET6_ADDRSTRLEN]; const auto* addr_sa = reinterpret_cast(&addr_); int err = getnameinfo( - addr_sa, sizeof(addr_), buff, sizeof(buff), NULL, 0, NI_NUMERICHOST); + addr_sa, sizeof(addr_), buff, sizeof(buff), nullptr, 0, NI_NUMERICHOST); if (err) /* [[unlikely]] */ { throw std::runtime_error(gai_strerror(err)); @@ -222,13 +233,19 @@ namespace Pistache return std::string(buff); } - void IP::toNetwork(in_addr_t* out) const + void IP::toNetwork(PST_IN_ADDR_T* out) const { if (addr_.ss_family != AF_INET) { throw std::invalid_argument("Inapplicable or invalid address family"); } - *out = reinterpret_cast(&addr_)->sin_addr.s_addr; + // Note that in Windows in_addr is a union - effectively a "u_long" + // that can be accessed as 4 u_char, 2 u_short, or 1 u_long. + // Meanwhile, s_addr on the right-hand side is type ULONG. So both + // sides are effectively "ulong *" and the cast is valid + const PST_IN_ADDR_T* out_ptr = reinterpret_cast + (&reinterpret_cast(&addr_)->sin_addr.s_addr); + *out = *out_ptr; } void IP::toNetwork(struct in6_addr* out) const @@ -242,12 +259,12 @@ namespace Pistache bool IP::supported() { - struct ifaddrs* ifaddr = nullptr; - struct ifaddrs* ifa = nullptr; + struct PST_IFADDRS* ifaddr = nullptr; + struct PST_IFADDRS* ifa = nullptr; int addr_family, n; bool supportsIpv6 = false; - if (getifaddrs(&ifaddr) == -1) + if (PST_GETIFADDRS(&ifaddr) == -1) { throw std::runtime_error("Call to getifaddrs() failed"); } @@ -267,7 +284,7 @@ namespace Pistache } } - freeifaddrs(ifaddr); + PST_FREEIFADDRS(ifaddr); return supportsIpv6; } @@ -326,9 +343,16 @@ namespace Pistache } // Check if port_ is a valid number - char* tmp; - std::strtol(port_.c_str(), &tmp, 10); - hasNumericPort_ = *tmp == '\0'; + char* tmp2 = nullptr; + long strtol_res = std::strtol(port_.c_str(), &tmp2, 10); + if ((strtol_res < 0) || (!tmp2)) + { + PS_LOG_DEBUG_ARGS("strtol failed for port_ %s, " + "throwing \"Invalid port\"", + port_.c_str()); + throw std::invalid_argument("Invalid port"); + } + hasNumericPort_ = (*tmp2 == '\0'); } } @@ -404,7 +428,7 @@ namespace Pistache { init(addr, 0 /*default port*/); } - + void Address::init(const std::string& addr, Port default_port) { // Handle unix domain addresses separately. @@ -430,7 +454,7 @@ namespace Pistache { addrLen_ = static_cast( offsetof(struct sockaddr_un, sun_path) + size); - std::strncpy(unAddr.sun_path, addr.c_str(), size); + PS_STRLCPY(unAddr.sun_path, addr.c_str(), size); if (size == sizeof unAddr.sun_path) { unAddr.sun_path[size - 1] = '\0'; @@ -530,11 +554,11 @@ namespace Pistache Error Error::system(const char* message) { - const char* err = strerror(errno); + PST_DECL_SE_ERR_P_EXTRA; std::string str(message); str += ": "; - str += err; + str += PST_STRERROR_R_ERRNO; return Error(std::move(str)); } diff --git a/src/common/os.cc b/src/common/os.cc index bfd3b7a96..2d27c07d8 100644 --- a/src/common/os.cc +++ b/src/common/os.cc @@ -9,21 +9,25 @@ */ +#include +#include + #include #include #include -#include +#include PIST_QUOTE(PST_FCNTL_HDR) +#include PIST_QUOTE(PIST_SOCKFNS_HDR) #include #include -#include + #ifndef _USE_LIBEVENT #include #endif -#include +#include PIST_QUOTE(PST_MISC_IO_HDR) // unistd.h e.g. close #include #include @@ -32,21 +36,24 @@ namespace Pistache { - uint hardware_concurrency() { return std::thread::hardware_concurrency(); } + unsigned int hardware_concurrency() { return std::thread::hardware_concurrency(); } - bool make_non_blocking(int fd) + bool make_non_blocking(em_socket_t fd) { PS_TIMEDBG_START; - int flags = fcntl(fd, F_GETFL, 0); + int flags = PST_FCNTL(fd, PST_F_GETFL, 0); if (flags == -1) { PS_LOG_WARNING_ARGS("make_non_blocking fail for fd %" PIST_QUOTE(PS_FD_PRNTFCD), fd); return false; } - flags |= O_NONBLOCK; - int ret = fcntl(fd, F_SETFL, flags); + if (flags == PST_FCNTL_GETFL_UNKNOWN) + flags = PST_O_NONBLOCK; + else + flags |= PST_O_NONBLOCK; + int ret = PST_FCNTL(fd, PST_F_SETFL, flags); #ifdef DEBUG if (ret == -1) { @@ -187,12 +194,13 @@ namespace Pistache #endif } - void Epoll::addFd(Fd fd, Flags interest, Tag tag, Mode mode) + void Epoll::addFd(Fd fd, Flags interest, Tag tag, + [[maybe_unused]] Mode mode) { PS_TIMEDBG_START_ARGS("fd %" PIST_QUOTE(PS_FD_PRNTFCD), fd); #ifdef _USE_LIBEVENT - short events = (short)epoll_fd->toEvEvents(interest); + short events = static_cast(epoll_fd->toEvEvents(interest)); events |= EVM_PERSIST; // since EPOLLONESHOT not to be set if (mode == Mode::Edge) @@ -200,7 +208,7 @@ namespace Pistache EventMethFns::setEmEventUserData(fd, tag.value_); TRY(epoll_fd->ctl(EvCtlAction::Add, - fd, events, NULL /* time */)); + fd, events, nullptr /* time */)); #else struct epoll_event ev; @@ -219,7 +227,8 @@ namespace Pistache PS_TIMEDBG_START_ARGS("fd %" PIST_QUOTE(PS_FD_PRNTFCD), fd); #ifdef _USE_LIBEVENT - short events = (short)epoll_fd->toEvEvents(interest); + short events = static_cast(epoll_fd->toEvEvents(interest)); + if (mode == Mode::Edge) events |= EVM_ET; @@ -233,7 +242,7 @@ namespace Pistache EventMethFns::setEmEventUserData(fd, tag.value_); TRY(epoll_fd->ctl(EvCtlAction::Add, - fd, events, NULL /* time */)); + fd, events, nullptr /* time */)); #else struct epoll_event ev; ev.events = toEpollEvents(interest); @@ -252,7 +261,7 @@ namespace Pistache #ifdef _USE_LIBEVENT TRY(epoll_fd->ctl(EvCtlAction::Del, - fd, 0 /* events */, NULL /* time */)); + fd, 0 /* events */, nullptr /* time */)); #else struct epoll_event ev; TRY(epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &ev)); @@ -260,12 +269,12 @@ namespace Pistache } void Epoll::rearmFd(Fd fd, Flags interest, Tag tag, - Mode mode) + [[maybe_unused]] Mode mode) { PS_TIMEDBG_START_ARGS("fd %" PIST_QUOTE(PS_FD_PRNTFCD), fd); #ifdef _USE_LIBEVENT - short events = (short)epoll_fd->toEvEvents(interest); + short events = static_cast(epoll_fd->toEvEvents(interest)); // Why do we set EVM_PERSIST here? Since rearmFd is being called, // presumably fd was previously a one-shot event. You might think @@ -286,7 +295,7 @@ namespace Pistache events |= EVM_ET; EventMethFns::setEmEventUserData(fd, tag.value_); TRY(epoll_fd->ctl(EvCtlAction::Mod, - fd, events, NULL /* time */)); + fd, events, nullptr /* time */)); #else struct epoll_event ev; @@ -328,13 +337,13 @@ namespace Pistache ss3 << fd; str += ss3.str(); - if (((unsigned int)interest) & ((unsigned int)Polling::NotifyOn::Read)) + if ((static_cast(interest)) & (static_cast(Polling::NotifyOn::Read))) str += " read"; - if (((unsigned int)interest) & ((unsigned int)Polling::NotifyOn::Write)) + if ((static_cast(interest)) & (static_cast(Polling::NotifyOn::Write))) str += " write"; - if (((unsigned int)interest) & ((unsigned int)Polling::NotifyOn::Hangup)) + if ((static_cast(interest)) & (static_cast(Polling::NotifyOn::Hangup))) str += " hangup"; - if (((unsigned int)interest) & ((unsigned int)Polling::NotifyOn::Shutdown)) + if ((static_cast(interest)) & (static_cast(Polling::NotifyOn::Shutdown))) str += " shutdown"; PS_LOG_DEBUG_ARGS("%s", str.c_str()); @@ -342,14 +351,14 @@ namespace Pistache #ifdef _USE_LIBEVENT #define PS_LOG_DBG_FD_AND_NOTIFY logFdAndNotifyOn(i, \ - epoll_fd.get(), \ - (Fd)tag.valueU64(), \ - event.flags) + epoll_fd.get(), \ + reinterpret_cast(tag.valueU64()), \ + event.flags) #else #define PS_LOG_DBG_FD_AND_NOTIFY logFdAndNotifyOn(i, \ - epoll_fd, \ - (Fd)tag.valueU64(), \ - event.flags) + epoll_fd, \ + static_cast(tag.valueU64()), \ + event.flags) #endif #else #define PS_LOG_DBG_FD_AND_NOTIFY @@ -368,7 +377,7 @@ namespace Pistache PS_TIMEDBG_START_ARGS("epoll on EMEE (epoll_fd) %d", epoll_fd); #endif - + #ifdef _USE_LIBEVENT std::set ready_evm_events; int ready_evs = -1; @@ -433,7 +442,7 @@ namespace Pistache if (ready_evm_events.empty()) return (0); // 0 FDs - return((int)events.size()); + return(static_cast(events.size())); #else // not ifdef _USE_LIBEVENT @@ -484,7 +493,7 @@ namespace Pistache f_setfd_flags, f_setfl_flags)); } - Fd Epoll::em_timer_new(clockid_t clock_id, + Fd Epoll::em_timer_new(PST_CLOCK_ID_T clock_id, // For setfd and setfl arg: // F_SETFDL_NOTHING - change nothing // Zero or pos number that is not @@ -574,7 +583,7 @@ namespace Pistache { #ifdef _USE_LIBEVENT FdEventFd emefd = TRY_NULL_RET(Polling::Epoll::em_eventfd_new( - 0, FD_CLOEXEC, O_NONBLOCK)); + 0, PST_FD_CLOEXEC, PST_O_NONBLOCK)); event_fd = EventMethFns::getAsEmEvent(emefd); #else diff --git a/src/common/peer.cc b/src/common/peer.cc index 82b0c2e2d..5aa178506 100644 --- a/src/common/peer.cc +++ b/src/common/peer.cc @@ -9,12 +9,15 @@ */ +#include + #include #include -#include -#include -#include +#include PIST_QUOTE(PST_ARPA_INET_HDR) +#include PIST_QUOTE(PST_NETDB_HDR) +#include PIST_QUOTE(PST_SOCKET_HDR) + #include #include @@ -126,7 +129,7 @@ namespace Pistache::Tcp return res_fd; } - int Peer::actualFd() const // can return -1 + em_socket_t Peer::actualFd() const // can return -1 { Fd this_fd(fd_); @@ -195,7 +198,7 @@ namespace Pistache::Tcp return it->second; } - Async::Promise Peer::send(const RawBuffer& buffer, int flags) + Async::Promise Peer::send(const RawBuffer& buffer, int flags) { return transport()->asyncWrite(fd_, buffer, flags); } diff --git a/src/common/pist_check.cc b/src/common/pist_check.cc index 5bbd55b6c..c8d8d8406 100644 --- a/src/common/pist_check.cc +++ b/src/common/pist_check.cc @@ -6,34 +6,136 @@ /****************************************************************************** * pist_check.cc - * + * * Debugging breakpoints * */ +#include + #include // memset #include #include #include #include -#include + +#include PIST_QUOTE(PST_MISC_IO_HDR) // unistd.h e.g. close + #include #include #include -#include + +#ifdef _IS_WINDOWS +#include // required for dbghelp.h +#include +// See pist_timelog.h for usage +#else +#include // for abi::__cxa_demangle #include +#include // Dl_info +#endif + +#include PIST_QUOTE(PST_MAXPATH_HDR) // for PST_MAXPATHLEN -#include // PATH_MAX +#include // for PS_BASENAME_R -#include #include "pistache/pist_syslog.h" #include "pistache/pist_check.h" /*****************************************************************************/ +#ifdef _IS_WINDOWS + +#include + +static bool sym_system_inited = false; +static std::mutex sym_system_inited_mutex; + static void logStackTrace(int pri) { PSLogNoLocFn(pri, PS_LOG_AND_STDOUT, - "%s", "PS Check failed. Stack trace follows..."); + "%s", "Stack trace follows..."); + + HANDLE process = GetCurrentProcess(); // never fails, apparently + + if (!sym_system_inited) + { + std::lock_guard lock(sym_system_inited_mutex); + + if (!sym_system_inited) + { + BOOL syn_init_res = SymInitialize(process, + nullptr, // symbol search path. Use CWD, + // _NT_SYMBOL_PATH environment + // variable, and then + // _NT_ALTERNATE_SYMBOL_PATH + TRUE // Enumerates the loaded modules for + // the process, calling SymLoadModule64 + // on each + ); + if (!syn_init_res) + { + DWORD last_err = GetLastError(); + + PS_LOG_INFO_ARGS( + "SymInitialize fail, system error code (WinError.h) 0x%x", + static_cast(last_err)); + return; + } + + sym_system_inited = true; + } + } + + SymSetOptions(SYMOPT_UNDNAME); // undecorated names. We call this each + // time, in case some other part of the + // process outside Pistache resets it + + void * stack[1024+16]; + unsigned short frames = CaptureStackBackTrace(2, // skip first 2 frames + 1024, stack, nullptr); + + if (frames == 0) + { + PS_LOG_DEBUG("Zero frames returned in backtrace"); + return; + } + if (frames > 1024) + { + PS_LOG_DEBUG("Too many frames?"); + frames = 1024; + } + + for(unsigned int i = 0; i < frames; i++ ) + { + const unsigned sym_max_size = 2048; + unsigned char symbol_and_name[sizeof(SYMBOL_INFO) + + (sym_max_size * sizeof(TCHAR)) + 16]; + memset(&(symbol_and_name[0]), 0, sizeof(symbol_and_name)); + + SYMBOL_INFO * symbol = + reinterpret_cast(&(symbol_and_name[0])); + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + symbol->MaxNameLen = sym_max_size; + + bool sfa_res = SymFromAddr(process, + static_cast(reinterpret_cast(stack[i])), + 0, symbol); + + const char * name = "{Unknown symbol name}"; + if (!sfa_res) + name = "{Unknown symbol - SymFromAddr failed}"; + else if (symbol->NameLen) + name = &(symbol->Name[0]); + + PSLogNoLocFn(pri, PS_LOG_AND_STDOUT, " ST- %s", name); + } +} + +#else +static void logStackTrace(int pri) +{ + PSLogNoLocFn(pri, PS_LOG_AND_STDOUT, + "%s", "Stack trace follows..."); // write the stack trace to the logging facility @@ -43,7 +145,7 @@ static void logStackTrace(int pri) Dl_info info; // Start from 1, not 0 since everyone knows we are in PS_Break() - for (size_t i = 1; i < size; ++i) + for (size_t i = 1; i < size; ++i) { if (!(stack[i])) { @@ -51,14 +153,17 @@ static void logStackTrace(int pri) "%s", " ST- [Null Stack entry] "); continue; } - - if (dladdr(stack[i], &info) != 0) + + if (dladdr(stack[i], &info) != 0) { int status = 0; - - char* realname = abi::__cxa_demangle(info.dli_sname, NULL, - NULL, &status); - if (realname && status == 0) + + // See pist_timelog.h for usage + + char* realname = abi::__cxa_demangle(info.dli_sname, nullptr, + nullptr, &status); + + if (realname && (realname[0]) && status == 0) { if (info.dli_saddr) { @@ -95,10 +200,10 @@ static void logStackTrace(int pri) // http://bit.ly/1KvWpPs (stackoverflow) } - // -1: A memory allocation failiure occurred. + // -1: A memory allocation failure occurred. if (realname && status != -1) free(realname); - } + } else { PSLogNoLocFn(pri, PS_LOG_AND_STDOUT, @@ -106,6 +211,7 @@ static void logStackTrace(int pri) } } } +#endif // of ifdef _IS_WINDOWS... else... int PS_LogWoBreak(int pri, const char *p, @@ -115,7 +221,7 @@ int PS_LogWoBreak(int pri, const char *p, int ln = 0; const char * p_prequote_symbol = (p && (strlen(p))) ? "\"" : ""; const char * p_postquote_symbol = (p && (strlen(p))) ? "\" @" : ""; - + if (m) ln = snprintf(buf, sizeof(buf), "PS_LogPt: %s%s%s %s:%d in %s()\n", @@ -125,9 +231,9 @@ int PS_LogWoBreak(int pri, const char *p, ln = snprintf(buf, sizeof(buf), "PS_LogPt: %s%s%s %s:%d\n", p_prequote_symbol, p, p_postquote_symbol, f, l); - + // Print it - if (ln >= ((int)sizeof(buf))) + if (ln >= (static_cast(sizeof(buf)))) { ln = sizeof(buf); buf[sizeof(buf)-2] = '\n'; @@ -135,7 +241,7 @@ int PS_LogWoBreak(int pri, const char *p, } logStackTrace(pri); - + if ((pri == LOG_EMERG) || (pri == LOG_ALERT) || (pri == LOG_CRIT) || (pri == LOG_ERR)) { @@ -157,9 +263,9 @@ GuardAndDbgLog::GuardAndDbgLog(const char * mtx_name, std::mutex * mutex_ptr) : mtx_name_(mtx_name), locked_ln_(ln), mutex_ptr_(mutex_ptr) { - char buff[PATH_MAX+6]; + char buff[PST_MAXPATHLEN+6]; buff[0] = 0; - locked_fn_ = std::string(my_basename_r(fn, &(buff[0]))); + locked_fn_ = std::string(PS_BASENAME_R(fn, &(buff[0]))); } GuardAndDbgLog::~GuardAndDbgLog() @@ -171,5 +277,3 @@ GuardAndDbgLog::~GuardAndDbgLog() } #endif - - diff --git a/src/common/pist_clock_gettime.cc b/src/common/pist_clock_gettime.cc new file mode 100644 index 000000000..41d976e81 --- /dev/null +++ b/src/common/pist_clock_gettime.cc @@ -0,0 +1,261 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Defines a pist_clock_gettime when in an OS that does not provide +// clock_gettime natively, notably windows + +#include + +/* ------------------------------------------------------------------------- */ + +#ifdef _IS_WINDOWS + +#include + +// Note - Certain Windows header files are "not self contained" and require you +// to include the big windows.h file, or else you get a compile time error +// 'fatal error C1189: #error: "No Target Architecture"'. Apparently +// sysinfoapi.h is one such. +#include +#include // for GetSystemTimeAsFileTime + +#include + +#include + +/* ------------------------------------------------------------------------- */ + +static bool lTimeAdjustmentInited = false; +static std::mutex lTimeAdjustmentInitedMutex; +static ULONGLONG lInitialMsSinceSystemStart = 0ull; +static struct PST_TIMESPEC lInitialTimespec = {0}; + +// rets 0 on success, -1 with errno on fail +static int initInitialMonoValsIfNotInited() +{ + if (lTimeAdjustmentInited) + return(0); + + std::lock_guard lock(lTimeAdjustmentInitedMutex); + if (lTimeAdjustmentInited) + return(0); + + __int64 wintime = 0; + GetSystemTimeAsFileTime(reinterpret_cast(&wintime)); + // GetSystemTimeAsFileTime retrieves the current system date and time. The + // information is in Coordinated Universal Time (UTC) + // format. GetSystemTimeAsFileTime is void / cannot fail. + + wintime -=116444736000000000ll; //1jan1601 to 1jan1970 + lInitialTimespec.tv_sec = static_cast(wintime / 10000000ll); //secs + lInitialTimespec.tv_nsec = static_cast(wintime % 10000000ll *100); + + lInitialMsSinceSystemStart = GetTickCount64(); + if ((lInitialMsSinceSystemStart == 0) || + ((static_cast(lInitialMsSinceSystemStart)) < 0)) + { + errno = EFAULT; + return(-1); + } + + lTimeAdjustmentInited = true; + + return 0; +} + + +/* ------------------------------------------------------------------------- */ + +extern "C" int PST_CLOCK_GETTIME(PST_CLOCK_ID_T clockid, + struct PST_TIMESPEC *spec) +{ + if (!spec) + { + errno = EFAULT; + return(-1); + } + memset(spec, 0, sizeof(*spec)); + + switch(clockid) + { + case PST_CLOCK_MONOTONIC: + case PST_CLOCK_MONOTONIC_RAW: + case PST_CLOCK_MONOTONIC_COARSE: + { + int init_res = initInitialMonoValsIfNotInited(); + if (init_res != 0) + return(init_res); + + ULONGLONG ms_since_mono_vals_inited = GetTickCount64(); + if (ms_since_mono_vals_inited < lInitialMsSinceSystemStart) + { // time went backwards? + errno = EFAULT; + return(-1); + } + ms_since_mono_vals_inited -= lInitialMsSinceSystemStart; + + ULONGLONG whole_s_since_mono_vals_inited = + (ms_since_mono_vals_inited/1000); + ULONGLONG remainder_ms_since_mono_vals_inited = + (ms_since_mono_vals_inited%1000); + + spec->tv_sec= (long) + (lInitialTimespec.tv_sec + whole_s_since_mono_vals_inited); + + long new_tv_nsec = static_cast( + (1000000l * remainder_ms_since_mono_vals_inited) + + lInitialTimespec.tv_nsec); + if (new_tv_nsec >= 1000000000l) + { + new_tv_nsec -= 1000000000l; + ++spec->tv_sec; + } + spec->tv_nsec = new_tv_nsec; + + break; + } + + case PST_CLOCK_PROCESS_CPUTIME_ID: + { + __int64 win_creation_time = 0; + __int64 win_exit_time = 0; + __int64 win_kernel_time = 0; + __int64 win_user_time = 0; + + BOOL res_gpt = GetProcessTimes(GetCurrentProcess(), + (FILETIME*)&win_creation_time, + (FILETIME*)&win_exit_time, + (FILETIME*)&win_kernel_time, + (FILETIME*)&win_user_time); + if (!res_gpt) + { // Possibly because of access rights + // The process handle must have PROCESS_QUERY_INFORMATION or + // PROCESS_QUERY_LIMITED_INFORMATION + + errno = ENOTSUP; + return(-1); + } + + __int64 wintime = win_kernel_time + win_user_time; + + wintime -=116444736000000000ll; //1jan1601 to 1jan1970 + spec->tv_sec =(long)(wintime / 10000000ll); //seconds + spec->tv_nsec =static_cast(wintime % 10000000ll *100); + + break; + // Alternatively, we could use std::clock(), but GetProcessTimes seems + // to ensure better precision + } + + case PST_CLOCK_THREAD_CPUTIME_ID: + { + __int64 win_creation_time = 0; + __int64 win_exit_time = 0; + __int64 win_kernel_time = 0; + __int64 win_user_time = 0; + + BOOL res_gtt = GetThreadTimes(GetCurrentThread(), + (FILETIME*)&win_creation_time, + (FILETIME*)&win_exit_time, + (FILETIME*)&win_kernel_time, + (FILETIME*)&win_user_time); + if (!res_gtt) + { // Possibly because of access rights + // The thread handle must have THREAD_QUERY_INFORMATION or + // THREAD_QUERY_LIMITED_INFORMATION + + errno = ENOTSUP; + return(-1); + } + + __int64 wintime = win_kernel_time + win_user_time; + + wintime -=116444736000000000ll; //1jan1601 to 1jan1970 + spec->tv_sec =(long)(wintime / 10000000ll); //seconds + spec->tv_nsec = static_cast(wintime % 10000000ll *100); + + break; + } + + case PST_CLOCK_REALTIME: + case PST_CLOCK_REALTIME_COARSE: + { + __int64 wintime = 0; + GetSystemTimeAsFileTime((FILETIME*)&wintime); + // GetSystemTimeAsFileTime retrieves the current system date and + // time. The information is in Coordinated Universal Time (UTC) + // format. GetSystemTimeAsFileTime is void. + + // There are other Get...Time... functions as well, see: + // https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ + + wintime -=116444736000000000ll; //1jan1601 to 1jan1970 + spec->tv_sec =(long)(wintime / 10000000ll); //seconds + spec->tv_nsec = static_cast(wintime % 10000000ll *100); + + break; + } + + default: + PS_LOG_WARNING("Unimplemented clockid"); + PS_LOGDBG_STACK_TRACE; + + errno = ENOTSUP; + return(-1); + } + + return(0); +} + +/* ------------------------------------------------------------------------- */ + +extern "C" struct tm *PST_GMTIME_R(const time_t *timep, struct tm *result) +{ + // See for instance https://en.cppreference.com/w/c/chrono/gmtime + errno_t res = gmtime_s(result, timep); + if (res == 0) + return(result); + + errno = res; + return(nullptr); +} + + +/* ------------------------------------------------------------------------- */ + +extern "C" char *PST_ASCTIME_R(const struct tm *tm, char *buf) +{ + errno_t res = asctime_s(buf, 26/*per Linux asctime_r man page*/, tm); + if (res == 0) + return(buf); + + errno = EOVERFLOW; + return(nullptr); +} + +/* ------------------------------------------------------------------------- */ + +extern "C" struct tm *PST_LOCALTIME_R(const time_t *timep, struct tm *result) +{ + if (!result) + { + errno = EINVAL; + return(nullptr); + } + + memset(result, 0, sizeof(*result)); + + errno_t res = localtime_s(result, timep); + if (res == 0) + return(result); // success + + errno = res; + return(nullptr); +} + +/* ------------------------------------------------------------------------- */ + +#endif // of ifdef _IS_WINDOWS diff --git a/src/common/pist_fcntl.cc b/src/common/pist_fcntl.cc new file mode 100644 index 000000000..d093ce95e --- /dev/null +++ b/src/common/pist_fcntl.cc @@ -0,0 +1,155 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Defines a pist_fcntl for Windows + +#include + +/* ------------------------------------------------------------------------- */ + +#ifdef _IS_WINDOWS + +#include +#include + +#include +#include // for stack trace + +/* ------------------------------------------------------------------------- */ + +/* +In Linux, only the CLOEXEC flag is supported for GET/SETFD +"In Linux, if the FD_CLOEXEC bit is set, the file descriptor will +automatically be closed during a successful execve(2). execve causes the +program that is currently being run by the calling process to be replaced +with a new program." +However, Windows has no execv/execve. It does have an _execv, but that just +calls CreateProcess, it doesn't replace the parent program. +So - FD_CLOEXEC is moot in Windows, and hence F_GETFD/SETFD is moot too +*/ + +static int fcntl_getfd([[maybe_unused]] em_socket_t fd) +{ + PS_TIMEDBG_START_ARGS("noop function, fd %d", fd); + + // Return (as the function result) the file descriptor flags + return(0); +} + +static int fcntl_setfd([[maybe_unused]] em_socket_t fd, int arg) +{ + PS_TIMEDBG_START_ARGS("fd %d, arg %d", fd, arg); + + if ((arg != 0) && (arg != PST_FD_CLOEXEC)) + { + PS_LOG_WARNING_ARGS("Unsupported fcntl F_SETFD arg %d", arg); + PS_LOGDBG_STACK_TRACE; + errno = EINVAL; + return(-1); + } + + return(0); // success +} + +/* ------------------------------------------------------------------------- */ + +static int fcntl_getfl([[maybe_unused]] em_socket_t fd) +{ + PS_TIMEDBG_START_ARGS("noop function, returns UNKNOWN, fd %d", fd); + + // Return (as the function result) the file access mode and the file status + // flags + return(PST_FCNTL_GETFL_UNKNOWN); +} + +static int fcntl_setfl(em_socket_t fd, int arg) +{ + PS_TIMEDBG_START_ARGS("fd %d, arg %d", fd, arg); + + if ((arg != 0) && (arg != PST_O_NONBLOCK)) + { + PS_LOG_WARNING_ARGS("Unsupported fcntl F_SETFL arg %d", arg); + PS_LOGDBG_STACK_TRACE; + errno = EINVAL; + return(-1); + } + + u_long opt = (arg == 0) ? 0 : 1; + int ioc_res = ioctlsocket(fd, FIONBIO, &opt); + if (ioc_res == 0) + return(0); // success + + // Re: FIONBIO + // https://learn.microsoft.com/en-us/windows/win32/winsock/winsock-ioctls + + if (ioc_res != SOCKET_ERROR) + { + PS_LOG_WARNING_ARGS("Unexpected ioc_res %d", ioc_res); + } + else + { + int last_err = WSAGetLastError(); + PS_LOG_INFO_ARGS("ioctlsocket FIONBIO failed, ioc_res = SOCKET_ERROR, " + "WSAGetLastError %d", last_err); + } + PS_LOGDBG_STACK_TRACE; + + errno = EINVAL; + return(-1); +} + +/* ------------------------------------------------------------------------- */ + +extern "C" int PST_FCNTL(em_socket_t fd, int cmd, ... /* arg */ ) +{ + int res = 0; + + va_list ptr; + va_start(ptr, cmd); + + switch(cmd) + { + case PST_F_GETFD: + res = fcntl_getfd(fd); + break; + + case PST_F_SETFD: + { + int arg_val = va_arg(ptr, int); + res = fcntl_setfd(fd, arg_val); + break; + } + + case PST_F_GETFL: + res = fcntl_getfl(fd); + break; + + case PST_F_SETFL: + { + int arg_val = va_arg(ptr, int); + res = fcntl_setfl(fd, arg_val); + break; + } + + default: + PS_LOG_WARNING_ARGS("Unsupported fcntl cmd %d", cmd); + PS_LOGDBG_STACK_TRACE; + + errno = EINVAL; + // Per Linux manpage, one meaning of EINVAL in fcntl is "The value + // specified in cmd is not recognized by this kernel." + res = -1; + } + + va_end(ptr); + return(res); +} + + + +/* ------------------------------------------------------------------------- */ + +#endif // of ifdef _IS_WINDOWS diff --git a/src/common/pist_filefns.cc b/src/common/pist_filefns.cc new file mode 100644 index 000000000..c3d9173fa --- /dev/null +++ b/src/common/pist_filefns.cc @@ -0,0 +1,145 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Defines certain file functions (operations on an 'int' file descriptor) that +// exist in macOS/Linux/BSD but which need to be defined in Windows +// +#include + +/* ------------------------------------------------------------------------- */ + +#ifdef _IS_WINDOWS + +#include // required by fileapi.h +#include // ReadFile +#include // for OVERLAPPED structure +#include // for HasOverlappedIoCompleted macro +#include // _get_osfhandle, _lseeki64 +#include // for _SH_DENYNO used by _sopen_s +#include // for _O_CREAT etc. + +#include + +/* ------------------------------------------------------------------------- */ + +extern "C" PST_SSIZE_T pist_pread(int fd, void *buf, + size_t count, off_t offset) +{ + OVERLAPPED overlapped; + memset(&overlapped, 0, sizeof(overlapped)); + + if (offset) + { + overlapped.Offset = (DWORD)(offset & 0xFFFFFFFF); + overlapped.OffsetHigh = (DWORD)(offset / 0x100000000ull); + } + + HANDLE fd_handle = (HANDLE) _get_osfhandle(fd); + if (fd_handle == INVALID_HANDLE_VALUE) + { + PS_LOG_INFO_ARGS("Invalid file descriptor %d", fd); + // _get_osfhandle will already have set errno = EBADF + return(-1); + } + + DWORD num_bytes_read = 0; + BOOL res = ReadFile(fd_handle, buf, (DWORD)count, + &num_bytes_read, // but may be wrong if async + &overlapped); + + if (!res) + { + int last_err = GetLastError(); + if (last_err == ERROR_IO_PENDING) + { + HasOverlappedIoCompleted(&overlapped); + + num_bytes_read = 0; // reset it in case ReadFile set it + BOOL res_overlapped_result = + GetOverlappedResult(fd_handle, &overlapped, + &num_bytes_read, + TRUE/* wait for completion */); + + if (res_overlapped_result) + { + res = TRUE; // success after IO completed + } + else + { + last_err = GetLastError(); + if (last_err != ERROR_HANDLE_EOF) + PS_LOG_INFO_ARGS("ReadFile GetOverlappedResult Windows " + "System Error Code (WinError.h) 0x%x", + (unsigned int) last_err); + } + } + else if (last_err != ERROR_HANDLE_EOF) + { + PS_LOG_INFO_ARGS("ReadFile Windows System Error Code " + "(WinError.h) 0x%x", (unsigned int) last_err); + } + + if (last_err == ERROR_HANDLE_EOF) + { + PS_LOG_DEBUG("EOF"); + res = TRUE; // successfully reached end of file + num_bytes_read = 0; // pread rets 0 to indicate EOF + } + } + + if (!res) + { + PS_LOG_DEBUG("Returning failure"); + errno = EIO; + return(-1); + } + + return(num_bytes_read); +} + +/* ------------------------------------------------------------------------- */ + +int pist_open(const char *pathname, int flags) +{ + // Regarding mode, Linux man page states: + // + // If neither O_CREAT nor O_TMPFILE is specified in flags, then mode is + // ignored (and can thus be specified as 0, or simply omitted). The mode + // argument must be supplied if O_CREAT or O_TMPFILE is specified in flags + + if (flags & (_O_CREAT | _O_TEMPORARY | _O_SHORT_LIVED)) + { + PS_LOG_DEBUG("Flags invalid without mode"); + errno = EINVAL; + return(-1); + } + + return(pist_open(pathname, flags, 0 /* mode */)); +} + +int pist_open(const char *pathname, int flags, PST_FILE_MODE_T mode) +{ + int fh = -1; + + errno_t sopen_res = _sopen_s(&fh, pathname, flags, _SH_DENYNO, mode); + // Re: _SH_DENYNO. + // The Linux man page for "open" says: "Each open() of a file creates a new + // open file description; thus, there may be multiple open file + // descriptions corresponding to a file inode." + // I think this means that the same process, or multiple processes, can + // freely open the same file. So we don't block that (aka "we don't lock + // the file"), which is why we use _SH_DENYNO for the sharing flag. + + if (sopen_res == 0) + return(fh); // Success + + return(-1); // errno already set by _sopen_s +} + + +/* ------------------------------------------------------------------------- */ + +#endif // of ifdef _IS_WINDOWS diff --git a/src/common/pist_ifaddrs.cc b/src/common/pist_ifaddrs.cc new file mode 100644 index 000000000..bf7dd2bbd --- /dev/null +++ b/src/common/pist_ifaddrs.cc @@ -0,0 +1,318 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Defines "struct pist_ifaddrs", and the functions pist_getifaddrs and +// pist_freeifaddrs for Windows +// + +/* ------------------------------------------------------------------------- */ + +#include + +#ifdef _IS_WINDOWS + +#include + +#include +#include +#include + +#include +#include // Required for netioapi.h +#include // for ConvertLengthToIpv4Mask +#include // For IP_ADAPTER_ADDRESSES + +/* ------------------------------------------------------------------------- */ + +extern "C" int PST_GETIFADDRS(struct PST_IFADDRS **ifap) +{ + if (!ifap) + { + PS_LOG_DEBUG("No ifap"); + errno = EINVAL; + return(-1); + } + *ifap = nullptr; + + ULONG buff_len = sizeof(IP_ADAPTER_ADDRESSES); + std::vector buff_try1_vec(buff_len+16); + PIP_ADAPTER_ADDRESSES fst_adap_addr = + (PIP_ADAPTER_ADDRESSES)(buff_try1_vec.data()); + + // First try for GetAdaptersAddresses, allowing space for just one address + // Probably that's not enough, but it will tell us how much buffer space we + // need (buff_len is written to, telling us) + ULONG gaa_res1 = GetAdaptersAddresses( + AF_UNSPEC, // IP4 and IP6 + GAA_FLAG_INCLUDE_ALL_INTERFACES | GAA_FLAG_INCLUDE_TUNNEL_BINDINGORDER, + nullptr, // reserved + fst_adap_addr, + &buff_len); + ULONG gaa_res = gaa_res1; + + std::vector buff_try2_vec(buff_len+16); + + if (gaa_res == ERROR_BUFFER_OVERFLOW) + { + fst_adap_addr = (PIP_ADAPTER_ADDRESSES)(buff_try2_vec.data()); + + gaa_res = GetAdaptersAddresses( + AF_UNSPEC, // IP4 and IP6 + GAA_FLAG_INCLUDE_ALL_INTERFACES | + GAA_FLAG_INCLUDE_TUNNEL_BINDINGORDER, + nullptr, // reserved + fst_adap_addr, + &buff_len); + } + + if (gaa_res != ERROR_SUCCESS) + { + PS_LOG_INFO_ARGS("GetAdaptersAddresses failed, gaa_res %d", gaa_res); + + int res = -1; + + switch(gaa_res) + { + case ERROR_BUFFER_OVERFLOW: + errno = EOVERFLOW; + break; + + case ERROR_NOT_ENOUGH_MEMORY: + errno = ENOMEM; + break; + + case ERROR_INVALID_PARAMETER: + errno = EINVAL; + break; + + case ERROR_ADDRESS_NOT_ASSOCIATED: + case ERROR_NO_DATA: + res = 0; + PS_LOG_DEBUG("No addresses found, ret success with empty list"); + break; + + default: + PS_LOG_DEBUG("Unexpected error for GetAdaptersAddresses"); + errno = EINVAL; + break; + } + + return(res); + } + + if (buff_len == 0) + { + PS_LOG_DEBUG("No addresses found, ret success with empty list"); + return(0); + } + + unsigned int num_adap_addr = 0; + for (PIP_ADAPTER_ADDRESSES adap_addr = fst_adap_addr; adap_addr; + adap_addr = adap_addr->Next) + { + PIP_ADAPTER_UNICAST_ADDRESS unicast_addr = + adap_addr->FirstUnicastAddress; + while(unicast_addr) + { + ++num_adap_addr; + unicast_addr = unicast_addr->Next; + }; + } + + if (!num_adap_addr) + { + PS_LOG_DEBUG( + "No unicast addresses found, ret success with empty list"); + return(0); + } + + + struct PST_IFADDRS * pst_ifaddrs = new PST_IFADDRS[num_adap_addr+1]; + if (!pst_ifaddrs) + { + PS_LOG_WARNING("new failed"); + errno = ENOMEM; + return(-1); + } + + #define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x)) + #define FREE(x) HeapFree(GetProcessHeap(), 0, (x)) + + unsigned int i = 0; + for (PIP_ADAPTER_ADDRESSES adap_addr = fst_adap_addr; adap_addr; + adap_addr = adap_addr->Next) + { + PIP_ADAPTER_UNICAST_ADDRESS unicast_addr = + adap_addr->FirstUnicastAddress; + if (!unicast_addr) + continue; + + /* + SIOCGIFFLAGS + Get or set the active flag word of the device. ifr_flags + contains a bit mask of the following values: + Device flags + IFF_UP Interface is running. + IFF_BROADCAST Valid broadcast address set. + IFF_DEBUG Internal debugging flag. + IFF_LOOPBACK Interface is a loopback interface. + IFF_POINTOPOINT Interface is a point-to-point link. + IFF_RUNNING Resources allocated. + IFF_NOARP No arp protocol, L2 destination address not + set. + IFF_PROMISC Interface is in promiscuous mode. + IFF_NOTRAILERS Avoid use of trailers. + IFF_ALLMULTI Receive all multicast packets. + IFF_MASTER Master of a load balancing bundle. + IFF_SLAVE Slave of a load balancing bundle. + IFF_MULTICAST Supports multicast + IFF_PORTSEL Is able to select media type via ifmap. + IFF_AUTOMEDIA Auto media selection active. + IFF_DYNAMIC The addresses are lost when the interface + goes down. + IFF_LOWER_UP Driver signals L1 up (since Linux 2.6.17) + IFF_DORMANT Driver signals dormant (since Linux 2.6.17) + IFF_ECHO Echo sent packets (since Linux 2.6.25) + */ + unsigned int adap_flags = 0; + adap_flags |= ((adap_addr->OperStatus == IfOperStatusUp) ? + PST_IFF_UP : 0); + adap_flags |= ((adap_addr->FirstMulticastAddress) ? + PST_IFF_BROADCAST : 0); + adap_flags |= ((adap_addr->IfType == IF_TYPE_SOFTWARE_LOOPBACK) ? + PST_IFF_LOOPBACK:0); + + // "Point-to-point link" usually maeans two machines linked with a + // single wire and no other devices on the wire. GetAdaptersAddresses + // doesn't seem to distinguish that from unicast + + adap_flags |= ((adap_addr->OperStatus == IfOperStatusUp) ? + PST_IFF_RUNNING : 0); + + adap_flags |= ((adap_addr->Flags & IP_ADAPTER_NO_MULTICAST) ? + 0 : PST_IFF_MULTICAST); + + adap_flags |= + ((adap_addr->ConnectionType == NET_IF_CONNECTION_DEDICATED) ? + PST_IFF_AUTOMEDIA : 0); + + // IFF_NOARP, IFF_PROMISC, IFF_NOTRAILERS, IFF_ALLMULTI, IFF_MASTER, + // IFF_SLAVE, IFF_PORTSEL, IFF_DYNAMIC, IFF_LOWER_UP, IFF_DORMANT and + // IFF_ECHO don't seem supported in GetAdaptersAddresses + + // We already checked that first unicast_addr is non null + do { + const LPSOCKADDR win_sock_addr = unicast_addr->Address.lpSockaddr; + int win_sock_addr_len = unicast_addr->Address.iSockaddrLength; + if ((!win_sock_addr) || (!win_sock_addr_len)) + continue; + + PST_IFADDRS & this_ifaddrs(pst_ifaddrs[i]); + memset(&this_ifaddrs, 0, sizeof(this_ifaddrs)); + + if (adap_addr->AdapterName) + { + size_t name_len = strlen(adap_addr->AdapterName); + this_ifaddrs.ifa_name = reinterpret_cast(MALLOC(name_len+1)); + if (!this_ifaddrs.ifa_name) + { + PS_LOG_WARNING("Name MALLOC failed"); + delete[] pst_ifaddrs; + errno = ENOMEM; + return(-1); + } + + PS_STRLCPY(this_ifaddrs.ifa_name, + adap_addr->AdapterName, name_len+1); + } + + this_ifaddrs.ifa_flags = adap_flags; + + LPSOCKADDR sock_addr = reinterpret_cast(MALLOC(sizeof(SOCKADDR))); + if (!sock_addr) + { + PS_LOG_WARNING("MALLOC failed"); + delete[] pst_ifaddrs; + errno = ENOMEM; + return(-1); + } + + *sock_addr = *win_sock_addr; + this_ifaddrs.ifa_addr = sock_addr; + + if (unicast_addr->OnLinkPrefixLength) + { + if (win_sock_addr->sa_family == AF_INET) + { // IPv4 + ULONG mask = 0; + if ((ConvertLengthToIpv4Mask( + unicast_addr->OnLinkPrefixLength, + &mask) == NO_ERROR) && (mask != 0)) + { + LPSOCKADDR mask_sock_addr = (LPSOCKADDR) + MALLOC(sizeof(SOCKADDR)); + if (!mask_sock_addr) + { + PS_LOG_WARNING("MALLOC failed"); + delete[] pst_ifaddrs; + errno = ENOMEM; + return(-1); + } + memset(mask_sock_addr, 0, sizeof(*mask_sock_addr)); + mask_sock_addr->sa_family = + win_sock_addr->sa_family; + ((sockaddr_in *)mask_sock_addr)-> + sin_addr.S_un.S_addr = mask; + + this_ifaddrs.ifa_netmask = mask_sock_addr; + } + } + } + + ++i; + if (i >= num_adap_addr) + break; + + this_ifaddrs.ifa_next = &(pst_ifaddrs[i]); + + unicast_addr = unicast_addr->Next; + } while(unicast_addr); + } + + *ifap = pst_ifaddrs; + return(0); +} + + +/* ------------------------------------------------------------------------- */ + +extern "C" void PST_FREEIFADDRS(struct PST_IFADDRS *ifa) +{ + if (!ifa) + { + PS_LOG_DEBUG("ifa is NULL"); + return; + } + + for (PST_IFADDRS * this_ifaddrs = ifa; this_ifaddrs; + this_ifaddrs = this_ifaddrs->ifa_next) + { + if (this_ifaddrs->ifa_name) + FREE(this_ifaddrs->ifa_name); + + if (this_ifaddrs->ifa_addr) + FREE(this_ifaddrs->ifa_addr); + + if (this_ifaddrs->ifa_netmask) + FREE(this_ifaddrs->ifa_netmask); + } + + delete ifa; +} + +/* ------------------------------------------------------------------------- */ + +#endif // of ifdef _IS_WINDOWS diff --git a/src/common/pist_resource.cc b/src/common/pist_resource.cc new file mode 100644 index 000000000..0f7f85249 --- /dev/null +++ b/src/common/pist_resource.cc @@ -0,0 +1,75 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Defines a getrusage in Windows + + +/* ------------------------------------------------------------------------- */ + +#include + +#ifdef _IS_WINDOWS + +#include + +#include + +#include // for FILETIME +#include // for ULARGE_INTEGER +#include // memset +#include // GetProcessTimes + +/* ------------------------------------------------------------------------- */ + +// Reference: https://github.com/postgres/postgres/blob/7559d8ebfa11d98728e816f6b655582ce41150f3/src/port/getrusage.c + +extern "C" int pist_getrusage(int who, struct PST_RUSAGE * rusage) +{ + FILETIME starttime; + FILETIME exittime; + FILETIME kerneltime; + FILETIME usertime; + ULARGE_INTEGER li; + + if (who != PST_RUSAGE_SELF) + { + /* Only RUSAGE_SELF is supported in this implementation for now */ + errno = EINVAL; + return -1; + } + + if (!rusage) + { + errno = EFAULT; + return -1; + } + std::memset(rusage, 0, sizeof(struct PST_RUSAGE)); + if (GetProcessTimes(GetCurrentProcess(), + &starttime, &exittime, &kerneltime, &usertime) == 0) + { + errno = EINVAL; + return -1; + } + + /* Convert FILETIMEs (0.1 us) to struct timeval */ + std::memcpy(&li, &kerneltime, sizeof(FILETIME)); + li.QuadPart /= 10L; /* Convert to microseconds */ + rusage->ru_stime.tv_sec = static_cast(li.QuadPart / 1000000L); + rusage->ru_stime.tv_usec = static_cast(li.QuadPart % 1000000L); + + std::memcpy(&li, &usertime, sizeof(FILETIME)); + li.QuadPart /= 10L; /* Convert to microseconds */ + rusage->ru_utime.tv_sec = static_cast(li.QuadPart / 1000000L); + rusage->ru_utime.tv_usec = static_cast(li.QuadPart % 1000000L); + + return(0); // success +} + + + +/* ------------------------------------------------------------------------- */ + +#endif // of ifdef _IS_WINDOWS diff --git a/src/common/pist_sockfns.cc b/src/common/pist_sockfns.cc new file mode 100644 index 000000000..358296859 --- /dev/null +++ b/src/common/pist_sockfns.cc @@ -0,0 +1,634 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + + +// Defines certain socket functions (operations on an em_socket_t) that we +// implement using the corresponding winsock2 methods. + +#include + +/* ------------------------------------------------------------------------- */ + +#ifdef _IS_WINDOWS + +#include +#include +#include +#include +#include + +#include + +#include +#include + +/* ------------------------------------------------------------------------- */ + +inline SOCKET get_win_socket_from_em_socket_t(em_socket_t ems) +{ return((ems < 0) ? INVALID_SOCKET : (static_cast(ems))); } +// Note: SOCKET is some kind of unsigned integer + +/* ------------------------------------------------------------------------- */ + +// Calls WSAGetLastError, sets errno and then returns -1 always +static int WSAGetLastErrorSetErrno() +{ + int wsa_last_err = WSAGetLastError(); + + switch(wsa_last_err) + { + case WSANOTINITIALISED: + PS_LOG_DEBUG("WSANOTINITIALISED"); + errno = EINVAL; + break; + + case WSAENETDOWN: + PS_LOG_DEBUG("WSAENETDOWN"); + errno = ENETDOWN; + break; + + case WSAENOTSOCK: + PS_LOG_DEBUG("WSAENOTSOCK"); + errno = ENOTSOCK; + break; + + case WSAEINPROGRESS: + PS_LOG_DEBUG("WSAEINPROGRESS"); + errno = EINPROGRESS; + break; + + case WSAEALREADY: + PS_LOG_DEBUG("WSAEALREADY"); + errno = EALREADY; + break; + + case WSAENETUNREACH: + PS_LOG_DEBUG("WSAENETUNREACH"); + errno = ENETUNREACH; + break; + + case WSAEINTR: + PS_LOG_DEBUG("WSAEINTR"); + errno = EINTR; + break; + + case WSAECONNREFUSED: + PS_LOG_DEBUG("WSAECONNREFUSED"); + errno = ECONNREFUSED; + break; + + case WSAEISCONN: + PS_LOG_DEBUG("WSAEISCONN"); + errno = EISCONN; + break; + + case WSAEWOULDBLOCK: + PS_LOG_DEBUG("WSAEWOULDBLOCK"); + errno = EWOULDBLOCK; + break; + + case WSAEFAULT: + PS_LOG_DEBUG("WSAEFAULT"); + errno = EFAULT; + break; + + case WSAENOTCONN: + PS_LOG_DEBUG("WSAENOTCONN"); + errno = ENOTCONN; + break; + + case WSAENETRESET: + PS_LOG_DEBUG("WSAENETRESET"); + errno = ENETRESET; + break; + + case WSAESHUTDOWN: + PS_LOG_DEBUG("WSAESHUTDOWN"); + errno = ECONNABORTED; // ESHUTDOWN not defined in Windows' errno.h + break; + + case WSAEMSGSIZE: + PS_LOG_DEBUG("WSAEMSGSIZE"); + errno = EMSGSIZE; + break; + + case WSAEINVAL: + PS_LOG_DEBUG("WSAEINVAL"); + errno = EINVAL; + break; + + case WSAECONNABORTED: + PS_LOG_DEBUG("WSAECONNABORTED"); + errno = ECONNABORTED; + break; + + case WSAETIMEDOUT: + PS_LOG_DEBUG("WSAETIMEDOUT"); + errno = ETIMEDOUT; + break; + + case WSAECONNRESET: + PS_LOG_DEBUG("WSAECONNRESET"); + errno = ECONNRESET; + break; + + case WSAEACCES: + PS_LOG_DEBUG("WSAEACCES"); + errno = EACCES; + break; + + case WSAENOBUFS: + PS_LOG_DEBUG("WSAENOBUFS"); + errno = ENOBUFS; + break; + + case WSAEHOSTUNREACH: + PS_LOG_DEBUG("WSAEHOSTUNREACH"); + errno = EHOSTUNREACH; + break; + + case WSAEAFNOSUPPORT: + PS_LOG_DEBUG("WSAEAFNOSUPPORT"); + errno = EAFNOSUPPORT; + break; + + case WSAEMFILE: + PS_LOG_DEBUG("WSAEMFILE"); + errno = EMFILE; + break; + + case WSAEPROTONOSUPPORT: + PS_LOG_DEBUG("WSAEPROTONOSUPPORT"); + errno = EPROTONOSUPPORT; + break; + + case WSAEPROTOTYPE: + PS_LOG_DEBUG("WSAEPROTOTYPE"); + errno = EPROTOTYPE; + break; + + case WSAEPROVIDERFAILEDINIT: + PS_LOG_DEBUG("WSAEPROVIDERFAILEDINIT"); + errno = EIO; + break; + + case WSAESOCKTNOSUPPORT: + PS_LOG_DEBUG("WSAESOCKTNOSUPPORT"); + errno = EPROTONOSUPPORT; // No ESOCKTNOSUPPORT in Windows' errno.h + break; + + case WSAEADDRINUSE: + PS_LOG_DEBUG("WSAEADDRINUSE"); + errno = EADDRINUSE; + break; + + case WSAEADDRNOTAVAIL: + PS_LOG_DEBUG("WSAEADDRNOTAVAIL"); + errno = EADDRNOTAVAIL; + break; + + case WSAEOPNOTSUPP: + PS_LOG_DEBUG("WSAEOPNOTSUPP"); + errno = EOPNOTSUPP; + break; + + default: + PS_LOG_DEBUG_ARGS("Unexpected WSA error %d:", wsa_last_err); + errno = EIO; + break; + } + + return(-1); +} + +/* ------------------------------------------------------------------------- */ + +std::atomic_bool lWsaStartupDone = false; +std::mutex lWsaStartupDoneMutex; + +// WsaStartupAndCleanup exists solely so it's destructor can be called on +// program shutdown, allowing us to free winsock resources +class WsaStartupAndCleanup +{ +public: + WsaStartupAndCleanup() : inited(true) {}; + ~WsaStartupAndCleanup(); + +private: + bool inited; +}; +static WsaStartupAndCleanup lWsaStartupAndCleanup; + +// pist_sock_startup_check must be called before any winsock2 function. It can +// be called as many times as you like, it does nothing after the first time it +// is called, and it is threadsafe. All the pist_sock_xxx functions in this +// file call it themselves, so you don't need to call pist_sock_startup_check +// before calling pist_sock_socket for instance. However, if code outside of +// this file is calling winsock functions, that code must call +// pist_sock_startup_check, using the macro provided in winornix.h. +// +// Returns 0 on success, or -1 on failure with errno set. +#define PIST_SOCK_STARTUP_CHECK_RET_MINUS_1_ON_ERR \ + if (pist_sock_startup_check() < 0) \ + return(-1); \ + +int pist_sock_startup_check() +{ + if (lWsaStartupDone) + return(0); + + GUARD_AND_DBG_LOG(lWsaStartupDoneMutex); + if (lWsaStartupDone) + return(0); + + const WORD version_required = (2 * 0x100) + 2; // version 2.2 + WSADATA wsadata; + memset(&wsadata, 0, sizeof(wsadata)); + + int wsastartup_res = WSAStartup(version_required, &wsadata); + if (wsastartup_res == 0) + { + lWsaStartupDone = true; + return(0); // success + } + + switch(wsastartup_res) + { + case WSASYSNOTREADY: + PS_LOG_DEBUG("WSAStartup WSASYSNOTREADY"); + errno = ENETUNREACH; + break; + + case WSAVERNOTSUPPORTED: + PS_LOG_DEBUG("WSAStartup WSAVERNOTSUPPORTED"); + errno = EOPNOTSUPP; + break; + + case WSAEINPROGRESS: + PS_LOG_DEBUG("WSAStartup WSAEINPROGRESS"); + errno = EINPROGRESS; + break; + + case WSAEPROCLIM: + PS_LOG_DEBUG("WSAStartup WSAEPROCLIM"); + errno = EMFILE; // too many files + break; + + case WSAEFAULT: + PS_LOG_DEBUG("WSAStartup WSAEFAULT"); + errno = EFAULT; + break; + + default: + PS_LOG_DEBUG_ARGS("Unexpected WSAStartup error %d:", wsastartup_res); + errno = EIO; + break; + } + + return(-1); +} + +WsaStartupAndCleanup::~WsaStartupAndCleanup() // DO NOT LOG +{ + // Since this is the destructor of what is in fact a static instance, we + // are careful not log here (we just use std::cout instead); the logging + // object may already have been destroyed before this destructor is called + + if (!lWsaStartupDone) + return; + + std::lock_guard guard(lWsaStartupDoneMutex); + if (!lWsaStartupDone) + return; + + int wsa_cleanupres = WSACleanup(); + + if (wsa_cleanupres == 0) + { + #ifdef DEBUG + std::cout << "WsaStartupAndCleanup: WSACleanup success" << std::endl; + #endif + lWsaStartupDone = false; + return; + } + + #ifdef DEBUG + std::cout << "WsaStartupAndCleanup: WSACleanup fail, ret " << + wsa_cleanupres << std::endl; + #endif +} + +/* ------------------------------------------------------------------------- */ + +// Returns 0 for success. On fail, rets -1, and errno is set +int pist_sock_getsockname(em_socket_t em_sock, + struct sockaddr *addr, PST_SOCKLEN_T *addrlen) +{ + PIST_SOCK_STARTUP_CHECK_RET_MINUS_1_ON_ERR; + + SOCKET win_sock = get_win_socket_from_em_socket_t(em_sock); + if (win_sock == INVALID_SOCKET) + { + PS_LOG_DEBUG("Invalid Socket"); + errno = EBADF; + return(-1); + } + + int getsockname_res = ::getsockname(win_sock, addr, addrlen); + if (getsockname_res == 0) + return(0); // success + + return(WSAGetLastErrorSetErrno()); +} + +/* ------------------------------------------------------------------------- */ + +// pist_sock_xxx fns ret 0 for success. On fail, ret -1, and errno is set + +int pist_sock_close(em_socket_t em_sock) +{ + PIST_SOCK_STARTUP_CHECK_RET_MINUS_1_ON_ERR; + + SOCKET win_sock = get_win_socket_from_em_socket_t(em_sock); + if (win_sock == INVALID_SOCKET) + { + PS_LOG_DEBUG("Invalid Socket"); + errno = EBADF; + return(-1); + } + + int closesocket_res = ::closesocket(win_sock); + if (closesocket_res == 0) + return(0); // success + + return(WSAGetLastErrorSetErrno()); +} + +/* ------------------------------------------------------------------------- */ +// On success, returns number of bytes read (zero meaning the connection has +// gracefully closed). On failure, -1 is returned and errno is set + +PST_SSIZE_T pist_sock_read(em_socket_t em_sock, void *buf, size_t count) +{ + PIST_SOCK_STARTUP_CHECK_RET_MINUS_1_ON_ERR; + + SOCKET win_sock = get_win_socket_from_em_socket_t(em_sock); + if (win_sock == INVALID_SOCKET) + { + PS_LOG_DEBUG("Invalid Socket"); + errno = EBADF; + return(-1); + } + + int recv_res = ::recv(win_sock, reinterpret_cast(buf), + static_cast(count), 0 /* flags*/); + if (recv_res != SOCKET_ERROR) + return(recv_res); // success - return bytes read + + return(WSAGetLastErrorSetErrno()); +} + +/* ------------------------------------------------------------------------- */ +// On success, returns number of bytes written. On failure, -1 is returned and +// errno is set. Note that, even on success, bytes written may be fewer than +// count. + +PST_SSIZE_T pist_sock_write(em_socket_t em_sock, const void *buf, size_t count) +{ + PIST_SOCK_STARTUP_CHECK_RET_MINUS_1_ON_ERR; + + SOCKET win_sock = get_win_socket_from_em_socket_t(em_sock); + if (win_sock == INVALID_SOCKET) + { + PS_LOG_DEBUG("Invalid Socket"); + errno = EBADF; + return(-1); + } + + int send_res = ::send(win_sock, reinterpret_cast(buf), + (static_cast(count)), 0/*flags*/); + if (send_res != SOCKET_ERROR) + return(send_res); // success - return bytes read + + return(WSAGetLastErrorSetErrno()); +} + +/* ------------------------------------------------------------------------- */ +// On success, returns em_socket_t. On failure, -1 is returned and errno is +// set. + +em_socket_t pist_sock_socket(int domain, int type, int protocol) +{ + PIST_SOCK_STARTUP_CHECK_RET_MINUS_1_ON_ERR; + + SOCKET socket_res = ::socket(domain, type, protocol); + if (socket_res != INVALID_SOCKET) + { + em_socket_t res = static_cast(socket_res); + return(res); + } + + return(WSAGetLastErrorSetErrno()); +} + +/* ------------------------------------------------------------------------- */ + +// On success, returns 0. On failure, -1 is returned and errno is set. +int pist_sock_bind(em_socket_t em_sock, const struct sockaddr *addr, + PST_SOCKLEN_T addrlen) +{ + PIST_SOCK_STARTUP_CHECK_RET_MINUS_1_ON_ERR; + + SOCKET win_sock = get_win_socket_from_em_socket_t(em_sock); + if (win_sock == INVALID_SOCKET) + { + PS_LOG_DEBUG("Invalid Socket"); + errno = EBADF; + return(-1); + } + + int bind_res = ::bind(win_sock, addr, addrlen); + if (bind_res != SOCKET_ERROR) + return(0); // success - return bytes read + + return(WSAGetLastErrorSetErrno()); +} + +/* ------------------------------------------------------------------------- */ + +// On success returns an em_socket_t for the accepted socket. On failure, -1 is +// returned and errno is set. +em_socket_t pist_sock_accept(em_socket_t em_sock, struct sockaddr *addr, + PST_SOCKLEN_T *addrlen) +{ + PIST_SOCK_STARTUP_CHECK_RET_MINUS_1_ON_ERR; + + SOCKET win_sock = get_win_socket_from_em_socket_t(em_sock); + if (win_sock == INVALID_SOCKET) + { + PS_LOG_DEBUG("Invalid Socket"); + errno = EBADF; + return(-1); + } + + SOCKET accept_res = ::accept(win_sock, addr, addrlen); + + if (accept_res != INVALID_SOCKET) + { + em_socket_t res = static_cast(accept_res); + return(res); // success - return em_socket_t for the accepted socket + } + + return(WSAGetLastErrorSetErrno()); +} + +/* ------------------------------------------------------------------------- */ + +// On success, returns 0. On failure, -1 is returned and errno is set. +int pist_sock_connect(em_socket_t em_sock, const struct sockaddr *addr, + PST_SOCKLEN_T addrlen) +{ + PIST_SOCK_STARTUP_CHECK_RET_MINUS_1_ON_ERR; + + SOCKET win_sock = get_win_socket_from_em_socket_t(em_sock); + if (win_sock == INVALID_SOCKET) + { + PS_LOG_DEBUG("Invalid Socket"); + errno = EBADF; + return(-1); + } + + SOCKET connect_res = ::connect(win_sock, addr, addrlen); + + if (connect_res != INVALID_SOCKET) + return(0); // success + + return(WSAGetLastErrorSetErrno()); +} + +/* ------------------------------------------------------------------------- */ + +// On success, returns 0. On failure, -1 is returned and errno is set. +int pist_sock_listen(em_socket_t em_sock, int backlog) +{ + PIST_SOCK_STARTUP_CHECK_RET_MINUS_1_ON_ERR; + + SOCKET win_sock = get_win_socket_from_em_socket_t(em_sock); + if (win_sock == INVALID_SOCKET) + { + PS_LOG_DEBUG("Invalid Socket"); + errno = EBADF; + return(-1); + } + + SOCKET listen_res = ::listen(win_sock, backlog); + + if (listen_res != INVALID_SOCKET) + return(0); // success + + return(WSAGetLastErrorSetErrno()); +} + +/* ------------------------------------------------------------------------- */ + +// On success, returns a nonnegative value which is the number of elements in +// the pollfds whose revents fields have been set to a non‐ zero value +// (indicating an event or an error). A return value of zero indicates that +// the system call timed out before any file descriptors became read. +// On error, -1 is returned, and errno is set. +int pist_sock_poll(PST_POLLFD_T * fds, PST_NFDS_T nfds, int timeout) +{ + PIST_SOCK_STARTUP_CHECK_RET_MINUS_1_ON_ERR; + + if (!nfds) + { + PS_LOG_DEBUG("Zero nfds"); + return(0); + } + + if (!fds) + { + PS_LOG_INFO("Null fds"); + errno = EINVAL; + return(-1); + } + + std::vector win_fds_vec(nfds); + for(unsigned int i=0; i 0) + { + for(unsigned int i=0; i(buf), + static_cast(len), flags); + + if (send_res != SOCKET_ERROR) + return(send_res); // success - ret number of bytes sent + + return(WSAGetLastErrorSetErrno()); +} + +/* ------------------------------------------------------------------------- */ + +// On success, returns the number of bytes received. On error, -1 is +// returned and errno is set. Returns 0 if connection closed gracefully. +PST_SSIZE_T pist_sock_recv(em_socket_t em_sock, void * buf, size_t len, + int flags) +{ + PIST_SOCK_STARTUP_CHECK_RET_MINUS_1_ON_ERR; + + SOCKET win_sock = get_win_socket_from_em_socket_t(em_sock); + if (win_sock == INVALID_SOCKET) + { + PS_LOG_DEBUG("Invalid Socket"); + errno = EBADF; + return(-1); + } + + int recv_res = ::recv(win_sock, reinterpret_cast(buf), + static_cast(len), flags); + + if (recv_res != SOCKET_ERROR) + return(recv_res); // success - ret number of bytes received + + return(WSAGetLastErrorSetErrno()); +} + +/* ------------------------------------------------------------------------- */ + +#endif // of ifdef _IS_WINDOWS diff --git a/src/common/pist_strerror_r.cc b/src/common/pist_strerror_r.cc new file mode 100644 index 000000000..926b13734 --- /dev/null +++ b/src/common/pist_strerror_r.cc @@ -0,0 +1,309 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Defines a pist_strerror_r for use in Windows + +#include +#include + +#include +#include + +#include PIST_QUOTE(PST_ERRNO_HDR) + +/* ------------------------------------------------------------------------- */ + +#ifdef _IS_WINDOWS + +#include + +/* ------------------------------------------------------------------------- */ + +// Note: We use the GNU-specific definition (which returns char *), not the +// XSI-compliant definition (which returns int) even in the non-GNU case. + +// strerror_s in Windows is the XSI form (returns int) + +static const char * const_bad_strerror_parms = "{Invalid strerror_r parms}"; +static char bad_strerror_parms_buff[128+16]; + +extern "C" char * pist_strerror_r(int errnum, char *buf, size_t buflen) +{ + if ((!buf) || (buflen <= 1)) + { + if (::strcmp(&(bad_strerror_parms_buff[0]), const_bad_strerror_parms)) + PS_STRLCPY(&(bad_strerror_parms_buff[0]), + const_bad_strerror_parms, 128); + + return(&(bad_strerror_parms_buff[0])); + } + + buf[0] = 0; + + errno_t res_strerror_s = strerror_s(buf, buflen, errnum); + if (res_strerror_s == 0) + { + if ((errnum >= 100) && ((::strncmp(&(buf[0]), "Unknown error", + sizeof("Unknown error")) == 0) || + (::strncmp(&(buf[0]), "Unknown Error", + sizeof("Unknown Error")) == 0) || + (::strncmp(&(buf[0]), "unknown error", + sizeof("unknown error")) == 0))) + { + // In Windows Server 2019 with Visual Studio 2019, the debug + // runtime generates a real error message ("address in use") for + // EADDRINUSE, but the release runtime simply outputs "Unknown + // error". The release runtime produces real error messages for + // errno below 100, e.g. "Resource temporarily unavailable" for + // EAGAIN. When "Unknown error" is produced, we'll generate our own + // message as per below. Note also that the Microsoft documentation + // states that errno values 100 (EADDRINUSE) and up are "supported + // for compatibility with POSIX". Finally, with Windows 11, even + // the release runtime manages to produce real error messages for + // errno 100; so this is presumably an issue with older versions of + // Windows such as 2019. + // + // See: https://learn.microsoft.com/en-us/cpp/c-runtime-library/errno-constants?view=msvc-170 + + const char * str = nullptr; + switch(errnum) + { + case EADDRINUSE: + str = "Address in use"; + break; + + case EADDRNOTAVAIL: + str = "Address not available"; + break; + + case EAFNOSUPPORT: + str = "Address family not supported"; + break; + + case EALREADY: + str = "Connection already in progress"; + break; + + case EBADMSG: + str = "Bad message"; + break; + + case ECANCELED: + str = "Operation canceled"; + break; + + case ECONNABORTED: + str = "Connection aborted"; + break; + + case ECONNREFUSED: + str = "Connection refused"; + break; + + case ECONNRESET: + str = "Connection reset"; + break; + + case EDESTADDRREQ: + str = "Destination address required"; + break; + + case EHOSTUNREACH: + str = "Host unreachable"; + break; + + case EIDRM: + str = "Identifier removed"; + break; + + case EINPROGRESS: + str = "Operation in progress"; + break; + + case EISCONN: + str = "Already connected"; + break; + + case ELOOP: + str = "Too many symbolic link levels"; + break; + + case EMSGSIZE: + str = "Message size"; + break; + + case ENETDOWN: + str = "Network down"; + break; + + case ENETRESET: + str = "Network reset"; + break; + + case ENETUNREACH: + str = "Network unreachable"; + break; + + case ENOBUFS: + str = "No buffer space"; + break; + + case ENODATA: + str = "No message available"; + break; + + case ENOLINK: + str = "No link"; + break; + + case ENOMSG: + str = "No message"; + break; + + case ENOPROTOOPT: + str = "No protocol option"; + break; + + case ENOSR: + str = "No stream resources"; + break; + + case ENOSTR: + str = "Not a stream"; + break; + + case ENOTCONN: + str = "Not connected"; + break; + + case ENOTRECOVERABLE: + str = "State not recoverable"; + break; + + case ENOTSOCK: + str = "Not a socket"; + break; + + case ENOTSUP: + str = "Not supported"; + break; + + case EOPNOTSUPP: + str = "Operation not supported"; + break; + +#ifndef __MINGW32__ + case EOTHER: + str = "Other"; + break; +#endif + + case EOVERFLOW: + str = "Value too large"; + break; + + case EOWNERDEAD: + str = "Owner dead"; + break; + + case EPROTO: + str = "Protocol error"; + break; + + case EPROTONOSUPPORT: + str = "Protocol not supported"; + break; + + case EPROTOTYPE: + str = "Wrong protocol type"; + break; + + case ETIME: + str = "Stream timeout"; + break; + + case ETIMEDOUT: + str = "Timed out"; + break; + + case ETXTBSY: + str = "Text file busy"; + break; + + case EWOULDBLOCK: + str = "Operation would block"; + break; + + default: + break; + } + if (str) + PS_STRLCPY(buf, str, buflen); + } + } + else + { + const char * dumb_err = "{unknown err - srterror}"; + if (res_strerror_s == EINVAL) + dumb_err = "{invalid errnum - srterror}"; + else if (res_strerror_s == ERANGE) + dumb_err = "{small buf - srterror}"; + + PS_STRLCPY(buf, dumb_err, buflen); + } + + return(buf); +} + +/* ------------------------------------------------------------------------- */ + +#elif !defined(__linux__) && ((!defined(__GNUC__)) || (defined(__MINGW32__)) \ + || (defined(__clang__)) || (defined(__NetBSD__)) || (defined(__APPLE__))) + +#include + +/* ------------------------------------------------------------------------- */ + +// Note: We use the GNU-specific definition (which returns char *), not the +// XSI-compliant definition (which returns int) even in the non-GNU case. + +// Here, we assume native strerror_r is the XSI form (returns int) + +static const char * const_bad_strerror_parms = "{Invalid strerror_r parms}"; +static char bad_strerror_parms_buff[128]; + +extern "C" char * pist_strerror_r(int errnum, char *buf, size_t buflen) +{ + if ((!buf) || (buflen <= 1)) + { + if (strcmp(&(bad_strerror_parms_buff[0]), const_bad_strerror_parms)) + strcpy(&(bad_strerror_parms_buff[0]), const_bad_strerror_parms); + + return(&(bad_strerror_parms_buff[0])); + } + + buf[0] = 0; + + // Since it's not GNUC, we assume native strerror_r is the XSI form + // (returns int) + int res_strerror_r = strerror_r(errnum, buf, buflen); + if (res_strerror_r != 0) + { + const char * dumb_err = "{unknown err - srterror}"; + if (res_strerror_r == EINVAL) + dumb_err = "{invalid errnum - srterror}"; + else if (res_strerror_r == ERANGE) + dumb_err = "{small buf - srterror}"; + + PS_STRLCPY(buf, dumb_err, buflen); + } + + return(buf); +} + +/* ------------------------------------------------------------------------- */ + +#endif // of ifdef _IS_WINDOWS... elsif ! defined(__GNUC__) diff --git a/src/common/pist_syslog.cc b/src/common/pist_syslog.cc index 2358f386b..dda20bdd6 100644 --- a/src/common/pist_syslog.cc +++ b/src/common/pist_syslog.cc @@ -5,7 +5,7 @@ */ /****************************************************************************** - * PistSysLog.cpp + * pist_syslog.cpp * * Logging Facilities * @@ -13,20 +13,33 @@ #include - +#include #include +#include -#include // for basename or basename_r +#include // for PS_BASENAME_R #include // snprintf #include // malloc + +#include PIST_QUOTE(PST_CLOCK_GETTIME_HDR) + #include #include +#include + +#ifdef _IS_WINDOWS +#include +#include +#include +#include +#endif -#include // basename #include // std::ispunct #include // std::remove_copy_if -#include // PATH_MAX +#include +// #include // PATH_MAX +#include PIST_QUOTE(PST_MAXPATH_HDR) #ifdef __APPLE__ #include // _NSGetExecutablePath @@ -68,26 +81,443 @@ #ifdef PIST_USE_OS_LOG #include +#elif defined _IS_WINDOWS + #include // needed for PST_THREAD_HDR (processthreadsapi.h) + #include + #include + + #ifdef __MINGW32__ + #ifndef _Pre_cap_ + // With Visual Studio, _Pre_cap_ is a multi-layer macro defined in sal.h, + // the source-code annotation language used by Microsoft; and it is used + // in the generated header file pist_winlog.h. For mingw, _Pre_cap_ + // doesn't seem to be defined in their sal.h, and we simply define it to + // be nothing so pist_winlog.h can compile. + #include + #ifndef _Pre_cap_ + #define _Pre_cap_(__T) + #endif + #endif + #endif + + #ifdef __MINGW32__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunknown-pragmas" + // The generated header file pist_winlog.h likely includes some Visual + // Studio specific pragmas such as "#pragma warning(suppress: + // 26451)". gcc/mingw would normally generate a warning for pragmas it + // doesn't recognize; here we temporarily hide such warnings while + // pist_winlog.h is compiled. + #endif + #include "pist_winlog.h" // generated header file + #ifdef __MINGW32__ + #pragma GCC diagnostic pop + + // pist_winlog.h declares this ULONG as an extern, but does not provide an + // instantiation for it. WIth mingw, we have to provide an instantiation to + // avoid an "undefined reference" linker error + ULONG Pistache_ProviderEnableBits[1] = {0}; + #endif + + // In Windows, Pistache uses the ETW ("Event Tracing") system for logging. + // https://learn.microsoft.com/en-us/windows/win32/etw/event-tracing-portal + // ETW events are defined in the pist_winlog.man manifest file, from which is + // generated the C header file pist_winlog.h, used here. + // + // For events above level DEBUG, the event is sent to the built-in + // Application channel. This make events appear automatically in Event + // Viewer, even if you don't run logman. + // + // DEBUG events (in debug builds) are sent to our custom Pistache debug + // channel. Verbose/debug event streams are not to be sent to the Application + // channel, because they will clog up the channel. + // + // Since DEBUG events are not consumed automatically by EventViewer via the + // Application channel, we will need another event consumer to record the + // events. We can use the Windows utility logman.exe. + // + // To use logman, you can do: + // logman start -ets Pistache -p "Pistache-Provider" 0 0 -o pistache.etl + // Then run your program, which you want to log + // Once your program is complete, you can do: + // logman stop Pistache -ets + // This causes the log information to be written out to pistache.etl + // + // You can view the etl file: + // 1/ Use Event Viewer. (In Event Viewer, Action -> Open Saved Log, then + // choose pistache.etl). + // 2/ Convert the etl to XML: + // tracerpt -y pistache.etl + // Then view dumpfile.xml in a text editor or XML viewer + // Alternatively, you can have logman generate a CSV file instead of an .etl + // by adding the "-f csv" option to the "logman start..." command. + // + // logman has many other options, do "logman -?" to see the list. + // + // To use logging, you must also: + // 1/ First, copy pistachelog.dll to the predefined location + // "$env:ProgramFiles\pistache_distribution\bin" + // 2/ Install the Pistache manifest file by doing: + // wevtutil im "pist_winlog.man" + // src/meson.build should do both 1/ and 2/ automatically upon "meson build" + #else #include #endif #include #include // for strcat -#include // for pthread_self (getting thread ID) -#include // for MAXPATHLEN +#include PIST_QUOTE(PST_THREAD_HDR) // for pthread_self (getting thread ID) +#include PIST_QUOTE(PST_MAXPATH_HDR) #include // for getpid() -#include +#include PIST_QUOTE(PST_MISC_IO_HDR) // unistd.h e.g. close #include "pistache/pist_syslog.h" #include // for std::shared_ptr +/*****************************************************************************/ +#ifdef _IS_WINDOWS + +#include // std::wostringstream +#include // Registry functions +#include + +class LogToStdPutAsWell +{ +public: + LogToStdPutAsWell() : + logToStdoutAsWell(-1), + pistacheHkey(0), + ltsawMonitorThreadNextToUseIdx(0) + {} + + ~LogToStdPutAsWell() + { + for(unsigned int i=0; i<2; ++i) + { + if (logToStdoutAsWellMonitorThread[i]) + { + // If we allow std::~thread to be called while thread is + // still joinable (i.e. before it is already joined), + // std::termimate will be called immediately + if (logToStdoutAsWellMonitorThread[i]->joinable()) + logToStdoutAsWellMonitorThread[i]->join(); + } + } + } +public: + // Don't access directly, use getLogToStdoutAsWell() + std::atomic_int logToStdoutAsWell; // -1 uninited, 0 false, 1 true + std::mutex logToStdoutAsWellMutex; + HKEY pistacheHkey; + std::unique_ptr logToStdoutAsWellMonitorThread[2]; + unsigned int ltsawMonitorThreadNextToUseIdx; + // We use the logToStdoutAsWellMonitorThread alternately - when one + // monitor thread is running, and it needs to create a new monitor thread, + // it uses the other logToStdoutAsWellMonitorThread for the new + // std::thread unique_ptr. +}; +static LogToStdPutAsWell lLogToStdOutAsWellInst; + + +// getAndInitPsLogToStdoutAsWellPrv helper - on a failure, we try and send an +// Alert to the Windows Application logging channel, and also to stderr - in +// the hope someone will notice why logging is not doing as expected. +static void getPsLogToStdoutAsWellLogFailPrv(const wchar_t * msg_prefix, + LSTATUS err_code) +{ + std::wostringstream err_wostringstream; + err_wostringstream << L"getPsLogToStdoutAsWell: " + << msg_prefix << L", error code " << err_code; + + TCHAR err_msg_buff_tch[2048+16]; + DWORD err_msg_res = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, + nullptr, // no msg source string + err_code, + 0, // unspecified language + &(err_msg_buff_tch[0]), + 2048, // buff size + nullptr); // msg arguments + if (err_msg_res == 0) + { + err_wostringstream << L". ."; + } + else + { + err_wostringstream << L", " << &(err_msg_buff_tch[0]); + } + + std::wstring msg_w(err_wostringstream.str()); + + EventWritePSTCH_CBLTIN_ALERT_NL_AssumeEnabled(msg_w.c_str()); + + char msg_buf_chs[2048+16]; + int wctmb_res = WideCharToMultiByte(CP_UTF8, + 0, // no conversion flags, use defaults + msg_w.c_str(), + -1, // msg_w is null-terminated + &(msg_buf_chs[0]), + 2048, + nullptr, // ptr to ch for invalid char + nullptr); // any invalid char? + if (wctmb_res <= 0) + { + EventWritePSTCH_CBLTIN_ALERT_NL_AssumeEnabled( + L"getPsLogToStdoutAsWell: WideCharToMultiByte failure for stderr"); + std::cerr << + "getPsLogToStdoutAsWell: WideCharToMultiByte failure for stderr" + << std::endl; + return; + } + + std::cerr << (&(msg_buf_chs[0])) << std::endl; +} + + +// Reads "HKCU:\Software\pistacheio\pistache\psLogToStdoutAsWell" property from +// the Windows registry, and returns its value, either 0, 1 or 10. Any Registry +// key property value other than 0, 1 or 10 causes 1 to be returned. If the key +// doesn't exist or can't be read, 0 is returned. +// +// If the property does not exist in the registry yet, we create it here, set +// it to zero, and return 0. +// +// logToStdoutAsWellMutex locked before this is called +// logToStdoutAsWell value is NOT set by this function +static DWORD getAndInitPsLogToStdoutAsWellPrv() +{ + HKEY hklm_software_key = 0; + LSTATUS open_res = RegOpenKeyExA( + HKEY_CURRENT_USER, + "Software", + 0, // options (not symbolic link) + KEY_READ, + &hklm_software_key); + if (open_res != ERROR_SUCCESS) + { + getPsLogToStdoutAsWellLogFailPrv( + L"Failed to open registry key HKCU:\\Software", open_res); + return(0); + } + + if (lLogToStdOutAsWellInst.pistacheHkey) + { + RegCloseKey(lLogToStdOutAsWellInst.pistacheHkey); + lLogToStdOutAsWellInst.pistacheHkey = 0; + } + + DWORD dw_disposition = 0; + LSTATUS create_res = RegCreateKeyExA( + hklm_software_key, + "pistacheio\\pistache", // will create both "pistacheio" and + // "pistache" if needed + 0, + nullptr, // lpClass + 0, // default flags + KEY_QUERY_VALUE | KEY_SET_VALUE | KEY_NOTIFY, // reqd rights + nullptr, // don't allow child process to inherit + &lLogToStdOutAsWellInst.pistacheHkey, // HKEY result + &dw_disposition); + // Note: if the key already exists, RegCreateKeyExA opens it + + if (create_res != ERROR_SUCCESS) + { + getPsLogToStdoutAsWellLogFailPrv( + L"Failed to create/open registry key " + "HKCU:\\Software\\pistacheio\\pistache", create_res); + return(0); + } + + #ifdef DEBUG + const std::wstring disposition_msg( + (dw_disposition == REG_OPENED_EXISTING_KEY) ? + L"Opened existing registry key HKCU:\\Software\\pistacheio\\pistache" : + ((dw_disposition == REG_CREATED_NEW_KEY) ? + L"Created new registry key HKCU:\\Software\\pistacheio\\pistache" : + L"Unknown RegCreateKeyExA disposition")); + EventWritePSTCH_DEBUG_NL(disposition_msg.c_str()); + #endif + + if (dw_disposition != REG_CREATED_NEW_KEY) + { + uint64_t val = 0; // only needs to be DWORD, but just in case + DWORD val_size = sizeof(DWORD); + const LSTATUS get_val_res = + RegGetValueA(lLogToStdOutAsWellInst.pistacheHkey, + nullptr, // subkey - none + "psLogToStdoutAsWell", // val name + RRF_RT_REG_DWORD, // type REG_DWORD + nullptr, // out type + &val, // out data + &val_size); // out data size + if (get_val_res == ERROR_SUCCESS) + { + if ((val != 10) && (val != 0) && (val != 1)) + return(1); + + return(static_cast(val)); + } + + if (get_val_res != ERROR_FILE_NOT_FOUND) + { + getPsLogToStdoutAsWellLogFailPrv( + L"Failed to get Registry value psLogToStdoutAsWell", + get_val_res); + return(0); + } + } + + // value didn't exist + + BYTE data_to_set[8] = {0}; // bigger than it needs to be + LSTATUS set_val_res = RegSetValueExA(lLogToStdOutAsWellInst.pistacheHkey, + "psLogToStdoutAsWell", // val name + 0, // Reserved + REG_DWORD, // type to set + &(data_to_set[0]), // data content + sizeof(DWORD)); // data size + + if (set_val_res != ERROR_SUCCESS) + { + getPsLogToStdoutAsWellLogFailPrv( + L"Failed to set Registry value psLogToStdoutAsWell", set_val_res); + return(0); + } + + return(0); +} + +// Calls getAndInitPsLogToStdoutAsWellPrv,and then sets up change monitoring +// for the key +// logToStdoutAsWellMutex locked before this is called +// logToStdoutAsWell value IS set by this function +static DWORD getLogToStdoutAsWellAndMonitorPrv() +{ + DWORD log_to_stdout_as_well = getAndInitPsLogToStdoutAsWellPrv(); + if (!lLogToStdOutAsWellInst.pistacheHkey) + { // Can't monitor without lLogToStdOutAsWellInst.pistacheHkey + lLogToStdOutAsWellInst.logToStdoutAsWell = log_to_stdout_as_well; + return(log_to_stdout_as_well); + } + + bool we_set_l_log_to_stdout_as_well = false; + + HANDLE h_event= CreateEventA(nullptr, // handle not inherited by child + FALSE,// auto-set to non-signaled after signal + FALSE,// initial state is non-signaled + nullptr);// Null = event name + if (h_event == nullptr) + { + DWORD err_code = GetLastError(); + getPsLogToStdoutAsWellLogFailPrv( + L"CreateEvent fail, cannot monitor psLogToStdoutAsWell Registry " + "value", + err_code); + } + else + { + LSTATUS reg_notify_res = RegNotifyChangeKeyValue( + lLogToStdOutAsWellInst.pistacheHkey, + false, // report changes in the key only, not subkeys + REG_NOTIFY_CHANGE_ATTRIBUTES | REG_NOTIFY_CHANGE_LAST_SET + | REG_NOTIFY_CHANGE_SECURITY + | REG_NOTIFY_THREAD_AGNOSTIC, // Changes that will be reported + h_event, // event to be signalled on reportable change + true); // true => RegNotifyChangeKeyValue to be asynchronous + + if (reg_notify_res == ERROR_SUCCESS) + { + lLogToStdOutAsWellInst.logToStdoutAsWell = log_to_stdout_as_well; + we_set_l_log_to_stdout_as_well = true; + + unsigned int thread_to_use_idx = + lLogToStdOutAsWellInst.ltsawMonitorThreadNextToUseIdx; + lLogToStdOutAsWellInst.ltsawMonitorThreadNextToUseIdx = + ((thread_to_use_idx + 1) % 2); + + if (lLogToStdOutAsWellInst. + logToStdoutAsWellMonitorThread[thread_to_use_idx]) + { + // Wait for the thread to exit BEFORE we delete the std::thread + // instance + lLogToStdOutAsWellInst. + logToStdoutAsWellMonitorThread[thread_to_use_idx]->join(); + + // Effectively, delete the std::thread instance + lLogToStdOutAsWellInst. + logToStdoutAsWellMonitorThread[thread_to_use_idx] = nullptr; + } + + lLogToStdOutAsWellInst. + logToStdoutAsWellMonitorThread[thread_to_use_idx] = + std::make_unique ([h_event] + { + auto wfso_res = WaitForSingleObject(h_event, INFINITE); + + std::lock_guard + guard(lLogToStdOutAsWellInst.logToStdoutAsWellMutex); + + if (wfso_res == WAIT_FAILED) + { + DWORD err_code = GetLastError(); + getPsLogToStdoutAsWellLogFailPrv( + L"WaitForSingleObject fail, cannot monitor " + "psLogToStdoutAsWell Registry value", + err_code); + } + else + { // A change has occured + // Note - the monitoring only lasts for one change + // event, so we monitor it again + DWORD log_to_stdout_as_well = + getLogToStdoutAsWellAndMonitorPrv(); + lLogToStdOutAsWellInst.logToStdoutAsWell = + log_to_stdout_as_well; + } + + CloseHandle(h_event); + }); + } + else + { + getPsLogToStdoutAsWellLogFailPrv( + L"RegNotifyChangeKeyValue fail, cannot monitor " + "psLogToStdoutAsWell Registry value", + reg_notify_res); + } + } + + if (!we_set_l_log_to_stdout_as_well) + lLogToStdOutAsWellInst.logToStdoutAsWell = log_to_stdout_as_well; + return(log_to_stdout_as_well); +} + + +static DWORD getLogToStdoutAsWell() +{ + { // encapsulate + int my_log_to_stdout_as_well(lLogToStdOutAsWellInst.logToStdoutAsWell); + if (my_log_to_stdout_as_well >= 0) + return(my_log_to_stdout_as_well > 0); + } + + std::lock_guard guard( + lLogToStdOutAsWellInst.logToStdoutAsWellMutex); + if (lLogToStdOutAsWellInst.logToStdoutAsWell >= 0) + return(lLogToStdOutAsWellInst.logToStdoutAsWell > 0); + + return(getLogToStdoutAsWellAndMonitorPrv()); +} + +#endif // of ifdef _IS_WINDOWS + /*****************************************************************************/ + class PSLogging { public: @@ -128,33 +558,42 @@ static os_log_t pist_os_log_ref = OS_LOG_DEFAULT; #endif static const char * gLogEntryPrefix = "PSTCH"; -static char gIdentBuff[PATH_MAX + 6] = {0}; +static char gIdentBuff[PST_MAXPATHLEN + 6] = {0}; static bool gSetPsLogCategoryCalledWithNull = false; static bool my_is_punct(std::string::value_type & ch) { // There's probably a way to do with with std::function - return((bool)(std::ispunct((int)ch))); + return(static_cast(std::ispunct(static_cast(ch)))); } static std::string getLogIdent() { - char prog_path[PATH_MAX+6]; + char prog_path[PST_MAXPATHLEN+6]; prog_path[0] = 0; #ifdef __APPLE__ - uint32_t bufsize = PATH_MAX; + uint32_t bufsize = PST_MAXPATHLEN; if (_NSGetExecutablePath(&(prog_path[0]), &bufsize) != 0) return(std::string()); + #elif defined _IS_WINDOWS + if (GetModuleFileNameA(nullptr, // NULL->executable file of current process + &(prog_path[0]), PST_MAXPATHLEN) == 0) + return(std::string()); // GetModuleFileNameA returns strlen on success #else - if (readlink("/proc/self/exe", &(prog_path[0]), PATH_MAX) == -1) + if (readlink("/proc/self/exe", &(prog_path[0]), PST_MAXPATHLEN) == -1) return(std::string()); #endif - if (!strlen(&(prog_path[0]))) + const size_t prog_path_len = strlen(&(prog_path[0])); + if (!prog_path_len) return(std::string()); - char * prog_name = basename(&(prog_path[0])); + std::vector bname_buff( + std::max(PST_MAXPATHLEN+16, prog_path_len+16)); + bname_buff[0] = 0; + + char * prog_name = PS_BASENAME_R(&(prog_path[0]), bname_buff.data()); if ((!prog_name) || (!strlen(prog_name))) prog_name = &(prog_path[0]); @@ -188,8 +627,9 @@ PSLogging::PSLogging() if (!gIdentBuff[0]) { std::string log_ident(getLogIdent()); - strcpy(&(gIdentBuff[0]), log_ident.empty() ? - gLogEntryPrefix : log_ident.c_str()); + PS_STRLCPY(&(gIdentBuff[0]), log_ident.empty() ? + gLogEntryPrefix : log_ident.c_str(), + PST_MAXPATHLEN); } #ifdef PIST_USE_OS_LOG @@ -200,6 +640,25 @@ PSLogging::PSLogging() pist_os_log_ref = os_log_create("com.github.pistacheio.pistache", &(gIdentBuff[0])); + #elif defined _IS_WINDOWS + + #ifdef DEBUG + ULONG reg_res = + #endif + EventRegisterPistache_Provider(); // macro calls EventRegister + #ifdef DEBUG + if (reg_res != ERROR_SUCCESS) + throw std::runtime_error("Windows logging EventRegister failed"); + #endif + + const wchar_t * pist_start_wmsg = + L"Pistache start. INFO and up log messages visible in Event Viewer." + #ifdef DEBUG + " See pist_syslog.cc comments to view DEBUG and up logging." + #endif + ; + EventWritePSTCH_CBLTIN_INFO_NL_AssumeEnabled(pist_start_wmsg); + #else if (!gSetPsLogCategoryCalledWithNull) @@ -219,8 +678,13 @@ PSLogging::PSLogging() PSLogging::~PSLogging() { + #ifdef _IS_WINDOWS + EventWritePSTCH_CBLTIN_INFO_NL_AssumeEnabled(L"Pistache exiting"); + EventUnregisterPistache_Provider(); // macro calls EventUnregister + #else if (!gSetPsLogCategoryCalledWithNull) closelog(); + #endif } // --------------------------------------------------------------------------- @@ -233,16 +697,17 @@ static int snprintProcessAndThread(char * _buff, size_t _buffSize) if (!_buffSize) return(-1); - pthread_t pt = pthread_self(); // This function always succeeds + PST_THREAD_ID pt = PST_THREAD_ID_SELF(); // This function always succeeds - unsigned char *ptc = (unsigned char*)(void*)(&pt); + unsigned char *ptc = + reinterpret_cast((reinterpret_cast(&pt))); int buff_would_have_been_len = 0; _buff[0] = 0; // Skip leading 0s to make str shorter (but don't reduce to zero len) size_t size_pt = sizeof(pt); unsigned int ch_to_skip = 0; - for(;(ch_to_skip+1) < size_pt; ch_to_skip++) + for(;(ch_to_skip+1) < size_pt; ++ch_to_skip) { if (ptc[ch_to_skip]) break; @@ -251,22 +716,23 @@ static int snprintProcessAndThread(char * _buff, size_t _buffSize) size_pt -= ch_to_skip; // Skip trailing 0s to make str shorter (but don't reduce to zero len) - for(; size_pt>1; size_pt--) + for(; size_pt>1; --size_pt) { if (ptc[size_pt-1]) break; } - for (int i=(((int)size_pt)-1); i>=0; i--) // little endian, if it matters + // little endian, if it matters + for (int i=((static_cast(size_pt))-1); i>=0; --i) { buff_would_have_been_len = - snprintf(_buff, _buffSize, "%02x", (unsigned)(ptc[i])); + snprintf(_buff, _buffSize, "%02x", static_cast(ptc[i])); if (buff_would_have_been_len <= 0) { _buff[0] = 0; return(-1); } - if (((unsigned int)buff_would_have_been_len) >= _buffSize) + if ((static_cast(buff_would_have_been_len)) >= _buffSize) return(-1); // output truncated, see snprintf man page _buff += buff_would_have_been_len; _buffSize -= buff_would_have_been_len; @@ -310,13 +776,88 @@ static int snprintProcessAndThread(char * _buff, size_t _buffSize) } #define OS_LOG_BY_PRIORITY OS_LOG_BY_PRIORITY_FORMAT_ARG("%s", &(buff[0])) + +#elif defined _IS_WINDOWS + +// POL_ARG is const char * +#define WIN_LOG_BY_PRIORITY_CSTR(POL_ARG) \ +{ \ + std::wstring dummy_buff_as_wstr(L"MultiByteToWideChar Fail"); \ + std::wstring buff_as_wstr; \ + const wchar_t * buff_as_wstr_data = nullptr; \ + \ + int convert_result = MultiByteToWideChar(CP_UTF8, 0, POL_ARG, \ + static_cast(strlen(POL_ARG)), nullptr, 0); \ + if (convert_result <= 0) \ + { \ + buff_as_wstr_data = dummy_buff_as_wstr.data(); \ + } \ + else \ + { \ + buff_as_wstr.resize(convert_result+10); \ + convert_result = MultiByteToWideChar(CP_UTF8, 0, POL_ARG, \ + static_cast(strlen(POL_ARG)), &buff_as_wstr[0], \ + static_cast(buff_as_wstr.size())); \ + buff_as_wstr_data = (convert_result <= 0) ? \ + dummy_buff_as_wstr.data() : buff_as_wstr.data(); \ + } \ + \ + switch(_priority) \ + { \ + case LOG_EMERG: \ + EventWritePSTCH_CBLTIN_EMERG_NL_AssumeEnabled(buff_as_wstr_data); \ + break; \ + \ + case LOG_ALERT: \ + EventWritePSTCH_CBLTIN_ALERT_NL_AssumeEnabled(buff_as_wstr_data); \ + break; \ + \ + case LOG_CRIT: \ + EventWritePSTCH_CBLTIN_CRIT_NL_AssumeEnabled(buff_as_wstr_data); \ + break; \ + \ + case LOG_ERR: \ + EventWritePSTCH_CBLTIN_ERR_NL_AssumeEnabled(buff_as_wstr_data); \ + break; \ + \ + case LOG_WARNING: \ + EventWritePSTCH_CBLTIN_WARNING_NL_AssumeEnabled(buff_as_wstr_data); \ + break; \ + \ + case LOG_NOTICE: \ + EventWritePSTCH_CBLTIN_NOTICE_NL_AssumeEnabled(buff_as_wstr_data); \ + break; \ + \ + case LOG_INFO: \ + EventWritePSTCH_CBLTIN_INFO_NL_AssumeEnabled(buff_as_wstr_data); \ + break; \ + \ + case LOG_DEBUG: \ + EventWritePSTCH_DEBUG_NL(buff_as_wstr_data); \ + break; \ + \ + default: \ + { \ + std::wstring _priority_as_wstr(std::to_wstring(_priority)); \ + EventWritePSTCH_CBLTIN_EMERG_NL_AssumeEnabled( \ + _priority_as_wstr.data()); \ + \ + EventWritePSTCH_CBLTIN_EMERG_NL_AssumeEnabled(buff_as_wstr_data); \ + break; \ + } \ + } \ +} + +#define WIN_LOG_BY_PRIORITY \ + WIN_LOG_BY_PRIORITY_CSTR(&(buff[0])) + #endif // --------------------------------------------------------------------------- static const char * levelCStr(int _pri) { - const char * res = NULL; + const char * res = nullptr; switch(_pri) { case LOG_ALERT: @@ -361,12 +902,14 @@ static int logToStdOutMaybeErr(int _priority, bool _andPrintf, #endif ) { - strcpy(&(dAndT[0]), ""); + PS_STRLCPY(&(dAndT[0]), "", sizeof(dAndT)); - time_t t = time(NULL); + time_t t = time(nullptr); if (t >= 0) { - struct tm * tm_ptr = localtime(&t); + struct tm this_tm; + memset(&this_tm, 0, sizeof(this_tm)); + struct tm * tm_ptr = PST_LOCALTIME_R(&t, &this_tm); if (tm_ptr) { snprintf(&(dAndT[0]), sizeof(dAndT)-9, "%d %02d:%02d:%02d", @@ -407,9 +950,9 @@ void PSLogging::log(int _priority, bool _andPrintf, buff[0] = '('; if (snprintProcessAndThread(&(buff[1]), sizeof(buff)-3-strlen(gLogEntryPrefix)) >= 0) - strcat(&(buff[0]), " "); - strcat(&(buff[0]), gLogEntryPrefix); - strcat(&(buff[0]), ") "); + PS_STRLCAT(&(buff[0]), " ", sizeof(buff)-8); + PS_STRLCAT(&(buff[0]), gLogEntryPrefix, sizeof(buff)-8); + PS_STRLCAT(&(buff[0]), ") ", sizeof(buff)-8); char * remaining_buff = &(buff[0]) + strlen(buff); size_t remaining_buff_size = sizeof(buff) - strlen(buff); @@ -447,6 +990,11 @@ void PSLogging::log(int _priority, bool _andPrintf, if (_format) OS_LOG_BY_PRIORITY_FORMAT_ARG( "Failing log vsnprintf format: %s", _format); + #elif defined _IS_WINDOWS + WIN_LOG_BY_PRIORITY_CSTR( + "Unable to log, vsnprintf failed"); + if (_format) + WIN_LOG_BY_PRIORITY_CSTR(_format); #else vsyslog(_priority, _format, _ap); @@ -459,6 +1007,8 @@ void PSLogging::log(int _priority, bool _andPrintf, { #ifdef PIST_USE_OS_LOG OS_LOG_BY_PRIORITY; + #elif defined _IS_WINDOWS + WIN_LOG_BY_PRIORITY; #else syslog(_priority, "%s", &(buff[0])); #endif @@ -476,9 +1026,9 @@ void PSLogging::log(int _priority, bool _andPrintf, const char * _str) buff[0] = '('; if (snprintProcessAndThread(&(buff[1]), sizeof(buff)-3-strlen(gLogEntryPrefix)) >= 0) - strcat(&(buff[0]), " "); - strcat(&(buff[0]), gLogEntryPrefix); - strcat(&(buff[0]), ") "); + PS_STRLCAT(&(buff[0]), " ", sizeof(buff)-8); + PS_STRLCAT(&(buff[0]), gLogEntryPrefix, sizeof(buff)-8); + PS_STRLCAT(&(buff[0]), ") ", sizeof(buff)-8); char * remaining_buff = &(buff[0]) + strlen(buff); size_t remaining_buff_size = sizeof(buff) - strlen(buff); @@ -491,6 +1041,8 @@ void PSLogging::log(int _priority, bool _andPrintf, const char * _str) #ifdef PIST_USE_OS_LOG OS_LOG_BY_PRIORITY_FORMAT_ARG("%s", _str); + #elif defined _IS_WINDOWS + WIN_LOG_BY_PRIORITY_CSTR(_str); #else syslog(_priority, "%s", _str); #endif @@ -501,6 +1053,8 @@ void PSLogging::log(int _priority, bool _andPrintf, const char * _str) { #ifdef PIST_USE_OS_LOG OS_LOG_BY_PRIORITY; + #elif defined _IS_WINDOWS + WIN_LOG_BY_PRIORITY; #else syslog(_priority, "%s", &(buff[0])); #endif @@ -545,6 +1099,18 @@ extern "C" void PSLogNoLocFn(int _pri, bool _andPrintf, if (!_format) return; + + #ifdef _IS_WINDOWS + // getLogToStdoutAsWell reads the + // "HKCU:\Software\pistacheio\pistache\psLogToStdoutAsWell" property from + // the Windows registry. + const DWORD log_to_stdout_as_well = getLogToStdoutAsWell(); + if (log_to_stdout_as_well == 10) + _andPrintf = false; + else + _andPrintf |= (log_to_stdout_as_well != 0); + #endif + va_list ap; va_start(ap, _format); PSLogPrv(_pri, _andPrintf, _format, ap); @@ -560,6 +1126,17 @@ extern "C" void PSLogFn(int _pri, bool _andPrintf, if (!_format) return; + #ifdef _IS_WINDOWS + // getLogToStdoutAsWell reads the + // "HKCU:\Software\pistacheio\pistache\psLogToStdoutAsWell" property from + // the Windows registry. + const DWORD log_to_stdout_as_well = getLogToStdoutAsWell(); + if (log_to_stdout_as_well == 10) + _andPrintf = false; + else + _andPrintf |= (log_to_stdout_as_well != 0); + #endif + // We preserve errno for this function since i) We don't want the act of // logging (e.g. an error) to alter errno, even if the logging fails; and // ii) Apple's os_log_xxx appears to set errno to zero even when successful @@ -567,10 +1144,10 @@ extern "C" void PSLogFn(int _pri, bool _andPrintf, // not preserve errno"). int tmp_errno = errno; - char bname_buff[MAXPATHLEN+6]; + char bname_buff[PST_MAXPATHLEN+6]; if ((f) && (f[0])) { - char * new_f = my_basename_r(f, &(bname_buff[0])); + char * new_f = PS_BASENAME_R(f, &(bname_buff[0])); if ((new_f) && (new_f[0])) f = new_f; } @@ -595,7 +1172,7 @@ extern "C" void PSLogFn(int _pri, bool _andPrintf, va_end(ap); if (pos >= (int) form_and_args_buf_size) - strcat(&(form_and_args_buf[0]), "..."); + PS_STRLCAT(&(form_and_args_buf[0]), "...", form_and_args_buf_size); } const unsigned int sizeof_buf = 4096; @@ -618,13 +1195,13 @@ extern "C" void PSLogFn(int _pri, bool _andPrintf, "line %d in %s()",l, m); if (ln >= (int) sizeof_buf_ex_form_and_args) - strcat(buf_ptr, "..."); + PS_STRLCAT(buf_ptr, "...", sizeof_buf); } if (form_and_args_buf[0]) { // Not empty string - strcat(buf_ptr, ": "); - strcat(buf_ptr, &(form_and_args_buf[0])); + PS_STRLCAT(buf_ptr, ": ", sizeof_buf); + PS_STRLCAT(buf_ptr, &(form_and_args_buf[0]), sizeof_buf); } PSLogStrPrv(_pri, _andPrintf, buf); @@ -662,39 +1239,12 @@ extern "C" void setPsLogCategory(const char * _category) return; } - if (strlen(_category) >= PATH_MAX) + if (strlen(_category) >= PST_MAXPATHLEN) return; gSetPsLogCategoryCalledWithNull = false; - strcpy(&(gIdentBuff[0]), _category); -} - -// --------------------------------------------------------------------------- - -#ifndef __APPLE__ -static std::mutex ps_basename_r_mutex; -extern "C" char * ps_basename_r(const char * path, char * bname) -{ - if (!bname) - return(NULL); - - bname[0] = 0; - - std::lock_guard l_guard(ps_basename_r_mutex); - - char * path_copy = (char *) malloc((path ? strlen(path) : 0) + 6); - strcpy(path_copy, path); // since basename may change path contents - - char * bname_res = basename(path_copy); - - if (bname_res) - strcpy(&(bname[0]), bname_res); - - free(path_copy); - return(bname); + PS_STRLCPY(&(gIdentBuff[0]), _category, PST_MAXPATHLEN); } -#endif // ifndef __APPLE__ - // --------------------------------------------------------------------------- diff --git a/src/common/pist_timelog.cc b/src/common/pist_timelog.cc index 5ad5016fd..63a2668e7 100644 --- a/src/common/pist_timelog.cc +++ b/src/common/pist_timelog.cc @@ -13,9 +13,69 @@ #include +#ifdef _IS_WINDOWS +#include // required for PST_THREAD_HDR (processthreadsapi.h) +#endif +#include PIST_QUOTE(PST_THREAD_HDR) //e.g. pthread.h + #ifdef DEBUG -unsigned __PS_TIMEDBG::mUniCounter = 0; +#include + +static std::atomic_uint gUniCounter = 0; // universal (static) counter + +static std::map gThreadMap; +static std::mutex gThreadMapMutex; + +/* ------------------------------------------------------------------------- */ + +unsigned __PS_TIMEDBG::getNextUniCounter() +{ + return(++gUniCounter); +} + +/* ------------------------------------------------------------------------- */ + +// returns depth value after increment +unsigned __PS_TIMEDBG::getThreadNextDepth() +{ + std::lock_guard l_guard(gThreadMapMutex); + PST_THREAD_ID pthread_id = PST_THREAD_ID_SELF(); + + std::map::iterator it = + gThreadMap.find(pthread_id); + if (it == gThreadMap.end()) + { + std::pair pr(pthread_id, 1); + gThreadMap.insert(pr); + return(1); + } + + return(++(it->second)); +} + +/* ------------------------------------------------------------------------- */ + +// returns depth value before decrement +unsigned __PS_TIMEDBG::decrementThreadDepth() +{ + std::lock_guard l_guard(gThreadMapMutex); + PST_THREAD_ID pthread_id = PST_THREAD_ID_SELF(); + + std::map::iterator it = + gThreadMap.find(pthread_id); + + unsigned old_depth = 1; + if (it->second) // else something went wrong + old_depth = ((it->second)--); + + if (old_depth <= 1) + gThreadMap.erase(it); // arguably optional, but avoids any risk + // of leaks + return(old_depth); +} + + + +/* ------------------------------------------------------------------------- */ -std::map __PS_TIMEDBG::mThreadMap; -std::mutex __PS_TIMEDBG::mThreadMapMutex; #endif diff --git a/src/common/ps_basename.cc b/src/common/ps_basename.cc new file mode 100644 index 000000000..a318bb238 --- /dev/null +++ b/src/common/ps_basename.cc @@ -0,0 +1,124 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Defines a ps_basename_r for OS that do not have basename_r natively + +#include + +/* ------------------------------------------------------------------------- */ + + +#ifndef __APPLE__ + +/* ------------------------------------------------------------------------- */ + +#ifdef _IS_WINDOWS + +#include // for _splitpath_s +#include // strlen +#include // PS_STRLCPY and PS_STRLCAT + +#include // std::max +#include + +/* ------------------------------------------------------------------------- */ + +extern "C" char * ps_basename_r(const char * path, char * bname) +{ + int tmp_errno = errno; // preserve errno + + if (!bname) + return(nullptr); + bname[0] = 0; + + if (!path) + return(bname); + size_t path_len = strlen(&(path[0])); + if (!path_len) + return(bname); + + char drive[24]; drive[0] = 0; + + const size_t mpl = std::max(path_len+16, PST_MAXPATHLEN+16); + + std::vector dirname_buff(mpl); + dirname_buff[0] = 0; + + std::vector fname_buff(mpl); + fname_buff[0] = 0; + + std::vector ext_buff(mpl); + ext_buff[0] = 0; + + errno_t sp_res = _splitpath_s(path, + &(drive[0]), sizeof(drive)-8, + dirname_buff.data(), mpl-8, + fname_buff.data(), mpl-8, + ext_buff.data(), mpl-8); + + if (sp_res) + { + // sp_res is an error code + // Don't log, since this function called by logging + + errno = tmp_errno; + return(nullptr); + } + + PS_STRLCPY(bname, fname_buff.data(), PST_MAXPATHLEN); + PS_STRLCAT(bname, ext_buff.data(), PST_MAXPATHLEN); + + errno = tmp_errno; + return(bname); +} + + +/* ------------------------------------------------------------------------- */ + +#else + +/* ------------------------------------------------------------------------- */ +// Linux or BSD + +#include +#include // for basename +#include // strlen +#include // malloc + +/* ------------------------------------------------------------------------- */ + +#define PS_BASENAME_R ps_basename_r +static std::mutex ps_basename_r_mutex; +extern "C" char * ps_basename_r(const char * path, char * bname) +{ + if (!bname) + return(nullptr); + + bname[0] = 0; + + std::lock_guard l_guard(ps_basename_r_mutex); + + char * path_copy = + reinterpret_cast(malloc((path ? strlen(path) : 0) + 6)); + strcpy(path_copy, path); // since basename may change path contents + + char * bname_res = basename(path_copy); + + if (bname_res) + strcpy(&(bname[0]), bname_res); + + free(path_copy); + return(bname); +} + + +/* ------------------------------------------------------------------------- */ + +#endif // of ifdef _IS_WINDOWS... else... + +/* ------------------------------------------------------------------------- */ + +#endif // of ifndef __APPLE__ diff --git a/src/common/ps_sendfile.cc b/src/common/ps_sendfile.cc new file mode 100644 index 000000000..cb9d17993 --- /dev/null +++ b/src/common/ps_sendfile.cc @@ -0,0 +1,282 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Defines a Linux-style ps_sendfile when in an OS that does not provide one +// natively (BSD) or with a different interface (Windows) + +#include +#include + +/* ------------------------------------------------------------------------- */ + +#ifdef _IS_WINDOWS + +#include +#include // for TransmitFile +#include // for OVERLAPPED structure +#include // for HasOverlappedIoCompleted macro +#include // _get_osfhandle, _lseeki64 + +#include // for std::min + +/* ------------------------------------------------------------------------- */ + +extern "C" PST_SSIZE_T ps_sendfile(em_socket_t out_fd, int in_fd, + off_t *offset, size_t count) +{ + __int64 in_fd_start_pos = -1; + if (offset) + { + in_fd_start_pos = _lseeki64(in_fd, 0, SEEK_CUR); + if (in_fd_start_pos < 0) + { + PS_LOG_INFO("lseek error"); + return(-1); + } + } + + auto in_fd_end_fl_pos = _lseeki64(in_fd, 0, SEEK_END); + if (in_fd_end_fl_pos < 0) + { + PS_LOG_INFO("lseek error"); + return(-1); + } + + // Set offset to starting offset specified by caller if any; otherwise, + // return file offset to what it was on entry to this function + off_t offs_to_start = + offset ? (*offset) : static_cast(in_fd_end_fl_pos); + if (offs_to_start > in_fd_end_fl_pos) + offs_to_start = static_cast(in_fd_end_fl_pos); + auto set_start_offs_res = _lseeki64(in_fd, offs_to_start, SEEK_SET); + if (set_start_offs_res < 0) + { + PS_LOG_INFO("lseek error"); + return(-1); + } + + // Note: Per Windows documentation, in TransmitFile (used below) "the + // transmission of data starts at the current offset in the file" (which of + // course is why we set the file offset immediately above). The Windows + // documentation is silent on whether the file offset is updated by + // TransmitFile. + // + // Note: Per Linux documentation: + // 1/ If offset ptr is NULL, then data will be read from in_fd starting + // at the file offset, and the file offset will be updated by the call. + // 2/ If offset ptr is NOT NULL, then sendfile() will start reading data + // from *offset in in_fd. When sendfile() returns, offset will be set + // to the offset of the byte following the last byte that was read, and + // sendfile() does NOT modify the file offset of in_fd. + + HANDLE in_fd_handle = reinterpret_cast(_get_osfhandle(in_fd)); + if (in_fd_handle == INVALID_HANDLE_VALUE) + { + PS_LOG_INFO_ARGS("Invalid file descriptor %d", in_fd); + // _get_osfhandle will already have set errno = EBADF + return(-1); + } + + BOOL res = TransmitFile(out_fd, in_fd_handle, + static_cast(count), // 0 => whole file + 0, // nNumberOfBytesPerSend => use default + nullptr, // no "overlapped" + nullptr, // lpTransmitBuffers => no pre/suffix buffs + 0); // flags + + DWORD num_bytes_transferred = 0; + int last_err = -1; + + if (res) + { + __int64 final_file_pos = in_fd_end_fl_pos; + if (count) + final_file_pos = std::min(offs_to_start+static_cast(count), + static_cast(in_fd_end_fl_pos)); + + num_bytes_transferred = + static_cast(final_file_pos - offs_to_start); + } + else + { + last_err = WSAGetLastError(); + PS_LOG_INFO_ARGS("TransmitFile failed, WSAGetLastError %d", + last_err); + errno = EIO; + return(-1); + } + + if (offset) + { + if (_lseeki64(in_fd, in_fd_start_pos, SEEK_SET) < 0) + { + // If offset is non-null, sendfile is not supposed to affect file + // position + PS_LOG_INFO("lseek error"); + errno = EIO; + return(-1); + } + *offset = (offs_to_start + num_bytes_transferred); + } + else + { + if (_lseeki64(in_fd, offs_to_start+num_bytes_transferred, SEEK_SET)< 0) + { + // If offset ptr is null, sendfile should make the file offset be + // immediately after the data that was read from the file + PS_LOG_INFO("lseek error"); + errno = EIO; + return(-1); + } + } + + return(num_bytes_transferred); +} + +/* ------------------------------------------------------------------------- */ + +#elif defined(_IS_BSD) + +/* ------------------------------------------------------------------------- */ + +// This is the sendfile function prototype found in Linux. However, sendfile +// does not exist in OpenBSD, so we make our own. +// +// https://www.man7.org/linux/man-pages/man2/sendfile.2.html +// Copies FROM "in_fd" TO "out_fd" Returns number of bytes written on success, +// -1 with errno set on error +// +// If offset is not NULL, then sendfile() does not modify the file offset of +// in_fd; otherwise the file offset is adjusted to reflect the number of bytes +// read from in_fd. +extern "C" +PST_SSIZE_T ps_sendfile(int out_fd, int in_fd, off_t* offset, size_t count) +{ + char buff[65536 + 16]; // 64KB is an efficient size for read/write + // blocks in most storage systems. The 16 bytes + // is to reduce risk of buffer overflow in the + // events of a bug. + + int read_errors = 0; + int write_errors = 0; + PST_SSIZE_T bytes_written_res = 0; + + off_t in_fd_start_pos = -1; + + if (offset) + { + in_fd_start_pos = lseek(in_fd, 0, SEEK_CUR); + if (in_fd_start_pos < 0) + { + PS_LOG_DEBUG("lseek error"); + return (in_fd_start_pos); + } + + if (lseek(in_fd, *offset, SEEK_SET) < 0) + { + PS_LOG_DEBUG("lseek error"); + return (-1); + } + } + + for (;;) + { + size_t bytes_to_read = count ? std::min(sizeof(buff) - 16, count) : (sizeof(buff) - 16); + + PST_SSIZE_T bytes_read = read(in_fd, &(buff[0]), bytes_to_read); + if (bytes_read == 0) // End of file + break; + + if (bytes_read < 0) + { + if ((errno == EINTR) || (errno == EAGAIN)) + { + PS_LOG_DEBUG("read-interrupted error"); + + ++read_errors; + if (read_errors < 256) + continue; + + PS_LOG_DEBUG("read-interrupted repeatedly error"); + errno = EIO; + } + + bytes_written_res = -1; + break; + } + read_errors = 0; + + bool re_adjust_pos = false; + + if ((count) && (bytes_read > ((PST_SSIZE_T)count))) + { + bytes_read = ((PST_SSIZE_T)count); + re_adjust_pos = true; + } + + if (offset) + { + *offset += bytes_read; + if (re_adjust_pos) + lseek(in_fd, *offset, SEEK_SET); + } + + auto p = &(buff[0]); + while (bytes_read > 0) + { + PST_SSIZE_T bytes_written = write(out_fd, p, bytes_read); + if (bytes_written <= 0) + { + if ((bytes_written == 0) || (errno == EINTR) || (errno == EAGAIN)) + { + PS_LOG_DEBUG("write-interrupted error"); + + ++write_errors; + if (write_errors < 256) + continue; + + PS_LOG_DEBUG("write-interrupted repeatedly error"); + errno = EIO; + } + + bytes_written_res = -1; + break; + } + write_errors = 0; + + bytes_read -= bytes_written; + p += bytes_written; + bytes_written_res += bytes_written; + } + + if (count) + count -= bytes_read; + } + + // if offset non null, set in_fd file pos to pos from start of this + // function + if ((offset) && (bytes_written_res >= 0) && (lseek(in_fd, in_fd_start_pos, SEEK_SET) < 0)) + { + PS_LOG_DEBUG("lseek error"); + bytes_written_res = -1; + } + + return (bytes_written_res); +} + +/* ------------------------------------------------------------------------- */ + +#else +// Presumably macOS or Linux here +#include + +#ifdef __linux__ +#include +#endif + +/* ------------------------------------------------------------------------- */ + +#endif // of ifdef _IS_WINDOWS... elif defined(_IS_BSD)... else... diff --git a/src/common/ps_strl.cc b/src/common/ps_strl.cc new file mode 100644 index 000000000..d5473d8b2 --- /dev/null +++ b/src/common/ps_strl.cc @@ -0,0 +1,124 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Defines a ps_strlcpy and ps_strlcat for OS that do not have strlcpy/strlcat +// natively, including Windows and (some) Linux + +#include + +/* ------------------------------------------------------------------------- */ + +#include +#include // for memcpy +#include +#include // for std::min + +#if defined(_IS_WINDOWS) || defined(__linux__) + +/* ------------------------------------------------------------------------- */ + +extern "C" size_t ps_strlcpy(char *dst, const char *src, size_t n) +{ + if ((!dst) || (!src) || (!n)) + return(0); + + if (n == 1) + { + dst[0] = 0; + return(0); + } + + size_t bytes_to_copy = (strlen(src)+1); + if (bytes_to_copy > n) + { + bytes_to_copy = n-1; + + std::memcpy(dst, src, bytes_to_copy); + dst[bytes_to_copy] = 0; + } + else + { + std::memcpy(dst, src, bytes_to_copy); + } + + return(bytes_to_copy); +} + +extern "C" size_t ps_strlcat(char *dst, const char *src, size_t n) +{ + if ((!dst) || (!src) || (n <= 1)) + return(0); + + const size_t dst_len= strlen(dst); + if ((n+1) <= dst_len) + return(dst_len); // no space to add onto dst + + const size_t len_added = ps_strlcpy(dst+dst_len, src, (n - dst_len)); + return(dst_len+len_added); +} + + +/* ------------------------------------------------------------------------- */ + +#endif // of #if defined(_IS_WINDOWS) || defined(__linux__) + +/* ------------------------------------------------------------------------- */ + + +/* ------------------------------------------------------------------------- */ + +// ps_strncpy_s returns 0 for success, -1 on failure with errno set. NB: This +// is different from C++ standard (Annex K) strncpy_s which returns an errno_t +// on failure; we diverge because errno_t is often not defined on non-Windows +// systems. If the copy would result in a truncation, errno is set to +// PS_ESTRUNCATE. +extern "C" int ps_strncpy_s(char *strDest, size_t numberOfElements, + const char *strSource, size_t count) +{ +#ifdef _IS_WINDOWS + errno_t win_strncpy_s_res = strncpy_s(strDest, numberOfElements, + strSource, count); + if (win_strncpy_s_res == 0) + return(0); // success + + if (win_strncpy_s_res == STRUNCATE) + { + errno = PS_ESTRUNCATE; + return(-1); + } + + errno = win_strncpy_s_res; + return(-1); +#else // i.e. NOT Windows + if ((!strDest) || (!strSource)) + { + errno = EINVAL; + return(-1); + } + + size_t non_null_bytes_to_copy = std::min(strlen(strSource), count); + if (non_null_bytes_to_copy >= numberOfElements) + { + errno = PS_ESTRUNCATE; + return(-1); + } + + std::memcpy(strDest, strSource, non_null_bytes_to_copy); + strDest[non_null_bytes_to_copy] = 0; + return(0); + +#endif // of #ifdef _IS_WINDOWS +} + + + + + +/* ------------------------------------------------------------------------- */ + + + +/* ------------------------------------------------------------------------- */ diff --git a/src/common/reactor.cc b/src/common/reactor.cc index c94b39852..c1b538cf4 100644 --- a/src/common/reactor.cc +++ b/src/common/reactor.cc @@ -25,12 +25,69 @@ #ifdef _IS_BSD // For pthread_set_name_np -#include +#include PIST_QUOTE(PST_THREAD_HDR) #ifndef __NetBSD__ #include #endif #endif +#ifdef _IS_WINDOWS +#include // Needed for PST_THREAD_HDR (processthreadsapi.h) +#include PIST_QUOTE(PST_THREAD_HDR) // for SetThreadDescription +#endif + +#ifdef _IS_WINDOWS +static std::atomic_bool lLoggedSetThreadDescriptionFail = false; +#ifdef __MINGW32__ + +#include +#include // for GetProcAddress and GetModuleHandleA +typedef HRESULT (WINAPI *TSetThreadDescription)(HANDLE, PCWSTR); + +static std::atomic_bool lSetThreadDescriptionLoaded = false; +static std::mutex lSetThreadDescriptionLoadMutex; +static TSetThreadDescription lSetThreadDescriptionPtr = nullptr; + +TSetThreadDescription getSetThreadDescriptionPtr() +{ + if (lSetThreadDescriptionLoaded) + return(lSetThreadDescriptionPtr); + + GUARD_AND_DBG_LOG(lSetThreadDescriptionLoadMutex); + if (lSetThreadDescriptionLoaded) + return(lSetThreadDescriptionPtr); + + HMODULE hKernelBase = GetModuleHandleA("KernelBase.dll"); + + if (!hKernelBase) + { + PS_LOG_WARNING( + "Failed to get KernelBase.dll for SetThreadDescription"); + lSetThreadDescriptionLoaded = true; + return(nullptr); + } + + FARPROC set_thread_desc_fpptr = + GetProcAddress(hKernelBase, "SetThreadDescription"); + + // We do the cast in two steps, otherwise mingw-gcc complains about + // incompatible types + void * set_thread_desc_vptr = + reinterpret_cast(set_thread_desc_fpptr); + lSetThreadDescriptionPtr = + reinterpret_cast(set_thread_desc_vptr); + + lSetThreadDescriptionLoaded = true; + if (!lSetThreadDescriptionPtr) + { + PS_LOG_WARNING( + "Failed to get SetThreadDescription from KernelBase.dll"); + } + return(lSetThreadDescriptionPtr); +} +#endif // of ifdef __MINGW32__ +#endif // of ifdef _IS_WINDOWS + using namespace std::string_literals; namespace Pistache::Aio @@ -122,7 +179,7 @@ namespace Pistache::Aio PS_TIMEDBG_START_THIS; handler->unregisterPoller(poller); - handler->reactor_ = NULL; + handler->reactor_ = nullptr; } void detachAndRemoveAllHandlers() override @@ -140,7 +197,7 @@ namespace Pistache::Aio std::shared_ptr handler(const Reactor::Key& key) const { - return handlers_.at(key.data()); + return handlers_.at(static_cast(key.data())); } std::vector> @@ -161,13 +218,13 @@ namespace Pistache::Aio ss << fd; str += ss.str(); - if (((unsigned int)interest) & ((unsigned int)Polling::NotifyOn::Read)) + if ((static_cast(interest)) & (static_cast(Polling::NotifyOn::Read))) str += " read"; - if (((unsigned int)interest) & ((unsigned int)Polling::NotifyOn::Write)) + if ((static_cast(interest)) & (static_cast(Polling::NotifyOn::Write))) str += " write"; - if (((unsigned int)interest) & ((unsigned int)Polling::NotifyOn::Hangup)) + if ((static_cast(interest)) & (static_cast(Polling::NotifyOn::Hangup))) str += " hangup"; - if (((unsigned int)interest) & ((unsigned int)Polling::NotifyOn::Shutdown)) + if ((static_cast(interest)) & (static_cast(Polling::NotifyOn::Shutdown))) str += " shutdown"; PS_LOG_DEBUG_ARGS("%s", str.c_str()); @@ -218,11 +275,14 @@ namespace Pistache::Aio void runOnce() override { + PS_TIMEDBG_START; + if (handlers_.empty()) throw std::runtime_error("You need to set at least one handler"); for (;;) { + PS_TIMEDBG_START; { // encapsulate l_guard(poller.reg_unreg_mutex_) // See comment in class Epoll regarding reg_unreg_mutex_ @@ -240,10 +300,16 @@ namespace Pistache::Aio case 0: break; default: - if (shutdown_) - return; + { + if (shutdown_) + return; - handleFds(std::move(events)); + GUARD_AND_DBG_LOG(shutdown_mutex_); + if (shutdown_) + return; + + handleFds(std::move(events)); + } } } } @@ -251,6 +317,8 @@ namespace Pistache::Aio void run() override { + PS_TIMEDBG_START; + // Note: poller_reg_unreg_mutex is already locked (by // Listener::run()) before calling here, so it is safe to call // handlers_.forEachHandler here @@ -260,7 +328,10 @@ namespace Pistache::Aio }); while (!shutdown_) + { + PS_TIMEDBG_START; runOnce(); + } } void shutdown() override @@ -268,6 +339,8 @@ namespace Pistache::Aio PS_TIMEDBG_START_THIS; shutdown_.store(true); + + GUARD_AND_DBG_LOG(shutdown_mutex_); shutdownFd.notify(); } @@ -352,7 +425,7 @@ namespace Pistache::Aio void removeAll() { index_ = 0; - handlers.fill(NULL); + handlers.fill(nullptr); } // poller.reg_unreg_mutex_ must be locked before calling @@ -381,8 +454,11 @@ namespace Pistache::Aio // encode the index of the handler is that in the fast path, we // won't need to shift the value to retrieve the fd if there is // only one handler as all the bits will already be set to 0. - auto encodedValue = (index << HandlerShift) | ((uint64_t)value); - Polling::TagValue encodedValueTV = ((Polling::TagValue)encodedValue); + auto encodedValue = + (index << HandlerShift) | + PS_FD_CAST_TO_UNUM(uint64_t, static_cast(value)); + Polling::TagValue encodedValueTV = + static_cast(PS_NUM_CAST_TO_FD(encodedValue)); return Polling::Tag(encodedValueTV); } @@ -392,7 +468,8 @@ namespace Pistache::Aio auto value = tag.valueU64(); size_t index = value >> HandlerShift; uint64_t maskedValue = value & DataMask; - Polling::TagValue maskedValueTV = ((Polling::TagValue)maskedValue); + Polling::TagValue maskedValueTV = + static_cast(PS_NUM_CAST_TO_FD(maskedValue)); return std::make_pair(index, maskedValueTV); } @@ -412,6 +489,7 @@ namespace Pistache::Aio HandlerList handlers_; + std::mutex shutdown_mutex_; std::atomic shutdown_; NotifyFd shutdownFd; @@ -453,17 +531,21 @@ namespace Pistache::Aio size_t threads, const std::string& threadsName) : Reactor::Impl(reactor) { + PS_TIMEDBG_START_THIS; if (threads > SyncImpl::MaxHandlers()) throw std::runtime_error("Too many worker threads requested (max "s + std::to_string(SyncImpl::MaxHandlers()) + ")."s); for (size_t i = 0; i < threads; ++i) workers_.emplace_back(std::make_unique(reactor, threadsName)); + PS_LOG_DEBUG_ARGS("threads %d, workers_.size() %d", + threads, workers_.size()); } Reactor::Key addHandler(const std::shared_ptr& handler, bool) override { + PS_TIMEDBG_START_THIS; std::array keys; @@ -529,6 +611,8 @@ namespace Pistache::Aio Polling::Tag tag, Polling::Mode mode = Polling::Mode::Level) override { + PS_TIMEDBG_START_THIS; + dispatchCall(key, &SyncImpl::registerFd, fd, interest, tag, mode); } @@ -536,6 +620,8 @@ namespace Pistache::Aio Polling::NotifyOn interest, Polling::Tag tag, Polling::Mode mode = Polling::Mode::Level) override { + PS_TIMEDBG_START_THIS; + dispatchCall(key, &SyncImpl::registerFdOneShot, fd, interest, tag, mode); } @@ -543,12 +629,14 @@ namespace Pistache::Aio Polling::Tag tag, Polling::Mode mode = Polling::Mode::Level) override { + PS_TIMEDBG_START_THIS; + dispatchCall(key, &SyncImpl::modifyFd, fd, interest, tag, mode); } void removeFd(const Reactor::Key& key, Fd fd) override { - PS_TIMEDBG_START_ARGS("Reactor %p, Fd %" PIST_QUOTE(PS_FD_PRNTFCD), + PS_TIMEDBG_START_ARGS("this %p, Fd %" PIST_QUOTE(PS_FD_PRNTFCD), this, fd); dispatchCall(key, &SyncImpl::removeFd, fd); } @@ -590,10 +678,14 @@ namespace Pistache::Aio template void dispatchCall(const Reactor::Key& key, Func func, Args&&... args) const { + PS_TIMEDBG_START_THIS; + PS_LOG_DEBUG_ARGS("workers_.size() %d", workers_.size()); + auto decoded = decodeKey(key); const auto& wrk = workers_.at(decoded.second); Reactor::Key originalKey(decoded.first); + CALL_MEMBER_FN(wrk->sync.get(), func) (originalKey, std::forward(args)...); } @@ -617,9 +709,40 @@ namespace Pistache::Aio void run() { + PS_TIMEDBG_START; + thread = std::thread([=]() { + PS_TIMEDBG_START; + if (!threadsName_.empty()) { + PS_LOG_DEBUG("Setting thread name/description"); +#ifdef _IS_WINDOWS + const std::string threads_name(threadsName_.substr(0, 15)); + const std::wstring temp(threads_name.begin(), + threads_name.end()); + const LPCWSTR wide_threads_name = temp.c_str(); + + HRESULT hr = E_NOTIMPL; +#ifdef __MINGW32__ + TSetThreadDescription set_thread_description_ptr = + getSetThreadDescriptionPtr(); + if (set_thread_description_ptr) + { + hr = set_thread_description_ptr( + GetCurrentThread(), wide_threads_name); + } +#else + hr = SetThreadDescription(GetCurrentThread(), + wide_threads_name); +#endif + if ((FAILED(hr)) && (!lLoggedSetThreadDescriptionFail)) + { + lLoggedSetThreadDescriptionFail = true; + // Log it just once + PS_LOG_INFO("SetThreadDescription failed"); + } +#else #if defined _IS_BSD && !defined __NetBSD__ pthread_set_name_np( #else @@ -643,7 +766,9 @@ namespace Pistache::Aio #endif threadsName_.substr(0, 15) .c_str()); +#endif // of ifdef _IS_WINDOWS... else... } + PS_LOG_DEBUG("Calling sync->run()"); sync->run(); }); } diff --git a/src/common/stream.cc b/src/common/stream.cc index 2bc6bc535..2a2350f15 100644 --- a/src/common/stream.cc +++ b/src/common/stream.cc @@ -9,6 +9,8 @@ */ +#include + #include #include @@ -16,10 +18,14 @@ #include #include -#include +#include // Needs this as well in Windows for file-open constants + +#include PIST_QUOTE(PST_FCNTL_HDR) + #include #include -#include +#include PIST_QUOTE(PST_MISC_IO_HDR) // unistd.h e.g. close +#include PIST_QUOTE(PIST_FILEFNS_HDR) // PST_FILE_OPEN namespace Pistache { @@ -66,7 +72,7 @@ namespace Pistache throw std::runtime_error("Empty fileName"); } - int fd = open(fileName.c_str(), O_RDONLY); + int fd = PST_FILE_OPEN(fileName.c_str(), PST_O_RDONLY); if (fd == -1) { throw std::runtime_error("Could not open file"); @@ -76,7 +82,7 @@ namespace Pistache int res = ::fstat(fd, &sb); if (res == -1) { - close(fd); + PST_FILE_CLOSE(fd); throw std::runtime_error("Could not get file stats"); } @@ -163,7 +169,7 @@ namespace Pistache bool StreamCursor::advance(size_t count) { - if (static_cast(count) > buf->in_avail()) + if (static_cast(count) > buf->in_avail()) return false; for (size_t i = 0; i < count; ++i) @@ -205,7 +211,7 @@ namespace Pistache return other.buf->position() - buf->position(); } - size_t StreamCursor::remaining() const { return buf->in_avail(); } + size_t StreamCursor::remaining() const {return static_cast(buf->in_avail());} void StreamCursor::reset() { buf->reset(); } diff --git a/src/common/string_logger.cc b/src/common/string_logger.cc index 89a0bad16..826636304 100644 --- a/src/common/string_logger.cc +++ b/src/common/string_logger.cc @@ -26,7 +26,7 @@ namespace Pistache::Log (*out_) << message << std::endl; // Save in syslog / os_log as well - PSLogNoLocFn((int)level, + PSLogNoLocFn(static_cast(level), false, // Don't send to stdout - just did that "%s", message.c_str()); } diff --git a/src/common/timer_pool.cc b/src/common/timer_pool.cc index 404418dfa..57a19a5af 100644 --- a/src/common/timer_pool.cc +++ b/src/common/timer_pool.cc @@ -55,9 +55,9 @@ namespace Pistache { #ifdef _USE_LIBEVENT fd_ = TRY_NULL_RET(EventMethFns::em_timer_new( - CLOCK_MONOTONIC, - F_SETFDL_NOTHING, O_NONBLOCK, - NULL /* EventMethEpollEquiv ptr */)); + PST_CLOCK_MONOTONIC, + F_SETFDL_NOTHING, PST_O_NONBLOCK, + nullptr /* EventMethEpollEquiv ptr */)); // The EventMethEpollEquiv ptr gets set // later, when // TimerPool::Entry::registerReactor is @@ -79,7 +79,7 @@ namespace Pistache } #ifdef _USE_LIBEVENT - TRY(EventMethFns::setEmEventTime(fd_, NULL)); + TRY(EventMethFns::setEmEventTime(fd_, nullptr)); #else itimerspec spec; spec.it_interval.tv_sec = 0; diff --git a/src/common/transport.cc b/src/common/transport.cc index 8af245bf3..dd5bdd383 100644 --- a/src/common/transport.cc +++ b/src/common/transport.cc @@ -14,20 +14,27 @@ #include #ifdef _USE_LIBEVENT_LIKE_APPLE -// For sendfile(...) function -#include -#include +#ifdef __NetBSD__ +// For TCP_NODELAY +#include +#include +#include +#else +#ifndef _IS_WINDOWS +// There is no TCP_NOPUSH/TCP_CORK in Windows or NetBSD #include // for TCP_NOPUSH #endif +#endif // of ifdef __NetBSD__ ... else ... -#ifdef __linux__ -#include -#endif +#endif // of ifdef _USE_LIBEVENT_LIKE_APPLE -#ifdef _IS_BSD -#include // for lseek -#endif +// ps_sendfile.h includes sys/uio.h in macOS, and sys/sendfile.h in Linux +#include + +#include PIST_QUOTE(PST_MISC_IO_HDR) // unistd.h/lseek in BSD. + +#include PIST_QUOTE(PIST_SOCKFNS_HDR) // socket read, write and close #ifndef _USE_LIBEVENT_LIKE_APPLE // Note: sys/timerfd.h is linux-only (and certainly POSIX only) @@ -42,6 +49,12 @@ using std::to_string; +#ifdef _USE_LIBEVENT_LIKE_APPLE +#if defined(__NetBSD__) || defined(_IS_WINDOWS) +#define PS_USE_TCP_NODELAY 1 +#endif +#endif + namespace Pistache::Tcp { using namespace Polling; @@ -105,7 +118,7 @@ namespace Pistache::Tcp PS_TIMEDBG_START_THIS; #ifdef _USE_LIBEVENT - epoll_fd = NULL; + epoll_fd = nullptr; #endif notifier.unbind(poller); @@ -118,13 +131,17 @@ namespace Pistache::Tcp { auto ctx = context(); const bool isInRightThread = std::this_thread::get_id() == ctx.thread(); + if (!isInRightThread) { + PS_LOG_DEBUG("Pushing to peersQueue"); + PeerEntry entry(peer); peersQueue.push(std::move(entry)); } else { + PS_LOG_DEBUG("Not pushing to peersQueue, handling directly"); handlePeer(peer); } @@ -148,11 +165,11 @@ namespace Pistache::Tcp std::string str("fd "); std::stringstream ss; - ss << ((Fd)entry.getTag().value()); + ss << (PS_NUM_CAST_TO_FD(entry.getTag().value())); str += ss.str(); #endif - const char* flag_str = NULL; + const char* flag_str = nullptr; if (entry.isReadable()) flag_str = " readable"; @@ -205,7 +222,7 @@ namespace Pistache::Tcp { auto tag = entry.getTag(); PS_LOG_DEBUG_ARGS("entry isReadable fd %" PIST_QUOTE(PS_FD_PRNTFCD), - ((Fd)tag.value())); + tag.value()); // TagValue type := Fd if (isPeerFd(tag)) { @@ -239,7 +256,7 @@ namespace Pistache::Tcp FdConst fdconst = static_cast(tag.value()); // Since fd is about to be written to, it isn't really const, // and we cast away the const - Fd fd = ((Fd)fdconst); + Fd fd = PS_CAST_AWAY_CONST_FD(fdconst); { Guard guard(toWriteLock); @@ -282,8 +299,8 @@ namespace Pistache::Tcp char buffer[Const::MaxBuffer] = { 0 }; - ssize_t totalBytes = 0; - int fdactual = peer->actualFd(); + PST_SSIZE_T totalBytes = 0; + em_socket_t fdactual = peer->actualFd(); if (fdactual < 0) { PS_LOG_DEBUG_ARGS("Peer %p has no actual Fd", peer.get()); @@ -293,31 +310,34 @@ namespace Pistache::Tcp for (;;) { - ssize_t bytes; + PST_SSIZE_T bytes; #ifdef PISTACHE_USE_SSL - if (peer->ssl() != NULL) + if (peer->ssl() != nullptr) { PS_LOG_DEBUG("SSL_read"); - bytes = SSL_read((SSL*)peer->ssl(), buffer + totalBytes, + bytes = SSL_read(reinterpret_cast(peer->ssl()), + buffer + totalBytes, static_cast(Const::MaxBuffer - totalBytes)); } else { #endif /* PISTACHE_USE_SSL */ PS_LOG_DEBUG("recv (read)"); - bytes = recv(fdactual, buffer + totalBytes, Const::MaxBuffer - totalBytes, 0); + bytes = PST_SOCK_READ(fdactual, buffer + totalBytes, + Const::MaxBuffer - totalBytes); #ifdef PISTACHE_USE_SSL } #endif /* PISTACHE_USE_SSL */ + PST_DBG_DECL_SE_ERR_P_EXTRA; PS_LOG_DEBUG_ARGS("Fd %" PIST_QUOTE(PS_FD_PRNTFCD) ", " "bytes read %d, totalBytes %d, " "err %d %s", peer->fd(), bytes, totalBytes, (bytes < 0) ? errno : 0, - (bytes < 0) ? strerror(errno) : ""); + (bytes < 0) ? (PST_STRERROR_R_ERRNO) : ""); if (bytes == -1) { @@ -460,8 +480,8 @@ namespace Pistache::Tcp #ifdef _USE_LIBEVENT_LIKE_APPLE bool msg_more_style = entry.msg_more_style; #endif - BufferHolder& buffer = entry.buffer; - Async::Deferred deferred = std::move(entry.deferred); + BufferHolder& buffer = entry.buffer; + Async::Deferred deferred = std::move(entry.deferred); auto cleanUp = [&]() { wq.pop_front(); @@ -478,8 +498,8 @@ namespace Pistache::Tcp size_t totalWritten = buffer.offset(); for (;;) { - ssize_t bytesWritten = 0; - auto len = buffer.size() - totalWritten; + PST_SSIZE_T bytesWritten = 0; + auto len = buffer.size() - totalWritten; if (buffer.isRaw()) { @@ -501,17 +521,18 @@ namespace Pistache::Tcp fd, len); auto file = buffer.fd(); - off_t offset = totalWritten; + off_t offset = static_cast(totalWritten); bytesWritten = sendFile(fd, file, offset, len); } if (bytesWritten < 0) { + PST_DBG_DECL_SE_ERR_P_EXTRA; PS_LOG_DEBUG_ARGS("fd %" PIST_QUOTE(PS_FD_PRNTFCD) " errno %d %s", - fd, errno, strerror(errno)); + fd, errno, PST_STRERROR_R_ERRNO); if (errno == EAGAIN || errno == EWOULDBLOCK) { - auto bufferHolder = buffer.detach(totalWritten); + auto bufferHolder = buffer.detach(static_cast(totalWritten)); // pop_front kills buffer - so we cannot continue loop or use buffer // after this point @@ -558,14 +579,14 @@ namespace Pistache::Tcp // done with the file buffer, nothing else knows // whether to close it with the way the code is // written. - ::close(buffer.fd()); + PST_FILE_CLOSE(buffer.fd()); } cleanUp(); // Cast to match the type of defered template // to avoid a BadType exception - deferred.resolve(static_cast(totalWritten)); + deferred.resolve(static_cast(totalWritten)); break; } } @@ -576,15 +597,17 @@ namespace Pistache::Tcp #ifdef _USE_LIBEVENT_LIKE_APPLE void Transport::configureMsgMoreStyle(Fd fd, bool msg_more_style) { + // PS_USE_TCP_NODELAY defined (or not) at top of file + int tcp_no_push = 0; socklen_t len = sizeof(tcp_no_push); int sock_opt_res = -1; -#ifdef __NetBSD__ +#ifdef PS_USE_TCP_NODELAY { // encapsulate - int tcp_nodelay = 0; - sock_opt_res = getsockopt(GET_ACTUAL_FD(fd), tcp_prot_num_, - TCP_NODELAY, &tcp_nodelay, &len); + PST_SOCK_OPT_VAL_T tcp_nodelay = 0; + sock_opt_res = getsockopt(GET_ACTUAL_FD(fd), tcp_prot_num_, + TCP_NODELAY, &tcp_nodelay, &len); if (sock_opt_res == 0) tcp_no_push = !tcp_nodelay; } @@ -596,7 +619,7 @@ namespace Pistache::Tcp TCP_CORK, #endif &tcp_no_push, &len); -#endif // of ifdef __NetBSD__ ... else +#endif // of if defined(__NetBSD__) || defined(_IS_WINDOWS) ... else if (sock_opt_res == 0) { @@ -605,8 +628,8 @@ namespace Pistache::Tcp PS_LOG_DEBUG_ARGS("Setting MSG_MORE style to %s", (msg_more_style) ? "on" : "off"); - int optval = -#ifdef __NetBSD__ + PST_SOCK_OPT_VAL_T optval = +#ifdef PS_USE_TCP_NODELAY // In NetBSD case we're getting/setting (or resetting) the // TCP_NODELAY socket option, which _stops_ data being held // prior to send, whereas in Linux, macOS, FreeBSD or @@ -618,14 +641,13 @@ namespace Pistache::Tcp msg_more_style ? 1 : 0; #endif - tcp_no_push = msg_more_style ? 1 : 0; sock_opt_res = setsockopt(GET_ACTUAL_FD(fd), tcp_prot_num_, -#ifdef __NetBSD__ +#ifdef PS_USE_TCP_NODELAY TCP_NODELAY, #elif defined __APPLE__ || defined _IS_BSD TCP_NOPUSH, #else - TCP_CORK, + TCP_CORK, #endif &optval, len); if (sock_opt_res < 0) @@ -635,29 +657,32 @@ namespace Pistache::Tcp else { PS_LOG_DEBUG_ARGS("MSG_MORE style is already %s", - (tcp_no_push) ? "on" : "off"); + (msg_more_style ? 1 : 0) ? "on" : "off"); } #endif } else { + PST_DBG_DECL_SE_ERR_P_EXTRA; PS_LOG_DEBUG_ARGS("getsockopt failed for fd %p, actual fd %d, " "errno %d, err %s", - fd, GET_ACTUAL_FD(fd), errno, strerror(errno)); + fd, GET_ACTUAL_FD(fd), errno, + PST_STRERROR_R_ERRNO); + throw std::runtime_error("getsockopt failed"); } } #endif // of ifdef _USE_LIBEVENT_LIKE_APPLE - ssize_t Transport::sendRawBuffer(Fd fd, const char* buffer, size_t len, - int flags + PST_SSIZE_T Transport::sendRawBuffer(Fd fd, const char* buffer, size_t len, + int flags #ifdef _USE_LIBEVENT_LIKE_APPLE - , - bool msg_more_style + , + bool msg_more_style #endif ) { - ssize_t bytesWritten = 0; + PST_SSIZE_T bytesWritten = 0; #ifdef PISTACHE_USE_SSL bool it_second_ssl_is_null = false; @@ -672,12 +697,12 @@ namespace Pistache::Tcp throw std::runtime_error( "No peer found for fd: " + to_string(fd)); - it_second_ssl_is_null = (it_->second->ssl() == NULL); + it_second_ssl_is_null = (it_->second->ssl() == nullptr); if (!it_second_ssl_is_null) { auto ssl_ = static_cast(it_->second->ssl()); - PS_LOG_DEBUG_ARGS("SSL_write, len %d", (int)len); + PS_LOG_DEBUG_ARGS("SSL_write, len %d", static_cast(len)); bytesWritten = SSL_write(ssl_, buffer, static_cast(len)); } @@ -686,19 +711,51 @@ namespace Pistache::Tcp if (it_second_ssl_is_null) { #endif /* PISTACHE_USE_SSL */ - // MSG_NOSIGNAL is used to prevent SIGPIPE on client connection termination +#ifdef PS_USE_TCP_NODELAY + // Note re: TCP_NODELAY. Per the Linux tcp man page, "setting this + // option forces an explicit flush of pending output". However, we + // don't want the waiting content to be sent until after the + // _current_ send, which can then be included in the data being + // flushed; i.e. we want to send any already-pending output, plus + // the new output we're adding here with "send", in one go. + // Accordingly, when TCP_NODELAY is used, if we are turning + // TCP_NODELAY to OFF (i.e. msg_more_style is true), we want to do + // so _before_ calling send; but if we are turning it ON, we want + // to do so _after_ calling send. + if (msg_more_style) +#endif #ifdef _USE_LIBEVENT_LIKE_APPLE - configureMsgMoreStyle(fd, msg_more_style); + configureMsgMoreStyle(fd, msg_more_style); #endif PS_LOG_DEBUG_ARGS("::send, fd %" PIST_QUOTE(PS_FD_PRNTFCD) ", actual_fd %d, len %d", - fd, GET_ACTUAL_FD(fd), (int)len); + fd, GET_ACTUAL_FD(fd), static_cast(len)); - bytesWritten = ::send(GET_ACTUAL_FD(fd), buffer, len, - flags | MSG_NOSIGNAL); + bytesWritten = +#ifdef _IS_WINDOWS + // Comparing with PST_SOCK_SEND below, there's no SIGPIPE in + // Windows and MSG_NOSIGNAL is not defined in Windows + PST_SOCK_SEND(GET_ACTUAL_FD(fd), buffer, len, flags); +#else + PST_SOCK_SEND(GET_ACTUAL_FD(fd), buffer, len, + flags | MSG_NOSIGNAL); + // MSG_NOSIGNAL is used to prevent SIGPIPE on client connection + // termination +#endif +#ifdef _USE_LIBEVENT_LIKE_APPLE + PS_LOG_DEBUG_ARGS("bytesWritten = %d, msg_more_style = %s", + bytesWritten, msg_more_style ? "on" : "off"); +#else + PS_LOG_DEBUG_ARGS("bytesWritten = %d", bytesWritten); +#endif - PS_LOG_DEBUG_ARGS("bytesWritten = %d", bytesWritten); +#ifdef PS_USE_TCP_NODELAY + // See comment above on why configureMsgMoreStyle is done after + // "send" in TCP_NODELAY case. + if (!msg_more_style) + configureMsgMoreStyle(fd, msg_more_style); +#endif #ifdef PISTACHE_USE_SSL } @@ -708,138 +765,14 @@ namespace Pistache::Tcp } #ifdef _IS_BSD - // This is the sendfile function prototype found in Linux. However, - // sendfile does not exist in OpenBSD, so we make our own. - // - // https://www.man7.org/linux/man-pages/man2/sendfile.2.html - // Copies FROM "in_fd" TO "out_fd" - // Returns number of bytes written on success, -1 with errno set on error - // - // If offset is not NULL, then sendfile() does not modify the file offset - // of in_fd; otherwise the file offset is adjusted to reflect the number of - // bytes read from in_fd. - ssize_t my_sendfile(int out_fd, int in_fd, off_t* offset, size_t count) - { - char buff[65536 + 16]; // 64KB is an efficient size for read/write - // blocks in most storage systems. The 16 bytes - // is to reduce risk of buffer overflow in the - // events of a bug. - - int read_errors = 0; - int write_errors = 0; - ssize_t bytes_written_res = 0; - - off_t in_fd_start_pos = -1; - - if (offset) - { - in_fd_start_pos = lseek(in_fd, 0, SEEK_CUR); - if (in_fd_start_pos < 0) - { - PS_LOG_DEBUG("lseek error"); - return (in_fd_start_pos); - } - - if (lseek(in_fd, *offset, SEEK_SET) < 0) - { - PS_LOG_DEBUG("lseek error"); - return (-1); - } - } - - for (;;) - { - size_t bytes_to_read = count ? std::min(sizeof(buff) - 16, count) : (sizeof(buff) - 16); - - ssize_t bytes_read = read(in_fd, &(buff[0]), bytes_to_read); - if (bytes_read == 0) // End of file - break; - - if (bytes_read < 0) - { - if ((errno == EINTR) || (errno == EAGAIN)) - { - PS_LOG_DEBUG("read-interrupted error"); - - read_errors++; - if (read_errors < 256) - continue; - - PS_LOG_DEBUG("read-interrupted repeatedly error"); - errno = EIO; - } - - bytes_written_res = -1; - break; - } - read_errors = 0; - - bool re_adjust_pos = false; - - if ((count) && (bytes_read > ((ssize_t)count))) - { - bytes_read = ((ssize_t)count); - re_adjust_pos = true; - } - - if (offset) - { - *offset += bytes_read; - if (re_adjust_pos) - lseek(in_fd, *offset, SEEK_SET); - } - - auto p = &(buff[0]); - while (bytes_read > 0) - { - ssize_t bytes_written = write(out_fd, p, bytes_read); - if (bytes_written <= 0) - { - if ((bytes_written == 0) || (errno == EINTR) || (errno == EAGAIN)) - { - PS_LOG_DEBUG("write-interrupted error"); - - write_errors++; - if (write_errors < 256) - continue; - - PS_LOG_DEBUG("write-interrupted repeatedly error"); - errno = EIO; - } - - bytes_written_res = -1; - break; - } - write_errors = 0; - - bytes_read -= bytes_written; - p += bytes_written; - bytes_written_res += bytes_written; - } - - if (count) - count -= bytes_read; - } - - // if offset non null, set in_fd file pos to pos from start of this - // function - if ((offset) && (bytes_written_res >= 0) && (lseek(in_fd, in_fd_start_pos, SEEK_SET) < 0)) - { - PS_LOG_DEBUG("lseek error"); - bytes_written_res = -1; - } - - return (bytes_written_res); - } - -#define SENDFILE my_sendfile +#define SENDFILE my_sendfile #else #define SENDFILE ::sendfile #endif // ifdef _IS_BSD - ssize_t Transport::sendFile(Fd fd, int file, off_t offset, size_t len) + PST_SSIZE_T Transport::sendFile(Fd fd, int file, off_t offset, size_t len) { - ssize_t bytesWritten = 0; + PST_SSIZE_T bytesWritten = 0; #ifdef PISTACHE_USE_SSL bool it_second_ssl_is_null = false; @@ -858,7 +791,7 @@ namespace Pistache::Tcp throw std::runtime_error( "No peer found for fd: " + to_string(fd)); } - it_second_ssl_is_null = (it_->second->ssl() == NULL); + it_second_ssl_is_null = (it_->second->ssl() == nullptr); if (!it_second_ssl_is_null) { @@ -874,16 +807,19 @@ namespace Pistache::Tcp #endif /* PISTACHE_USE_SSL */ #ifdef DEBUG - const char* sendfile_fn_name = PIST_QUOTE(SENDFILE); + const char* sendfile_fn_name = PIST_QUOTE(PS_SENDFILE); #endif PS_LOG_DEBUG_ARGS( "%s fd %" PIST_QUOTE(PS_FD_PRNTFCD) " actual-fd %d, file fd %d, len %d", sendfile_fn_name, fd, GET_ACTUAL_FD(fd), file, len); -#ifdef _USE_LIBEVENT_LIKE_APPLE +#if defined(_USE_LIBEVENT_LIKE_APPLE) && !defined(PS_USE_TCP_NODELAY) + // See prior comment on why configureMsgMoreStyle is done after + // "send" in TCP_NODELAY case. + configureMsgMoreStyle(fd, false /*msg_more_style*/); + // !!!! Should we do configureMsgMoreStyle for SSL as well? And // same question in sendRawBuffer - configureMsgMoreStyle(fd, false /*msg_more_style*/); #endif #ifdef __APPLE__ @@ -898,14 +834,14 @@ namespace Pistache::Tcp // experimentation it appears sendfile does not advance the file // position of "file", which is the same as the behavior described // in Linux sendfile man page - int sendfile_res = ::sendfile(file, GET_ACTUAL_FD(fd), - offset, &len_as_off_t, - NULL, // no new prefix/suffix content - 0 /*reserved, must be zero*/); + int sendfile_res = PS_SENDFILE(file, GET_ACTUAL_FD(fd), + offset, &len_as_off_t, + nullptr, // no new prefix/suffix content + 0 /*reserved, must be zero*/); if (sendfile_res == 0) { - bytesWritten = (ssize_t)len_as_off_t; + bytesWritten = (PST_SSIZE_T)len_as_off_t; offset += len_as_off_t; // to match what Linux sendfile does } else @@ -914,7 +850,12 @@ namespace Pistache::Tcp } #else - bytesWritten = SENDFILE(GET_ACTUAL_FD(fd), file, &offset, len); + bytesWritten = PS_SENDFILE(GET_ACTUAL_FD(fd), file, &offset, len); +#endif +#ifdef PS_USE_TCP_NODELAY + // See prior comment on why configureMsgMoreStyle is done after + // "send" in TCP_NODELAY case. + configureMsgMoreStyle(fd, false /*msg_more_style*/); #endif PS_LOG_DEBUG_ARGS( @@ -1004,8 +945,9 @@ namespace Pistache::Tcp #endif if (res == -1) { - PS_LOG_DEBUG_ARGS("Fd %" PIST_QUOTE(PS_FD_PRNTFCD) ", ernno %d %s", - entry.fd, errno, strerror(errno)); + PST_DBG_DECL_SE_ERR_P_EXTRA; + PS_LOG_DEBUG_ARGS("Fd %" PIST_QUOTE(PS_FD_PRNTFCD) ", ernno %d %s", + entry.fd, errno, PST_STRERROR_R_ERRNO); entry.deferred.reject(Pistache::Error::system("Could not set timer time")); return; @@ -1109,12 +1051,13 @@ namespace Pistache::Tcp while (this->notifier.tryRead()) ; - rusage now; + PST_RUSAGE now; - auto res = getrusage( + auto res = PST_GETRUSAGE( #ifdef _USE_LIBEVENT_LIKE_APPLE - RUSAGE_SELF, // usage for whole process, not just current thread - // (macOS getrusage doesn't support RUSAGE_THREAD) + PST_RUSAGE_SELF, // usage for whole process, not just current + // thread (macOS getrusage doesn't support + // RUSAGE_THREAD) #else RUSAGE_THREAD, #endif @@ -1162,11 +1105,7 @@ namespace Pistache::Tcp { PS_TIMEDBG_START_THIS; - // Cast away const so we can lock the mutex. Nonetheless, this function - // overall locks then unlocks the mutex, leaving it unchanged - Transport* non_const_this = (Transport*)this; - - std::lock_guard l_guard(non_const_this->peers_mutex_); + std::lock_guard l_guard(peers_mutex_); return (isPeerFdNoPeersMutexLock(fdconst)); } @@ -1176,7 +1115,7 @@ namespace Pistache::Tcp PS_TIMEDBG_START_THIS; // Can cast away const since we're not actually going to change fd - Fd fd = ((Fd)fdconst); + Fd fd = PS_CAST_AWAY_CONST_FD(fdconst); return peers_.find(fd) != std::end(peers_); } @@ -1186,7 +1125,7 @@ namespace Pistache::Tcp PS_TIMEDBG_START_THIS; // Can cast away const since we're not actually going to change fd - Fd fd = ((Fd)fdconst); + Fd fd = PS_CAST_AWAY_CONST_FD(fdconst); bool res = (timers.find(fd) != std::end(timers)); PS_LOG_DEBUG_ARGS("Fd %" PIST_QUOTE(PS_FD_PRNTFCD) " %s in timers", @@ -1210,7 +1149,7 @@ namespace Pistache::Tcp PS_TIMEDBG_START_THIS; // Can cast away const since we're not actually going to change fd - Fd fd = ((Fd)fdconst); + Fd fd = PS_CAST_AWAY_CONST_FD(fdconst); // See comment in transport.h on why peers_ must be mutex-protected std::lock_guard l_guard(peers_mutex_); diff --git a/src/common/utils.cc b/src/common/utils.cc index 0c3c41c2d..ae62daa43 100644 --- a/src/common/utils.cc +++ b/src/common/utils.cc @@ -10,16 +10,20 @@ Utilities for pistache */ +#include + #include -#include + +#include PIST_QUOTE(PST_MISC_IO_HDR) // unistd.h e.g. close +#include PIST_QUOTE(PIST_FILEFNS_HDR) // for pist_pread #ifdef PISTACHE_USE_SSL -ssize_t SSL_sendfile(SSL* out, int in, off_t* offset, size_t count) +PST_SSIZE_T SSL_sendfile(SSL* out, int in, off_t* offset, size_t count) { unsigned char buffer[4096] = { 0 }; - ssize_t ret; - ssize_t written; + PST_SSIZE_T ret; + PST_SSIZE_T written; size_t to_read; if (in == -1) @@ -27,17 +31,17 @@ ssize_t SSL_sendfile(SSL* out, int in, off_t* offset, size_t count) to_read = sizeof(buffer) > count ? count : sizeof(buffer); - if (offset != NULL) - ret = pread(in, buffer, to_read, *offset); + if (offset != nullptr) + ret = PST_FILE_PREAD(in, buffer, to_read, *offset); else - ret = read(in, buffer, to_read); + ret = PST_FILE_READ(in, buffer, to_read); if (ret == -1) return -1; written = SSL_write(out, buffer, static_cast(ret)); - if (offset != NULL) - *offset += written; + if (offset != nullptr) + *offset += (static_cast(written)); return written; } diff --git a/src/meson.build b/src/meson.build index 92de9438f..f35c32152 100644 --- a/src/meson.build +++ b/src/meson.build @@ -15,9 +15,19 @@ pistache_common_src = [ 'common'/'net.cc', 'common'/'os.cc', 'common'/'peer.cc', - 'common'/'pist_check.cc', - 'common'/'pist_syslog.cc', - 'common'/'pist_timelog.cc', + 'common'/'pist_check.cc', + 'common'/'pist_clock_gettime.cc', + 'common'/'pist_fcntl.cc', + 'common'/'pist_filefns.cc', + 'common'/'pist_sockfns.cc', + 'common'/'pist_syslog.cc', + 'common'/'pist_ifaddrs.cc', + 'common'/'pist_resource.cc', + 'common'/'pist_strerror_r.cc', + 'common'/'pist_timelog.cc', + 'common'/'ps_basename.cc', + 'common'/'ps_sendfile.cc', + 'common'/'ps_strl.cc', 'common'/'reactor.cc', 'common'/'stream.cc', 'common'/'string_logger.cc', @@ -65,22 +75,304 @@ if get_option('PISTACHE_FORCE_LIBEVENT') add_project_arguments('-DPISTACHE_FORCE_LIBEVENT', language: 'cpp') endif +# To add symbols to release MSVC build in Windows: +# add_project_arguments('-Zi', language: 'cpp') +# add_project_arguments('-O2', language: 'cpp') # May not be needed +# See also the later additions needed for libpistache_links_args +# NB: We do NOT do +# DON'T: "add_project_arguments('-MDd', language: 'cpp')" +# We leave the release '-Md' option. '-MD' means build DLL, '-MDd' +# means build debug DLL. In cases of interest, '-MDd' hides crashes +# that will show up with '-MD'. But you still have symbols and source +# with '-MD'. -libpistache = library( +# generate Windows import library (.lib) + +# implib: true is not accepted by "library" or "shared_library", +# though it is accepted by "executable" + +# Useful info here: +# https://stackoverflow.com/questions/225432/export-all-symbols-when-creating-a-dll + +libpistache_gen_cts = [] # generated targets, e.g. pistache.lib in Windows +libpistache_links_args = [] +pistache_extra_src = [] + +if host_machine.system() == 'windows' + + # Here we are a taking a Windows "manifest" file + # (pist_winlog.man), an XML file that defines the logging objects to be + # used by Pistache in Windows, and using it to generate: + # 1) pist_winlog.h, C hdr included by Pistache logging code + # 2) pist_winlog.res, Windows resource file linked with pistache.dll + # 3) pistachelog.dll, DLL used when installing logging manifest/template + # (we also generate pist_winlog.rc, a purely intermediate file) + # + # The causal chain that generates those files is: + # pist_winlog.man + # | + # *mc.exe* + # | + # pist_winlog.rc + pist_winlog.h + # | + # *rc.exe* or (if using gcc) windres.exe + # | + # pist_winlog.res + # | + # *link* + # | + # pistachelog.dll + # mc.exe and rc.exe being utilities provided by Windows + + mc_prog = find_program('mc.exe') + if compiler.get_id() == 'gcc' + rc_prog = find_program('windres.exe') + else + rc_prog = find_program('rc.exe') + endif + + gen_src_log_rc_ct = custom_target( + 'gen-log-rc', + input: ['winlog'/'pist_winlog.man'], + output: ['pist_winlog.rc', 'pist_winlog.h'], + command: [mc_prog, '-um', '-h', '@OUTDIR@', '-r', '@OUTDIR@', '@INPUT@'] + ) + + if compiler.get_id() == 'gcc' + gen_src_log_res_ct = custom_target('gen-log-res', + input: [gen_src_log_rc_ct[0]], # pist_winlog.rc + output: ['pist_winlog.o'], + command: [rc_prog, '@INPUT@', '-o', '@OUTPUT@']) + else + gen_src_log_res_ct = custom_target('gen-log-res', + input: [gen_src_log_rc_ct[0]], # pist_winlog.rc + output: ['pist_winlog.res'], + command: [rc_prog, '@INPUT@']) + endif + + # We don't really need a pistachelog import library (.lib file) - + # nothing is going to link to pistachelog.dll - but "meson + # install" expects the .lib file to exist, so we create it. + pistachelog_implib_linkarg = 'NONE' + if compiler.get_id() == 'gcc' + pistachelog_implib_linkarg = '-Wl,--out-implib=src/libpistachelog.dll.a' + else + # Using the /DEF: linker arg to create the import library (.lib) + # pistachelog.def is an empty .def file provided in the source + # tree + + if meson.version().version_compare('>=1.4.0') + pistachelog_def_fl = files('winlog'/'pistachelog.def') + pistachelog_implib_linkarg = '/DEF:' + pistachelog_def_fl[0].full_path() + else + pistachelog_implib_linkarg = '/DEF:' + meson.current_source_dir() + '/winlog/pistachelog.def' + endif + endif + + pistachelog_linkargs = [] + if compiler.get_id() == 'msvc' + pistachelog_linkargs += '/noentry' + endif + pistachelog_linkargs += pistachelog_implib_linkarg + + win_log_dll = shared_library( + 'pistachelog', + sources: [gen_src_log_res_ct], + link_args: pistachelog_linkargs, + install: true # pistachelog.dll's location is hardcoded in logging + # manifest (.man) file, so we kind of have to + # install it in that location if we're going to + # use it + ) + + # Add script to install logging manifest when "meson install..." is run + install_headers( + 'winlog'/'pist_winlog.man', 'winlog'/'installmanatinstall.ps1', + install_dir : 'src'/'winlog') + meson.add_install_script('winlog'/'installmanatinstall.ps1') + + pistache_extra_src += gen_src_log_rc_ct[1] # for pist_winlog.h +endif + + +if host_machine.system() == 'windows' + if compiler.get_id() == 'msvc' + + # In order for other programs to use pistache.dll, we need to + # provide a pistache import library, pistache.lib, which provides + # the pistache symbols that the pistache.dll-using-code can access. + # + # In Windows, unlike in Linux, a symbol is not exported from a + # shared library (dll) unless explicitly defined to be an + # export. This can be done either by adding __declspec(dllexport) + # to each exported symbol in the C++ source, or by providing a + # .def file that lists all the exported symbols. However, we don't + # want to have to decorated the source code with + # __declspec(dllexport) everywhere, nor do we want to maintain the + # .def file manually, so we automate the creation of the .def file + # as follows. + # + # 1/ Generate the pistache library in static-library form. + # 2/ Use Window's dumpbin.exe to list all pistache's public symbols + # 3/ Use dump2def.exe to turn the dumpbin output into a pistache.def file + # (dump2def is our own utility that we have made a Pistache subproject) + # 4/ Link in pistache.def when linking pistache.dll + # 5/ Provide pistache.def as a dependency to pistache.dll-using code + # + # This means that every public symbol in pistache.dll is exported, + # just like pistache.so under linux + + dump2def_subproj = subproject('dump2def') + my_dump2def_exe = dump2def_subproj.get_variable('dump2def_exe') + + libpistache_static = static_library( + 'pistache', + sources: pistache_common_src + pistache_server_src + pistache_client_src + pistache_extra_src, + cpp_args: public_args, + include_directories: incl_pistache, + dependencies: deps_libpistache, + install: false, # Could do get_option('PISTACHE_INSTALL') if preferred + pic: get_option('b_staticpic') + ) + + dumpbin_prog = find_program('dumpbin.exe') + + gen_src_dump = custom_target('gen-pistachedump', + input: [libpistache_static], # src/libpistache.a + output: ['pistache.dump'], + command: [dumpbin_prog, '/LINKERMEMBER', '@INPUT@', '/OUT:@OUTPUT0@'], + depends: [libpistache_static]) + + gen_src_def = custom_target('gen-pistachedef', + input: [gen_src_dump], # src/pistache.dump + output: ['pistache.def'], + command: [my_dump2def_exe, '@INPUT@', '@OUTPUT0@'], + depends: [gen_src_dump, my_dump2def_exe]) + + gen_src_def_dep = declare_dependency(sources: [gen_src_def]) + deps_libpistache += gen_src_def_dep # DLL is dependent on .def file + + libpistache_links_args += '/DEF:src/pistache.def' + libpistache_links_args += '/IGNORE:4102' # label defined but not referenced + + # Take DLL library name from the linker cmd line, not from + # .def/.exp file Alternately, could fix this in dump2def + # (LIBRARY statement). + # Error LNK4070: /OUT:filename directive in .EXP differs from + # output filename 'filename'; ignoring directive + libpistache_links_args += '/IGNORE:4070' + + else # Not MSVC + if compiler.get_id() == 'gcc' + libpistache_links_args += '-Wl,--export-all-symbols' + endif + endif + + # To add symbols to release MSVC build in Windows: + # libpistache_links_args += '/DEBUG' + # libpistache_links_args += '/PDB:src/pistache-0.4.pdb' + # See also the required "add_project_arguments(...)", above + +endif + +if host_machine.system() == 'windows' + # Next we install the logging manifest and pistachelog.dll in the + # system. We do this (install logging components alone, without + # Pistache as a whole) as part of meson build, not just as part of + # meson install, so that test programs can log without having to + # install the whole, updated Pistache in the system. + # Also of note, the logging manifest and pistachelog.dll are + # expected to change very rarely. + # + # We do this pistachelog.dll and manifest install after + # libpistache_static has compiled+linked successfully, to reduce + # the risk of doing it prematurely i.e. at a time when there are + # errors in the Pistache code that prevent compiling+linking from + # completing successfully. + + powershell_prog = find_program('powershell.exe') + instl_log_scrpt = 'NONE' + if meson.version().version_compare('>=1.4.0') + instl_log_scrpt_arr = files('winlog'/'installman.ps1') + instl_log_scrpt = instl_log_scrpt_arr[0].full_path() + else + instl_log_scrpt = join_paths(meson.current_source_dir(), + 'winlog'/'installman.ps1') + endif + proj_bld_rt = 'NONE' + if meson.version().version_compare('>=0.56.0') + proj_bld_rt = meson.project_build_root() + else + proj_bld_rt = meson.build_root() + endif + gen_logging_txtfl_ct = custom_target( + 'gen-logging-txtfl', + input: [win_log_dll, 'winlog'/'pist_winlog.man'], + output: ['pist_winlog_inst_record.txt'], + # Note: the output file, pist_winlog_inst_record.txt, is + # created purely so that its date+time stamp can record when + # the instl_log_scrpt script last ran and copied + # pistachelog.dll to its correct location and installed the + # corresponding logging manifest. Also, we define the + # pist_winlog_inst_record.txt file to be dependent on the + # manifest definition file, such that, if the manifest + # definition file is changed, instl_log_scrpt will be rerun + # and pistachelog.dll and the installed manifest will be + # updated. + # Further on, we also make library('pistache'...) (aka + # pistache.dll) dependent on gen_logging_txtfl_ct, + # i.e. dependent on pist_winlog_inst_record.txt, so that the + # pistachelog.dll install and manifest install are carried + # out as part of building library('pistache'...). + command: [powershell_prog, '-NoProfile', + '-NonInteractive', '-WindowStyle', 'Hidden', '-NoLogo', + '-Command', + instl_log_scrpt, + '-dirinpstlogdll', proj_bld_rt, + '-inpstlogdll', '@INPUT0@', + '-inpstman', '@INPUT1@', + '-outpstmaninst', '@OUTPUT@'], + depends: [win_log_dll, gen_src_log_rc_ct] + ) + gen_logging_txtfl_dep = declare_dependency(sources: [gen_logging_txtfl_ct]) + deps_libpistache += gen_logging_txtfl_dep +endif + +if host_machine.system() == 'windows' and compiler.get_id() == 'msvc' + libpistache = library( 'pistache', - sources: pistache_common_src + pistache_server_src + pistache_client_src, + sources: pistache_common_src + pistache_server_src + pistache_client_src + pistache_extra_src, cpp_args: public_args, include_directories: incl_pistache, dependencies: deps_libpistache, + link_args: libpistache_links_args, install: get_option('PISTACHE_INSTALL'), soversion: version_major + '.' + version_minor, version: version_str, + vs_module_defs: gen_src_def, pic: get_option('b_staticpic') -) + ) +else # No vs_module_defs + libpistache = library( + 'pistache', + sources: pistache_common_src + pistache_server_src + pistache_client_src + pistache_extra_src, + cpp_args: public_args, + include_directories: incl_pistache, + dependencies: deps_libpistache, + link_args: libpistache_links_args, + install: get_option('PISTACHE_INSTALL'), + soversion: version_major + '.' + version_minor, + version: version_str, + pic: get_option('b_staticpic') + ) +endif + + pistache_dep = declare_dependency( compile_args: public_args, include_directories: incl_pistache, + sources: libpistache_gen_cts, link_with: libpistache, dependencies: public_deps ) @@ -89,12 +381,42 @@ if meson.version().version_compare('>=0.54.0') meson.override_dependency('libpistache', pistache_dep) endif -import('pkgconfig').generate( - libpistache, - name: 'Pistache', - description: 'An elegant C++ REST framework', - url: 'https://pistacheio.github.io/pistache/', - version: '@0@-git@1@'.format(version_str, version_git_date), - filebase: 'libpistache', - extra_cflags: public_args -) +if host_machine.system() == 'windows' + if compiler.get_id() == 'gcc' + my_filebase = 'libpistache' + else + my_filebase = 'pistache' + endif + + import('pkgconfig').generate( + libpistache, + # libraries: 'pistachelog', + # + # I think pistachelog can be ommitted, since per meson + # "dependencies of built libraries will be automatically + # added... custom_target() objects are supported as long + # as they are linkable", and pistachelog.dll is a + # dependency of libpistache. In fact, adding pistachelog + # to libraries may be harmful since it makes pistachelog + # appear on the .pc file's "Libs" line, and we don't + # actually want applications to try and link with + # pistachelog. + # ref: https://mesonbuild.com/Pkgconfig-module.html + name: 'Pistache', + description: 'An elegant C++ REST framework', + url: 'https://pistacheio.github.io/pistache/', + version: '@0@-git@1@'.format(version_str, version_git_date), + filebase: my_filebase, + extra_cflags: public_args + ) +else + import('pkgconfig').generate( + libpistache, + name: 'Pistache', + description: 'An elegant C++ REST framework', + url: 'https://pistacheio.github.io/pistache/', + version: '@0@-git@1@'.format(version_str, version_git_date), + filebase: 'libpistache', + extra_cflags: public_args + ) +endif diff --git a/src/server/endpoint.cc b/src/server/endpoint.cc index 3faade7ed..b061b51bd 100644 --- a/src/server/endpoint.cc +++ b/src/server/endpoint.cc @@ -76,8 +76,9 @@ namespace Pistache::Http timerFd = #ifdef _USE_LIBEVENT - TRY_NULL_RET(poller.em_timer_new(CLOCK_MONOTONIC, - F_SETFDL_NOTHING, O_NONBLOCK)); + TRY_NULL_RET(poller.em_timer_new(PST_CLOCK_MONOTONIC, + F_SETFDL_NOTHING, + PST_O_NONBLOCK)); #else TRY_RET(timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK)); #endif @@ -235,7 +236,7 @@ namespace Pistache::Http else { ResponseWriter response(Http::Version::Http11, this, static_cast(handler_.get()), peer); - response.send(Http::Code::Request_Timeout).then([=](ssize_t) { removePeer(peer); }, [=](std::exception_ptr) { removePeer(peer); }); + response.send(Http::Code::Request_Timeout).then([=](PST_SSIZE_T) { removePeer(peer); }, [=](std::exception_ptr) { removePeer(peer); }); } } diff --git a/src/server/listener.cc b/src/server/listener.cc index 371c73630..8157207f1 100644 --- a/src/server/listener.cc +++ b/src/server/listener.cc @@ -9,6 +9,8 @@ */ +#include + #include #include #include @@ -17,27 +19,22 @@ #include #include -#include -#include -#include -#include +#include PIST_QUOTE(PST_ARPA_INET_HDR) +#include PIST_QUOTE(PST_NETDB_HDR) +#include PIST_QUOTE(PST_NETINET_IN_HDR) +#include PIST_QUOTE(PST_NETINET_TCP_HDR) #include -#ifndef _USE_LIBEVENT_LIKE_APPLE -#include -#endif -#include +#include PIST_QUOTE(PST_MISC_IO_HDR) // unistd.h e.g. close +#include PIST_QUOTE(PST_FCNTL_HDR) +#include PIST_QUOTE(PIST_SOCKFNS_HDR) // socket read, write and close #ifndef _USE_LIBEVENT #include #endif -#ifdef _USE_LIBEVENT_LIKE_APPLE -#include // for getprotobyname -#endif - -#include +#include PIST_QUOTE(PST_SOCKET_HDR) #ifndef _USE_LIBEVENT_LIKE_APPLE // Note: sys/timerfd.h is linux-only (and certainly POSIX only) @@ -137,7 +134,7 @@ namespace Pistache::Tcp } } - if (cb != NULL) + if (cb != nullptr) { /* Use the user-defined callback for password if provided */ SSL_CTX_set_default_passwd_cb(GetSSLContext(ctx), cb); @@ -191,18 +188,18 @@ namespace Pistache::Tcp } #endif /* PISTACHE_USE_SSL */ - void setSocketOptions(int actualFd, Flags options) + void setSocketOptions(em_socket_t actualFd, Flags options) { PS_TIMEDBG_START; #ifdef _USE_LIBEVENT_LIKE_APPLE if (options.hasFlag(Options::CloseOnExec)) { - int f_setfd_flags = fcntl(actualFd, F_GETFD, (int)0); - if (!(f_setfd_flags & FD_CLOEXEC)) + int f_setfd_flags = PST_FCNTL(actualFd, PST_F_GETFD, 0); + if (!(f_setfd_flags & PST_FD_CLOEXEC)) { - f_setfd_flags |= FD_CLOEXEC; - int fcntl_res = fcntl(actualFd, F_SETFD, f_setfd_flags); + f_setfd_flags |= PST_FD_CLOEXEC; + int fcntl_res = PST_FCNTL(actualFd, PST_F_SETFD, f_setfd_flags); if (fcntl_res == -1) throw std::runtime_error("fcntl set failed"); } @@ -213,15 +210,27 @@ namespace Pistache::Tcp { PS_LOG_DEBUG("Set SO_REUSEADDR"); - int one = 1; + PST_SOCK_OPT_VAL_T one = 1; + // Note: TRY also invokes PST_SOCK_STARTUP_CHECK TRY(::setsockopt(actualFd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))); } if (options.hasFlag(Options::ReusePort)) { PS_LOG_DEBUG("Set SO_REUSEPORT"); - int one = 1; + PST_SOCK_OPT_VAL_T one = 1; +#ifdef _IS_WINDOWS + // Note: Windows doesn't have SO_REUSEPORT, but if caller has + // requested Options::ReusePort, but not Options::ReuseAddr, then + // in Windows we set SO_REUSEADDR here + if (!(options.hasFlag(Options::ReuseAddr))) + { + TRY(::setsockopt(actualFd, SOL_SOCKET, + SO_REUSEADDR, &one, sizeof(one))); + } +#else TRY(::setsockopt(actualFd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one))); +#endif } if (options.hasFlag(Options::Linger)) @@ -229,7 +238,9 @@ namespace Pistache::Tcp struct linger opt; opt.l_onoff = 1; opt.l_linger = 1; - TRY(::setsockopt(actualFd, SOL_SOCKET, SO_LINGER, &opt, sizeof(opt))); + TRY(::setsockopt(actualFd, SOL_SOCKET, SO_LINGER, + reinterpret_cast(&opt), + sizeof(opt))); } #ifdef _USE_LIBEVENT_LIKE_APPLE @@ -247,13 +258,13 @@ namespace Pistache::Tcp if (options.hasFlag(Options::FastOpen)) { - int hint = 5; + PST_SOCK_OPT_VAL_T hint = 5; TRY(::setsockopt(actualFd, tcp_prot_num, TCP_FASTOPEN, &hint, sizeof(hint))); } if (options.hasFlag(Options::NoDelay)) { - int one = 1; + PST_SOCK_OPT_VAL_T one = 1; TRY(::setsockopt(actualFd, tcp_prot_num, TCP_NODELAY, &one, sizeof(one))); } @@ -340,12 +351,14 @@ namespace Pistache::Tcp // SOCK_CLOEXEC not defined in macOS Nov 2023 // In the _USE_LIBEVENT_LIKE_APPLE case, we set FD_CLOEXEC using fcntl // in the setSocketOptions function that is invoked below +// It also doesn't exist for Windows (Windows sets _USE_LIBEVENT_LIKE_APPLE) #ifndef _USE_LIBEVENT_LIKE_APPLE if (options_.hasFlag(Options::CloseOnExec)) socktype |= SOCK_CLOEXEC; #endif - int actual_fd = ::socket(addr->ai_family, socktype, addr->ai_protocol); + em_socket_t actual_fd = PST_SOCK_SOCKET(addr->ai_family, socktype, + addr->ai_protocol); PS_LOG_DEBUG_ARGS("::socket actual_fd %d", actual_fd); if (actual_fd < 0) @@ -360,16 +373,19 @@ namespace Pistache::Tcp LOG_DEBUG_ACT_FD_AND_FDL_FLAGS(actual_fd); - if (::bind(actual_fd, addr->ai_addr, addr->ai_addrlen) < 0) + if (PST_SOCK_BIND(actual_fd, addr->ai_addr, addr->ai_addrlen) < 0) { + auto tmp_errno = errno; // in case sock-close changes errno PS_LOG_DEBUG_ARGS("::bind failed, actual_fd %d", actual_fd); - close(actual_fd); + PST_SOCK_CLOSE(actual_fd); + errno = tmp_errno; + return false; } LOG_DEBUG_ACT_FD_AND_FDL_FLAGS(actual_fd); - TRY(::listen(actual_fd, backlog_)); + TRY(PST_SOCK_LISTEN(actual_fd, backlog_)); LOG_DEBUG_ACT_FD_AND_FDL_FLAGS(actual_fd); @@ -389,7 +405,7 @@ namespace Pistache::Tcp // Use EVM_READ, as per call to addFd below Fd event_fd = TRY_NULL_RET( Polling::Epoll::em_event_new(actual_fd, // pre-allocated file desc - EVM_READ | EVM_PERSIST, + EVM_READ | EVM_PERSIST | EVM_ET, F_SETFDL_NOTHING, // f_setfd_flags - don't change F_SETFDL_NOTHING // f_setfl_flags - don't change )); @@ -470,7 +486,8 @@ namespace Pistache::Tcp // if (!found) { - throw std::runtime_error(strerror(errno)); + PST_DECL_SE_ERR_P_EXTRA; + throw std::runtime_error(PST_STRERROR_R_ERRNO); } } @@ -495,7 +512,7 @@ namespace Pistache::Tcp socklen_t addrlen = sizeof(sock_addr); auto* sock_addr_alias = reinterpret_cast(&sock_addr); - if (-1 == getsockname(GET_ACTUAL_FD(listen_fd), sock_addr_alias, &addrlen)) + if (-1 == PST_SOCK_GETSOCKNAME(GET_ACTUAL_FD(listen_fd), sock_addr_alias, &addrlen)) { return Port(); } @@ -518,6 +535,8 @@ namespace Pistache::Tcp void Listener::run() { + PS_TIMEDBG_START; + if (!shutdownFd.isBound()) shutdownFd.bind(poller); reactor_->run(); @@ -526,6 +545,7 @@ namespace Pistache::Tcp { { // encapsulate l_guard(poller.reg_unreg_mutex_) // See comment in class Epoll regarding reg_unreg_mutex_ + PS_TIMEDBG_START; std::mutex& poller_reg_unreg_mutex(poller.reg_unreg_mutex_); GUARD_AND_DBG_LOG(poller_reg_unreg_mutex); @@ -544,7 +564,7 @@ namespace Pistache::Tcp if (event.flags.hasFlag(Polling::NotifyOn::Read)) { - Fd fd = (Fd)event.tag.value(); + Fd fd = static_cast(event.tag.value()); if (fd == listen_fd) { try @@ -571,8 +591,15 @@ namespace Pistache::Tcp void Listener::runThreaded() { + PS_TIMEDBG_START; + shutdownFd.bind(poller); - acceptThread = std::thread([=]() { this->run(); }); + PS_LOG_DEBUG("shutdownFd.bind done"); + + acceptThread = std::thread([=]() { + PS_TIMEDBG_START; + this->run(); + }); } void Listener::shutdown() @@ -595,7 +622,7 @@ namespace Pistache::Tcp auto handlers = reactor_->handlers(transportKey); - std::vector> loads; + std::vector> loads; for (const auto& handler : handlers) { auto transport = std::static_pointer_cast(handler); @@ -604,7 +631,7 @@ namespace Pistache::Tcp return Async::whenAll(std::begin(loads), std::end(loads)) .then( - [=](const std::vector& usages) { + [=](const std::vector& usages) { PS_TIMEDBG_START; Load res; res.raw = usages; @@ -618,7 +645,7 @@ namespace Pistache::Tcp else { - auto totalElapsed = [](rusage usage) { + auto totalElapsed = [](PST_RUSAGE usage) { return static_cast((usage.ru_stime.tv_sec * 1000000 + usage.ru_stime.tv_usec) + (usage.ru_utime.tv_sec * 1000000 + usage.ru_utime.tv_usec)); }; @@ -657,7 +684,7 @@ namespace Pistache::Tcp PS_TIMEDBG_START_THIS; struct sockaddr_storage peer_addr; - int actual_cli_fd = acceptConnection(peer_addr); + em_socket_t actual_cli_fd = acceptConnection(peer_addr); void* ssl = nullptr; @@ -671,7 +698,7 @@ namespace Pistache::Tcp { PS_LOG_DEBUG("SSL_new failed"); - close(actual_cli_fd); + PST_SOCK_CLOSE(actual_cli_fd); std::string err = "SSL error - cannot create SSL connection: " + ssl_print_errors_to_string(); throw ServerError(err.c_str()); @@ -688,16 +715,34 @@ namespace Pistache::Tcp struct timeval timeout; - timeout.tv_sec = std::chrono::duration_cast(sslHandshakeTimeout_).count(); + timeout.tv_sec = static_cast(std::chrono::duration_cast(sslHandshakeTimeout_).count()); const auto residual_microseconds = std::chrono::duration_cast(sslHandshakeTimeout_) - std::chrono::duration_cast(sslHandshakeTimeout_); - timeout.tv_usec = (suseconds_t)(residual_microseconds.count()); - - TRY(::setsockopt(actual_cli_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout))); - TRY(::setsockopt(actual_cli_fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout))); + timeout.tv_usec = static_cast(residual_microseconds.count()); + + TRY(::setsockopt(actual_cli_fd, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&timeout), + sizeof(timeout))); + TRY(::setsockopt(actual_cli_fd, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&timeout), + sizeof(timeout))); } - SSL_set_fd(ssl_data, actual_cli_fd); + SSL_set_fd(ssl_data, +#ifdef _IS_WINDOWS + // SSL_set_fd takes type int for the FD parm, resulting + // in a compiler warning since em_socket_t (and Windows' + // SOCKET) may be wider than "int". However, according + // to the SLL documentation, the warning can be + // suppressed / ignored. @Aug/2024, see 'NOTES' in: + // https://docs.openssl.org/3.1/man3/SSL_set_fd/ + static_cast( +#endif + actual_cli_fd +#ifdef _IS_WINDOWS + ) +#endif + ); SSL_set_accept_state(ssl_data); int ssl_accept_res = SSL_accept(ssl_data); @@ -725,7 +770,7 @@ namespace Pistache::Tcp PISTACHE_LOG_STRING_INFO(logger_, err); SSL_free(ssl_data); - close(actual_cli_fd); + PST_SOCK_CLOSE(actual_cli_fd); return; } @@ -741,8 +786,12 @@ namespace Pistache::Tcp timeout.tv_sec = 0; timeout.tv_usec = 0; - TRY(::setsockopt(actual_cli_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout))); - TRY(::setsockopt(actual_cli_fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout))); + TRY(::setsockopt(actual_cli_fd, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&timeout), + sizeof(timeout))); + TRY(::setsockopt(actual_cli_fd, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&timeout), + sizeof(timeout))); } ssl = static_cast(ssl_data); @@ -763,8 +812,8 @@ namespace Pistache::Tcp // Since we're accepting a remote connection here, presumably it makes // sense to have it be able to read *or* write? Fd client_fd = TRY_NULL_RET( - Polling::Epoll::em_event_new(actual_cli_fd, // pre-allocated file dsc - EVM_READ | EVM_WRITE | EVM_PERSIST, + Polling::Epoll::em_event_new(actual_cli_fd, // pre-alloced file dsc + EVM_READ | EVM_WRITE | EVM_PERSIST | EVM_ET, F_SETFDL_NOTHING, // f_setfd_flags - don't change F_SETFDL_NOTHING // f_setfl_flags - don't change )); @@ -791,13 +840,13 @@ namespace Pistache::Tcp dispatchPeer(peer); } - int Listener::acceptConnection(struct sockaddr_storage& peer_addr) const + em_socket_t Listener::acceptConnection(struct sockaddr_storage& peer_addr) const { PS_TIMEDBG_START_THIS; socklen_t peer_addr_len = sizeof(peer_addr); - int listen_fd_actual = GET_ACTUAL_FD(listen_fd); + em_socket_t listen_fd_actual = GET_ACTUAL_FD(listen_fd); PS_LOG_DEBUG_ARGS("listen_fd %" PIST_QUOTE(PS_FD_PRNTFCD) ", " "listen_fd_actual %d", @@ -806,11 +855,11 @@ namespace Pistache::Tcp LOG_DEBUG_ACT_FD_AND_FDL_FLAGS(listen_fd_actual); // Do not share open FD with forked processes - int client_actual_fd = + em_socket_t client_actual_fd = #ifdef _USE_LIBEVENT_LIKE_APPLE - ::accept(listen_fd_actual, - reinterpret_cast(&peer_addr), - &peer_addr_len); + PST_SOCK_ACCEPT(listen_fd_actual, + reinterpret_cast(&peer_addr), + &peer_addr_len); // Note: macOS doesn't support accept4 nor SOCK_CLOEXEC as of Nov-2023 // accept4 is an extended form of "accept" with additional flags @@ -842,10 +891,12 @@ namespace Pistache::Tcp if (client_actual_fd < 0) { + PST_DECL_SE_ERR_P_EXTRA; + if (errno == EBADF || errno == ENOTSOCK) - throw ServerError(strerror(errno)); + throw ServerError(PST_STRERROR_R_ERRNO); else - throw SocketError(strerror(errno)); + throw SocketError(PST_STRERROR_R_ERRNO); } LOG_DEBUG_ACT_FD_AND_FDL_FLAGS(client_actual_fd); @@ -854,25 +905,29 @@ namespace Pistache::Tcp // We set CLOEXEC and unset all other flags to exactly match what // happens in Linux with accept4 (see comment to "::accept" above) - int fcntl_res = fcntl(client_actual_fd, F_SETFD, FD_CLOEXEC); + int fcntl_res = PST_FCNTL(client_actual_fd, PST_F_SETFD, PST_FD_CLOEXEC); if (fcntl_res == -1) { + PST_DBG_DECL_SE_ERR_P_EXTRA; PS_LOG_DEBUG_ARGS("fcntl F_SETFD fail for fd %d, errno %d %s", - client_actual_fd, errno, strerror(errno)); + client_actual_fd, errno, + PST_STRERROR_R_ERRNO); - ::close(client_actual_fd); + PST_SOCK_CLOSE(client_actual_fd); PS_LOG_DEBUG_ARGS("::close actual_fd %d", client_actual_fd); return (fcntl_res); } - fcntl_res = fcntl(client_actual_fd, F_SETFL, 0 /*clear everything*/); + fcntl_res = PST_FCNTL(client_actual_fd, PST_F_SETFL, 0 /*clear everything*/); if (fcntl_res == -1) { + PST_DBG_DECL_SE_ERR_P_EXTRA; PS_LOG_DEBUG_ARGS("fcntl F_SETFL fail for fd %d, errno %d %s", - client_actual_fd, errno, strerror(errno)); + client_actual_fd, errno, + PST_STRERROR_R_ERRNO); - ::close(client_actual_fd); + PST_SOCK_CLOSE(client_actual_fd); PS_LOG_DEBUG_ARGS("::close actual_fd %d", client_actual_fd); return (fcntl_res); @@ -902,7 +957,7 @@ namespace Pistache::Tcp // To guard against that, we simply need to check for an invalid Fd. We // also check for an invalid actual-fd for safety's sake. - int actual_fd = -1; + em_socket_t actual_fd = -1; try { actual_fd = peer->actualFd(); @@ -919,8 +974,32 @@ namespace Pistache::Tcp return; } + em_socket_t input_for_idx = 0; +#ifdef _IS_WINDOWS + // actual_fd in Windows seems to be a multiple of 4, so we'll fail to + // use a bunch of handlers if we just do "idx = actual_fd % + // handlers.size()". For instance, if handlers.size() is 4, idx will + // always be zero. We use a monotonic and atomic counter here instead + // of the file handle divided by 4, since there is no guarantee that + // the Windows file handle will always be a multiple of 4, and indeed + // it appears it is sometimes not a multiple of 4 in Windows Server + // 2019. + + { // encapsulate + auto this_ctr = (idxCtr_++); + if (!this_ctr) + { + PS_LOG_WARNING("Apparent idxCtr overflow"); + this_ctr = (idxCtr_++); + } + input_for_idx = this_ctr; + } +#else + input_for_idx = actual_fd; +#endif + auto handlers = reactor_->handlers(transportKey); - auto idx = actual_fd % handlers.size(); + auto idx = input_for_idx % handlers.size(); auto transport = std::static_pointer_cast(handlers[idx]); transport->handleNewPeer(peer); @@ -940,12 +1019,12 @@ namespace Pistache::Tcp void Listener::setupSSLAuth(const std::string& ca_file, const std::string& ca_path, - int (*cb)(int, void*) = NULL) + int (*cb)(int, void*) = nullptr) { PS_TIMEDBG_START_THIS; - const char* __ca_file = NULL; - const char* __ca_path = NULL; + const char* __ca_file = nullptr; + const char* __ca_path = nullptr; if (ssl_ctx_ == nullptr) { @@ -979,7 +1058,7 @@ namespace Pistache::Tcp #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) (int (*)(int, X509_STORE_CTX*))cb #else - (SSL_verify_cb)cb + reinterpret_cast(cb) #endif /* OPENSSL_VERSION_NUMBER */ ); } diff --git a/src/server/router.cc b/src/server/router.cc index a516ab967..1bd74ee7d 100644 --- a/src/server/router.cc +++ b/src/server/router.cc @@ -542,10 +542,10 @@ namespace Pistache::Rest for (const auto& handler : customHandlers) { - auto resp = response.clone(); + auto cloned_resp = response.clone(); auto handler1 = handler( Request(req, std::vector(), std::vector()), - std::move(resp)); + std::move(cloned_resp)); if (handler1 == Route::Result::Ok) return Route::Status::Match; } @@ -599,7 +599,7 @@ namespace Pistache::Rest const auto sanitized = SegmentTreeNode::sanitizeResource(resource); std::shared_ptr ptr(new char[sanitized.length()], std::default_delete()); - memcpy(ptr.get(), sanitized.data(), sanitized.length()); + std::memcpy(ptr.get(), sanitized.data(), sanitized.length()); const std::string_view path { ptr.get(), sanitized.length() }; r.addRoute(path, handler, ptr); } diff --git a/src/winlog/installman.ps1 b/src/winlog/installman.ps1 new file mode 100644 index 000000000..2fa710128 --- /dev/null +++ b/src/winlog/installman.ps1 @@ -0,0 +1,117 @@ +#!/usr/bin/env powershell + +# +# SPDX-FileCopyrightText: 2024 Duncan Greatwood +# +# SPDX-License-Identifier: Apache-2.0 +# + +# installman.ps1 is used by the build process under Windows to install +# pistachelog.dll and the Pistache logging manifest + +# Usage: +# installman.ps1 -dirinpstlogdll -inpstlogdll ` +# -inpstman ` +# -outpstmaninst + +# This script is expected to be run at build time ("meson +# compile..."), not at install time ("meson install..."). By +# installing these logging components at build time, we can use +# logging while debugging Pistache code, prior to installing the whole +# Pistache package to its place in the OS. + +# Note: the outpstmaninst file is created purely so that the file's +# date+time stamp can record when this script copied pistachelog.dll +# to its correct location and installed the corresponding logging +# manifest. So then the build system can make the outpstmaninst file +# dependent on the manifest definition file, such that, if the +# manifest definition file is changed, this script will be rerun and +# pistachelog.dll and the installed manifest will be updated. + +param ( + [Parameter(Mandatory=$true)][string]$dirinpstlogdll, + [Parameter(Mandatory=$true)][string]$inpstlogdll, + [Parameter(Mandatory=$true)][string]$inpstman, + [Parameter(Mandatory=$true)][string]$outpstmaninst +) + +if (-Not (Test-Path -Path "$dirinpstlogdll")) +{ + throw "Directory $dirinpstlogdll not found" +} + +$fpinpstlogdll = Join-Path -Path "$dirinpstlogdll" -ChildPath "$inpstlogdll" + +Write-Host "fpinpstlogdll is $fpinpstlogdll" +if (-Not (Test-Path -Path "$fpinpstlogdll")) +{ + throw "DLL not found at $fpinpstlogdll" +} + +$logdll_name = Split-Path -Path "$fpinpstlogdll" -Leaf +if ("$logdll_name" -ne "pistachelog.dll") +{ + $alt_logdll_name = "pistachelog.dll" +} + +if (-Not (Test-Path "$inpstman")) +{ + throw "Pistache manifest file not found at $inpstman" +} + +if (-Not (Test-Path "$env:ProgramFiles\pistache_distribution")) +{ + mkdir "$env:ProgramFiles\pistache_distribution" +} +if (-Not (Test-Path "$env:ProgramFiles\pistache_distribution\bin")) +{ + mkdir "$env:ProgramFiles\pistache_distribution\bin" +} + +# Check we'll be able to write $outpstmaninst +# And remove the old one, if any +if (-Not (Test-Path "$outpstmaninst")) +{ + "Dummy" | out-file -Encoding ascii -filepath "$outpstmaninst" +} +rm "$outpstmaninst" + +cp "$fpinpstlogdll" "$env:ProgramFiles\pistache_distribution\bin\." +if ($alt_logdll_name) +{ + cp "$fpinpstlogdll" ` + "$env:ProgramFiles\pistache_distribution\bin\$alt_logdll_name" +} + +wevtutil um "$inpstman" # Uninstall - does nothing if not installed +wevtutil im "$inpstman" # Install + +# Next we create the Windows Registry log-to-stdout-as-well value for +# Pistache, if it doesn't exist already. If that registry property +# already exists, we don't change it. +if (-Not (Test-Path HKCU:\Software\pistacheio)) +{ + $key1 = New-Item -Path HKCU:\Software -Name pistacheio +} +if (Test-Path HKCU:\Software\pistacheio\pistache) +{ + $key2 = Get-Item -Path HKCU:\Software\pistacheio\pistache +} +else +{ + $key2 = New-Item -Path HKCU:\Software\pistacheio -Name pistache +} +if (-Not ($key2.Property -contains "psLogToStdoutAsWell")) +{ + $newItemPropertySplat = @{ + Path = $key2.PSPath + Name = 'psLogToStdoutAsWell' + PropertyType = 'DWord' + Value = 0 + } + New-ItemProperty @newItemPropertySplat +} + +# Finally we create the 'out' marker file +"At $(Get-Date):`r`n $inpstlogdll copied to $env:ProgramFiles\pistache_distribution\bin\.`r`n $inpstman logging manifest installed" ` +| out-file -Encoding utf8 -width 2560 -filepath "$outpstmaninst" diff --git a/src/winlog/installmanatinstall.ps1 b/src/winlog/installmanatinstall.ps1 new file mode 100644 index 000000000..75da08896 --- /dev/null +++ b/src/winlog/installmanatinstall.ps1 @@ -0,0 +1,89 @@ +#!/usr/bin/env powershell + +# +# SPDX-FileCopyrightText: 2024 Duncan Greatwood +# +# SPDX-License-Identifier: Apache-2.0 +# + +# This script is expected to be run at install time ("meson +# install..."), in contrast to installman.ps1. It doesn't take any +# parameters but uses the environment variables DESTDIR and +# MESON_INSTALL_PREFIX, which are set by "meson install...". +# +# This script doesn't copy pistachelog.dll (which will be put in place +# by the normal "meson install..." mechanisms - except with gcc, where +# the file needs to be renamed due to gcc naming convention); this +# script exists to install the logging manifest file pist_winlog.man +# and set the Windows Registry key property value +# HKCU:\Software\pistacheio\pistache\psLogToStdoutAsWell. + +$pistinstbase="$env:DESTDIR\$env:MESON_INSTALL_PREFIX" +if (($env:MESON_INSTALL_PREFIX) -and` + [System.IO.Path]::IsPathRooted($env:MESON_INSTALL_PREFIX)) +{ # Ignore DESTDIR if MESON_INSTALL_PREFIX is an absolute path + $pistinstbase="$env:MESON_INSTALL_PREFIX" +} +Write-Host "pistinstbase is $pistinstbase" + +if (-Not (Test-Path -Path "$pistinstbase")) +{ + throw "Pistache install directory not found at $pistinstbase" +} + +$pistwinlogman="$pistinstbase\src\winlog\pist_winlog.man" +if (-Not (Test-Path -Path "$pistwinlogman")) +{ + throw "pist_winlog.man not found at $pistwinlogman" +} + +$pistachelogdll="$pistinstbase\bin\pistachelog.dll" +$pistacheloggccdll="$pistinstbase\bin\libpistachelog.dll" +if (Test-Path -Path "$pistacheloggccdll") +{ + if (Test-Path -Path "$pistachelogdll") + { + $pistachelogdlldate = ` + Get-Item "$pistachelogdll" | Foreach {$_.LastWriteTime} + } + if ((! ($pistachelogdlldate)) -or ` + (Test-Path -Path "$pistacheloggccdll" -NewerThan "$pistachelogdlldate")) + { + cp "$pistacheloggccdll" "$pistachelogdll" + Write-Host "Copied $pistacheloggccdll to $pistachelogdll" + } +} + +if (-Not (Test-Path "$pistachelogdll")) +{ + throw "pistachelog.dll not found at $pistachelogdll" +} + +wevtutil um "$pistwinlogman" # Uninstall - does nothing if not installed +wevtutil im "$pistwinlogman" # Install + +# Next we create the Windows Registry log-to-stdout-as-well value for +# Pistache, if it doesn't exist already. If that registry property +# already exists, we don't change it. +if (-Not (Test-Path HKCU:\Software\pistacheio)) +{ + $key1 = New-Item -Path HKCU:\Software -Name pistacheio +} +if (Test-Path HKCU:\Software\pistacheio\pistache) +{ + $key2 = Get-Item -Path HKCU:\Software\pistacheio\pistache +} +else +{ + $key2 = New-Item -Path HKCU:\Software\pistacheio -Name pistache +} +if (-Not ($key2.Property -contains "psLogToStdoutAsWell")) +{ + $newItemPropertySplat = @{ + Path = $key2.PSPath + Name = 'psLogToStdoutAsWell' + PropertyType = 'DWord' + Value = 0 + } + New-ItemProperty @newItemPropertySplat +} diff --git a/src/winlog/pist_winlog.man b/src/winlog/pist_winlog.man new file mode 100644 index 000000000..c525f95c3 --- /dev/null +++ b/src/winlog/pist_winlog.man @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/winlog/pistachelog.def b/src/winlog/pistachelog.def new file mode 100644 index 000000000..11266c126 --- /dev/null +++ b/src/winlog/pistachelog.def @@ -0,0 +1,8 @@ +;; SPDX-FileCopyrightText: 2024 Duncan Greatwood +;; +;; SPDX-License-Identifier: Apache-2.0 + +LIBRARY pistachelog +EXPORTS + + ;; 0 symbols diff --git a/subprojects/dump2def/dump2def.cc b/subprojects/dump2def/dump2def.cc new file mode 100644 index 000000000..d63de3bb4 --- /dev/null +++ b/subprojects/dump2def/dump2def.cc @@ -0,0 +1,167 @@ +/* + * From https://stackoverflow.com/questions/225432/export-all-symbols-when-creating-a-dll + * Public domain + */ + +#define UNICODE +#define _UNICODE + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include // for path.stem() + +typedef std::set SymbolMap; + +void PrintHelpAndExit(int code) +{ + std::cout << "dump2def - create a module definitions file from a dumpbin file" << std::endl; + std::cout << " Written and placed in public domain by Jeffrey Walton" << std::endl; + std::cout << " Updated by DMG" << std::endl; + std::cout << std::endl; + + std::cout << "Usage: " << std::endl; + + std::cout << " dump2def " << std::endl; + std::cout << " - Create a def file from and write it to a file with" << std::endl; + std::cout << " the same name as but using the .def extension" << std::endl; + + std::cout << " dump2def " << std::endl; + std::cout << " - Create a def file from and write it to " << std::endl; + + std::exit(code); +} + +int main(int argc, char* argv[]) +{ + // ******************** Handle Options ******************** // + + // Convenience item + std::vector opts; + for (int i=0; i as needed + if (opts.size() == 2) + { + std::string outfile = opts[1]; + std::string::size_type pos = outfile.length() < 5 ? std::string::npos : outfile.length() - 5; + if (pos == std::string::npos || outfile.substr(pos) != ".dump") + PrintHelpAndExit(1); + + outfile.replace(pos, 5, ".def"); + opts.push_back(outfile); + } + + // Check or exit + if (opts.size() != 3) + PrintHelpAndExit(1); + + // ******************** Read MAP file ******************** // + + SymbolMap symbols; + + unsigned int num_sym_found = 0; + + try + { + std::cout << "Accessing file: " << opts[1] << std::endl; + + std::wifstream infile(opts[1].c_str()); + std::string::size_type fnd_pos; + std::wstring line; + + unsigned int num_line = 0; + + while (std::getline(infile, line)) + { + ++num_line; + + fnd_pos = line.find(L"public symbols"); + if (fnd_pos == std::string::npos) { continue; } + + std::cout << "Public symbols line found" << std::endl; + + // Eat the whitespace after the table heading + infile >> std::ws; + break; + } + + while (std::getline(infile, line)) + { + // End of table + if (line.empty()) + { + if (num_sym_found) + break; + else + continue; + } + + ++num_sym_found; + + std::wistringstream iss(line); + std::wstring address, symbol; + iss >> address >> symbol; + + symbols.insert(symbol); + } + } + catch (const std::exception& ex) + { + std::cerr << "Unexpected exception:" << std::endl; + std::cerr << ex.what() << std::endl; + std::cerr << std::endl; + + PrintHelpAndExit(1); + } + + std::cout << "Number of symbols found " << num_sym_found << + ", vector size: " << symbols.size() << std::endl; + + // ******************** Write DEF file ******************** // + + try + { + std::wofstream outfile(opts[2].c_str()); + + std::filesystem::path name_stem(std::filesystem::path(opts[2]).stem()); + const std::wstring name_ws(name_stem.native()); + + outfile << L"LIBRARY " << name_ws << std::endl; + outfile << L"EXPORTS" << std::endl; + outfile << std::endl; + + outfile << L"\t;; " << symbols.size() << L" symbols" << std::endl; + + // Symbols from our object files + SymbolMap::const_iterator it = symbols.begin(); + for ( ; it != symbols.end(); ++it) + { + outfile << L"\t" << (*it) << std::endl; + } + } + catch (const std::exception& ex) + { + std::cerr << "Unexpected exception:" << std::endl; + std::cerr << ex.what() << std::endl; + std::cerr << std::endl; + + PrintHelpAndExit(1); + } + + return 0; +} diff --git a/subprojects/dump2def/meson.build b/subprojects/dump2def/meson.build new file mode 100644 index 000000000..a3e5af7db --- /dev/null +++ b/subprojects/dump2def/meson.build @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: 2024 Duncan Greatwood +# +# SPDX-License-Identifier: Apache-2.0 + +project( + 'dump2def', + 'cpp', + version: '0.1.0', + license: 'Apache-2.0', + default_options: [ + 'cpp_std=c++17', + 'buildtype=release', + 'b_ndebug=if-release', + 'b_lto=false', + 'warning_level=3' + ], + meson_version: '>=0.53.2' +) + +if host_machine.system() == 'windows' + dump2def_exe = executable('dump2def', 'dump2def.cc', install : false) +endif + + diff --git a/tests/async_test.cc b/tests/async_test.cc index 3e4c7ea94..9a5e67f5d 100644 --- a/tests/async_test.cc +++ b/tests/async_test.cc @@ -293,10 +293,29 @@ TEST(async_test, when_any) [](double val) { return -val; }); auto p2 = doAsyncTimed(std::chrono::seconds(1), std::string("Hello"), [](std::string val) { - std::transform(std::begin(val), std::end(val), - std::begin(val), ::toupper); + std::transform(std::cbegin(val), + std::cend(val), std::begin(val), + [](const char ch) + { + const unsigned char uch = + static_cast(ch); + auto ires = ::toupper(uch); + return(static_cast(ires)); + }); return val; }); + // Note re: the lambda function being used for std::transform + // above. Previously (before 10/2024), std::transform was simply being + // passed ::tolower/::toupper for the transformer function, but in fact we + // need to make two changes to that: i) we need to cast the input parm to + // "unsigned char" to avoid errors due to sign extension as explained on + // the Linux man page + // (e.g. https://www.man7.org/linux/man-pages/man3/toupper.3.html, Notes + // section); and ii) since the result of the transformer function is + // written into std::string, i.e. to a char, the transformer function needs + // to return a char, not (as ::tolower/::toupper does) an int, otherwise + // the compiler may complain about loss of integer size in writing the int + // to a char (and in fact MSVC was complaining in exactly this way). bool resolved = false; Async::whenAny(p1, p2).then( diff --git a/tests/fuzzers/fuzz_parser.cpp b/tests/fuzzers/fuzz_parser.cpp index 7bfd981c8..c382fda29 100644 --- a/tests/fuzzers/fuzz_parser.cpp +++ b/tests/fuzzers/fuzz_parser.cpp @@ -117,7 +117,7 @@ void fuzz_router(const std::string& input) const auto sanitized = Pistache::Rest::SegmentTreeNode::sanitizeResource(path_input); std::shared_ptr ptr(new char[sanitized.length()], std::default_delete()); - memcpy(ptr.get(), sanitized.data(), sanitized.length()); + std::memcpy(ptr.get(), sanitized.data(), sanitized.length()); const std::string_view path { ptr.get(), sanitized.length() }; switch (test_case) @@ -126,7 +126,7 @@ void fuzz_router(const std::string& input) ignoreExceptions([&] { Pistache::Rest::Route::Handler handler = [](auto...) { return Pistache::Rest::Route::Result::Ok; }; std::shared_ptr ptr(new char[sanitized.length()], std::default_delete()); - memcpy(ptr.get(), sanitized.data(), sanitized.length()); + std::memcpy(ptr.get(), sanitized.data(), sanitized.length()); const std::string_view path { ptr.get(), sanitized.length() }; tree.addRoute(path, handler, ptr); }); diff --git a/tests/headers_test.cc b/tests/headers_test.cc index 5825db083..0f99589ec 100644 --- a/tests/headers_test.cc +++ b/tests/headers_test.cc @@ -513,12 +513,12 @@ TEST(headers_test, connection) for (auto test : tests) { - Pistache::Http::Header::Connection connection; + Pistache::Http::Header::Connection this_conn; std::ostringstream oss; - connection.parse(test.data); - connection.write(oss); + this_conn.parse(test.data); + this_conn.write(oss); - ASSERT_EQ(connection.control(), test.expected); + ASSERT_EQ(this_conn.control(), test.expected); ASSERT_EQ(oss.str(), test.expected_string); } } @@ -573,6 +573,8 @@ TEST(headers_test, date_test_ostream) const char* cstr_to_compare = "Fri, 25 Jan 2019 21:04:45." #if defined __clang__ && !defined __linux__ "000000" +#elif defined _MSC_VER // Microsoft Visual Compiler + "0000000" #else "000000000" #endif diff --git a/tests/helpers/fd_utils.cc b/tests/helpers/fd_utils.cc index 21af7ee97..10b7731b9 100644 --- a/tests/helpers/fd_utils.cc +++ b/tests/helpers/fd_utils.cc @@ -30,7 +30,9 @@ namespace filesystem = std::experimental::filesystem; // buffersize - size of buffer // // Return: if buffer non-null, number of proc_fdinfo written, -1 on fail - +#elif defined _WIN32 +#include +#include // for GetProcessHandleCount #elif !defined __linux__ #include // for sysconf @@ -87,6 +89,15 @@ namespace Pistache return std::distance(directory_iterator(fds_dir), directory_iterator {}); +#elif defined _WIN32 + DWORD dw_handle_count = 0; + BOOL gphc_res = GetProcessHandleCount(GetCurrentProcess(), + &dw_handle_count); + if (!gphc_res) + throw std::runtime_error("GetProcessHandleCount failed"); + + return(static_cast(dw_handle_count)); + #else // fallback case, e.g. *BSD #ifndef OPEN_MAX #define OPEN_MAX 4096 @@ -120,7 +131,7 @@ namespace Pistache int fd = dup((int)j); if (fd < 0) continue; - n++; + ++n; close(fd); } diff --git a/tests/helpers/meson.build b/tests/helpers/meson.build index bd69a12a0..87f451d95 100644 --- a/tests/helpers/meson.build +++ b/tests/helpers/meson.build @@ -6,9 +6,19 @@ helpers_src = [ 'fd_utils.cc' ] -tests_helpers = library( - 'tests_helpers', - sources: helpers_src -) +# It is easier to link with a static test-helpers library in the +# Windows case (no need to create an "import library" matching the +# DLL). +if host_machine.system() == 'windows' + tests_helpers = static_library( + 'tests_helpers', + sources: helpers_src + ) +else + tests_helpers = library( + 'tests_helpers', + sources: helpers_src + ) +endif tests_helpers_dep = declare_dependency(link_with: tests_helpers) diff --git a/tests/helpers_test.cc b/tests/helpers_test.cc index 17bd6428c..69cf14149 100644 --- a/tests/helpers_test.cc +++ b/tests/helpers_test.cc @@ -7,11 +7,13 @@ #include "helpers/fd_utils.h" #include +#include + #include #include #include -#include +#include PIST_QUOTE(PIST_SOCKFNS_HDR) // e.g. unistd.h using namespace Pistache; @@ -28,14 +30,15 @@ namespace // em_event_new does not allocate an actual fd, so we provide // one to achieve the same effect - int actual_fd = ::socket(AF_INET, SOCK_STREAM, 0); + em_socket_t actual_fd = PST_SOCK_SOCKET(AF_INET, SOCK_STREAM, 0); if (actual_fd < 0) throw std::runtime_error("::socket failed"); fd_ = Polling::Epoll::em_event_new(actual_fd, EVM_WRITE | EVM_PERSIST, - FD_CLOEXEC, F_SETFDL_NOTHING); + PST_FD_CLOEXEC, + F_SETFDL_NOTHING); if (fd_ == PS_FD_EMPTY) throw std::runtime_error("Epoll::em_event_new failed"); @@ -66,6 +69,15 @@ namespace } // namespace +#if defined(_WIN32) && defined(__MINGW32__) && defined(DEBUG) + // In this special case, we allow the number of FDs in use to grow by + // one. This may be related to the use of GetModuleHandleA to load + // KernelBase.dll. Or not. We have only seen the number of file handles in + // use grow in DEBUG mode, so it is also possible it's related to Windows + // logging. +#define ALLOW_OPEN_FDS_TO_GROW_BY_ONE 1 +#endif + TEST(fd_utils_test, same_result_for_two_calls) { // We do an initial log, since the first time something is logged may cause @@ -77,7 +89,12 @@ TEST(fd_utils_test, same_result_for_two_calls) const auto count1 = get_open_fds_count(); const auto count2 = get_open_fds_count(); - ASSERT_EQ(count1, count2); +#ifdef ALLOW_OPEN_FDS_TO_GROW_BY_ONE + if ((count1+1) == count2) + ASSERT_EQ(count1+1, count2); + else +#endif + ASSERT_EQ(count1, count2); } TEST(fd_utils_test, delect_new_descriptor) @@ -85,8 +102,14 @@ TEST(fd_utils_test, delect_new_descriptor) const auto count1 = get_open_fds_count(); const ScopedFd new_fd; const auto count2 = get_open_fds_count(); - +#ifdef _WIN32 + // Doing a winsock "socket" call to allocate a socket handle actually seems + // to use up 7 handles in total (Windows 11, Sept/2024) + ASSERT_GT(count2, count1); + ASSERT_GT(count1+32, count2); +#else ASSERT_EQ(count1 + 1, count2); +#endif } TEST(fd_utils_test, delect_descriptor_close) @@ -96,5 +119,10 @@ TEST(fd_utils_test, delect_descriptor_close) fd.close(); const auto count2 = get_open_fds_count(); - ASSERT_EQ(count1, count2 + 1); +#ifdef ALLOW_OPEN_FDS_TO_GROW_BY_ONE + if (count1 == count2) + ASSERT_EQ(count1, count2); + else +#endif + ASSERT_EQ(count1, count2 + 1); } diff --git a/tests/http_client_test.cc b/tests/http_client_test.cc index 7e089b8b8..6d686f034 100644 --- a/tests/http_client_test.cc +++ b/tests/http_client_test.cc @@ -377,6 +377,10 @@ TEST( [&](Http::Response rsp) { if (rsp.code() == Http::Code::Ok) ++response_counter; +#ifdef DEBUG + else + PS_LOG_WARNING_ARGS("Http failure code %d", rsp.code()); +#endif }, Async::IgnoreException); responses.push_back(std::move(response)); @@ -427,10 +431,26 @@ TEST(http_client_test, test_client_timeout) [&res, num = i](Http::Response rsp) { if (rsp.code() == Http::Code::Ok) { + PS_LOG_DEBUG_ARGS("Http::Response num %d", num); res[num] = rsp.body(); } +#ifdef DEBUG + else + { + PS_LOG_DEBUG_ARGS("Http::Response num %d resp code %d", + num, rsp.code()); + } +#endif }, - [&rejects_counter](std::exception_ptr) { ++rejects_counter; }); + [&rejects_counter +#ifdef DEBUG + , num = i +#endif + ](std::exception_ptr) + { + PS_LOG_DEBUG_ARGS("Http::Response reject num %d", num); + ++rejects_counter; + }); responses.push_back(std::move(response)); } diff --git a/tests/http_server_test.cc b/tests/http_server_test.cc index 57268e175..ca356914b 100644 --- a/tests/http_server_test.cc +++ b/tests/http_server_test.cc @@ -50,45 +50,25 @@ using namespace std::chrono; namespace { - - class SimpleLogger - { - public: - static SimpleLogger& instance() - { - static SimpleLogger logger; - return logger; - } - - void log(const std::string& message) - { - std::lock_guard guard { m_coutLock }; - std::cout << message << std::endl; - - // Save in syslog / os_log as well - PSLogNoLocFn(LOG_INFO, - false, // don't send to stdout - just did that - "%s", message.c_str()); - } - - private: - SimpleLogger() = default; - std::mutex m_coutLock; - }; - // from // https://stackoverflow.com/questions/9667963/can-i-rewrite-a-logging-macro-with-stream-operators-to-use-a-c-template-functi class ScopedLogger { public: - ScopedLogger(const std::string& prefix) + ScopedLogger(const std::string& prefix, + const char * f, int l, const char * m) : + f_(f), l_(l), m_(m) { - m_stream << "[" << prefix << "] [" << std::hex << std::this_thread::get_id() << "] " << std::dec; + m_stream << "[" << prefix << "] "; } ~ScopedLogger() { - SimpleLogger::instance().log(m_stream.str()); + // Save in syslog / os_log and send to stdout + PSLogFn(LOG_INFO, true, f_.c_str(), l_, m_.c_str(), + "%s", m_stream.str().c_str()); + // The "true" in PSLogFn (normally PS_LOG_AND_STDOUT) is "send to + // both stdout and log" } std::stringstream& stream() @@ -98,10 +78,13 @@ namespace private: std::stringstream m_stream; + std::string f_; + int l_; + std::string m_; }; -#define LOGGER(prefix, message) ScopedLogger(prefix).stream() << message; - +#define LOGGER(prefix, message) ScopedLogger( \ + prefix, __FILE__, __LINE__, __FUNCTION__).stream() << message; } struct HelloHandlerWithDelay : public Http::Handler @@ -180,7 +163,7 @@ struct FileHandler : public Http::Handler Http::serveFile(writer, fileName_) .then( - [this](ssize_t bytes) { + [this](PST_SSIZE_T bytes) { LOGGER("server", "Sent " << bytes << " bytes from " << fileName_ << " file"); }, Async::IgnoreException); @@ -588,14 +571,45 @@ TEST(http_server_test, server_with_static_file) { // encapsulate - const std::string data("Hello, World!"); - char fileName[PATH_MAX] = "/tmp/pistacheioXXXXXX"; +#ifdef _IS_WINDOWS + // Note there is no mkstemp on Windows + + const char * fileName = nullptr; + std::string fn_buf_sstr("C:\\temp\\pistacheio76191"); + + { // encapsulate + TCHAR tmp_path[PST_MAXPATHLEN+16]; + tmp_path[0] = 0; + DWORD gtp_res = GetTempPathA(PST_MAXPATHLEN, &(tmp_path[0])); + if ((!gtp_res) || (gtp_res > PST_MAXPATHLEN)) + { + std::cerr << "No temp path found!" << std::endl; + } + else + { + TCHAR tmp_fn_buf[PST_MAXPATHLEN+16]; + tmp_fn_buf[0] = 0; + UINT gtfn_res = GetTempFileNameA(&(tmp_path[0]), + "PST", // prefix + 0, // Windows chooses the name + &(tmp_fn_buf[0])); + if (!gtfn_res) + std::cerr << "No temp path found!" << std::endl; + else + fn_buf_sstr = std::string(&(tmp_fn_buf[0])); + } + } + fileName = fn_buf_sstr.c_str(); +#else + char fileName[PST_MAXPATHLEN] = "/tmp/pistacheioXXXXXX"; if (!mkstemp(fileName)) { std::cerr << "No suitable filename can be generated!" << std::endl; } +#endif LOGGER("test", "Creating temporary file: " << fileName); + const std::string data("Hello, World!"); std::ofstream tmpFile; tmpFile.open(fileName); tmpFile << data; @@ -969,54 +983,109 @@ TEST(http_server_test, client_request_timeout_on_delay_in_request_line_send_rais { // encapsulate - Pistache::Address address("localhost", Pistache::Port(0)); + Pistache::Address address("localhost", Pistache::Port(0)); - const auto headerTimeout = std::chrono::seconds(2); + const auto headerTimeout = std::chrono::seconds(2); - Http::Endpoint server(address); - auto flags = Tcp::Options::ReuseAddr; - auto opts = Http::Endpoint::options() - .flags(flags) - .headerTimeout(headerTimeout); + Http::Endpoint server(address); + auto flags = Tcp::Options::ReuseAddr; + auto opts = Http::Endpoint::options() + .flags(flags) + .headerTimeout(headerTimeout); - server.init(opts); - server.setHandler(Http::make_handler()); - server.serveThreaded(); + server.init(opts); + server.setHandler(Http::make_handler()); + server.serveThreaded(); - auto port = server.getPort(); - auto addr = "localhost:" + port.toString(); - LOGGER("test", "Server address: " << addr); + auto port = server.getPort(); + auto addr = "localhost:" + port.toString(); + LOGGER("test", "Server address: " << addr); - const std::string reqStr { "GET /ping HTTP/1.1\r\n" }; - TcpClient client; - EXPECT_TRUE(client.connect(Pistache::Address("localhost", port))) << client.lastError(); - bool send_failed = false; - for (size_t i = 0; i < reqStr.size(); ++i) - { - if (!client.send(reqStr.substr(i, 1))) + const std::string reqStr { "GET /ping HTTP/1.1\r\n" }; + TcpClient client; + EXPECT_TRUE(client.connect(Pistache::Address("localhost", port))) << client.lastError(); + bool send_failed = false; + + char recvBuf[1024] = { + 0, + }; + size_t bytes = 0; + bool cli_rx_res = false; + + for (size_t i = 0; i < reqStr.size(); ++i) { - send_failed = true; - break; + if (!client.send(reqStr.substr(i, 1))) + { + send_failed = true; + break; + } + + // This code sends a request from client to server one character at + // a time, with delays between characters. Eventually the server + // gets bored of waiting for a fully formed request (i.e. times + // out), sends an HTTP error response, and closes the connection + // (does CLOSE_FD). Once the server has closed the connection, the + // next client.send (see above) will fail. + // + // In Linux, we can then do "poll" and read the server's + // HTTP response at the client. + // + // However, in Windows once the connection has been closed an + // attempt to do a read on the socket will result in an + // ECONNABORTED error. This is because, in Windows, the failing + // send has error WSAECONNABORTED, and then any further call on the + // client socket, other than close, will generate an additional + // ECONNABORTED error (see // https://learn.microsoft.com/ + // en-us/windows/win32/api/winsock2/nf-winsock2-send). + // + // At the level of the "poll" call, after the send has failed, in + // Linux the poll revents flags are POLLIN | POLLHUP (read + // available | connection hung-up); but in Windows they are POLLERR + // | POLLHUP (connection error | connection hung-up). + // + // To workaround this, in Windows we will attempt a receive after + // each send, and we'll expect the attempt at receive that follows + // the last successful send to read back the server's timeout error + // message. Then we check that the correct HTTP message has been + // returned. +#ifdef _WIN32 + bool this_cli_rx_res = + client.receive(recvBuf, sizeof(recvBuf), + &bytes, std::chrono::milliseconds(300)); + PS_LOG_DEBUG_ARGS("i = %u, client.receive %s%s", i, + (this_cli_rx_res ? "succeeded" : "failed, "), + (this_cli_rx_res ? "" : client.lastError().c_str())); + cli_rx_res |= this_cli_rx_res; + // Note: We expect the succeeding receive to be either the last, or + // the last-but-one +#else + std::this_thread::sleep_for(std::chrono::milliseconds(300)); +#endif } - std::this_thread::sleep_for(std::chrono::milliseconds(300)); - } if (send_failed) { // Usually, send does fail; but on macOS occasionally it does not fail // We workaround that here, since of course we can only check for an // error code when there is an actual error - EXPECT_EQ(client.lastErrno(), EPIPE) << "Errno: " << client.lastErrno(); - char recvBuf[1024] = { - 0, - }; - size_t bytes; - EXPECT_TRUE(client.receive(recvBuf, sizeof(recvBuf), &bytes, std::chrono::seconds(5))) << client.lastError(); +#ifdef _WIN32 + if (client.lastErrno() == ECONNABORTED) // Windows 11 Home + EXPECT_EQ(client.lastErrno(), ECONNABORTED) << "Errno: " << client.lastErrno(); + else if (client.lastErrno() == ECONNRESET) // Windows Server 2022 + EXPECT_EQ(client.lastErrno(), ECONNRESET) << "Errno: " << client.lastErrno(); + else +#endif + EXPECT_EQ(client.lastErrno(), EPIPE) << "Errno: " << client.lastErrno(); + +#ifndef _WIN32 + cli_rx_res = client.receive(recvBuf, sizeof(recvBuf), + &bytes, std::chrono::seconds(5)); +#endif + EXPECT_TRUE(cli_rx_res) << client.lastError(); EXPECT_EQ(0, strncmp(recvBuf, ExpectedResponseLine, strlen(ExpectedResponseLine))); } server.shutdown(); - } // end encapsulate #ifdef _USE_LIBEVENT_LIKE_APPLE @@ -1330,6 +1399,12 @@ struct ContentEncodingHandler : public Http::Handler } }; +#define DECLARE_ORIGINAL_UNCOMPRESSED_DATA \ + std::vector originalUncompressedData( \ + reinterpret_cast(originalUncDataAsUs.data()), \ + reinterpret_cast(originalUncDataAsUs.data() + \ + originalUncDataAsUs.size())); + #ifdef PISTACHE_USE_CONTENT_ENCODING_ZSTD TEST(http_server_test, server_with_content_encoding_zstd) { @@ -1338,18 +1413,25 @@ TEST(http_server_test, server_with_content_encoding_zstd) // Data to send to server to expect it to return compressed... // Allocate storage... - std::vector originalUncompressedData(1024); + std::vector originalUncDataAsUs(512); + + // See comment on server_with_content_encoding_brotli on why we use an + // "unsigned short" for independent_bits_engine below // Random bytes engine... - using random_bytes_engine_type = std::independent_bits_engine< - std::default_random_engine, CHAR_BIT, unsigned char>; - random_bytes_engine_type randomEngine; + using random_us_engine_type = std::independent_bits_engine< + std::default_random_engine, + 8*sizeof(unsigned short), + unsigned short>; + random_us_engine_type randomEngine; - // Fill with random bytes... + // Fill with random unsigned short std::generate( - std::begin(originalUncompressedData), - std::end(originalUncompressedData), - [&randomEngine]() { return static_cast(randomEngine()); }); + std::begin(originalUncDataAsUs), + std::end(originalUncDataAsUs), + [&randomEngine]() { return (randomEngine()); }); + + DECLARE_ORIGINAL_UNCOMPRESSED_DATA; // Bind server to localhost on a random port... const Pistache::Address address("localhost", Pistache::Port(0)); @@ -1453,20 +1535,25 @@ TEST(http_server_test, server_with_content_encoding_zstd) std::vector newlyDecompressedData( originalUncompressedData.size()); - // Size of destination buffer, but will be updated by uncompress() to - // actual size used... - size_t destinationLength = originalUncompressedData.size(); - // Decompress... - const auto compressionStatus = ZSTD_getFrameContentSize(newlyDecompressedData.data(), newlyDecompressedData.size()); + auto decompressedSzFromFrame = ZSTD_getFrameContentSize(newlyCompressedResponse.data(), newlyCompressedResponse.size()); + if (ZSTD_isError(decompressedSzFromFrame)) + { + LOGGER("test", "getFrameContentSize result: " << + ((decompressedSzFromFrame == ZSTD_CONTENTSIZE_UNKNOWN) ? + "Content Size Unknown" : + (decompressedSzFromFrame == ZSTD_CONTENTSIZE_ERROR) ? + "Content Size Error" : "Other")); + decompressedSzFromFrame = newlyDecompressedData.size(); + } - const auto decompressed_size = ZSTD_decompress((void*)newlyDecompressedData.data(), compressionStatus, newlyCompressedResponse.data(), newlyCompressedResponse.size()); + const auto decompressed_size = ZSTD_decompress(reinterpret_cast(newlyDecompressedData.data()), decompressedSzFromFrame, newlyCompressedResponse.data(), newlyCompressedResponse.size()); ASSERT_EQ(ZSTD_isError(decompressed_size), 0u); // The sizes of both the original uncompressed data we sent the server // and the result of decompressing what it sent back should match... - ASSERT_EQ(originalUncompressedData.size(), destinationLength); + ASSERT_EQ(originalUncompressedData.size(), decompressed_size); // Check to ensure the compressed data received back from server after // decompression matches exactly what we originally sent it... @@ -1491,18 +1578,28 @@ TEST(http_server_test, server_with_content_encoding_brotli) // Data to send to server to expect it to return compressed... // Allocate storage... - std::vector originalUncompressedData(1024); + std::vector originalUncDataAsUs(512); + + // Previously, randomEngine was using a std::vector and an + // UIntType of "char". However, only one of unsigned short, unsigned + // int, unsigned long, or unsigned long long are permitted - and Visual + // Studio generates an error otherwise. So switched to using one of the + // permitted types. (@Aug/2024). // Random bytes engine... - using random_bytes_engine_type = std::independent_bits_engine< - std::default_random_engine, CHAR_BIT, unsigned char>; - random_bytes_engine_type randomEngine; + using random_us_engine_type = std::independent_bits_engine< + std::default_random_engine, + 8*sizeof(unsigned short), + unsigned short>; + random_us_engine_type randomEngine; - // Fill with random bytes... + // Fill with random unsigned short std::generate( - std::begin(originalUncompressedData), - std::end(originalUncompressedData), - [&randomEngine]() { return static_cast(randomEngine()); }); + std::begin(originalUncDataAsUs), + std::end(originalUncDataAsUs), + [&randomEngine]() { return (randomEngine()); }); + + DECLARE_ORIGINAL_UNCOMPRESSED_DATA; // Bind server to localhost on a random port... const Pistache::Address address("localhost", Pistache::Port(0)); @@ -1657,18 +1754,25 @@ TEST(http_server_test, server_with_content_encoding_deflate) // Data to send to server to expect it to return compressed... // Allocate storage... - std::vector originalUncompressedData(1024); + std::vector originalUncDataAsUs(512); + + // See comment on server_with_content_encoding_brotli on why we use an + // "unsigned short" for independent_bits_engine below // Random bytes engine... - using random_bytes_engine_type = std::independent_bits_engine< - std::default_random_engine, CHAR_BIT, unsigned char>; - random_bytes_engine_type randomEngine; + using random_us_engine_type = std::independent_bits_engine< + std::default_random_engine, + 8*sizeof(unsigned short), + unsigned short>; + random_us_engine_type randomEngine; - // Fill with random bytes... + // Fill with random unsigned short std::generate( - std::begin(originalUncompressedData), - std::end(originalUncompressedData), - [&randomEngine]() { return static_cast(randomEngine()); }); + std::begin(originalUncDataAsUs), + std::end(originalUncDataAsUs), + [&randomEngine]() { return (randomEngine()); }); + + DECLARE_ORIGINAL_UNCOMPRESSED_DATA; // Bind server to localhost on a random port... const Pistache::Address address("localhost", Pistache::Port(0)); @@ -1773,14 +1877,15 @@ TEST(http_server_test, server_with_content_encoding_deflate) // Size of destination buffer, but will be updated by uncompress() to // actual size used... - unsigned long destinationLength = originalUncompressedData.size(); + unsigned long destinationLength = + static_cast(originalUncompressedData.size()); // Decompress... const auto compressionStatus = ::uncompress( reinterpret_cast(newlyDecompressedData.data()), &destinationLength, reinterpret_cast(resultStringData.data()), - resultStringData.size()); + static_cast(resultStringData.size())); // Check for failure... ASSERT_EQ(compressionStatus, Z_OK); @@ -1807,6 +1912,7 @@ TEST(http_server_test, server_with_content_encoding_deflate) } #endif + TEST(http_server_test, http_server_is_not_leaked) { PS_TIMEDBG_START; @@ -1847,7 +1953,16 @@ TEST(http_server_test, http_server_is_not_leaked) server.reset(); const auto fds_after = get_open_fds_count(); +#if defined(_WIN32) && defined(__MINGW32__) && defined(DEBUG) + // In this special case, we allow the number of FDs in use to grow by + // one. This may be related to the use of GetModuleHandleA to load + // KernelBase.dll. Or not. We have only seen the number of file handles in + // use grow in DEBUG mode, so it is also possible it's related to Windows + // logging. + ASSERT_GE(fds_before+1, fds_after); +#else ASSERT_EQ(fds_before, fds_after); +#endif #ifdef _USE_LIBEVENT_LIKE_APPLE #ifdef DEBUG diff --git a/tests/http_uri_test.cc b/tests/http_uri_test.cc index eefa077cd..a1bd2ff24 100644 --- a/tests/http_uri_test.cc +++ b/tests/http_uri_test.cc @@ -19,7 +19,18 @@ TEST(http_uri_test, query_as_string_test) ASSERT_STREQ(query2.as_str().c_str(), "?value1=name1"); Http::Uri::Query query3; + query3.add("value1", "name1"); query3.add("value2", "name2"); - ASSERT_STREQ(query3.as_str().c_str(), "?value2=name2&value1=name1"); -} \ No newline at end of file + + // Note: Named URI paramaters have the same meaning regardless of order + // And in fact, they appear in opposite order here in Linux vs. Windows + if (query3.as_str().compare("?value2=name2&value1=name1") == 0) + { + ASSERT_STREQ(query3.as_str().c_str(), "?value2=name2&value1=name1"); + } + else + { + ASSERT_STREQ(query3.as_str().c_str(), "?value1=name1&value2=name2"); + } +} diff --git a/tests/https_server_test.cc b/tests/https_server_test.cc index 49bc8c2fc..d5ffe9b05 100644 --- a/tests/https_server_test.cc +++ b/tests/https_server_test.cc @@ -7,6 +7,8 @@ #include #include +#include +#include // for PS_STRNCPY_S #include #include #include @@ -21,6 +23,15 @@ using namespace Pistache; * directory. */ +/* Sept/2024: In Windows, if basic_tls_request_with_auth and + * basic_tls_request_with_auth_with_cb fail, you may need to uninstall the + * default (schannel) libcurl, and install the openssl one instead: + * vcpkg remove curl + * vcpkg install curl[openssl] + * + * See https://github.com/openssl/openssl/issues/25520 for more details + */ + static size_t write_cb(void* contents, size_t size, size_t nmemb, void* userp) { (static_cast(userp))->append(static_cast(contents), size * nmemb); @@ -52,7 +63,7 @@ struct ServeFileHandler : public Http::Handler { Http::serveFile(writer, "./certs/rootCA.crt") .then( - [](ssize_t bytes) { + [](PST_SSIZE_T bytes) { std::cout << "Sent " << bytes << " bytes" << std::endl; }, Async::NoExcept); @@ -114,6 +125,29 @@ TEST(https_server_test, first_curl_global_init) ASSERT_EQ(res, CURLE_OK); } +#ifdef _WIN32 +// CURLSSLOPT_REVOKE_BEST_EFFORT tells libcurl to ignore certificate revocation +// checks in case of missing or offline distribution points for those SSL +// backends where such behavior is present. This option is only supported for +// Schannel (the native Windows SSL library). Setting this option eliminates +// the stderr "schannel: CertGetCertificateChain trust error +// CERT_TRUST_REVOCATION_STATUS_UNKNOWN" message, and makes the following tests +// work in Windows which otherwise failed: +// https_server_test.basic_tls_request +// https_server_test.basic_tls_request_with_chained_server_cert +// https_server_test.basic_tls_request_with_servefile +// https_server_test.basic_tls_request_with_password_cert + +#define CSO_WIN_REVOKE_BEST_EFFORT \ + { \ + CURLcode set_ssl_opts_res = curl_easy_setopt( \ + curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_REVOKE_BEST_EFFORT); \ + ASSERT_EQ(set_ssl_opts_res, CURLE_OK); \ + } +#else +#define CSO_WIN_REVOKE_BEST_EFFORT +#endif + TEST(https_server_test, basic_tls_request) { Http::Endpoint server(Address("localhost", Pistache::Port(0))); @@ -138,6 +172,7 @@ TEST(https_server_test, basic_tls_request) curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &write_cb); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + CSO_WIN_REVOKE_BEST_EFFORT; // Skip hostname check curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); @@ -183,6 +218,7 @@ TEST(https_server_test, basic_tls_request_with_chained_server_cert) curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &write_cb); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + CSO_WIN_REVOKE_BEST_EFFORT; /* Skip hostname check */ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); @@ -225,6 +261,7 @@ TEST(https_server_test, basic_tls_request_with_auth) curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &write_cb); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + CSO_WIN_REVOKE_BEST_EFFORT; /* Skip hostname check */ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); @@ -265,6 +302,7 @@ TEST(https_server_test, basic_tls_request_with_auth_no_client_cert) curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &write_cb); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + CSO_WIN_REVOKE_BEST_EFFORT; /* Skip hostname check */ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); @@ -306,6 +344,7 @@ TEST(https_server_test, basic_tls_request_with_auth_client_cert_not_signed) curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &write_cb); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + CSO_WIN_REVOKE_BEST_EFFORT; /* Skip hostname check */ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); @@ -357,6 +396,7 @@ TEST(https_server_test, basic_tls_request_with_auth_with_cb) curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &write_cb); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + CSO_WIN_REVOKE_BEST_EFFORT; /* Skip hostname check */ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); @@ -402,6 +442,8 @@ TEST(https_server_test, basic_tls_request_with_servefile) curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errorstring.data()); // curl_easy_setopt(curl, CURLOPT_VERBOSE, true); + CSO_WIN_REVOKE_BEST_EFFORT; + /* Skip hostname check */ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); @@ -426,7 +468,7 @@ TEST(https_server_test, basic_tls_request_with_password_cert) const auto passwordCallback = [](char* buf, int size, int /*rwflag*/, void* /*u*/) -> int { static constexpr const char* const password = "test"; - std::strncpy(buf, password, size); + PS_STRNCPY_S(buf, size, password, size); return static_cast(std::strlen(password)); }; @@ -448,6 +490,7 @@ TEST(https_server_test, basic_tls_request_with_password_cert) curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &write_cb); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + CSO_WIN_REVOKE_BEST_EFFORT; /* Skip hostname check */ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); diff --git a/tests/listener_test.cc b/tests/listener_test.cc index 706bc37df..4516a8b2e 100644 --- a/tests/listener_test.cc +++ b/tests/listener_test.cc @@ -6,18 +6,29 @@ #include -#include +#include +#include PIST_QUOTE(PST_ARPA_INET_HDR) +#include PIST_QUOTE(PST_NETDB_HDR) +#include PIST_QUOTE(PST_NETINET_IN_HDR) + + #include -#include -#include #include -#include +#include PIST_QUOTE(PST_SOCKET_HDR) +#include PIST_QUOTE(PIST_SOCKFNS_HDR) + #include -#include +#include PIST_QUOTE(PST_MISC_IO_HDR) // unistd.h #include #include +#include +#include // provides "sleep_for" +using namespace std::chrono_literals; + +#include + #include #include #include @@ -26,17 +37,23 @@ #include // for wait #endif +#ifdef _IS_WINDOWS +#include // for fileapi.h +#include // for GetTempPathA +#include +#endif + class SocketWrapper { public: - explicit SocketWrapper(int fd) + explicit SocketWrapper(em_socket_t fd) : fd_(fd) { } ~SocketWrapper() { PS_TIMEDBG_START; - close(fd_); + PST_SOCK_CLOSE(fd_); } uint16_t port() @@ -47,7 +64,7 @@ class SocketWrapper socklen_t len = sizeof(sin); uint16_t port = 0; - if (getsockname(fd_, reinterpret_cast(&sin), &len) == -1) + if (PST_SOCK_GETSOCKNAME(fd_, reinterpret_cast(&sin), &len) == -1) { perror("getsockname"); } @@ -61,7 +78,7 @@ class SocketWrapper } private: - int fd_; + em_socket_t fd_; }; // Just there for show. @@ -86,16 +103,17 @@ SocketWrapper bind_free_port_helper(int ai_family) { PS_TIMEDBG_START; - int sockfd = -1; // listen on sock_fd, new connection on new_fd + em_socket_t sockfd = -1; // listen on sock_fd, new connection on new_fd struct addrinfo hints = {}, *servinfo, *p; - int yes = 1; + PST_SOCK_OPT_VAL_T yes = 1; int rv; hints.ai_family = ai_family; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; // use my IP + PST_SOCK_STARTUP_CHECK; if ((rv = getaddrinfo(nullptr, "0", &hints, &servinfo)) != 0) { if (ai_family == AF_UNSPEC) @@ -108,7 +126,7 @@ SocketWrapper bind_free_port_helper(int ai_family) for (p = servinfo; p != nullptr; p = p->ai_next) { - if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) + if ((sockfd = PST_SOCK_SOCKET(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { PS_LOG_DEBUG("server: socket"); if (ai_family == AF_UNSPEC) @@ -116,7 +134,7 @@ SocketWrapper bind_free_port_helper(int ai_family) continue; } - if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) { PS_LOG_DEBUG("setsockopt"); if (ai_family == AF_UNSPEC) @@ -127,10 +145,10 @@ SocketWrapper bind_free_port_helper(int ai_family) throw std::runtime_error("setsockopt fail"); } - if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) + if (PST_SOCK_BIND(sockfd, p->ai_addr, p->ai_addrlen) == -1) { PS_LOG_DEBUG_ARGS("server: bind failed, sockfd %d", sockfd); - close(sockfd); + PST_SOCK_CLOSE(sockfd); if (ai_family == AF_UNSPEC) perror("server: bind"); continue; @@ -150,7 +168,7 @@ SocketWrapper bind_free_port_helper(int ai_family) } throw std::runtime_error("failed to bind"); } - + return SocketWrapper(sockfd); } @@ -163,7 +181,7 @@ SocketWrapper bind_free_port() // IPv4 when available. However, in FreeBSD, it causes us to use IPv6 when // available. Since Pistache itself defaults to IPv4, we try IPv4 first for // bind_free_port_helper, and only try AF_UNSPEC if IPv4 fails. - + try { return(bind_free_port_helper(AF_INET/*IPv4*/)); @@ -195,7 +213,7 @@ TEST(listener_test, listener_bind_port_free) FAIL() << "Could not find a free port. Abort test.\n"; } - PS_LOG_DEBUG_ARGS("port_nb %u", (unsigned int)port_nb); + PS_LOG_DEBUG_ARGS("port_nb %u", static_cast(port_nb)); Pistache::Port port(port_nb); Pistache::Address address(Pistache::Ipv4::any(), port); @@ -221,7 +239,7 @@ TEST(listener_test, listener_uses_default) PS_LOG_DEBUG("Failed to get port"); FAIL() << "Could not find a free port. Abort test.\n"; } - PS_LOG_DEBUG_ARGS("port_nb %u", (unsigned int)port_nb); + PS_LOG_DEBUG_ARGS("port_nb %u", static_cast(port_nb)); Pistache::Port port(port_nb); Pistache::Address address(Pistache::Ipv4::any(), port); @@ -266,16 +284,26 @@ TEST(listener_test, listener_bind_port_not_free_throw_runtime) std::cout << err.what() << std::endl; int flag = 0; - // GNU libc - if (strncmp(err.what(), "Address already in use", - sizeof("Address already in use")) - == 0) + + PST_DECL_SE_ERR_P_EXTRA; + char * desired_str = // Try strerror_r in case str affected by locale + PST_STRERROR_R(EADDRINUSE, &se_err[0], sizeof(se_err)-16); + if ((desired_str) && (strlen(desired_str)) && + (strncmp(err.what(), desired_str, strlen(desired_str)) == 0)) { flag = 1; } - // Musl libc - if (strncmp(err.what(), "Address in use", sizeof("Address in use")) == 0) - { + else if (strncmp(err.what(), "Address already in use", + sizeof("Address already in use")) == 0) + { // GNU libc + flag = 1; + } + else if (strncmp(err.what(), "Address in use", sizeof("Address in use")) == 0) + { // Musl libc + flag = 1; + } + else if (strncmp(err.what(), "address in use", sizeof("address in use")) == 0) + { // MSVS flag = 1; } ASSERT_EQ(flag, 1); @@ -300,7 +328,7 @@ TEST(listener_test, listener_bind_ephemeral_v4_port) listener.bind(address); Pistache::Port bound_port = listener.getPort(); - ASSERT_TRUE(bound_port > (uint16_t)0); + ASSERT_TRUE(bound_port > static_cast(0)); } TEST(listener_test, listener_bind_ephemeral_v6_port) @@ -318,7 +346,7 @@ TEST(listener_test, listener_bind_ephemeral_v6_port) listener.bind(address); Pistache::Port bound_port = listener.getPort(); - ASSERT_TRUE(bound_port > (uint16_t)0); + ASSERT_TRUE(bound_port > static_cast(0)); } ASSERT_TRUE(true); } @@ -328,9 +356,37 @@ TEST(listener_test, listener_bind_unix_domain) PS_TIMEDBG_START; // Avoid name conflict by binding within a fresh temporary directory. +#ifdef _IS_WINDOWS + // No mkdtemp or equivalent in Windows + + const char * tmpDir = 0; + std::string td_buf_sstr("C:\\temp\\bind_test_852823"); + + { // encapsulate + TCHAR tmp_path[PST_MAXPATHLEN+16]; + tmp_path[0] = 0; + DWORD gtp_res = GetTempPathA(PST_MAXPATHLEN, &(tmp_path[0])); + if ((!gtp_res) || (gtp_res > PST_MAXPATHLEN)) + { + std::cerr << "No temp path found!" << std::endl; + } + else + { + std::random_device rd; // a seed source for the engine + std::mt19937 gen(rd()); // mersenne_twister_engine + std::uniform_int_distribution<> distrib(100000, 999999); + auto rnd_6_digits = distrib(gen); + + td_buf_sstr = std::string(&(tmp_path[0])) + "bind_test_" + + std::to_string(rnd_6_digits); + } + } + tmpDir = td_buf_sstr.c_str(); +#else #define DIR_TEMPLATE "/tmp/bind_test_XXXXXX" auto dirTemplate = std::array { DIR_TEMPLATE }; const auto tmpDir = ::mkdtemp(dirTemplate.data()); +#endif ASSERT_TRUE(tmpDir != nullptr); auto ss = std::stringstream(); ss << tmpDir << "/unix_socket"; @@ -338,9 +394,7 @@ TEST(listener_test, listener_bind_unix_domain) struct sockaddr_un sa = {}; sa.sun_family = AF_UNIX; - std::strncpy(sa.sun_path, sockName.c_str(), sizeof sa.sun_path - 1); - // Belt and suspenders... - sa.sun_path[sizeof sa.sun_path - 1] = '\0'; + PS_STRLCPY(sa.sun_path, sockName.c_str(), sizeof sa.sun_path); auto address = Pistache::Address::fromUnix(reinterpret_cast(&sa)); auto opts = Pistache::Http::Endpoint::options().threads(2); @@ -353,10 +407,20 @@ TEST(listener_test, listener_bind_unix_domain) endpoint.shutdown(); // Clean up. - (void)::unlink(sockName.c_str()); - (void)::rmdir(tmpDir); + std::ignore = PST_UNLINK(sockName.c_str()); + std::ignore = PST_RMDIR(tmpDir); } +#ifndef _WIN32 +// CLOEXEC doesn't exist on Windows, and forking is a lower-level system not +// exposed to the user with documented APIs, so these are not really meaningful +// Windows tests +// +// For more inform on the not-officially-documented Windows forking +// capabilities, see: +// https://github.com/huntandhackett/process-cloning +// https://captmeelo.com/redteam/maldev/2022/05/10/ntcreateuserprocess.html + class CloseOnExecTest : public testing::Test { public: @@ -389,8 +453,8 @@ class CloseOnExecTest : public testing::Test { PS_TIMEDBG_START; - pid_t id = fork(); - if (is_child_process(id)) + pid_t fork_res = fork(); + if (is_child_process(fork_res)) { PS_TIMEDBG_START; @@ -398,18 +462,21 @@ class CloseOnExecTest : public testing::Test server->bind(); // leak open socket to child of our child process + // static_cast to ignore return value [[maybe_unused]] int sys_res = (std::system("sleep 10 <&- &")); // Assign result of std::system to suppress Linux warning: // warning: ignoring return value of ‘int system(const char*)’ // declared with attribute ‘warn_unused_result’ [-Wunused-result] - + exit(0); } int status = 0; wait(&status); ASSERT_EQ(0, status); - usleep(100000); // wait 100 ms, so socket gets a chance to be closed + + // wait 100 ms, so socket gets a chance to be closed + std::this_thread::sleep_for(100ms); } uint16_t port = get_free_port(); @@ -448,3 +515,5 @@ TEST_F(CloseOnExecTest, socket_leaked) }, std::runtime_error); } + +#endif // of ifndef _WIN32 diff --git a/tests/meson.build b/tests/meson.build index a3ebc114a..137388e01 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -61,6 +61,62 @@ if get_option('PISTACHE_USE_CONTENT_ENCODING_ZSTD') zstd_dep = dependency('libzstd') endif +# In Windows, when one of the test programs runs, it looks for +# "pistache.dll". If pistache.dll has not been installed in the +# system, then we can make the test program work by having a soft link +# to the DLL (aka "symbolic link") in the directory containing the +# test program. That makes the test program pick up the DLL from the DLL's +# own build directory, which is typically what we want when we're +# testing. +unversioned_dll_cts = [] +if host_machine.system() == 'windows' + powershell_prog = find_program('powershell.exe') + libpistache_filename = libpistache.full_path() + + unversioned_dll_ct1 = custom_target('unversioned_dll', + # input: [libpistache] + output: ['pistache.dll'], + command: [powershell_prog, '-NoProfile', + '-NonInteractive', '-WindowStyle', 'Hidden', + '-NoLogo', + '-Command', + 'if (!(Test-Path -Path "@OUTPUT0@")) { New-Item -ItemType SymbolicLink -Path "@OUTPUT0@" -Target "'+libpistache_filename+'" }'], + depends: [libpistache]) + + unversioned_dll_cts += unversioned_dll_ct1 + + if compiler.get_id() == 'gcc' + unversioned_dll_ct2 = custom_target('altname_dll', + input: [libpistache], + output: ['@PLAINNAME@'], + command: [powershell_prog, '-NoProfile', + '-NonInteractive', '-WindowStyle', 'Hidden', + '-NoLogo', + '-Command', + 'if (!(Test-Path -Path "@OUTPUT0@")) { New-Item -ItemType SymbolicLink -Path "@OUTPUT0@" -Target "'+libpistache_filename+'" }'], + depends: [libpistache]) + unversioned_dll_cts += unversioned_dll_ct2 + endif +endif + +test_link_args = [] +if host_machine.system() == 'windows' and compiler.get_id() == 'gcc' + # If we don't make libstdc++ static, we leave it to the Windows OS + # to find the dynamic libstdc++-6.dll. Normally, that would be OK, + # since Windows checks the Path for DLL locations and we could + # could have the gcc libstdc++-6.dll appear on the path before + # Visual Studio's libstdc++-6.dll. However, Windows actually + # treats libstdc++-6.dll as a "Known DLL", which means it fetches + # it from Visual Studio regardless of what we have on the + # Path. Which is not OK if we're building with gcc. + # Ref: https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order + # The alternative might be do it using DLL redirection (see + # https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-redirection) + + test_link_args += '-static-libgcc' + test_link_args += '-static-libstdc++' +endif + foreach test_name : pistache_test_files suite = {} if test_name in network_tests @@ -74,6 +130,7 @@ foreach test_name : pistache_test_files executable( 'run_'+test_name, test_name+'.cc', + link_args: test_link_args, dependencies: [ pistache_dep, tests_helpers_dep, @@ -83,14 +140,16 @@ foreach test_name : pistache_test_files curl_dep, cpp_httplib_dep, brotli_dep, - zstd_dep + zstd_dep ] ), + depends: unversioned_dll_cts, timeout: 600, workdir: meson.current_build_dir(), is_parallel: false, kwargs: suite ) + endforeach cppcheck = find_program('cppcheck', required: false) diff --git a/tests/net_test.cc b/tests/net_test.cc index 7001590d1..708384f34 100644 --- a/tests/net_test.cc +++ b/tests/net_test.cc @@ -7,15 +7,17 @@ #include #include +#include + #include #include #include -#include -#include -#include -#include +#include PIST_QUOTE(PST_ARPA_INET_HDR) +#include PIST_QUOTE(PST_NETDB_HDR) +#include PIST_QUOTE(PST_NETINET_IN_HDR) +#include PIST_QUOTE(PST_SYS_UN_HDR) using namespace Pistache; using testing::Eq; diff --git a/tests/rest_server_test.cc b/tests/rest_server_test.cc index 1cdc67bd6..76481ae5e 100644 --- a/tests/rest_server_test.cc +++ b/tests/rest_server_test.cc @@ -16,6 +16,10 @@ #include #include +#ifdef _WIN32 +#include // used for choosing certain timeouts +#endif + using namespace std; using namespace Pistache; @@ -257,15 +261,62 @@ TEST(rest_server_test, keepalive_multithread_client_request) std::this_thread::sleep_for(KEEPALIVE_TIMEOUT + std::chrono::milliseconds(700)); })); } - std::this_thread::sleep_for(std::chrono::milliseconds(700)); - auto peer = stats.getAllPeer(); - EXPECT_EQ(peer.size(), THR_NUMBER); + // @Sept/2024. Based on behavior seen in Windows 11, changed this timeout + // from 700ms to 3500ms. + // + // The longer timeout is required due to the behavior of httplib.h + // client.Get. In overview, httplib.h loops through the client addr it + // finds, issuing a "connect" for each addr in series. Following each + // "connect", it calls wait_until_socket_is_ready, which uses "select" with + // a 300ms timeout to get a result for the connect. Now, suppose there + // were 10 addr that failed select based on the timeout, it would take + // 3000ms until finally connect was issued on the addr that works. + // + // On the server side, the peer cannot be created until the "connect" is + // received. So if the sleep_for below were 700ms, there would still be + // zero server-side peers once the sleep_for completes, causing: + // EXPECT_EQ(peer.size(), THR_NUMBER) + // to fail below. + // + // In practice, we saw ~2200ms delay before the socket connected + // successfully - Windows 11 running in a VM. So in Windows, this test + // would fail with a 700ms sleep_for, but succeed with 3500ms. + // + // Conversely, for macOS if we use 3500ms then the test fails - presumably, + // the connect happens so fast that not only has the connect happened but + // also the clients connections have timed out and the peers at the server + // have all closed again before the EXPECT_EQ(peer.size(), THR_NUMBER) test + // below executes. + // + // Experimentation with Windows 2019 server (using Visual Studio + // 2019), indicates further variability in the correct sleep time ahead of + // testing that peer.size() equals THR_NUMBER. + // In Debug build, it works with sleep times between 3000 and 3750ms. + // In Release build, it works with sleep times between 1063 and 3063. + // + // Given this level of variability in the time taken to connect the peers, + // we make the sleep time dynamic, checking every 700ms to see if the peers + // have successfully connected. + + size_t max_peers_seen = 0; + for(unsigned int i=0; i<8; i++) + { + std::this_thread::sleep_for(std::chrono::milliseconds(700)); + auto peer = stats.getAllPeer(); + if (peer.size() > max_peers_seen) + { + max_peers_seen = peer.size(); + if (max_peers_seen >= THR_NUMBER) + break; + } + } + EXPECT_EQ(max_peers_seen, THR_NUMBER); for (auto it = threads.begin(); it != threads.end(); ++it) { it->join(); } - peer = stats.getAllPeer(); + auto peer = stats.getAllPeer(); EXPECT_EQ(peer.size(), 0ul); stats.shutdown(); diff --git a/tests/rest_swagger_server_test.cc b/tests/rest_swagger_server_test.cc index ee7e92f9f..2400e4bea 100644 --- a/tests/rest_swagger_server_test.cc +++ b/tests/rest_swagger_server_test.cc @@ -47,7 +47,7 @@ class SwaggerEndpoint Rest::Swagger swagger(desc); swagger .uiPath("/doc") - .uiDirectory(filesystem::current_path() / "assets") + .uiDirectory((filesystem::current_path() / "assets").string()) .apiPath("/banker-api.json") .serializer(&Rest::Serializer::rapidJson) .install(router); @@ -70,47 +70,56 @@ TEST(rest_swagger_server_test, basic_test) { filesystem::create_directory("assets"); - ofstream("assets/good.txt") << "good"; + { // encapsulate - ofstream("bad.txt") << "bad"; + ofstream("assets/good.txt") << "good"; + ofstream("bad.txt") << "bad"; - Address addr(Ipv4::loopback(), Port(0)); - SwaggerEndpoint swagger(addr); + Address addr(Ipv4::loopback(), Port(0)); + SwaggerEndpoint swagger(addr); - swagger.init(); - thread t([&swagger]() { - while (swagger.getPort() == 0) - { - this_thread::yield(); - } + swagger.init(); + thread t([&swagger]() { + while (swagger.getPort() == 0) + { + this_thread::yield(); + } - Port port = swagger.getPort(); + Port port = swagger.getPort(); - cout << "CWD = " << filesystem::current_path() << endl; - cout << "Port = " << port << endl; + cout << "CWD = " << filesystem::current_path() << endl; + cout << "Port = " << port << endl; - httplib::Client client("localhost", port); + httplib::Client client("localhost", port); - // Test if we have access to files inside the UI folder. - auto goodRes = client.Get("/doc/good.txt"); - // Attempt to read file outside of the UI directory should fail even if - // the file exists. - client.set_connection_timeout(1000); - client.set_read_timeout(1000); - auto badRes = client.Get("/doc/../bad.txt"); - // Ensure the server is shut down before calling asserts that could - // terminate the thread without cleaning up - swagger.shutdown(); + // Test if we have access to files inside the UI folder. + auto goodRes = client.Get("/doc/good.txt"); + // Attempt to read file outside of the UI directory should fail + // even if the file exists. + client.set_connection_timeout(1000); + client.set_read_timeout(1000); + auto badRes = client.Get("/doc/../bad.txt"); + // Ensure the server is shut down before calling asserts that could + // terminate the thread without cleaning up + swagger.shutdown(); - ASSERT_EQ(goodRes->status, 200); - ASSERT_EQ(goodRes->body, "good"); + ASSERT_EQ(goodRes->status, 200); + ASSERT_EQ(goodRes->body, "good"); - ASSERT_EQ(badRes->status, 404); - ASSERT_NE(badRes->body, "bad"); - }); - swagger.start(); + ASSERT_EQ(badRes->status, 404); + ASSERT_NE(badRes->body, "bad"); + }); + swagger.start(); - t.join(); - filesystem::remove_all("assets"); - filesystem::remove_all("bad.txt"); + t.join(); + } + + try + { + filesystem::remove_all("assets"); + filesystem::remove_all("bad.txt"); + } + catch (...) + { + } } diff --git a/tests/router_test.cc b/tests/router_test.cc index 0321ed491..42e317406 100644 --- a/tests/router_test.cc +++ b/tests/router_test.cc @@ -76,10 +76,10 @@ bool matchSplat(const SegmentTreeNode& routes, const std::string& req, return false; size_t i = 0; - for (const auto& s : list) + for (const auto& an_s : list) { auto splat = splats[i].as(); - if (splat != s) + if (splat != an_s) return false; ++i; } diff --git a/tests/stream_test.cc b/tests/stream_test.cc index b1a09b97b..e5eade516 100644 --- a/tests/stream_test.cc +++ b/tests/stream_test.cc @@ -16,6 +16,12 @@ #include #include +#ifdef _IS_WINDOWS +#include +#include // for GetTempPathW +#endif + + using namespace Pistache; TEST(stream, test_buffer) @@ -43,11 +49,43 @@ TEST(stream, test_buffer) TEST(stream, test_file_buffer) { - char fileName[PATH_MAX] = "/tmp/pistacheioXXXXXX"; + #ifdef _IS_WINDOWS + // Note there is no mkstemp on Windows + + const char * fileName = 0; + std::string fn_buf_sstr("C:\\temp\\pistacheio76191"); + + { // encapsulate + TCHAR tmp_path[PST_MAXPATHLEN+16]; + tmp_path[0] = 0; + DWORD gtp_res = GetTempPathA(PST_MAXPATHLEN, &(tmp_path[0])); + if ((!gtp_res) || (gtp_res > PST_MAXPATHLEN)) + { + std::cerr << "No temp path found!" << std::endl; + } + else + { + TCHAR tmp_fn_buf[PST_MAXPATHLEN+16]; + tmp_fn_buf[0] = 0; + UINT gtfn_res = GetTempFileNameA(&(tmp_path[0]), + "PST", // prefix + 0, // Windows chooses the name + &(tmp_fn_buf[0])); + if (!gtfn_res) + std::cerr << "No temp path found!" << std::endl; + else + fn_buf_sstr = std::string(&(tmp_fn_buf[0])); + } + } + fileName = fn_buf_sstr.c_str(); +#else + char fileName[PST_MAXPATHLEN] = "/tmp/pistacheioXXXXXX"; if (!mkstemp(fileName)) { std::cerr << "No suitable filename can be generated!" << std::endl; } +#endif + std::cout << "Temporary file name: " << fileName << std::endl; const std::string dataToWrite("Hello World!"); diff --git a/tests/streaming_test.cc b/tests/streaming_test.cc index b81d4e431..38bd5ee8a 100644 --- a/tests/streaming_test.cc +++ b/tests/streaming_test.cc @@ -45,11 +45,17 @@ void dumpData(const Rest::Request& /*req*/, Http::ResponseWriter response) for (size_t j = 0; j < N_WORKERS; ++j) { - workers.emplace_back([&jobCounter, &cv, &jobLock, &jobs]() { + // the ["="...] tells the compiler to capture-by-copy (i.e. by value) + // by default, and then specifies the exceptions (jobCounter etc.) that + // are to be captured by reference. Even though JOB_LIMIT is a + // constexpr, MSVC 2019 requires that the default capture method be + // provided when implicitly passing JOB_LIMIT to lambda function by + // copy/value + workers.emplace_back([=, &jobCounter, &cv, &jobLock, &jobs]() { while (jobCounter < JOB_LIMIT) { std::unique_lock l(jobLock); - cv.wait(l, [&jobCounter, &jobs] { + cv.wait(l, [=, &jobCounter, &jobs] { return !jobs.empty() || !(jobCounter < JOB_LIMIT); }); if (!jobs.empty()) @@ -72,8 +78,8 @@ void dumpData(const Rest::Request& /*req*/, Http::ResponseWriter response) for (size_t s = 0; s < SET_REPEATS; ++s) { for (size_t i = 0; i < N_LETTERS; ++i) - { - auto job = [&stream, &responseLock, i]() -> void { + { // Re: '=', see prior comment about capture of const/constexpr + auto job = [=, &stream, &responseLock]() -> void { constexpr size_t nchunks = 10; constexpr size_t chunk_size = LETTER_REPEATS / nchunks; const std::string payload(chunk_size, static_cast(letter + i)); @@ -251,7 +257,7 @@ TEST_F(StreamingTests, ChunkedStream) PS_TIMEDBG_START; { // encapsulate - + SyncContext ctx; // force unbuffered @@ -322,7 +328,7 @@ TEST(StreamingTest, ClientDisconnect) DBG_LOG_ALL_EMEVENTS; { // encapsulate - + Http::Endpoint endpoint(Address(IP::loopback(), Port(0))); endpoint.init(Http::Endpoint::options().flags(Tcp::Options::ReuseAddr)); endpoint.setHandler(Http::make_handler()); @@ -355,7 +361,7 @@ TEST(StreamingTest, ClientDisconnect) curl_multi_perform(curlm, &still_running); if (still_running) { - curl_multi_wait(curlm, NULL, 0, 1000, NULL); + curl_multi_wait(curlm, nullptr, 0, 1000, nullptr); curl_multi_perform(curlm, &still_running); } diff --git a/tests/tcp_client.h b/tests/tcp_client.h index 2752d885e..b014d6dcf 100644 --- a/tests/tcp_client.h +++ b/tests/tcp_client.h @@ -6,12 +6,14 @@ #pragma once +#include #include #include -#include -#include -#include +#include PIST_QUOTE(PST_NETDB_HDR) +#include PIST_QUOTE(PIST_POLL_HDR) +#include PIST_QUOTE(PST_SOCKET_HDR) // best in C/C++, not .h, for non-test code + #include // In CLIENT_TRY, note that strerror is allowed to change errno in certain @@ -22,7 +24,6 @@ static const char * strerror_errstr = ""; namespace Pistache { - #define CLIENT_TRY(...) \ do \ { \ @@ -32,7 +33,10 @@ namespace Pistache if (errno) \ { \ lastErrno_ = errno; \ - lastError_ = strerror(errno); \ + PST_DECL_SE_ERR_P_EXTRA; \ + const char * se = PST_STRERROR_R_ERRNO; \ + if (se) \ + lastError_ = std::string(se); \ if (errno != lastErrno_) \ std::cout << "strerror changed errno (was " << \ lastErrno_ << ", now " << errno << " )" << std::endl; \ @@ -71,12 +75,12 @@ namespace Pistache CLIENT_TRY(addrInfo.invoke(host.c_str(), port.c_str(), &hints)); const auto* addrs = addrInfo.get_info_ptr(); - int sfd = -1; + em_socket_t sfd = -1; auto* addr = addrs; for (; addr; addr = addr->ai_next) { - sfd = ::socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + sfd = PST_SOCK_SOCKET(addr->ai_family, addr->ai_socktype, addr->ai_protocol); PS_LOG_DEBUG_ARGS("::socket actual_fd %d", sfd); if (sfd < 0) @@ -86,7 +90,8 @@ namespace Pistache } CLIENT_TRY(sfd); - CLIENT_TRY(::connect(sfd, addr->ai_addr, addr->ai_addrlen)); + CLIENT_TRY(PST_SOCK_CONNECT(sfd, addr->ai_addr, + static_cast(addr->ai_addrlen))); make_non_blocking(sfd); fd_ = sfd; @@ -103,7 +108,13 @@ namespace Pistache size_t total = 0; while (total < len) { - ssize_t n = ::send(fd_, data + total, len - total, MSG_NOSIGNAL); + int send_flags = +#ifdef _IS_WINDOWS + 0; +#else + MSG_NOSIGNAL; // Doesn't exist in Windows +#endif + PST_SSIZE_T n = PST_SOCK_SEND(fd_, data + total, len - total, send_flags); if (n == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) @@ -126,16 +137,19 @@ namespace Pistache template bool receive(void* buffer, size_t size, size_t* bytes, Duration timeout) { - struct pollfd fds[1]; + struct PST_POLLFD fds[1]; fds[0].fd = fd_; fds[0].events = POLLIN; auto timeoutMs = std::chrono::duration_cast(timeout); - auto ret = ::poll(fds, 1, static_cast(timeoutMs.count())); + auto ret = PST_SOCK_POLL(fds, 1, static_cast(timeoutMs.count())); if (ret < 0) { - lastError_ = strerror(errno); + PST_DECL_SE_ERR_P_EXTRA; + const char * se = PST_STRERROR_R_ERRNO; + if (se) + lastError_ = std::string(se); return false; } if (ret == 0) @@ -150,10 +164,13 @@ namespace Pistache return false; } - auto res = ::recv(fd_, buffer, size, 0); + auto res = PST_SOCK_READ(fd_, buffer, size); if (res < 0) { - lastError_ = strerror(errno); + PST_DECL_SE_ERR_P_EXTRA; + const char * se = PST_STRERROR_R_ERRNO; + if (se) + lastError_ = std::string(se); return false; } @@ -172,7 +189,7 @@ namespace Pistache } private: - int fd_ = -1; + em_socket_t fd_ = -1; std::string lastError_; int lastErrno_ = 0; }; diff --git a/version.txt b/version.txt index 9e5185b14..861d507fc 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.4.18.20241120 +0.4.21.20241121 diff --git a/winscripts/clean.ps1 b/winscripts/clean.ps1 new file mode 100644 index 000000000..107882d69 --- /dev/null +++ b/winscripts/clean.ps1 @@ -0,0 +1,33 @@ +#!/usr/bin/env powershell + +# +# SPDX-FileCopyrightText: 2024 Duncan Greatwood +# +# SPDX-License-Identifier: Apache-2.0 +# + +# Execute this script from the parent directory by invoking: +# winscripts/clean.ps1 + +# Use "dot source" to include another file +. $PSScriptRoot/helpers/messetdirvars.ps1 +. $PSScriptRoot/helpers/adjbuilddirformesbuild.ps1 + +if (($MESON_BUILD_DIR) -and (Test-Path -Path "$MESON_BUILD_DIR")) { + Write-Host "Removing build dir $MESON_BUILD_DIR" + Remove-Item "$MESON_BUILD_DIR" -Recurse -Force +} +else { + Write-Host "Build dir $MESON_BUILD_DIR not found" +} + +. $PSScriptRoot/helpers/mesdebugsetdirvars.ps1 +. $PSScriptRoot/helpers/adjbuilddirformesbuild.ps1 + +if (($MESON_BUILD_DIR) -and (Test-Path -Path "$MESON_BUILD_DIR")) { + Write-Host "Removing debug build dir $MESON_BUILD_DIR" + Remove-Item "$MESON_BUILD_DIR" -Recurse -Force +} +else { + Write-Host "Debug build dir $MESON_BUILD_DIR not found" +} diff --git a/winscripts/gccsetup.ps1 b/winscripts/gccsetup.ps1 new file mode 100644 index 000000000..2d857deeb --- /dev/null +++ b/winscripts/gccsetup.ps1 @@ -0,0 +1,181 @@ +#!/usr/bin/env powershell + +# +# SPDX-FileCopyrightText: 2024 Duncan Greatwood +# +# SPDX-License-Identifier: Apache-2.0 +# + +# Setup dev environment for mingw64's gcc + +$savedpwd=$pwd + +. $PSScriptRoot/helpers/commonsetup.ps1 + +# Set $env:force_msys_gcc if you want to force the use of msys64's gcc +# even if a different gcc is already installed. (Note: +# $env:force_msys_gcc is used by windows.yaml) +if (($env:force_msys_gcc) -or ` + (! (Get-Command gcc.exe -errorAction SilentlyContinue))) { + if (Test-Path -Path "$env:SYSTEMDRIVE\msys64") { + $msys64_dir = "$env:SYSTEMDRIVE\msys64" + } + elseif (Test-Path -Path "$env:HOMEDRIVE\msys64") { + $msys64_dir = "$env:HOMEDRIVE\msys64" + } + else { + cd ~ + Write-Host "Downloading msys2-x86_64-latest.sfx.exe" + Invoke-WebRequest -Uri ` + "https://repo.msys2.org/distrib/msys2-x86_64-latest.sfx.exe" ` + -Outfile "msys2-x86_64-latest.sfx.exe" + Write-Host "Self-extracting msys2-x86_64-latest.sfx.exe" + .\msys2-x86_64-latest.sfx.exe -y -o$env:SYSTEMDRIVE\ + if (Test-Path -Path "$env:SYSTEMDRIVE\msys64") { + $msys64_dir = "$env:SYSTEMDRIVE\msys64" + } + else { + throw "msys64 didn't install as expected?" + } + Write-Host "Checking msys2 shell" + & $msys64_dir\msys2_shell.cmd -defterm -here -no-start -ucrt64 -c “echo msysFirstTerminal” + } + + if (! (Test-Path -Path "$msys64_dir\ucrt64\bin\gcc.exe")) { + Write-Host "Installing gcc" + & $msys64_dir\msys2_shell.cmd -defterm -here -no-start -ucrt64 -c "pacman --noconfirm -S mingw-w64-ucrt-x86_64-gcc" + if (! (Test-Path -Path "$msys64_dir\ucrt64\bin\gcc.exe")) { + throw "gcc didn't install in msys as expected?" + } + } + + if (! (Test-Path -Path "$msys64_dir\ucrt64\bin\gdb.exe")) { + Write-Host "Installing gdb" + & $msys64_dir\msys2_shell.cmd -defterm -here -no-start -ucrt64 -c "pacman --noconfirm -S mingw-w64-ucrt-x86_64-gdb" + } + + $env:PATH="$msys64_dir\ucrt64\bin;$env:PATH" + } + +$env:CXX="g++" +$env:CC="gcc" + +$savedpwd = $pwd + +if (! (Get-Command mc.exe -errorAction SilentlyContinue)) { + if (Test-Path -Path "$env:ProgramFiles\Windows Kits") { + $win_sdk_found=1 + cd "$env:ProgramFiles\Windows Kits" + $mc_exes_found=Get-ChildItem -Path "mc.exe" -Recurse |` + Sort-Object -Descending -Property LastWriteTime + foreach ($mc_exe_found in $mc_exes_found) { + if ($mc_exe_found -like "*\x64\mc*") { + $mc_exe = $mc_exe_found + break + } + if ((! ($mc_exe)) -and ($mc_exe_found -like "*\x86\mc*")) { + $mc_exe = $mc_exe_found + } + } + } + + if (! ($mc_exe)) { + if (Test-Path -Path "${env:ProgramFiles(x86)}\Windows Kits") { + $win_sdk_found=1 + cd "${env:ProgramFiles(x86)}\Windows Kits" + $mc_exes_found=Get-ChildItem -Path "mc.exe" -Recurse |` + Sort-Object -Descending -Property LastWriteTime + foreach ($mc_exe_found in $mc_exes_found) { + if ($mc_exe_found -like "*\x64\mc*") { + $mc_exe = $mc_exe_found + break + } + if ((! ($mc_exe)) -and ($mc_exe_found -like "*\x86\mc*")) { + $mc_exe = $mc_exe_found + } + } + } + } +} + +cd "$savedpwd" + +if (! ($win_sdk_found)) { + throw "Unable to find Windows Kits (SDKs) folder" +} +if (! ($mc_exe)) { + throw "Unable to find mc.exe in Windows Kits (SDKs)" +} + +$mc_exe_dir = Split-Path -Path $mc_exe +$env:PATH="$env:PATH;$mc_exe_dir" + +if (! (Get-Command ninja -errorAction SilentlyContinue)) { + if (($env:VCPKG_DIR) -And (Test-Path -Path "$env:VCPKG_DIR\installed")) { + cd "$env:VCPKG_DIR\installed" + + $ninja_dir=Get-ChildItem -Path "ninja.exe" -Recurse | ` + Select -ExpandProperty "FullName" | Sort-Object { $_.Length } | ` + Select -Index 0 | Split-Path + } + + if ((! ($ninja_dir)) -or (! (Test-Path -Path $ninja_dir))) { + # Can't find ninja in "$env:VCPKG_DIR\installed"; install it now + + if (Get-Command winget -errorAction SilentlyContinue) { + winget install "Ninja-build.Ninja"; + # Don't set ninja_dir - leaving it empty will mean it + # doesn't get added to $env:path below, which is good + # since winget takes care of the path for us + } + else { + if (! (vcpkg list "vcpkg-tool-ninja")) { + vcpkg install vcpkg-tool-ninja + + if (($env:VCPKG_DIR) -And ` + (Test-Path -Path "$env:VCPKG_DIR\installed")) { + cd "$env:VCPKG_DIR\installed" + + $ninja_dir = Get-ChildItem -Path "ninja.exe" -Recurse | ` + Select -ExpandProperty "FullName" | ` + Sort-Object { $_.Length } | Select -Index 0 ` + | Split-Path + } + } + else { + Write-Host ` + "WARNING: ninja already installed by vcpkg, but not found?" + } + } + } + + if (($ninja_dir) -And (Test-Path -Path $ninja_dir)) { + $env:Path="$ninja_dir;$env:Path" # Add ninja.exe to Path + } + + cd "$savedpwd" +} + +if ((! ($env:plain_prompt)) -or ($env:plain_prompt -ne "Y")) +{ + $prompt_existing = (Get-Command prompt).ScriptBlock + if ($prompt_existing) { + $prompt_current=(prompt) + if ((!$prompt_current) -or ($prompt_current.length -lt 3)) { + function global:prompt {"MVS> "} + } + elseif (! ($prompt_current.SubString(0, 3) -eq "GCC")) { + $prompt_new = "`"GCC `" + " + $prompt_existing + $def_fn_prompt_new = ` + "function global:prompt { " + $prompt_new + " }" + Invoke-Expression $def_fn_prompt_new + } + } + else { + function global:prompt {"GCC> "} + } +} + +cd "$savedpwd" + +Write-Host "SUCCESS: gcc.exe, mc.exe and ninja.exe set up" diff --git a/winscripts/helpers/adjbuilddirformesbuild.ps1 b/winscripts/helpers/adjbuilddirformesbuild.ps1 new file mode 100644 index 000000000..340094693 --- /dev/null +++ b/winscripts/helpers/adjbuilddirformesbuild.ps1 @@ -0,0 +1,36 @@ +# PowerShell Script + +# +# SPDX-FileCopyrightText: 2024 Duncan Greatwood +# +# SPDX-License-Identifier: Apache-2.0 +# + +# Do not execute this script directly. In can be sourced into other +# scripts that need it. + +# This script checks for the existence of a directory +# "~/mesbuild/"; if that directory exists, +# $MESON_BUILD_DIR is set to point to a subdirectory within it. In +# this way, if desired we can keep the build dir well away from the +# sources. + +if (Test-Path -Path "$env:USERPROFILE/mesbuild") { + $gitprojroot = git rev-parse --show-toplevel + if (($gitprojroot) -and (Test-Path -Path "$gitprojroot")) { + $gitprojrootleaf = Split-Path -Path "$gitprojroot" -Leaf + $bldpathbase = "$env:USERPROFILE/mesbuild/$gitprojrootleaf" + if (Test-Path -Path "$bldpathbase") { + $MESON_BUILD_DIR = "$bldpathbase/$MESON_BUILD_DIR" + } + else { + Write-Host "Create dir $bldpathbase if you prefer to build there" + } + } +} + +if ((Get-Command gcc.exe -errorAction SilentlyContinue) -and ` + (("$env:CC" -eq "gcc") -or ("$env:CC" -eq "g++") -or ` + ("$env:CXX" -eq "gcc") -or ("$env:CXX" -eq "g++"))) { + $MESON_BUILD_DIR="$MESON_BUILD_DIR.gcc" +} diff --git a/winscripts/helpers/commonsetup.ps1 b/winscripts/helpers/commonsetup.ps1 new file mode 100644 index 000000000..e6e168a44 --- /dev/null +++ b/winscripts/helpers/commonsetup.ps1 @@ -0,0 +1,445 @@ +# commonsetup.ps1 + +# +# SPDX-FileCopyrightText: 2024 Duncan Greatwood +# +# SPDX-License-Identifier: Apache-2.0 +# + +# Setup that is shared between MSVC or GCC + +$savedpwd = $pwd + +cd ~ + +if (! (Get-Command git -errorAction SilentlyContinue)) { + + if ((Test-Path -Path "$env:ProgramFiles/Git/cmd/git.exe") -Or ` + (Test-Path -Path "$env:ProgramFiles/Git/bin/git.exe")) { + $git_dir = "$env:ProgramFiles/Git" + } + elseif ((Test-Path -Path "${env:ProgramFiles(x86)}/Git/cmd/git.exe") -Or ` + (Test-Path -Path "${env:ProgramFiles(x86)}/Git/bin/git.exe")) { + $git_dir = "${env:ProgramFiles(x86)}/Git" + } + + if (! ($git_dir)) { + Write-Host "git not found; will attempt to install git" + if (Get-Command winget -errorAction SilentlyContinue) { + Invoke-WebRequest -Uri "https://git.io/JXGyd" -OutFile "git.inf" + winget install Git.Git --override '/SILENT /LOADINF="git.inf"' + # Don't set $git_dir; winget takes care of path + } + else { + $mingit_latest_url=(Invoke-WebRequest -Uri "https://raw.githubusercontent.com/git-for-windows/git-for-windows.github.io/refs/heads/main/latest-64-bit-mingit.url").Content + Write-Host "Downloading mingit" + Invoke-WebRequest -Uri "$mingit_latest_url" -Outfile "mingit_latest.zip" + Expand-Archive -Path "mingit_latest.zip" -DestinationPath "$env:ProgramFiles/Git" + $git_dir = "$env:ProgramFiles/Git" + } + + Write-Host "git installed" + Write-Host "You may want to configure git as well:" + Write-Host ' git config --global user.email "you@somedomain.com"' + Write-Host ' git config --global user.name "Your Name"' + Write-Host ' git config --global core.editor "Your favorite editor"' + Write-Host ' etc.' + } + + if ($git_dir) { + $env:Path="$git_dir\cmd;$git_dir\mingw64\bin;$git_dir\usr\bin;$env:Path" + } +} + + +# Looking for vcpkg +if (Test-Path -Path "$env:HOMEDRIVE\vcpkg\vcpkg.exe") { + $env:VCPKG_DIR="$env:HOMEDRIVE\vcpkg" +} +elseif (Test-Path -Path "$env:SYSTEMDRIVE\vcpkg\vcpkg.exe") { + $env:VCPKG_DIR="$env:SYSTEMDRIVE\vcpkg" +} +elseif (Test-Path -Path "$env:USERPROFILE\vcpkg\vcpkg.exe") { + $env:VCPKG_DIR="$env:USERPROFILE\vcpkg" +} +elseif (Test-Path -Path "$env:USERPROFILE\progsloc\vcpkg\vcpkg.exe") { + $env:VCPKG_DIR="$env:USERPROFILE\progsloc\vcpkg" +} +elseif (Test-Path -Path "$env:USERPROFILE\progs\vcpkg\vcpkg.exe") { + $env:VCPKG_DIR="$env:USERPROFILE\progs\vcpkg" +} +elseif (Test-Path -Path "$env:USERPROFILE\programs\vcpkg\vcpkg.exe") { + $env:VCPKG_DIR="$env:USERPROFILE\programs\vcpkg" +} +else { + # Search the user's directory + cd "~" + $vcpkg_exe_path = Get-ChildItem -Path "vcpkg.exe" -Recurse ` + -ErrorAction SilentlyContinue| ` + Sort-Object -Descending -Property LastWriteTime | ` + Select -Index 0 | Select -ExpandProperty "FullName" | Split-Path + if (($vcpkg_exe_path) -And (Test-Path -Path $vcpkg_exe_path)) { + $env:VCPKG_DIR=$vcpkg_exe_path + } + else { + # Search system drive, ugh + cd "$env:SYSTEMDRIVE\" + $vcpkg_exe_path = Get-ChildItem -Path "vcpkg.exe" -Recurse ` + -ErrorAction SilentlyContinue| ` + Sort-Object -Descending -Property LastWriteTime | ` + Select -Index 0 | Select -ExpandProperty "FullName" | Split-Path + if (($vcpkg_exe_path) -And (Test-Path -Path $vcpkg_exe_path)) { + $env:VCPKG_DIR=$vcpkg_exe_path + } + } + if (! ($env:VCPKG_DIR)) { + # -cne is case-insensitive string compare + if ($env:HOMEDRIVE -cne $env:SYSTEMDRIVE) { + # Search home drive, also ugh + cd "$env:HOMEDRIVE\" + $vcpkg_exe_path = Get-ChildItem -Path "vcpkg.exe" -Recurse ` + -ErrorAction SilentlyContinue| ` + Sort-Object -Descending -Property LastWriteTime | ` + Select -Index 0 | Select -ExpandProperty "FullName" | Split-Path + if (($vcpkg_exe_path) -And (Test-Path -Path $vcpkg_exe_path)) { + $env:VCPKG_DIR=$vcpkg_exe_path + } + } + else { + Write-Host "vcpkg.exe not found" + } + } +} + +if ((! ($env:VCPKG_DIR)) -Or (! (Test-Path -Path "$env:VCPKG_DIR"))) { + $vcpkg_new_inst_dir = $env:VCPKG_DIR + if (! ($vcpkg_new_inst_dir)) { + $vcpkg_new_inst_dir = "$env:SYSTEMDRIVE\vcpkg" + } + Write-Host "Will attempt to install VCPKG to $vcpkg_new_inst_dir" + if (! (Test-Path -Path "$vcpkg_new_inst_dir")) { + mkdir "$vcpkg_new_inst_dir" + cd "$vcpkg_new_inst_dir" + cd .. + } + git clone https://github.com/Microsoft/vcpkg.git + cd vcpkg + .\bootstrap-vcpkg.bat + vcpkg.exe integrate install + $env:VCPKG_DIR = $vcpkg_new_inst_dir +} + +if (($env:VCPKG_DIR) -And (Test-Path -Path "$env:VCPKG_DIR")) { + $env:VCPKG_INSTALLATION_ROOT=$env:VCPKG_DIR # As per github Windows images + $env:VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT + $env:Path="$env:Path;$env:VCPKG_DIR" +} + +if ((! (Get-Command pkg-config -errorAction SilentlyContinue)) -or ` + ((where.exe pkg-config) -like "*Strawberry*")) { + # meson does not accept the version of pkg-config in Strawberry Perl + + if (Get-Command winget -errorAction SilentlyContinue) + { # winget exists on newer versions of Windows 10 and on + # Windows 11 and after. It is is not supported on Windows + # Server 2019, and is experimental only on Windows Server + # 2022. + + winget install bloodrock.pkg-config-lite # For pkg-config + } + else + { + # First, check pkgconfig installed, and install if not + + if (($env:VCPKG_DIR) -And ` + (Test-Path -Path "$env:VCPKG_DIR\installed")) { + # Note "$env:VCPKG_DIR\installed" directory may not + # exist if nothing has been installed in vcpkg yet + cd "$env:VCPKG_DIR\installed" + $my_vcpkg_pkgconfig_dir = + Get-ChildItem -Path "pkgconfig" -Recurse | ` + Sort-Object -Descending -Property LastWriteTime | ` + Select -Index 0 | Select -ExpandProperty "FullName" + } + if ((! ($my_vcpkg_pkgconfig_dir)) -Or ` + (! (Test-Path -Path $my_vcpkg_pkgconfig_dir))) { + Write-Host "Installing pkgconf with vcpkg" + vcpkg install pkgconf + cd "$env:VCPKG_DIR\installed" + $my_vcpkg_pkgconfig_dir = ` + Get-ChildItem -Path "pkgconfig" -Recurse | ` + Sort-Object -Descending -Property LastWriteTime | ` + Select -Index 0 | Select -ExpandProperty "FullName" + } + if (($my_vcpkg_pkgconfig_dir) -And ` + (Test-Path -Path $my_vcpkg_pkgconfig_dir)) { + $my_vcpkg_pkgconfig_dir = ` + "$env:VCPKG_DIR\installed\x64-windows\lib\pkgconfig" + if ($env:PKG_CONFIG_PATH) { + $env:PKG_CONFIG_PATH = ` + "$env:PKG_CONFIG_PATH;$my_vcpkg_pkgconfig_dir" + } + else { + $env:PKG_CONFIG_PATH="$my_vcpkg_pkgconfig_dir" + } + } + else { + throw "pkgconf not installed as expected" + } + } + } + +if (! (vcpkg list "brotli")) { vcpkg install brotli } +if (! (vcpkg list "zstd")) { vcpkg install zstd } + +if (($env:VCPKG_DIR) -And (Test-Path -Path "$env:VCPKG_DIR\installed")) { + cd "$env:VCPKG_DIR\installed" + + # We add the ...\vcpkg\installed\...\bin directory to the + # path. Not only does this mean that executables can be executed + # from that directory, it also allows DLLS to be loaded from that + # bin directory (since Windows DLL loader checks path), notably + # event_core.dll from libevent. + + # Find ...\installed\...\bin directory. Choose shortest path + # if more than one + $vcpkg_installed_bin_dir=Get-ChildItem -Path "bin" -Recurse | ` + Select -ExpandProperty "FullName" | Sort-Object { $_.Length } | ` + Select -Index 0 + if (($vcpkg_installed_bin_dir) -And ` + (Test-Path -Path $vcpkg_installed_bin_dir)) { + $env:Path="$env:Path;$vcpkg_installed_bin_dir" + } + else { + throw "...\vcpkg\installed\...\bin directory not found" + } + + $vcpkg_installed_pkgconf_dir=Get-ChildItem -Path "pkgconf.exe" -Recurse | ` + Select -ExpandProperty "FullName" | Sort-Object { $_.Length } | ` + Select -Index 0 | Split-Path + if (($vcpkg_installed_pkgconf_dir) -And ` + (Test-Path -Path $vcpkg_installed_pkgconf_dir)) { + $env:Path="$vcpkg_installed_pkgconf_dir;$env:Path" + # Puts pkgconf.exe on the Path + + if ((! (Get-Command pkg-config.exe -errorAction SilentlyContinue)) ` + -or ((where.exe pkg-config) -like "*Strawberry*")) + { + New-Item -ItemType SymbolicLink -Path "$vcpkg_installed_pkgconf_dir\pkg-config.exe" -Target "$vcpkg_installed_pkgconf_dir\pkgconf.exe" + } + } +} +else { + throw "$env:VCPKG_DIR\installed not found" +} + +if ((! (Get-Command python3 -errorAction SilentlyContinue)) -and ` + (! (vcpkg list "python3"))) { + if ((! (Get-Command python -errorAction SilentlyContinue)) -or ` + (! (((python --version).SubString(7, 1)/1) -ge 3))) { + vcpkg install python3 + } + } + +if (! (Get-Command pip3 -errorAction SilentlyContinue)) { + python -m ensurepip +} + +function Find-Meson-Exe { + if (Test-Path "~/AppData/Local/Packages") { + cd "~/AppData/Local/Packages" + $meson_exe_path = Get-ChildItem -Path "meson.exe" -Recurse | ` + Sort-Object -Descending -Property LastWriteTime | ` + Select -Index 0 | Select -ExpandProperty "FullName" | Split-Path + } + + if (((! ($meson_exe_path)) -or (! (Test-Path -Path $meson_exe_path))) -and (Test-Path "~/AppData/Roaming/Python")) { + cd "~/AppData/Roaming/Python" + $meson_exe_path = Get-ChildItem -Path "meson.exe" -Recurse | ` + Sort-Object -Descending -Property LastWriteTime | ` + Select -Index 0 | Select -ExpandProperty "FullName" | Split-Path + } + + if (((! ($meson_exe_path)) -or (! (Test-Path -Path $meson_exe_path))) -and (Test-Path "~/AppData")) { + cd "~/AppData" + $meson_exe_path = Get-ChildItem -Path "meson.exe" -Recurse | ` + Sort-Object -Descending -Property LastWriteTime | ` + Select -Index 0 | Select -ExpandProperty "FullName" | Split-Path + } + + return $meson_exe_path +} + +if (! (Get-Command meson -errorAction SilentlyContinue)) { + $meson_exe_path = Find-Meson-Exe + if ((! ($meson_exe_path)) -Or (! (Test-Path -Path $meson_exe_path))) { + Write-Host "Installing meson with pip3" + pip3 install --user meson + $meson_exe_path = Find-Meson-Exe + } + + if (($meson_exe_path) -And (Test-Path -Path $meson_exe_path)) { + $env:Path="$env:Path;$meson_exe_path" + } + else { + throw "ERROR: meson.exe directory not added to path" + } +} + +if (! (vcpkg list "curl[openssl]")) { # vcpkg list - list installed packages + vcpkg install curl[openssl] +} + +if (! (vcpkg list "openssl")) { vcpkg install openssl } +if (! (vcpkg list "libevent")) { vcpkg install libevent } + +if ((! (Get-ChildItem -Path "$env:ProgramFiles\googletest*" ` + -ErrorAction SilentlyContinue)) -And ` + (! (Get-ChildItem -Path "${env:ProgramFiles(x86)}\googletest*" ` + -ErrorAction SilentlyContinue))) { + git clone https://github.com/google/googletest.git + cd googletest + mkdir build + cd build + cmake .. + cmake --build . + cmake --install . --config Debug + cd ~ + } + +if (! (vcpkg list "date")) { vcpkg install date } # Howard-Hinnant-Date + +if ((! (Get-ChildItem -Path "$env:ProgramFiles\zlib*" ` + -ErrorAction SilentlyContinue)) -And ` + (! (Get-ChildItem -Path "${env:ProgramFiles(x86)}\zlib*" ` + -ErrorAction SilentlyContinue))) { + Invoke-WebRequest -Uri https://zlib.net/current/zlib.tar.gz -OutFile zlib.tar.gz + tar -xvzf .\zlib.tar.gz + cd "zlib*" # Adjusts to whatever we downloaded, e.g. zlib-1.3.1 + mkdir build + cd build + cmake .. + cmake --build . --config Release + cmake --install . --config Release + } + +if (! (Get-Command doxygen -errorAction SilentlyContinue)) { + if (Test-Path -Path "$env:USERPROFILE\doxygen.bin") { + $env:Path="$env:Path;$env:USERPROFILE\doxygen.bin" + } + else { + if (Get-Command winget -errorAction SilentlyContinue) + { + winget install doxygen + } + else + { + try { + cd ~ + Write-Host "doxygen: Fetching version number of latest release" + $doxygen_latest_links = (Invoke-WebRequest -Uri "https://github.com/doxygen/doxygen/releases/latest").Links + foreach ($itm in $doxygen_latest_links) { + $ot = $itm.outerText + if ($ot -like "Release_*") { + $ot_ver_with_underbars = $ot.Substring(8) + $ot_ver_with_dots = ` + $ot_ver_with_underbars -replace "_", "." + $download_uri = ` + -join("https://www.doxygen.nl/files/doxygen-", ` + $ot_ver_with_dots.TrimEnd(), ".windows.x64.bin.zip") + Write-Host "doxygen: Fetching $download_uri" + Invoke-WebRequest -Uri $download_uri ` + -OutFile doxygen.bin.zip + break + } + } + } + catch { + } + if (! (Test-Path -Path "doxygen.bin.zip")) { + Write-Host "Failed to download latest doxygen.bin.zip" + $download_uri = "https://www.doxygen.nl/files/doxygen-1.12.0.windows.x64.bin.zip" + Write-Host "Fetching $download_uri" + Invoke-WebRequest -Uri $download_uri -OutFile doxygen.bin.zip + } + try { + Expand-Archive doxygen.bin.zip -DestinationPath doxygen.bin + } + catch { + if ($download_uri) { + # Occasionally (1 time in 100?) Expand-Archive + # will fail, with an error like: + # New-Object : Exception calling ".ctor" with "3"... + # Apparently, this happens when the downloaded zip + # file was corrupt. We try downloading it again; + # and apparently setting a different UserAgent may + # help. + Invoke-WebRequest -Uri $download_uri ` + -OutFile doxygen.bin.zip -UserAgent "NativeHost" + Expand-Archive doxygen.bin.zip -DestinationPath doxygen.bin + } + else { + throw "Unknown $download_uri for doxygen" + } + } + $env:Path="$env:Path;$env:USERPROFILE\doxygen.bin" + } + } +} + +# We want Visual Studio to be installed even if we're not going to use +# the Visual Studio compiler, so we can have access to the Windows +# SDK(s) that are sintalled along with Visual Studio +if (! ((Test-Path -Path "$env:ProgramFiles/Microsoft Visual Studio") -or ` + (Test-Path -Path "${env:ProgramFiles(x86)}/Microsoft Visual Studio"))) { + Write-Host "No existing Visual Studio detected" + $vsyr="2022" + try { + Write-Host "Looking for current Visual Studio major release" + $wiki_links = (Invoke-WebRequest -Uri ` + "https://en.wikipedia.org/wiki/Visual_Studio").Links + foreach ($itm in $wiki_links) { + $href = $itm.href + if ($href -like ` + "https://learn.microsoft.com/en-us/visualstudio/releases/2*") { + $foundvsyr = $href.Substring(56, 4) + if (($foundvsyr/1) -ge ($vsyr/1)) { + $vsyr_was_found = $foundvsyr + $vsyr = $foundvsyr + } + } + } + } + catch {} + if (! ($vsyr_was_found)) + { + Write-Host "VS major release not determined, defaulting to $vsyr" + } + + try { + Write-Host "Fetching link to Visual Studio $vsyr" + $releases_url = -join("https://learn.microsoft.com/en-us/visualstudio/releases/", $vsyr, "/release-history#release-dates-and-build-numbers") + $links = (Invoke-WebRequest -Uri $releases_url).Links + foreach ($itm in $links) { + $href = $itm.href + if ($href -like "*vs_community.exe*") { + Write-Host "Fetching Visual Studio $vsyr vs_community.exe" + Invoke-WebRequest -Uri $href -OutFile "vs_community.exe" + break + } + } + } + catch {} + + if (! (Test-Path -Path "vs_community.exe")) { + $vs_uri = "https://aka.ms/vs/17/release/vs_community.exe" + Write-Host ` + "vs_community.exe not found, attempting download from $vs_uri" + Invoke-WebRequest -Uri $vs_uri -OutFile "vs_community.exe" + } + + vs_community.exe --includeRecommended --wait --norestart + } + + +cd $savedpwd diff --git a/winscripts/helpers/mesdebugsetdirvars.ps1 b/winscripts/helpers/mesdebugsetdirvars.ps1 new file mode 100644 index 000000000..e826a818e --- /dev/null +++ b/winscripts/helpers/mesdebugsetdirvars.ps1 @@ -0,0 +1,16 @@ +# PowerShell Script + +# +# SPDX-FileCopyrightText: 2024 Duncan Greatwood +# +# SPDX-License-Identifier: Apache-2.0 +# +# Sets MESON_BUILD_DIR and MESON_PREFIX_DIR +# +# Use by: +# . $PSScriptRoot/helpers/mesdebugsetdirvars.sh + +. $PSScriptRoot/messetdirvars.ps1 + +$MESON_BUILD_DIR="$MESON_BUILD_DIR.debug" +$MESON_PREFIX_DIR="$MESON_PREFIX_DIR.debug" diff --git a/winscripts/helpers/messetdirvars.ps1 b/winscripts/helpers/messetdirvars.ps1 new file mode 100644 index 000000000..779a970dc --- /dev/null +++ b/winscripts/helpers/messetdirvars.ps1 @@ -0,0 +1,59 @@ +# PowerShell Script + +# +# SPDX-FileCopyrightText: 2024 Duncan Greatwood +# +# SPDX-License-Identifier: Apache-2.0 +# +# Sets MESON_BUILD_DIR and MESON_PREFIX_DIR +# +# Use by: +# . $PSScriptRoot/helpers/messetdirvars.ps1 + +# To see available properties: +# (Get-CIMClass -classname "CIM_Processor").CimClassProperties | ft -wrap -autosize +# (Get-CIMClass -classname CIM_OperatingSystem).CimClassProperties | ft -wrap -autosize +# +# (Get-CimInstance CIM_Processor).AddressWidth is a UInt16, e.g.: 64 +# +# (Get-CimInstance CIM_Processor).Caption is CimType String +# e.g.: Intel64 Family 6 Model 158 Stepping 9 + +$MY_ARCH_NM="x86" +$cpu_cim_width=(Get-CimInstance CIM_Processor).AddressWidth +$cpu_cim_caption=(Get-CimInstance CIM_Processor).Caption +$cpu_cim_caption_lwr=$cpu_cim_caption.ToLower() +if ((-not ($cpu_cim_caption_lwr.contains("intel"))) -and ` + (-not ($cpu_cim_caption.contains("AMD"))) -and ` + (-not ($cpu_cim_caption_lwr.contains("advanced micro devices"))) -and ` + (($cpu_cim_caption_lwr.contains("arm")))) { + # Assume this is an ARM of some kind + if ($cpu_cim_width -eq 32) {$MY_ARCH_NM="a32"} + else {$MY_ARCH_NM="a64"} +} +else { + # Assume this is an Intel or AMD processor of some kind + if ($cpu_cim_width -eq 32) {$MY_ARCH_NM="x32"} + else {$MY_ARCH_NM="x86"} +} + +# On 32-bit Windows, there is only the "C:\Program Files" program +# folder, no "C:\Program Files (x86)" +# On 64-bit Windows, 64-bit programs go in "C:\Program Files", while +# 32-bit programs go in "C:\Program Files (x86)". +# +# So we will always want to use "C:\Program Files" as the destination +# for Pistache unless we were compiling a 32-bit Pistache library on +# 64-bit windows. +# +# Using the "pistache_distribution" directory name is modelled on the +# behaviour of googletest upon install. + +if ([Environment]::Is64BitOperatingSystem) { + $MESON_BUILD_DIR="build$MY_ARCH_NM.mes.w64" + $MESON_PREFIX_DIR="$env:ProgramFiles\pistache_distribution" +} +else { + $MESON_BUILD_DIR="build$MY_ARCH_NM.mes.w32" + $MESON_PREFIX_DIR="$env:ProgramFiles\pistache_distribution" +} diff --git a/winscripts/mesbuild.ps1 b/winscripts/mesbuild.ps1 new file mode 100644 index 000000000..52f67a41b --- /dev/null +++ b/winscripts/mesbuild.ps1 @@ -0,0 +1,39 @@ +#!/usr/bin/env powershell + +# +# SPDX-FileCopyrightText: 2024 Duncan Greatwood +# +# SPDX-License-Identifier: Apache-2.0 +# + +# Execute this script from the parent directory by invoking: +# winscripts/mesbuild.ps1 + +# Use "dot source" to include another file +. $PSScriptRoot/helpers/messetdirvars.ps1 +. $PSScriptRoot/helpers/adjbuilddirformesbuild.ps1 + +if (($MESON_PREFIX_DIR) -and (-not (Test-Path -Path "$MESON_PREFIX_DIR"))) ` + {mkdir "$MESON_PREFIX_DIR"} + +if ((Test-Path -Path "$MESON_BUILD_DIR") -and` + (Test-Path -Path "$MESON_PREFIX_DIR")) { + Write-Host "Using existing build dir $MESON_BUILD_DIR" + Write-Host "Using existing prefix/install dir $MESON_PREFIX_DIR" +} +else { + Write-Host "Going to use build dir $MESON_BUILD_DIR" + Write-Host "Going to use prefix/install dir $MESON_PREFIX_DIR" + meson.exe setup "$MESON_BUILD_DIR" ` + --buildtype=release ` + -DPISTACHE_USE_SSL=true ` + -DPISTACHE_BUILD_EXAMPLES=true ` + -DPISTACHE_BUILD_TESTS=true ` + -DPISTACHE_BUILD_DOCS=false ` + -DPISTACHE_USE_CONTENT_ENCODING_DEFLATE=true ` + -DPISTACHE_USE_CONTENT_ENCODING_BROTLI=true ` + -DPISTACHE_USE_CONTENT_ENCODING_ZSTD=true ` + --prefix="$MESON_PREFIX_DIR" +} + +meson compile -C ${MESON_BUILD_DIR} # "...compile -v -C ..." for verbose diff --git a/winscripts/mesbuilddebug.ps1 b/winscripts/mesbuilddebug.ps1 new file mode 100644 index 000000000..3d51f14c5 --- /dev/null +++ b/winscripts/mesbuilddebug.ps1 @@ -0,0 +1,37 @@ +#!/usr/bin/env powershell + +# +# SPDX-FileCopyrightText: 2024 Duncan Greatwood +# +# SPDX-License-Identifier: Apache-2.0 +# + +# Execute this script from the parent directory by invoking: +# winscripts/mesbuilddebug.ps1 + +. $PSScriptRoot/helpers/mesdebugsetdirvars.ps1 +. $PSScriptRoot/helpers/adjbuilddirformesbuild.ps1 + +if (($MESON_PREFIX_DIR) -and (-not (Test-Path -Path "$MESON_PREFIX_DIR"))) + {mkdir "$MESON_PREFIX_DIR"} + +if (Test-Path -Path "$MESON_BUILD_DIR") { + Write-Host "Using existing build dir $MESON_BUILD_DIR" +} +else { + Write-Host "Going to use build dir $MESON_BUILD_DIR" + meson setup ${MESON_BUILD_DIR} ` + --buildtype=debug ` + -DPISTACHE_USE_SSL=true ` + -DPISTACHE_BUILD_EXAMPLES=true ` + -DPISTACHE_BUILD_TESTS=true ` + -DPISTACHE_BUILD_DOCS=false ` + -DPISTACHE_USE_CONTENT_ENCODING_DEFLATE=true ` + -DPISTACHE_USE_CONTENT_ENCODING_BROTLI=true ` + -DPISTACHE_USE_CONTENT_ENCODING_ZSTD=true ` + -DPISTACHE_DEBUG=true ` + --prefix="${MESON_PREFIX_DIR}" + +} + +meson compile -C ${MESON_BUILD_DIR} # "...compile -v -C ..." for verbose diff --git a/winscripts/mesinstall.ps1 b/winscripts/mesinstall.ps1 new file mode 100644 index 000000000..ead264eef --- /dev/null +++ b/winscripts/mesinstall.ps1 @@ -0,0 +1,41 @@ + #!/usr/bin/env powershell + +# +# SPDX-FileCopyrightText: 2024 Duncan Greatwood +# +# SPDX-License-Identifier: Apache-2.0 +# + +# Execute this script from the parent directory by invoking: +# winscripts/mesinstall.ps1 + +# Use "dot source" to include another file +. $PSScriptRoot/helpers/messetdirvars.ps1 +. $PSScriptRoot/helpers/adjbuilddirformesbuild.ps1 + +# Installs to [C]:\Program Files\pistache_distribution + +try { meson install -C ${MESON_BUILD_DIR} } +catch { + Write-Host "Plain meson install failed, trying again with Admin rights" + + # We use "Start-Process" so we can do "-verb RunAs", which provides + # admin-level privileges, like doing "sudo" on Linux + $insproc = Start-Process -FilePath powershell.exe -ArgumentList "-Command","meson install -C ${MESON_BUILD_DIR}" -PassThru -verb RunAs + + # keep track of timeout event + $instimeouted = $null + + # wait up to 60 seconds for normal termination + $insproc | Wait-Process -Timeout 60 -ErrorAction SilentlyContinue -ErrorVariable instimeouted + + if ($instimeouted) + { + $insproc | kill + Write-Error "Error: meson install timed out" + } + elseif ($insproc.ExitCode -ne 0) + { + Write-Error "Error: meson install returned error" + } +} diff --git a/winscripts/mesinstalldebug.ps1 b/winscripts/mesinstalldebug.ps1 new file mode 100644 index 000000000..862293330 --- /dev/null +++ b/winscripts/mesinstalldebug.ps1 @@ -0,0 +1,38 @@ + #!/usr/bin/env powershell + +# +# SPDX-FileCopyrightText: 2024 Duncan Greatwood +# +# SPDX-License-Identifier: Apache-2.0 +# + +# Execute this script from the parent directory by invoking: +# winscripts/mesinstalldebug.ps1 + +# Use "dot source" to include another file +. $PSScriptRoot/helpers/mesdebugsetdirvars.ps1 +. $PSScriptRoot/helpers/adjbuilddirformesbuild.ps1 + +Write-Host "Using debug build dir $MESON_BUILD_DIR" + +# Installs to [C]:\Program Files\pistache_distribution + +# We use "Start-Process" so we can do "-verb RunAs", which provides +# admin-level privileges, like doing "sudo" on Linux +$insproc = Start-Process -FilePath powershell.exe -ArgumentList "-Command","meson install -C ${MESON_BUILD_DIR}" -PassThru -verb RunAs + +# keep track of timeout event +$instimeouted = $null + +# wait up to 60 seconds for normal termination +$insproc | Wait-Process -Timeout 60 -ErrorAction SilentlyContinue -ErrorVariable instimeouted + +if ($instimeouted) +{ + $insproc | kill + Write-Error "Error: meson install timed out" +} +elseif ($insproc.ExitCode -ne 0) +{ + Write-Error "Error: meson install returned error" +} diff --git a/winscripts/mestest.ps1 b/winscripts/mestest.ps1 new file mode 100755 index 000000000..70ddd85d8 --- /dev/null +++ b/winscripts/mestest.ps1 @@ -0,0 +1,26 @@ +#!/usr/bin/env powershell + +# +# SPDX-FileCopyrightText: 2024 Duncan Greatwood +# +# SPDX-License-Identifier: Apache-2.0 +# + +# Run the test suite for Meson release (non debug) build + +# Execute this script from the parent directory by invoking: +# winscripts/mestest.ps1 + +. $PSScriptRoot/helpers/messetdirvars.ps1 +. $PSScriptRoot/helpers/adjbuilddirformesbuild.ps1 + +if (($MESON_PREFIX_DIR) -and (-not (Test-Path -Path "$MESON_PREFIX_DIR"))) + {mkdir "$MESON_PREFIX_DIR"} + +if (Test-Path -Path "$MESON_BUILD_DIR") { + Write-Host "Using existing build dir $MESON_BUILD_DIR" + meson test -C ${MESON_BUILD_DIR} +} +else { + Write-Host "Build dir $MESON_BUILD_DIR doesn't exist" +} diff --git a/winscripts/mestestdebug.ps1 b/winscripts/mestestdebug.ps1 new file mode 100755 index 000000000..85871fd47 --- /dev/null +++ b/winscripts/mestestdebug.ps1 @@ -0,0 +1,26 @@ +#!/usr/bin/env powershell + +# +# SPDX-FileCopyrightText: 2024 Duncan Greatwood +# +# SPDX-License-Identifier: Apache-2.0 +# + +# Run the test suite for Meson debug build + +# Execute this script from the parent directory by invoking: +# winscripts/mestestdebug.ps1 + +. $PSScriptRoot/helpers/mesdebugsetdirvars.ps1 +. $PSScriptRoot/helpers/adjbuilddirformesbuild.ps1 + +if (($MESON_PREFIX_DIR) -and (-not (Test-Path -Path "$MESON_PREFIX_DIR"))) + {mkdir "$MESON_PREFIX_DIR"} + +if (Test-Path -Path "$MESON_BUILD_DIR") { + Write-Host "Using existing build dir $MESON_BUILD_DIR" + meson test -C ${MESON_BUILD_DIR} +} +else { + Write-Host "Build dir $MESON_BUILD_DIR doesn't exist" +} diff --git a/winscripts/msvcsetup.ps1 b/winscripts/msvcsetup.ps1 new file mode 100644 index 000000000..847b73201 --- /dev/null +++ b/winscripts/msvcsetup.ps1 @@ -0,0 +1,275 @@ +#!/usr/bin/env powershell + +# +# SPDX-FileCopyrightText: 2024 Duncan Greatwood +# +# SPDX-License-Identifier: Apache-2.0 +# + +# Setup dev environment for Visual Studio + +$savedpwd=$pwd + +. $PSScriptRoot/helpers/commonsetup.ps1 + +if (Get-Command cl.exe -errorAction SilentlyContinue) { + Write-Host "WARNING: MSVC's cl.exe already setup? Skipping MSVC prompt" +} +else { + if (Test-Path -Path "$env:ProgramFiles\Microsoft Visual Studio") { + cd "$env:ProgramFiles\Microsoft Visual Studio" + $dirents=Get-ChildItem -Filter "????" -Name | ` + Sort-Object -Descending -Property Name + foreach ($itm in $dirents) { + if ("$itm" -match "^\d+$") { + $itm_as_num=$itm/1 + if ($itm_as_num -gt 1970) { + if (Test-Path ` + "$itm/Enterprise/Common7/Tools/Launch-VsDevShell.ps1") { + $launch_vs_dev_shell_dir = ` + "$pwd\$itm\Enterprise\Common7\Tools" + break + } + elseif (Test-Path ` + "$itm/Professional/Common7/Tools/Launch-VsDevShell.ps1") { + $launch_vs_dev_shell_dir = ` + "$pwd\$itm\Professional\Common7\Tools" + break + } + elseif (Test-Path ` + "$itm/Community/Common7/Tools/Launch-VsDevShell.ps1") { + $launch_vs_dev_shell_dir = ` + "$pwd\$itm\Community\Common7\Tools" + break + } + } + } + } + if (! ($launch_vs_dev_shell_dir)) { + $launch_vs_dev_shell_dir = ` + Get-ChildItem -Path "Launch-VsDevShell.ps1" -Recurse | ` + Sort-Object -Descending -Property LastWriteTime | ` + Select -Index 0 | Select -ExpandProperty "FullName" | Split-Path + } + } + elseif (Test-Path -Path ` + "${env:ProgramFiles(x86)}\Microsoft Visual Studio") { + cd "${env:ProgramFiles(x86)}\Microsoft Visual Studio" + $dirents=Get-ChildItem -Filter "????" -Name | ` + Sort-Object -Descending -Property Name + foreach ($itm in $dirents) { + if ("$itm" -match "^\d+$") { + $itm_as_num=$itm/1 + if ($itm_as_num -gt 1970) { + if (Test-Path ` + "$itm/Enterprise/Common7/Tools/Launch-VsDevShell.ps1") { + $launch_vs_dev_shell_dir = ` + "$pwd\$itm\Enterprise\Common7\Tools" + break + } + elseif (Test-Path "$itm/Professional/Common7/Tools/Launch-VsDevShell.ps1") { + $launch_vs_dev_shell_dir = ` + "$pwd\$itm\Professional\Common7\Tools" + break + } + elseif (Test-Path ` + "$itm/Community/Common7/Tools/Launch-VsDevShell.ps1") { + $launch_vs_dev_shell_dir = ` + "$pwd\$itm\Community\Common7\Tools" + break + } + } + } + } + if (! ($launch_vs_dev_shell_dir)) { + $launch_vs_dev_shell_dir = ` + Get-ChildItem -Path "Launch-VsDevShell.ps1" -Recurse | ` + Sort-Object -Descending -Property LastWriteTime | ` + Select -Index 0 | Select -ExpandProperty "FullName" | ` + Split-Path + } + } +} + +if (($launch_vs_dev_shell_dir) -And ` + (Test-Path -Path $launch_vs_dev_shell_dir)) { + cd "$launch_vs_dev_shell_dir" + # As at Aug-2024, VS 2022, Launch-VsDevShell.ps1 defaults to + # 32-bit host and target architectures. However you can specify + # both target architecture (-Arch option, valid values: x86, + # amd64, arm, arm64) and host architecture (-HostArch option, + # valid values: x86, amd64) + # + # If we don't do this, then meson will pickup 32-bit as the host + # and target architecture. Meanwhile, vcpkg defaults to 64-bit + # libraries (since we're on a 64-bit Windows). So then the + # linker can't link, because it is trying to link 64-bit vcpkg + # libs with our 32-bit object files. + # + # Ref: https://learn.microsoft.com/en-us/visualstudio/ + # ide/reference/command-prompt-powershell?view=vs-2022 + try { ./Launch-VsDevShell.ps1 -Arch amd64 -HostArch amd64 } + catch { + cd "$launch_vs_dev_shell_dir" + if (! ((./Launch-VsDevShell.ps1 -?) -like "*-HostArch*")) { + Write-Host "Launch-VsDevShell.ps1 has no parameter -HostArch" + if (Test-Path -Path "${env:ProgramFiles(x86)}/Microsoft Visual Studio/Installer/vswhere.exe") + { + Write-Host "Going to try Import-Module + Enter-VsDevShell" + cd ` + "${env:ProgramFiles(x86)}/Microsoft Visual Studio/Installer" + $vs_instance_id=.\vswhere.exe -property instanceId + cd "${env:ProgramFiles(x86)}/Microsoft Visual Studio" + $dirents=Get-ChildItem -Filter "????" -Name | ` + Sort-Object -Descending -Property Name + foreach ($itm in $dirents) { + if ("$itm" -match "^\d+$") { + $itm_as_num=$itm/1 + if ($itm_as_num -gt 1970) { + if (Test-Path "$itm/Enterprise/Common7/Tools/Microsoft.VisualStudio.DevShell.dll") { + $dev_shell_path = ` + "$pwd\$itm\Enterprise\Common7\Tools" + break + } + elseif (Test-Path "$itm/Professional/Common7/Tools/Microsoft.VisualStudio.DevShell.dll") { + $dev_shell_path = ` + "$pwd\$itm\Professional\Common7\Tools" + break + } + elseif (Test-Path "$itm/Community/Common7/Tools/Microsoft.VisualStudio.DevShell.dll") { + $dev_shell_path = ` + "$pwd\$itm\Community\Common7\Tools" + break + } + } + } + } + if ("$dev_shell_path") { + $dev_shell_path = ` + "$dev_shell_path\Microsoft.VisualStudio.DevShell.dll" + } + else { + Write-Host ` + "Searching for Microsoft.VisualStudio.DevShell.dll" + $dev_shell_path = Get-ChildItem -Path ` + "Microsoft.VisualStudio.DevShell.dll" -Recurse | ` + Sort-Object -Descending -Property LastWriteTime | ` + Select -Index 0 | ` + Select -ExpandProperty "FullName" + } + if ($dev_shell_path) { + Import-Module "$dev_shell_path" + $env:VSCMD_DEBUG=1 + Enter-VsDevShell -VsInstanceId $vs_instance_id ` + -SkipAutomaticLocation -DevCmdDebugLevel Basic ` + -DevCmdArguments '-arch=x64' + } + else { + throw ` + "ERROR: No Microsoft.VisualStudio.DevShell.dll" + } + } + else { + throw "ERROR: vswhere.exe not found" + } + } + else { + throw "ERROR: Unexpected Launch-VsDevShell.ps1 error" + } + } + } +else { + throw("ERROR: Launch-VsDevShell.ps1 not found") +} + +$env:CXX="cl" +$env:CC=$env:CXX + +if (! (Get-Command ninja -errorAction SilentlyContinue)) { + # Try looking in Microsoft Visual Studio + + if (Test-Path -Path "$env:ProgramFiles/Microsoft Visual Studio") { + cd "$env:ProgramFiles/Microsoft Visual Studio" + $ninja_dir = Get-ChildItem -Path "ninja.exe" -Recurse | ` + Sort-Object -Descending -Property LastWriteTime | ` + Select -Index 0 | Select -ExpandProperty "FullName" | Split-Path + } + + if ((! ($ninja_dir)) -or (! (Test-Path -Path $ninja_dir))) { + # Try looking in 32-bit Microsoft Visual Studio + + if (Test-Path -Path ` + "${env:ProgramFiles(x86)}/Microsoft Visual Studio") { + cd "${env:ProgramFiles(x86)}/Microsoft Visual Studio" + $ninja_dir = Get-ChildItem -Path "ninja.exe" -Recurse | ` + Sort-Object -Descending -Property LastWriteTime | ` + Select -Index 0 | Select -ExpandProperty "FullName" | Split-Path + } + } + + if (((! ($ninja_dir)) -or (! (Test-Path -Path $ninja_dir))) -and ` + (($env:VCPKG_DIR) -And (Test-Path -Path "$env:VCPKG_DIR\installed"))) { + # Can't find ninja in Microsoft Visual Studio - look in vcpkg + cd "$env:VCPKG_DIR\installed" + + $ninja_dir=Get-ChildItem -Path "ninja.exe" -Recurse | ` + Select -ExpandProperty "FullName" | Sort-Object { $_.Length } | ` + Select -Index 0 | Split-Path + + if ((! ($ninja_dir)) -or (! (Test-Path -Path $ninja_dir))) { + if (Get-Command winget -errorAction SilentlyContinue) { + if (! (winget list "ninja")) { + winget install "Ninja-build.Ninja" + # Don't set ninja_dir - leaving it empty will + # mean it doesn't get added to $env:path below, + # which is good since winget takes care of the + # path for us + } + else { + Write-Host "WARNING: ninja already installed by winget, but not on path?" + } + } + else { + if (! (vcpkg list "vcpkg-tool-ninja")) { + vcpkg install vcpkg-tool-ninja + $ninja_dir=Get-ChildItem -Path "ninja.exe" -Recurse | ` + Select -ExpandProperty "FullName" | ` + Sort-Object { $_.Length } | Select -Index 0 | ` + Split-Path + } + else { + Write-Host "WARNING: ninja already installed by vcpkg, but not found?" + } + } + } + } + + if (($ninja_dir) -And (Test-Path -Path $ninja_dir)) { + $env:Path="$ninja_dir;$env:Path" # Puts ninja.exe on the Path + } + + cd ~ +} + + +if ((! ($env:plain_prompt)) -or ($env:plain_prompt -ne "Y")) +{ + $prompt_existing = (Get-Command prompt).ScriptBlock + if ($prompt_existing) { + $prompt_current=(prompt) + if ((!$prompt_current) -or ($prompt_current.length -lt 3)) { + function global:prompt {"MVS> "} + } + elseif (! ($prompt_current.SubString(0, 3) -eq "MVS")) { + $prompt_new = "`"MVS `" + " + $prompt_existing + $def_fn_prompt_new = ` + "function global:prompt { " + $prompt_new + " }" + Invoke-Expression $def_fn_prompt_new + } + } + else { + function global:prompt {"MVS> "} + } +} + +cd "$savedpwd"