diff --git a/.drone.star b/.drone.star new file mode 100644 index 000000000..44e54b758 --- /dev/null +++ b/.drone.star @@ -0,0 +1,37 @@ +# Use, modification, and distribution are +# subject to the Boost Software License, Version 1.0. (See accompanying +# file LICENSE.txt) +# +# Copyright Rene Rivera 2020. + +# For Drone CI we use the Starlark scripting language to reduce duplication. +# As the yaml syntax for Drone CI is rather limited. +# +# +globalenv={'B2_CI_VERSION': '1', 'B2_VARIANT': 'release'} +linuxglobalimage="cppalliance/droneubuntu1804:1" +windowsglobalimage="cppalliance/dronevs2019" + +def main(ctx): + return [ + #freebsd_cxx("FreeBSD", "g++10", packages="g++10", buildtype="boost", buildscript="drone", environment={ "VARIANT": "release", "TOOLSET": "gcc", "COMPILER": "g++", "CXXSTD": "11" }, globalenv=globalenv), + linux_cxx("docs", "", packages="docbook docbook-xml docbook-xsl xsltproc libsaxonhe-java default-jre-headless flex libfl-dev bison unzip rsync mlocate", image="cppalliance/droneubuntu1804:1", buildtype="docs", buildscript="drone", environment={"COMMENT": "docs"}, globalenv=globalenv), + linux_cxx("asan", "g++-8", packages="g++-8", buildtype="boost", buildscript="drone", image=linuxglobalimage, environment={'COMMENT': 'asan', 'B2_VARIANT': 'debug', 'B2_TOOLSET': 'gcc-8', 'B2_CXXSTD': '11', 'B2_ASAN': '1', 'B2_DEFINES': 'BOOST_NO_STRESS_TEST=1', 'DRONE_EXTRA_PRIVILEGED': 'True', 'DRONE_JOB_UUID': '356a192b79'}, globalenv=globalenv, privileged=True), + linux_cxx("ubsan", "g++-8", packages="g++-8", buildtype="boost", buildscript="drone", image=linuxglobalimage, environment={'COMMENT': 'ubsan', 'B2_VARIANT': 'debug', 'B2_TOOLSET': 'gcc-8', 'B2_CXXSTD': '11', 'B2_UBSAN': '1', 'B2_DEFINES': 'BOOST_NO_STRESS_TEST=1', 'B2_LINKFLAGS': '-fuse-ld=gold', 'DRONE_JOB_UUID': '77de68daec'}, globalenv=globalenv), + linux_cxx("gcc 11 arm64", "g++-11", packages="g++-11", buildtype="boost", buildscript="drone", image="cppalliance/droneubuntu2004:multiarch", environment={ 'B2_TOOLSET': 'gcc-11', 'B2_CXXSTD': '11', 'DRONE_JOB_UUID': '17ba079169m'}, arch="arm64", globalenv=globalenv), + linux_cxx("GCC 10, Debug + Coverage", "g++-10", packages="g++-10 libssl-dev libffi-dev binutils-gold gdb mlocate", + image="cppalliance/droneubuntu2004:1", buildtype="boost", buildscript="drone", environment={"GCOV": "gcov-10", "LCOV_VERSION": "1.15", "VARIANT": "process_coverage", "TOOLSET": "gcc", "COMPILER": "g++-10", "CXXSTD": "11", "DRONE_BEFORE_INSTALL" : "process_coverage", "CODECOV_TOKEN": {"from_secret": "codecov_token"}}, globalenv=globalenv, privileged=True), + # A set of jobs based on the earlier .travis.yml configuration: + linux_cxx("Default clang++ with libc++", "clang++-libc++", packages="libc++-dev mlocate", image="cppalliance/droneubuntu1604:1", buildtype="buildtype", buildscript="drone", environment={ "B2_TOOLSET": "clang-7", "B2_CXXSTD": "11", "VARIANT": "debug", "TOOLSET": "clang", "COMPILER": "clang++-libc++", "CXXSTD": "11", "CXX_FLAGS": "-stdlib=libc++ -stdlib=libc++", "TRAVISCLANG" : "yes" }, globalenv=globalenv), + linux_cxx("Default g++", "g++", packages="mlocate", image="cppalliance/droneubuntu1604:1", buildtype="buildtype", buildscript="drone", environment={ "VARIANT": "release", "TOOLSET": "gcc", "COMPILER": "g++", "CXXSTD": "11" }, globalenv=globalenv), + linux_cxx("Clang 3.8, UBasan", "clang++-3.8", packages="clang-3.8 libssl-dev mlocate", llvm_os="precise", llvm_ver="3.8", image="cppalliance/droneubuntu1604:1", buildtype="boost", buildscript="drone", environment={"VARIANT": "process_ubasan", "TOOLSET": "clang", "COMPILER": "clang++-3.8", "CXXSTD": "11", "UBSAN_OPTIONS": 'print_stacktrace=1', "DRONE_BEFORE_INSTALL": "UBasan" }, globalenv=globalenv), + linux_cxx("gcc 6", "g++-6", packages="g++-6", buildtype="boost", buildscript="drone", image=linuxglobalimage, environment={'B2_TOOLSET': 'gcc-6', 'B2_CXXSTD': '11', 'DRONE_JOB_UUID': '902ba3cda1'}, globalenv=globalenv), + linux_cxx("clang 3.8", "clang++-3.8", packages="clang-3.8", buildtype="boost", buildscript="drone", image="cppalliance/droneubuntu1604:1", environment={'B2_TOOLSET': 'clang', 'COMPILER': 'clang++-3.8', 'B2_CXXSTD': '11', 'DRONE_JOB_UUID': '7b52009b64'}, globalenv=globalenv), + osx_cxx("clang", "g++", packages="", buildtype="boost", buildscript="drone", environment={'B2_TOOLSET': 'clang', 'B2_CXXSTD': '11,17', 'DRONE_JOB_UUID': '91032ad7bb'}, globalenv=globalenv), + linux_cxx("coverity", "g++", packages="", buildtype="coverity", buildscript="drone", image=linuxglobalimage, environment={'COMMENT': 'Coverity Scan', 'B2_TOOLSET': 'clang', 'DRONE_JOB_UUID': '472b07b9fc'}, globalenv=globalenv), + windows_cxx("msvc-14.1", "", image="cppalliance/dronevs2017", buildtype="boost", buildscript="drone", environment={ "VARIANT": "release", "TOOLSET": "msvc-14.1", "CXXSTD": "11", "DEFINE" : "BOOST_BEAST_USE_STD_STRING_VIEW", "ADDRESS_MODEL": "64"}), + windows_cxx("msvc-14.3", "", image="cppalliance/dronevs2022:1", buildtype="boost", buildscript="drone", environment={ "VARIANT": "release", "TOOLSET": "msvc-14.3", "CXXSTD": "11", "DEFINE" : "BOOST_BEAST_USE_STD_STRING_VIEW", "ADDRESS_MODEL": "64"}), + ] + +# from https://github.com/boostorg/boost-ci +load("@boost_ci//ci/drone/:functions.star", "linux_cxx","windows_cxx","osx_cxx","freebsd_cxx") diff --git a/.drone/drone.bat b/.drone/drone.bat new file mode 100755 index 000000000..0eb2236e3 --- /dev/null +++ b/.drone/drone.bat @@ -0,0 +1,36 @@ +@ECHO ON +setlocal enabledelayedexpansion + +if "%DRONE_JOB_BUILDTYPE%" == "boost" ( + +echo '==================================> INSTALL' + +git clone https://github.com/boostorg/boost-ci.git boost-ci-cloned --depth 1 +cp -prf boost-ci-cloned/ci . +rm -rf boost-ci-cloned + +REM source ci/travis/install.sh +REM The contents of install.sh below: + +for /F %%i in ("%DRONE_REPO%") do @set SELF=%%~nxi +SET BOOST_CI_TARGET_BRANCH=%DRONE_COMMIT_BRANCH% +SET BOOST_CI_SRC_FOLDER=%cd% + +call ci\common_install.bat + +echo '==================================> COMPILE' + +REM set B2_TARGETS=libs/!SELF!/test libs/!SELF!/example +set B2_TARGETS=libs/!SELF!/test + +cd !BOOST_ROOT! +call bootstrap.bat +b2 headers +b2 --debug-configuration variant=%VARIANT% cxxstd=%CXXSTD% define=%DEFINE% address-model=%ADDRESS_MODEL% toolset=%TOOLSET% --verbose-test libs/!SELF!/test -j3 +b2 --debug-configuration variant=%VARIANT% cxxstd=%CXXSTD% define=%DEFINE% address-model=%ADDRESS_MODEL% toolset=%TOOLSET% --verbose-test libs/!SELF!/example -j3 + +) else if "%DRONE_JOB_BUILDTYPE%" == "standalone-windows" ( + +REM not used + +) \ No newline at end of file diff --git a/.drone/drone.sh b/.drone/drone.sh new file mode 100755 index 000000000..ed3bfae07 --- /dev/null +++ b/.drone/drone.sh @@ -0,0 +1,199 @@ +#!/bin/bash + +# Copyright 2020 Rene Rivera, Sam Darwin +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE.txt or copy at http://boost.org/LICENSE_1_0.txt) + +set -xe + +export TRAVIS_BUILD_DIR=$(pwd) +export DRONE_BUILD_DIR=$(pwd) +export TRAVIS_BRANCH=$DRONE_BRANCH +export TRAVIS_EVENT_TYPE=$DRONE_BUILD_EVENT +export VCS_COMMIT_ID=$DRONE_COMMIT +export GIT_COMMIT=$DRONE_COMMIT +export REPO_NAME=$DRONE_REPO +export USER=$(whoami) +export CC=${CC:-gcc} +export PATH=~/.local/bin:/usr/local/bin:$PATH + +common_install () { + git clone https://github.com/boostorg/boost-ci.git boost-ci-cloned --depth 1 + cp -prf boost-ci-cloned/ci . + rm -rf boost-ci-cloned + + if [ "$TRAVIS_OS_NAME" == "osx" ]; then + unset -f cd + fi + + export SELF=`basename $REPO_NAME` + export BOOST_CI_TARGET_BRANCH="$TRAVIS_BRANCH" + export BOOST_CI_SRC_FOLDER=$(pwd) + + . ./ci/common_install.sh +} + +if [ "$DRONE_JOB_BUILDTYPE" == "boost" ]; then + +echo '==================================> INSTALL' + +common_install + +echo '==================================> SCRIPT' + +$BOOST_ROOT/libs/$SELF/ci/travis/build.sh + +elif [ "$DRONE_JOB_BUILDTYPE" == "docs" ]; then + +echo '==================================> INSTALL' + +export SELF=`basename $REPO_NAME` + +pwd +cd .. +mkdir -p $HOME/cache && cd $HOME/cache +if [ ! -d doxygen ]; then git clone -b 'Release_1_8_15' --depth 1 https://github.com/doxygen/doxygen.git && echo "not-cached" ; else echo "cached" ; fi +cd doxygen +cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release +cd build +sudo make install +cd ../.. +if [ ! -f saxonhe.zip ]; then wget -O saxonhe.zip https://sourceforge.net/projects/saxon/files/Saxon-HE/9.9/SaxonHE9-9-1-4J.zip/download && echo "not-cached" ; else echo "cached" ; fi +unzip -o saxonhe.zip +sudo rm /usr/share/java/Saxon-HE.jar +sudo cp saxon9he.jar /usr/share/java/Saxon-HE.jar +cd .. +BOOST_BRANCH=develop && [ "$TRAVIS_BRANCH" == "master" ] && BOOST_BRANCH=master || true +git clone -b $BOOST_BRANCH https://github.com/boostorg/boost.git boost-root --depth 1 +cd boost-root +export BOOST_ROOT=$(pwd) +git submodule update --init libs/context +git submodule update --init tools/boostbook +git submodule update --init tools/boostdep +git submodule update --init tools/docca +git submodule update --init tools/quickbook +rsync -av $TRAVIS_BUILD_DIR/ libs/$SELF +python tools/boostdep/depinst/depinst.py ../tools/quickbook +./bootstrap.sh +./b2 headers + +echo '==================================> SCRIPT' + +echo "using doxygen ; using boostbook ; using saxonhe ;" > tools/build/src/user-config.jam +./b2 -j3 libs/$SELF/doc//boostrelease + +elif [ "$DRONE_JOB_BUILDTYPE" == "codecov" ]; then + +echo '==================================> INSTALL' + +common_install + +echo '==================================> SCRIPT' + +cd $BOOST_ROOT/libs/$SELF +ci/travis/codecov.sh + +elif [ "$DRONE_JOB_BUILDTYPE" == "valgrind" ]; then + +echo '==================================> INSTALL' + +common_install + +echo '==================================> SCRIPT' + +cd $BOOST_ROOT/libs/$SELF +ci/travis/valgrind.sh + +elif [ "$DRONE_JOB_BUILDTYPE" == "standalone" ]; then + +echo '==================================> INSTALL' + +# Installing cmake with apt-get, so not required here: +# pip install --user cmake + +echo '==================================> SCRIPT' + +export CXXFLAGS="-Wall -Wextra -Werror -std=c++17" +mkdir __build_17 +cd __build_17 +cmake -DBOOST_JSON_STANDALONE=1 .. +cmake --build . +ctest -V . +export CXXFLAGS="-Wall -Wextra -Werror -std=c++2a" +mkdir ../__build_2a +cd ../__build_2a +cmake -DBOOST_JSON_STANDALONE=1 .. +cmake --build . +ctest -V . + +elif [ "$DRONE_JOB_BUILDTYPE" == "coverity" ]; then + +echo '==================================> INSTALL' + +common_install + +echo '==================================> SCRIPT' + +if [ $VARIANT = "process_valgrind" ]; +then export USE_VALGRIND="testing.launcher=valgrind valgrind=on"; +fi ; + +if [ -n "${COVERITY_SCAN_NOTIFICATION_EMAIL}" -a \( "$TRAVIS_BRANCH" = "develop" -o "$TRAVIS_BRANCH" = "master" \) -a \( "$DRONE_BUILD_EVENT" = "push" -o "$DRONE_BUILD_EVENT" = "cron" \) ] ; then +cd $BOOST_ROOT/libs/$SELF +ci/travis/coverity.sh +fi + +elif [ "$DRONE_JOB_BUILDTYPE" == "cmake-superproject" ]; then + +echo '==================================> INSTALL' + +common_install + +echo '==================================> COMPILE' + +export CXXFLAGS="-Wall -Wextra -Werror" + +mkdir __build_static +cd __build_static +cmake -DBOOST_ENABLE_CMAKE=1 -DBUILD_TESTING=ON -DBoost_VERBOSE=1 \ + -DBOOST_INCLUDE_LIBRARIES=$SELF .. +cmake --build . +ctest --output-on-failure -R boost_$SELF + +cd .. + +mkdir __build_shared +cd __build_shared +cmake -DBOOST_ENABLE_CMAKE=1 -DBUILD_TESTING=ON -DBoost_VERBOSE=1 \ + -DBOOST_INCLUDE_LIBRARIES=$SELF -DBUILD_SHARED_LIBS=ON .. +cmake --build . +ctest --output-on-failure -R boost_$SELF + +elif [ "$DRONE_JOB_BUILDTYPE" == "cmake1" ]; then + +echo '==================================> INSTALL' + +pip install --user cmake + +echo '==================================> SCRIPT' + +export SELF=`basename $REPO_NAME` +BOOST_BRANCH=develop && [ "$DRONE_BRANCH" == "master" ] && BOOST_BRANCH=master || true +echo BOOST_BRANCH: $BOOST_BRANCH +cd .. +git clone -b $BOOST_BRANCH --depth 1 https://github.com/boostorg/boost.git boost-root +cd boost-root +mkdir -p libs/$SELF +cp -r $DRONE_BUILD_DIR/* libs/$SELF +# git submodule update --init tools/boostdep +git submodule update --init --recursive + +cd libs/$SELF + +../../../b2 -sBOOST_BUILD_PATH=. +../../../b2 $MULTITHREAD with-valgrind address-model=64 architecture=x86 $USE_VALGRIND toolset=$TOOLSET cxxflags="--coverage -DBOOST_TRAVISCI_BUILD -std=$CXX_STANDARD" linkflags="--coverage" -sBOOST_BUILD_PATH=. $REPORT_CI +../../../b2 $MULTITHREAD without-valgrind address-model=64 architecture=x86 toolset=$TOOLSET cxxflags="--coverage -DBOOST_TRAVISCI_BUILD -std=$CXX_STANDARD" linkflags="--coverage" -sBOOST_BUILD_PATH=. $REPORT_CI + + + +fi \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 94f9b9c87..c0c1bfef9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,6 @@ add_library(boost_process INTERFACE) add_library(Boost::process ALIAS boost_process) target_include_directories(boost_process INTERFACE include) - target_link_libraries(boost_process INTERFACE Boost::algorithm diff --git a/README.md b/README.md index 69aec235a..cc1de7cc0 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,21 @@ -# [Boost Process (Boost.Process)](https://github.com/klemens-morgenstern/boost-process) +# [Boost Process (Boost.Process)](https://github.com/boostorg/process) Boost.process is a library for comfortable management of processes, released with boost 1.64.0. ### Test results -Branches | Linux | OSX | Windows | Code coverage | Matrix | -----------------|-------|-----|---------| ------------- |--------| -Develop: | [![Build Status](https://travis-ci.org/klemens-morgenstern/boost-process.svg?branch=develop&env=BADGE=linux)](https://travis-ci.org/klemens-morgenstern/boost-process) [![badge](https://api.report.ci/status/klemens-morgenstern/boost-process/badge.svg?branch=develop&build=linux)](https://api.report.ci/status/klemens-morgenstern/boost-process?branch=develop&build=linux) | [![Build Status](https://travis-ci.org/klemens-morgenstern/boost-process.svg?branch=develop&env=BADGE=osx)](https://travis-ci.org/klemens-morgenstern/boost-process) [![badge](https://api.report.ci/status/klemens-morgenstern/boost-process/badge.svg?branch=develop&build=osx)](https://api.report.ci/status/klemens-morgenstern/boost-process?branch=develop&build=osx) | [![Build status](https://ci.appveyor.com/api/projects/status/peup7e6m0e1bb5ba/branch/develop?svg=true)](https://ci.appveyor.com/project/klemens-morgenstern/boost-process/branch/develop) [![badge](https://api.report.ci/status/klemens-morgenstern/boost-process/badge.svg?branch=develop&build=windows)](https://api.report.ci/status/klemens-morgenstern/boost-process?branch=develop&build=windows) | [![Coverage Status](https://coveralls.io/repos/github/klemens-morgenstern/boost-process/badge.svg?branch=develop)](https://coveralls.io/github/klemens-morgenstern/boost-process?branch=develop) | [![Matrix](https://img.shields.io/badge/matrix-develop-lightgray.svg)](http://www.boost.org/development/tests/develop/developer/process.html) -Master: | [![Build Status](https://travis-ci.org/klemens-morgenstern/boost-process.svg?branch=master&env=BADGE=linux)](https://travis-ci.org/klemens-morgenstern/boost-process) [![badge](https://api.report.ci/status/klemens-morgenstern/boost-process/badge.svg?branch=master&build=linux)](https://api.report.ci/status/klemens-morgenstern/boost-process?branch=master&build=linux) | [![Build Status](https://travis-ci.org/klemens-morgenstern/boost-process.svg?branch=master&env=BADGE=osx)](https://travis-ci.org/klemens-morgenstern/boost-process) [![badge](https://api.report.ci/status/klemens-morgenstern/boost-process/badge.svg?branch=master&build=osx)](https://api.report.ci/status/klemens-morgenstern/boost-process?branch=master&build=osx) | [![Build status](https://ci.appveyor.com/api/projects/status/peup7e6m0e1bb5ba/branch/master?svg=true)](https://ci.appveyor.com/project/klemens-morgenstern/boost-process/branch/master) [![badge](https://api.report.ci/status/klemens-morgenstern/boost-process/badge.svg?branch=master&build=windows)](https://api.report.ci/status/klemens-morgenstern/boost-process?branch=master&build=windows) | [![Coverage Status](https://coveralls.io/repos/github/klemens-morgenstern/boost-process/badge.svg?branch=master)](https://coveralls.io/github/klemens-morgenstern/boost-process?branch=master) | [![Matrix](https://img.shields.io/badge/matrix-master-lightgray.svg)](http://www.boost.org/development/tests/master/developer/process.html) +| Branches | Linux / Windows | Code coverage | Matrix | +|----------|----------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| +| Develop: | [![Build Status](https://drone.cpp.al/api/badges/boostorg/process/status.svg)](https://drone.cpp.al/boostorg/process) | [![codecov](https://codecov.io/gh/boostorg/process/branch/develop/graph/badge.svg?token=AhunMqTSpA)](https://codecov.io/gh/boostorg/process) | [![Matrix](https://img.shields.io/badge/matrix-develop-lightgray.svg)](http://www.boost.org/development/tests/develop/developer/process.html) | +| Master: | [![Build Status](https://drone.cpp.al/api/badges/boostorg/process/status.svg?ref=refs/heads/develop)](https://drone.cpp.al/boostorg/process) | [![codecov](https://codecov.io/gh/boostorg/process/branch/master/graph/badge.svg?token=AhunMqTSpA)](https://codecov.io/gh/boostorg/process) | [![Matrix](https://img.shields.io/badge/matrix-master-lightgray.svg)](http://www.boost.org/development/tests/master/developer/process.html) | -[Open Issues](https://github.com/klemens-morgenstern/boost-process/issues) -[Latest developer documentation](http://klemens-morgenstern.github.io/process/) + + +[Open Issues](https://github.com/boostorg/process/issues) + +[Latest developer documentation](https://www.boost.org/doc/libs/develop/doc/html/process.html) ### About This C++11 library is the current result of a long attempt to get a boost.process library, which is going on since 2006. diff --git a/doc/Jamfile.jam b/doc/Jamfile.jam index e361b45e9..15ff339dd 100644 --- a/doc/Jamfile.jam +++ b/doc/Jamfile.jam @@ -24,12 +24,13 @@ generators.register-standard common.copy : XML : XMLPROCESSWORKAROUND ; xmlprocessworkaround posix_pseudocode : posix_pseudocode.xml ; xmlprocessworkaround windows_pseudocode : windows_pseudocode.xml ; +path-constant INCLUDES : ../../.. ; doxygen autodoc : - ../../../boost/process.hpp - [ glob ../../../boost/process/*.hpp ] -: + $(INCLUDES)/boost/process.hpp + [ glob $(INCLUDES)/boost/process/*.hpp ] + : EXCLUDE_SYMBOLS=BOOST_ASIO_INITFN_RESULT_TYPE PREDEFINED=BOOST_PROCESS_DOXYGEN HIDE_UNDOC_CLASSES=YES @@ -42,11 +43,49 @@ doxygen autodoc +doxygen reference_v2 +: + $(INCLUDES)/boost/process/v2.hpp + [ glob $(INCLUDES)/boost/process/v2/*.hpp ] + : + EXCLUDE_SYMBOLS=BOOST_ASIO_INITFN_RESULT_TYPE + PROJECT_NAME="Process V2" + PROJECT_BRIEF="The process library" + MACRO_EXPANSION=YES + EXPAND_ONLY_PREDEF=YES + "PREDEFINED=\\ + GENERATING_DOCUMENTATION=1 \\ + BOOST_PROCESS_V2_ASIO_NAMESPACE=boost::asio \\ + \"BOOST_PROCESS_V2_BEGIN_NAMESPACE=namespace boost { namespace process { namespace v2 { \" \\ + \"BOOST_PROCESS_V2_END_NAMESPACE= } } }\" \\ + BOOST_PROCESS_V2_NAMESPACE=boost::process::v2 \\ + BOOST_PROCESS_V2_DECL \\ + BOOST_PROCESS_V2_SOURCE \\ + BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(x,y)=deduced \\ + BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(X)=Token \\ + BOOST_PROCESS_V2_DEFAULT_COMPLETION_TOKEN_TYPE(E)=DEFAULT_TYPE \\ + BOOST_ASIO_DEFAULT_COMPLETION_TOKEN=DEFAULT \\ + BOOST_CONSTEXPR=constexpr \\ + BOOST_CXX14_CONSTEXPR=constexpr \\ + BOOST_ATTRIBUTE_NODISCARD=[[nodiscard]] + " + reference_v2 + SHOW_USED_FILES=NO + SHOW_FILES=NO + SHOW_NAMESPACES=YES + CLASS_DIAGRAMS=NO + SORT_MEMBERS_CTORS_1ST=YES + HIDE_UNDOC_CLASSES=NO + . +; + + boostbook standalone : process.qbk : autodoc + reference_v2 images images_glob boost.root=../../../.. diff --git a/doc/process.qbk b/doc/process.qbk index d7a158d9e..6dbfa57ad 100644 --- a/doc/process.qbk +++ b/doc/process.qbk @@ -11,6 +11,8 @@ ] ] +[note [link process.v2 Process V2] is available as experimental] + [include introduction.qbk] [include concepts.qbk] [include tutorial.qbk] @@ -19,3 +21,4 @@ [include faq.qbk] [xinclude autodoc.xml] [include acknowledgements.qbk] +[include v2.qbk] \ No newline at end of file diff --git a/doc/tutorial.qbk b/doc/tutorial.qbk index 4365f0f54..6c88f9398 100644 --- a/doc/tutorial.qbk +++ b/doc/tutorial.qbk @@ -82,10 +82,10 @@ int result = bp::system("/usr/bin/g++", "main.cpp"); ``` With that syntax we still have "g++" hard-coded, so let's assume we get the string -from an external source as `boost::filesystem::path`, we can do this too. +from an external source as `boost::process::filesystem::path`, we can do this too. ``` -boost::filesystem::path p = "/usr/bin/g++"; //or get it from somewhere else. +boost::process::filesystem::path p = "/usr/bin/g++"; //or get it from somewhere else. int result = bp::system(p, "main.cpp"); ``` @@ -93,7 +93,7 @@ Now we might want to find the `g++` executable in the `PATH`-variable, as the `c `Boost.process` provides a function to this end: bp::search_path. ``` -boost::filesystem::path p = bp::search_path("g++"); //or get it from somewhere else. +boost::process::filesystem::path p = bp::search_path("g++"); //or get it from somewhere else. int result = bp::system(p, "main.cpp"); ``` diff --git a/doc/v2.qbk b/doc/v2.qbk new file mode 100644 index 000000000..3b6bb08ef --- /dev/null +++ b/doc/v2.qbk @@ -0,0 +1,11 @@ +[section:v2 Process V2] + +[include v2/introduction.qbk] +[include v2/quickstart.qbk] +[include v2/launcher.qbk] +[include v2/start_dir.qbk] +[include v2/stdio.qbk] +[include v2/env.qbk] +[xinclude reference_v2.xml] + +[endsect] diff --git a/doc/v2/env.qbk b/doc/v2/env.qbk new file mode 100644 index 000000000..3f44dc2ab --- /dev/null +++ b/doc/v2/env.qbk @@ -0,0 +1,48 @@ +[section:env Environment] + +The `environment` namespace provides all sorts of facilities to query and manipulate the environment of the current process. + +The api should be straight forward, but one oddity that needs to be pointed out is, that environment names +are not case sensitive on windows. The key_traits class implements the proper traits depending on the current system. + +Additionally, environment can be lists separated by `:` or `;`; `environment::value` and +`environment::value_view` can be used to iterate those. + +Beyond that, the requirements on an environment are a low as possible; +an environment is either a list of strings or a list of string-pairs. It is however recommented to use the environment types, +as to have the right value comparisons. + +To note is the `find_executable` functions, which searches in an environment for an executable. + +``` + // search in the current environment + auto exe = environment::find_executable("g++"); + + std::unordered_map my_env = + { + {"SECRET", "THIS_IS_A_TEST"} + {"PATH", {"/bin", "/usr/bin"} + }; + + auto other_exe = environment::find_executable("g++", my_env); +``` + +[section:process_env Subprocess environment] + +The subprocess environment assignment follows the same constraints: + +``` + asio::io_context ctx; + std::unordered_map my_env = + { + {"SECRET", "THIS_IS_A_TEST"} + {"PATH", {"/bin", "/usr/bin"} + }; + auto exe = find_executable("g++"), my_env); + process proc(ctx, exe, {"main.cpp"}, process_environment(my_env)); + process pro2(ctx, exe, {"test.cpp"}, process_environment(my_env)); +``` + +[endsect] + +[endsect] \ No newline at end of file diff --git a/doc/v2/introduction.qbk b/doc/v2/introduction.qbk new file mode 100644 index 000000000..2554d3f82 --- /dev/null +++ b/doc/v2/introduction.qbk @@ -0,0 +1,94 @@ +[section:introduction Introduction] + +Boost.process V2 is an redesign of boost.process, based on previous +design mistakes & improved system APIs. + +The major changes are + +* Simplified interface +* Reliance on pidfd_open on linux +* Full asio integration +* Removed unreliable functionality +* UTF8 Support +* separate compilation +* fd safe by default + +[section:simplified Simplified Interface] + +In process v1 one can define partial settings in the constructor of the process, +which has lead to a small DSL. + + child c{exe="test", args+="--help", std_in < null(), env["FOO"] += "BAR"}; + +While this looks fancy at first, it really does not scale well with more parameters. +For process v2, the interfaces is simple: + + extern std::unordered_map my_env; + extern asio::io_context ctx; + process proc(ctx, "./test", {"--help"}, process_io{nullptr, {}, {}}, process_environment(my_env)); + +Every initializer adresses one logical compoent (e.g. stdio) instead of multiple ones accumulating. +Furthermore, every process has a path and arguments, instead of a confusing mixture of cmd-style and +exe-args that can be randomly spread out. + +[endsect] + +[section:pidfd_open pidfd_open] + +Since process v1 came out, linux has moved along and added pidfd_open which allows users to get a +file descriptor for a process. This is much more reliable since it is not as easy to miss as a `SIGCHLD`. +FreeBSD has a similar feature with `pdfork` which is also supported, while windows has provided `HANDLE`s +for processes all along. +Unless the OS doesn't support it, process v2 will use file descriptors and handles to implement waiting +for processes. + +[endsect] + +[section:asio Full asio integration] + +Process v1 aimed to make asio optional, but synchronous IO with subprocesses usually means one is begging +for deadlocks. +Since asio added pipes in boost 1.78, boost process V2 is fully asio based and uses it's pipes and +file-handles for the subprocess. + +[endsect] + +[section:unreliable Unreliable functionality] + +Certain parts of boost.process were not as reliable as they should've been. + +This concerns especially the `wait_for`` and `wait_until` functions on the process. +The latter are easy to do on windows, but posix does not provide an API for this. +Thus the wait_for used signals or fork, which was all but safe. +Since process v2 is based on asio and thus supports cancellation, +a wait_for can not safely be implemented with an async_wait + timeout. + +[endsect] + +[section:utf8 UTF-8] + +["UTF-8 or GTFO]--Vinnie Falco + +Instead of using ascii-APIs on windows, process V2 just assumes UTF-8 everywhere. + +[endsect] + +[section:src Separate compilation] + +Boost.process v2 supports separate compilation similar to other boost libraries. +It can be achieved by defining `BOOST_PROCESS_V2_SEPARATE_COMPILATION` and including +`` in a single compile unit. + +[endsect] + +[section:limit_fd Fd safe by default] + +While not a problem on windows (since HANDLEs get manually enabled for inheritance), +posix systems create a problem with inheriting file handles by default. + +Process V2 will automatically close all non-whitelisted descriptors, +without needing any option to enable it. + +[endsect] + +[endsect] diff --git a/doc/v2/launcher.qbk b/doc/v2/launcher.qbk new file mode 100644 index 000000000..4e25728e8 --- /dev/null +++ b/doc/v2/launcher.qbk @@ -0,0 +1,128 @@ +[section:launchers Launcher] + +The process creation is done by a process_launcher. +The constructor of `process` will use the default_launcher, which varies by system. +There are additional launcher available on most systems. + +[table:launchers Launcher overview + [[Name] [Summary] [Default on] [Available on]] + [[`windows::default_launcher`] [Launcher using `CreateProcessW`] [windows] [windows]] + [[`windows::as_user_launcher`] [Launcher using `CreateProcessAsUserW`] [] [windows]] + [[`windows::with_logon_launcher`] [Launcher using `CreateProcessWithLogonW`] [] [windows]] + [[`windows::with_token_launcher`] [Launcher using `CreateProcessWithTokenW`] [] [windows]] + [[`posix::default_launcher`] [Launcher using fork & an error pipe] [most of posix] [posix]] + [[`posix::fork_and_forget`] [Launcher using fork without error pipe] [] [posix]] + [[`posix::pdfork_launcher`] [Launcher using pdfork with an error pipe] [FreeBSD] [FreeBSD]] + [[`posix::vfork_launcher`] [Launcher using vfork] [] [posix]] +] + +A launcher is invoked through the call operator. + +``` + auto l = windows::as_user_launcher((HANDLE)0xDEADBEEF); + asio::io_context ctx; + boost::system::eror_code ec; + auto proc = l(ctx, ec, "C:\\User\\boost\\Downloads\\totally_not_a_virus.exe", {}); +``` + +The launcher will call certain functions on the initializer if they're present, as documented below. +The initializer are used to modify the process behaviour. + +[section:linux Linux Launchers] + +The default and pdfork launchers on linux open an internal pipe to communicate errors that occur after forking back to the parent process. + +This can be prevented by using the `fork_and_forget_launcher`. +Alternatively, the `vfork_launcher` can report errors directly back to the parent process. + +Thus some calls to the initializers occur after forking from the child process. + +``` + struct custom_initalizer + { + // functions called from the parent process: + + + // called before a call to fork. A returned error will cancel the launch. + template + error_code on_setup(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line)); + + // called for every initializer if an error occured during setup or process creation + template + void on_error(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), + const error_code & ec); + + // called after successful process creation + template + void on_success(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line)); + + // called for every initializer if an error occured when forking, in addtion to on_error. + template + void on_fork_error(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line), + const error_code & ec); + + + // called before a call to execve. A returned error will cancel the launch. Called from the child process. + template + error_code on_exec_setup(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line)); + + + // called after a failed call to execve from the child process. + template + void on_exec_error(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line)); + }; +``` + +The call sequence on success: +''' + +''' + +The call sequence when fork fails: +''' + +''' + +The call sequence when exec fails: +''' + +''' + +The launcher will close all non-whitelisted file descriptors after `on_exec_setup`. + +[endsect] + +[section:windows Windows Launchers] + +Windows launchers are pretty streight forward, they will call the following functions on the initializer if present. + +``` + struct custom_initializer + { + // called before a call to CreateProcess. A returned error will cancel the launch. + template + error_code on_setup(Launcher & launcher, const filesystem::path &executable, std::wstring &cmd_line); + + // called for every initializer if an error occured during setup or process creation + template + void on_error(Launcher & launcher, const filesystem::path &executable, std::wstring &cmd_line, + const error_code & ec); + + // called after successful process creation + template + void on_success(Launcher & launcher, const filesystem::path &executable, std::wstring &cmd_line); + + }; +``` + +[note All the additional launchers for windows inherit `default_launcher`] + +The call sequence is as follows: +''' + +''' + +[endsect] + + +[endsect] \ No newline at end of file diff --git a/doc/v2/quickstart.qbk b/doc/v2/quickstart.qbk new file mode 100644 index 000000000..73ef4338b --- /dev/null +++ b/doc/v2/quickstart.qbk @@ -0,0 +1,124 @@ +[section:quickstart Quickstrat] + +A process needs four things to be launched: + +* an asio execution_context / executor +* a path to an executable +* a list of arguments +* a variadic set of initializers + +``` + // process(asio::any_io_executor, filesystem::path, range args, AdditionalInitializers...) + asio::io_context ctx; + process proc(ctx, "/usr/bin/cp", {"source.txt", "target.txt"}); +``` + + +The started process can then be awaited or terminated. + +[section:lifetime Lifetime] + +If the process handle goes out of scope, it will terminate the subprocess. +You can prevent this, by calling `proc.detach()`; do however note that this +can lead to zombie processes. + +A process that completed will deliver an exit-code, +which can be obtained by calling `.exit_code` on the exited process and which is +also returned from `.wait()`. + +``` + process proc("/bin/ls", {}); + assert(proc.wait() == 0); +``` + +The normal exit-code is what the subprocess returned from `main`; +posix will however add addtional information about the process. +This is called the `native_exit_code`. + + +The `.running()` function can be used to detect if the process is still active. + +[endsect] + +[section:signal Signalling the subprocess] + +The parent process can signal the subprocess demaning certain actions. + +`.terminate` will cause the subprocess to exit immediately (`SIGKILL` on posix). +This is the only reliable & portable way to end a subprocess. + +``` + process proc("/bin/totally-not-a-virus", {}); + proc.terminate(); +``` + +`.request_exit` will ask the subprocess to shutdown (`SIGTERM` on posix), +which the subprocess might ignore. + +``` + process proc("/bin/bash", {}); + proc.request_exit(); + proc.wait(); +``` + +`.interrupt` will send an SIGINT to the subprocess, which a subprocess might +interpret as a signal to shutdown. + +[warning interrupt requires the initializer `windows::create_new_process_group` to be set] + +``` + process proc("/usr/bin/addr2line", {}); + proc.request_exit(); + proc.wait(); +``` + +[endsect] + +[section:execute Execute functions] + +Process v2 provides `execute` and `async_execute` functons that can be used for managed executions. + +``` + assert(execute(process("/bin/ls", {}) == 0)); +``` + +The async version supports cancellation and will forward cancellation types as follows: + +- asio::cancellation_type::total -> interrupt +- asio::cancellation_type::partial -> request_exit +- asio::cancellation_type::terminal -> terminate + +``` + asio::io_context ctx; + asio::steady_timer timout{ctx, std::chrono::seconds(10)}; + + asio::cancellation_signal sig; + async_execute(process("/usr/bin/g++", {"hello_world.cpp"}), + asio::bind_cancellation_slot(sig.slot(), + [&](error_code ec, int exit_code) + { + timeout.cancel(); // we're done earlier + })); + + timeout.async_wait( + [&](error_code ec) + { + if (ec) // we were cancelled, do nothing + return ; + sig.emit(asio::cancellation_type::partial); + // request exit first, but terminate after another 10 sec + timeout.expires_after(std::chrono::seconds(10)); + timeout.async_wait( + [&](error_code ec) + { + if (!ec) + sig.emit(asio::cancellation_type::terminal); + }); + ); + }); + +``` + +[endsect] + +[endsect] \ No newline at end of file diff --git a/doc/v2/start_dir.qbk b/doc/v2/start_dir.qbk new file mode 100644 index 000000000..3751edff9 --- /dev/null +++ b/doc/v2/start_dir.qbk @@ -0,0 +1,16 @@ +[section:start_dir process_start_dir] + +The easier initializer to use is `process_start_dir`: + +``` + asio::io_context ctx; + process ls(ctx, "/ls", {}, process_start_dir("/home")); + ls.wait(); +``` + +This will run `ls` in the folder `/home` instead of the current folder. + +[warning If your path is relative, it may fail on posix, because the directory is changed before a call to execve.] + + +[endsect] \ No newline at end of file diff --git a/doc/v2/stdio.qbk b/doc/v2/stdio.qbk new file mode 100644 index 000000000..09a3b6315 --- /dev/null +++ b/doc/v2/stdio.qbk @@ -0,0 +1,89 @@ +[section:stdio stdio] + +When using io with a subprocess, all three standard streams (stdin, stdout, stderr) get set for the child-process. +The default setting is to inherit the parent process. + +This feature meant to be flexible, which is why there is little checking on the arguments assigned to one of those streams. + +[section:pipe Pipe] + +asio pipes can be used for io. When using in process_stdio they will get +automatically connected and the other side will get assigned to the child process: + +``` + asio::io_context ctx; + asio::readable_pipe rp; + + process proc(ctx, "/usr/bin/g++", {"--version"}, process_stdio{{ /* in to default */}, rp, { /* err to default */ }}); + std::string output; + + system::error_code ec; + rp.read(asio::dynamic_buffer(output), ec); + assert(ec == asio::eof); + proc.wait(); +``` + +readable pipes can be assigned to `out` an `err``, while writable_pipes can be assigned to `in`. + +[endsect] + +[section:file `FILE*`] + +`FILE*` can also be used for either side; this allows the `stdin`, `stderr`, `stdout` macros to be used: + +``` + asio::io_context ctx; + // forward both stderr & stdout to stdout of the parent process + process proc(ctx, "/usr/bin/g++", {"--version"}, process_stdio{{ /* in to default */}, stdout, stdout}); + proc.wait(); +``` + +[endsect] + +[section:null `nullptr`] + +`nullptr` may be used to set a given stream to be opened on the null-device (`/dev/null` on posix, `NUL` on windows). +This is used to ignore output or give only EOF as input. + +``` + asio::io_context ctx; + // ignore stderr + process proc(ctx, "/usr/bin/g++", {"--version"}, process_stdio{{ /* in to default */}, {}, nullptr}); + proc.wait(); +``` + +[endsect] + + +[section:native_handle `native_handle`] + +A native handle can be used as well, which means an `int` on posix or a `HANDLE` on windows. +Furthermore, any object that has a `native_handle` returning that native handle type is valid, too. + + +``` + asio::io_context ctx; + // ignore stderr + asio::ip::tcp::socket sock{ctx}; + connect_my_socket(sock); + process proc(ctx, "~/not-a-virus", {}, process_stdio{sock, sock, nullptr}); + proc.wait(); +``` + +[endsect] + +[section:popen popen] + +Additionally, process v2 provides a `popen` class. +It starts a process and connects pipes for stdin and stdout, so that the popen object can be used as a stream. + +``` + popen proc(executor, "/usr/bin/addr2line, {argv[0]}); + asio::write(proc, asio::buffer("main\n")); + std::string line; + asio::read_until(proc, asio::dynamic_buffer(line), '\n'); +``` + +[endsect] + +[endsect] \ No newline at end of file diff --git a/example/io.cpp b/example/io.cpp index 1b1e8eb77..97ace20ad 100644 --- a/example/io.cpp +++ b/example/io.cpp @@ -22,7 +22,7 @@ int main() bp::std_in < bp::null //null in ); - boost::filesystem::path p = "input.txt"; + boost::process::filesystem::path p = "input.txt"; bp::system( "test.exe", diff --git a/example/start_dir.cpp b/example/start_dir.cpp index 896ef8cc8..fbeb19d52 100644 --- a/example/start_dir.cpp +++ b/example/start_dir.cpp @@ -8,7 +8,7 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) #include -#include +#include namespace bp = boost::process; @@ -19,9 +19,9 @@ int main() bp::start_dir="../foo" ); - boost::filesystem::path exe = "test.exe"; + boost::process::filesystem::path exe = "test.exe"; bp::system( - boost::filesystem::absolute(exe), + boost::process::filesystem::absolute(exe), bp::start_dir="../foo" ); } diff --git a/example/v2/Jamfile.jam b/example/v2/Jamfile.jam new file mode 100644 index 000000000..26da639a2 --- /dev/null +++ b/example/v2/Jamfile.jam @@ -0,0 +1,17 @@ +# Copyright (c) 2022 Klemens Morgenstern +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +project : requirements + ../../.. + msvc:_SCL_SECURE_NO_WARNINGS + windows:WIN32_LEAN_AND_MEAN +; + +import testing ; + +alias filesystem : /boost//filesystem : static ; + +exe intro : intro.cpp filesystem ; +exe intro_popen : intro_popen.cpp filesystem ; diff --git a/example/v2/intro.cpp b/example/v2/intro.cpp new file mode 100644 index 000000000..815792250 --- /dev/null +++ b/example/v2/intro.cpp @@ -0,0 +1,39 @@ +// Copyright (c) 2022Klemens Morgernstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +//[intro +#include + +#include +#include +#include + +#include +#include + +namespace proc = boost::process::v2; +namespace asio = boost::asio; + + +int main() +{ + asio::io_context ctx; + asio::readable_pipe p{ctx}; + + const auto exe = proc::environment::find_executable("gcc"); + + proc::process c{ctx, exe, {"--version"}, proc::process_stdio{nullptr, p}}; + + std::string line; + boost::system::error_code ec; + + auto sz = asio::read(p, asio::dynamic_buffer(line), ec); + assert(ec == asio::error::eof); + + std::cout << "Gcc version: '" << line << "'" << std::endl; + + c.wait(); +} +//] diff --git a/example/v2/intro_popen.cpp b/example/v2/intro_popen.cpp new file mode 100644 index 000000000..47e94bb96 --- /dev/null +++ b/example/v2/intro_popen.cpp @@ -0,0 +1,36 @@ +// Copyright (c) 2022Klemens Morgernstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +//[intro +#include + +#include +#include +#include + +#include +#include + +namespace proc = boost::process::v2; +namespace asio = boost::asio; + + +int main() +{ + asio::io_context ctx; + const auto exe = proc::environment::find_executable("gcc"); + proc::popen c{ctx, exe, {"--version"}}; + + std::string line; + boost::system::error_code ec; + + auto sz = asio::read(c, asio::dynamic_buffer(line), ec); + assert(ec == asio::error::eof); + + std::cout << "Gcc version: '" << line << "'" << std::endl; + + c.wait(); +} +//] diff --git a/example/windows.cpp b/example/windows.cpp index 98a838c8a..aba35853f 100644 --- a/example/windows.cpp +++ b/example/windows.cpp @@ -8,6 +8,7 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) #include +#include #include #include @@ -22,9 +23,9 @@ int main() bp::system("test.exe", - bp::on_setup([](auto &e) + bp::extend::on_setup([](auto &e) { e.startup_info.dwFlags = STARTF_RUNFULLSCREEN; }), - bp::on_error([](auto&, const std::error_code & ec) + bp::extend::on_error([](auto&, const std::error_code & ec) { std::cerr << ec.message() << std::endl; }) ); } diff --git a/include/boost/process/detail/basic_cmd.hpp b/include/boost/process/detail/basic_cmd.hpp index 3da51ffe1..f3405bbb1 100644 --- a/include/boost/process/detail/basic_cmd.hpp +++ b/include/boost/process/detail/basic_cmd.hpp @@ -168,7 +168,7 @@ struct exe_builder string_type exe; std::vector args; - void operator()(const boost::filesystem::path & data) + void operator()(const boost::process::filesystem::path & data) { not_cmd = true; if (exe.empty()) diff --git a/include/boost/process/detail/posix/basic_pipe.hpp b/include/boost/process/detail/posix/basic_pipe.hpp index f9d0a4545..965db6370 100644 --- a/include/boost/process/detail/posix/basic_pipe.hpp +++ b/include/boost/process/detail/posix/basic_pipe.hpp @@ -11,7 +11,7 @@ #define BOOST_PROCESS_POSIX_PIPE_HPP -#include +#include #include #include #include @@ -95,7 +95,7 @@ class basic_pipe int_type read(char_type * data, int_type count) { int_type read_len; - while ((read_len = ::read(_source, data, count * sizeof(char_type))) == -1) + while ((read_len = static_cast(::read(_source, data, count * sizeof(char_type)))) == -1) { //Try again if interrupted auto err = errno; diff --git a/include/boost/process/detail/posix/executor.hpp b/include/boost/process/detail/posix/executor.hpp index fb2efedef..8b9328c3e 100644 --- a/include/boost/process/detail/posix/executor.hpp +++ b/include/boost/process/detail/posix/executor.hpp @@ -26,6 +26,8 @@ #include #include +#include + namespace boost { namespace process { namespace detail { namespace posix { template @@ -149,14 +151,15 @@ class executor int _pipe_sink = -1; + void write_error(const std::error_code & ec, const char * msg) { //I am the child const auto len = std::strlen(msg); int data[2] = {ec.value(), static_cast(len + 1)}; - ::write(_pipe_sink, &data[0], sizeof(int) * 2); - ::write(_pipe_sink, msg, len); + boost::ignore_unused(::write(_pipe_sink, &data[0], sizeof(int) * 2)); + boost::ignore_unused(::write(_pipe_sink, msg, len)); } void internal_error_handle(const std::error_code &ec, const char* msg, boost::mpl::true_ , boost::mpl::false_, boost::mpl::false_) @@ -325,6 +328,13 @@ class executor } void set_error(const std::error_code &ec, const std::string &msg) {set_error(ec, msg.c_str());}; + std::vector get_used_handles() const + { + if (_pipe_sink == -1) + return {}; + else + return {_pipe_sink}; + }; }; template diff --git a/include/boost/process/detail/posix/file_descriptor.hpp b/include/boost/process/detail/posix/file_descriptor.hpp index 0dcb99caf..1d897b11d 100644 --- a/include/boost/process/detail/posix/file_descriptor.hpp +++ b/include/boost/process/detail/posix/file_descriptor.hpp @@ -8,7 +8,8 @@ #include #include -#include +#include +#include namespace boost { namespace process { namespace detail { namespace posix { @@ -23,7 +24,7 @@ struct file_descriptor file_descriptor() = default; - explicit file_descriptor(const boost::filesystem::path& p, mode_t mode = read_write) + explicit file_descriptor(const boost::process::filesystem::path& p, mode_t mode = read_write) : file_descriptor(p.native(), mode) { } @@ -39,10 +40,22 @@ struct file_descriptor } file_descriptor(const file_descriptor & ) = delete; - file_descriptor(file_descriptor && ) = default; + file_descriptor(file_descriptor &&other) + : _handle(boost::exchange(other._handle, -1)) + { + } file_descriptor& operator=(const file_descriptor & ) = delete; - file_descriptor& operator=(file_descriptor && ) = default; + file_descriptor& operator=(file_descriptor &&other) + { + if (this != &other) + { + if (_handle != -1) + ::close(_handle); + _handle = boost::exchange(other._handle, -1); + } + return *this; + } ~file_descriptor() { diff --git a/include/boost/process/detail/posix/handles.hpp b/include/boost/process/detail/posix/handles.hpp index 1572f0593..cd9e1ce5a 100644 --- a/include/boost/process/detail/posix/handles.hpp +++ b/include/boost/process/detail/posix/handles.hpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include namespace boost { namespace process { namespace detail { namespace posix { diff --git a/include/boost/process/detail/posix/search_path.hpp b/include/boost/process/detail/posix/search_path.hpp index ad781e57d..e06f4163e 100644 --- a/include/boost/process/detail/posix/search_path.hpp +++ b/include/boost/process/detail/posix/search_path.hpp @@ -11,7 +11,7 @@ #define BOOST_PROCESS_POSIX_SEARCH_PATH_HPP #include -#include +#include #include #include #include @@ -20,15 +20,15 @@ namespace boost { namespace process { namespace detail { namespace posix { -inline boost::filesystem::path search_path( - const boost::filesystem::path &filename, - const std::vector &path) +inline boost::process::filesystem::path search_path( + const boost::process::filesystem::path &filename, + const std::vector &path) { - for (const boost::filesystem::path & pp : path) + for (const boost::process::filesystem::path & pp : path) { auto p = pp / filename; boost::system::error_code ec; - bool file = boost::filesystem::is_regular_file(p, ec); + bool file = boost::process::filesystem::is_regular_file(p, ec); if (!ec && file && ::access(p.c_str(), X_OK) == 0) return p; } diff --git a/include/boost/process/detail/posix/shell_path.hpp b/include/boost/process/detail/posix/shell_path.hpp index 870cab6b9..c05d8ad92 100644 --- a/include/boost/process/detail/posix/shell_path.hpp +++ b/include/boost/process/detail/posix/shell_path.hpp @@ -12,16 +12,16 @@ #include #include -#include +#include namespace boost { namespace process { namespace detail { namespace posix { -inline boost::filesystem::path shell_path() +inline boost::process::filesystem::path shell_path() { return "/bin/sh"; } -inline boost::filesystem::path shell_path(std::error_code &ec) +inline boost::process::filesystem::path shell_path(std::error_code &ec) { ec.clear(); return "/bin/sh"; diff --git a/include/boost/process/detail/posix/start_dir.hpp b/include/boost/process/detail/posix/start_dir.hpp index ee4cb6bfb..5483bb73d 100644 --- a/include/boost/process/detail/posix/start_dir.hpp +++ b/include/boost/process/detail/posix/start_dir.hpp @@ -13,6 +13,7 @@ #include #include #include +#include namespace boost { namespace process { namespace detail { namespace posix { @@ -26,7 +27,7 @@ struct start_dir_init : handler_base_ext template void on_exec_setup(PosixExecutor&) const { - ::chdir(s_.c_str()); + boost::ignore_unused(::chdir(s_.c_str())); } const string_type & str() const {return s_;} private: diff --git a/include/boost/process/detail/traits/cmd_or_exe.hpp b/include/boost/process/detail/traits/cmd_or_exe.hpp index 1c627122d..66af5b32d 100644 --- a/include/boost/process/detail/traits/cmd_or_exe.hpp +++ b/include/boost/process/detail/traits/cmd_or_exe.hpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include namespace boost { namespace process { namespace detail { @@ -53,12 +53,12 @@ template<> struct initializer_tag> { type template<> struct initializer_tag { - typedef cmd_or_exe_tag type; + typedef cmd_or_exe_tag type; }; -template<> struct initializer_tag +template<> struct initializer_tag { - typedef cmd_or_exe_tag type; + typedef cmd_or_exe_tag type; }; template diff --git a/include/boost/process/detail/traits/wchar_t.hpp b/include/boost/process/detail/traits/wchar_t.hpp index f509dd06d..6e10cb14f 100644 --- a/include/boost/process/detail/traits/wchar_t.hpp +++ b/include/boost/process/detail/traits/wchar_t.hpp @@ -20,7 +20,7 @@ namespace boost { namespace process { namespace detail { template struct is_wchar_t : std::false_type {}; -template<> struct is_wchar_t : std::is_same +template<> struct is_wchar_t : std::is_same { }; diff --git a/include/boost/process/detail/used_handles.hpp b/include/boost/process/detail/used_handles.hpp index 4d56af357..8db226d43 100644 --- a/include/boost/process/detail/used_handles.hpp +++ b/include/boost/process/detail/used_handles.hpp @@ -69,7 +69,7 @@ template std::vector<::boost::process::detail::api::native_handle_type> get_used_handles(Executor &exec) { - std::vector<::boost::process::detail::api::native_handle_type> res; + std::vector<::boost::process::detail::api::native_handle_type> res = exec.get_used_handles(); foreach_used_handle(exec, [&](::boost::process::detail::api::native_handle_type handle){res.push_back(handle);}); return res; } diff --git a/include/boost/process/detail/windows/environment.hpp b/include/boost/process/detail/windows/environment.hpp index 13aa587da..53408ac7a 100644 --- a/include/boost/process/detail/windows/environment.hpp +++ b/include/boost/process/detail/windows/environment.hpp @@ -232,6 +232,8 @@ basic_environment_impl::basic_environment_impl(const native_environment_im template inline auto basic_environment_impl::get(const string_type &id) -> string_type { + if (id.size() >= _data.size()) //ok, so it's impossible id is in there. + return string_type(_data.data()); if (std::equal(id.begin(), id.end(), _data.begin()) && (_data[id.size()] == equal_sign())) return string_type(_data.data()); //null-char is handled by the string. diff --git a/include/boost/process/detail/windows/file_descriptor.hpp b/include/boost/process/detail/windows/file_descriptor.hpp index 187f6ebab..033df0288 100644 --- a/include/boost/process/detail/windows/file_descriptor.hpp +++ b/include/boost/process/detail/windows/file_descriptor.hpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include namespace boost { namespace process { namespace detail { namespace windows { @@ -40,7 +40,7 @@ struct file_descriptor } file_descriptor() = default; - file_descriptor(const boost::filesystem::path& p, mode_t mode = read_write) + file_descriptor(const boost::process::filesystem::path& p, mode_t mode = read_write) : file_descriptor(p.native(), mode) { } diff --git a/include/boost/process/detail/windows/handles.hpp b/include/boost/process/detail/windows/handles.hpp index 7a93ac254..bf06ce76a 100644 --- a/include/boost/process/detail/windows/handles.hpp +++ b/include/boost/process/detail/windows/handles.hpp @@ -11,6 +11,7 @@ #include #include #include +#include namespace boost { namespace process { namespace detail { diff --git a/include/boost/process/detail/windows/search_path.hpp b/include/boost/process/detail/windows/search_path.hpp index fe267bdf7..681722128 100644 --- a/include/boost/process/detail/windows/search_path.hpp +++ b/include/boost/process/detail/windows/search_path.hpp @@ -11,8 +11,7 @@ #define BOOST_PROCESS_WINDOWS_SEARCH_PATH_HPP #include -#include -#include +#include #include #include #include @@ -24,9 +23,9 @@ namespace boost { namespace process { namespace detail { namespace windows { -inline boost::filesystem::path search_path( - const boost::filesystem::path &filename, - const std::vector &path) +inline boost::process::filesystem::path search_path( + const boost::process::filesystem::path &filename, + const std::vector &path) { const ::boost::process::wnative_environment ne{}; typedef typename ::boost::process::wnative_environment::const_entry_type value_type; @@ -55,15 +54,15 @@ inline boost::filesystem::path search_path( for (auto & ext : extensions) boost::to_lower(ext); - for (const boost::filesystem::path & pp_ : path) + for (const boost::process::filesystem::path & pp_ : path) { auto p = pp_ / filename; - for (boost::filesystem::path ext : extensions) + for (boost::process::filesystem::path ext : extensions) { - boost::filesystem::path pp_ext = p; + boost::process::filesystem::path pp_ext = p; pp_ext += ext; boost::system::error_code ec; - bool file = boost::filesystem::is_regular_file(pp_ext, ec); + bool file = boost::process::filesystem::is_regular_file(pp_ext, ec); if (!ec && file && ::boost::winapi::sh_get_file_info(pp_ext.native().c_str(), 0, 0, 0, ::boost::winapi::SHGFI_EXETYPE_)) { diff --git a/include/boost/process/detail/windows/shell_path.hpp b/include/boost/process/detail/windows/shell_path.hpp index 263a41054..bb150c944 100644 --- a/include/boost/process/detail/windows/shell_path.hpp +++ b/include/boost/process/detail/windows/shell_path.hpp @@ -12,29 +12,29 @@ #include #include -#include +#include #include #include namespace boost { namespace process { namespace detail { namespace windows { -inline boost::filesystem::path shell_path() +inline boost::process::filesystem::path shell_path() { ::boost::winapi::WCHAR_ sysdir[260]; unsigned int size = ::boost::winapi::get_system_directory(sysdir, sizeof(sysdir)); if (!size) throw_last_error("GetSystemDirectory() failed"); - boost::filesystem::path p = sysdir; + boost::process::filesystem::path p = sysdir; return p / "cmd.exe"; } -inline boost::filesystem::path shell_path(std::error_code &ec) noexcept +inline boost::process::filesystem::path shell_path(std::error_code &ec) noexcept { ::boost::winapi::WCHAR_ sysdir[260]; unsigned int size = ::boost::winapi::get_system_directory(sysdir, sizeof(sysdir)); - boost::filesystem::path p; + boost::process::filesystem::path p; if (!size) ec = std::error_code( ::boost::winapi::GetLastError(), diff --git a/include/boost/process/env.hpp b/include/boost/process/env.hpp index 108028312..47258c281 100644 --- a/include/boost/process/env.hpp +++ b/include/boost/process/env.hpp @@ -6,6 +6,7 @@ #ifndef BOOST_PROCESS_DETAIL_ENV_HPP_ #define BOOST_PROCESS_DETAIL_ENV_HPP_ +#include #include #include diff --git a/include/boost/process/environment.hpp b/include/boost/process/environment.hpp index 205e1130a..3aa4c9160 100644 --- a/include/boost/process/environment.hpp +++ b/include/boost/process/environment.hpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include #if defined(BOOST_POSIX_API) #include @@ -263,7 +263,9 @@ class basic_environment_impl : public Implementation auto st1 = key + ::boost::process::detail::equal_sign(); while (*p != nullptr) { - if (std::equal(st1.begin(), st1.end(), *p)) + const int len = std::char_traits::length(*p); + if ((std::distance(st1.begin(), st1.end()) < len) + && std::equal(st1.begin(), st1.end(), *p)) break; p++; } @@ -275,7 +277,9 @@ class basic_environment_impl : public Implementation auto st1 = key + ::boost::process::detail::equal_sign(); while (*p != nullptr) { - if (std::equal(st1.begin(), st1.end(), *p)) + const int len = std::char_traits::length(*p); + if ((std::distance(st1.begin(), st1.end()) < len) + && std::equal(st1.begin(), st1.end(), *p)) break; p++; } @@ -288,7 +292,9 @@ class basic_environment_impl : public Implementation auto st1 = st + ::boost::process::detail::equal_sign(); while (*p != nullptr) { - if (std::equal(st1.begin(), st1.end(), *p)) + const int len = std::char_traits::length(*p); + if ((std::distance(st1.begin(), st1.end()) < len) + && std::equal(st1.begin(), st1.end(), *p)) return 1u; p++; } @@ -672,7 +678,7 @@ inline native_environment environment() { return ::boost::process:: native_env ///Get the enviroment of the current process. inline wnative_environment wenvironment() { return ::boost::process::wnative_environment(); } ///Get the path environment variable of the current process runs. -inline std::vector path() +inline std::vector path() { #if defined(BOOST_WINDOWS_API) const ::boost::process::wnative_environment ne{}; @@ -693,7 +699,7 @@ inline std::vector path() auto vec = itr->to_vector(); - std::vector val; + std::vector val; val.resize(vec.size()); std::copy(vec.begin(), vec.end(), val.begin()); diff --git a/include/boost/process/exe.hpp b/include/boost/process/exe.hpp index 8949984c0..c9358c0a7 100644 --- a/include/boost/process/exe.hpp +++ b/include/boost/process/exe.hpp @@ -36,15 +36,15 @@ namespace detail { struct exe_ { template - inline exe_setter_ operator()(const boost::filesystem::path & pth) const + inline exe_setter_ operator()(const boost::process::filesystem::path & pth) const { - return exe_setter_(pth.native()); + return exe_setter_(pth.native()); } template - inline exe_setter_ operator=(const boost::filesystem::path & pth) const + inline exe_setter_ operator=(const boost::process::filesystem::path & pth) const { - return exe_setter_(pth.native()); + return exe_setter_(pth.native()); } @@ -79,7 +79,7 @@ The overload form applies when to the first, when several strings are passed to function. The following expressions are valid, with `value` being either a C-String or -a `std::basic_string` with `char` or `wchar_t` or a `boost::filesystem::path`. +a `std::basic_string` with `char` or `wchar_t` or a `boost::process::filesystem::path`. \code{.cpp} exe="value"; diff --git a/include/boost/process/filesystem.hpp b/include/boost/process/filesystem.hpp new file mode 100644 index 000000000..4e1c12e84 --- /dev/null +++ b/include/boost/process/filesystem.hpp @@ -0,0 +1,28 @@ +// Copyright (c) 2021 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_FILESYSTEM_HPP +#define BOOST_PROCESS_FILESYSTEM_HPP + +#ifdef BOOST_PROCESS_USE_STD_FS +#include +#else +#include +#include +#endif + +namespace boost +{ +namespace process +{ +#ifdef BOOST_PROCESS_USE_STD_FS +namespace filesystem = std::filesystem; +#else +namespace filesystem = boost::filesystem; +#endif + +} +} + +#endif //BOOST_PROCESS_FILESYSTEM_HPP diff --git a/include/boost/process/io.hpp b/include/boost/process/io.hpp index c929e799f..8238de686 100644 --- a/include/boost/process/io.hpp +++ b/include/boost/process/io.hpp @@ -60,9 +60,9 @@ namespace boost { The library allows full redirection of streams to files as shown below. \code{.cpp} -boost::filesystem::path log = "my_log_file.txt"; -boost::filesystem::path input = "input.txt"; -boost::filesystem::path output = "output.txt"; +boost::process::filesystem::path log = "my_log_file.txt"; +boost::process::filesystem::path input = "input.txt"; +boost::process::filesystem::path output = "output.txt"; system("my_prog", std_out>output, std_inlog); \endcode @@ -152,13 +152,13 @@ struct std_in_ api::null_in operator=(const null_t &) const {return api::null_in();} api::null_in operator<(const null_t &) const {return api::null_in();} - api::file_in operator=(const boost::filesystem::path &p) const {return p;} + api::file_in operator=(const boost::process::filesystem::path &p) const {return p;} api::file_in operator=(const std::string & p) const {return p;} api::file_in operator=(const std::wstring &p) const {return p;} api::file_in operator=(const char * p) const {return p;} api::file_in operator=(const wchar_t * p) const {return p;} - api::file_in operator<(const boost::filesystem::path &p) const {return p;} + api::file_in operator<(const boost::process::filesystem::path &p) const {return p;} api::file_in operator<(const std::string &p) const {return p;} api::file_in operator<(const std::wstring &p) const {return p;} api::file_in operator<(const char*p) const {return p;} @@ -209,13 +209,13 @@ struct std_out_ api::null_out operator=(const null_t &) const {return api::null_out();} api::null_out operator>(const null_t &) const {return api::null_out();} - api::file_out operator=(const boost::filesystem::path &p) const {return api::file_out(p);} + api::file_out operator=(const boost::process::filesystem::path &p) const {return api::file_out(p);} api::file_out operator=(const std::string &p) const {return api::file_out(p);} api::file_out operator=(const std::wstring &p) const {return api::file_out(p);} api::file_out operator=(const char * p) const {return api::file_out(p);} api::file_out operator=(const wchar_t * p) const {return api::file_out(p);} - api::file_out operator>(const boost::filesystem::path &p) const {return api::file_out(p);} + api::file_out operator>(const boost::process::filesystem::path &p) const {return api::file_out(p);} api::file_out operator>(const std::string &p) const {return api::file_out(p);} api::file_out operator>(const std::wstring &p) const {return api::file_out(p);} api::file_out operator>(const char * p) const {return api::file_out(p);} @@ -282,7 +282,7 @@ This property allows to set the input stream for the child process. The file I/O simple redirects the stream to a file, for which the possible types are - - `boost::filesystem::path` + - `boost::process::filesystem::path` - `std::basic_string` - `const char_type*` - `FILE*` @@ -424,7 +424,7 @@ This property allows to set the output stream for the child process. The file I/O simple redirects the stream to a file, for which the possible types are - - `boost::filesystem::path` + - `boost::process::filesystem::path` - `std::basic_string` - `const char_type*` - `FILE*` diff --git a/include/boost/process/search_path.hpp b/include/boost/process/search_path.hpp index ee1f60525..7e33ec552 100644 --- a/include/boost/process/search_path.hpp +++ b/include/boost/process/search_path.hpp @@ -44,8 +44,8 @@ namespace boost { namespace process { * \returns the absolute path to the executable filename or an * empty string if filename isn't found */ -inline boost::filesystem::path search_path(const boost::filesystem::path &filename, - const std::vector path = ::boost::this_process::path()) +inline boost::process::filesystem::path search_path(const boost::process::filesystem::path &filename, + const std::vector path = ::boost::this_process::path()) { return ::boost::process::detail::api::search_path(filename, path); } diff --git a/include/boost/process/shell.hpp b/include/boost/process/shell.hpp index 64314a688..87313d53f 100644 --- a/include/boost/process/shell.hpp +++ b/include/boost/process/shell.hpp @@ -44,18 +44,18 @@ struct shell_ { constexpr shell_() {} - boost::filesystem::path operator()() const + boost::process::filesystem::path operator()() const { return boost::process::detail::api::shell_path(); } - boost::filesystem::path operator()(std::error_code & ec) const noexcept + boost::process::filesystem::path operator()(std::error_code & ec) const noexcept { return boost::process::detail::api::shell_path(ec); } }; template<> -struct is_wchar_t : is_wchar_t +struct is_wchar_t : is_wchar_t { }; diff --git a/include/boost/process/start_dir.hpp b/include/boost/process/start_dir.hpp index 406e78e41..49f4354a8 100644 --- a/include/boost/process/start_dir.hpp +++ b/include/boost/process/start_dir.hpp @@ -23,7 +23,7 @@ #include #include -#include +#include /** \file boost/process/start_dir.hpp * @@ -53,8 +53,8 @@ struct start_dir_ api::start_dir_init operator()(std::basic_string && s) const {return {std::move(s)}; } template api::start_dir_init operator()(const Char* s) const {return {s}; } - api::start_dir_init - operator()(const boost::filesystem::path & st) const {return {st.native()}; } + api::start_dir_init + operator()(const boost::process::filesystem::path & st) const {return {st.native()}; } template api::start_dir_init operator= (const std::basic_string & st) const {return {st}; } @@ -62,8 +62,8 @@ struct start_dir_ api::start_dir_init operator= (std::basic_string && s) const {return {std::move(s)}; } template api::start_dir_init operator= (const Char* s) const {return {s}; } - api::start_dir_init - operator= (const boost::filesystem::path & st) const {return {st.native()}; } + api::start_dir_init + operator= (const boost::process::filesystem::path & st) const {return {st.native()}; } }; @@ -100,7 +100,7 @@ start_dir=path start_dir(path) \endcode -It can be used with `std::string`, `std::wstring` and `boost::filesystem::path`. +It can be used with `std::string`, `std::wstring` and `boost::process::filesystem::path`. */ diff --git a/include/boost/process/v2.hpp b/include/boost/process/v2.hpp new file mode 100644 index 000000000..c3f54bd91 --- /dev/null +++ b/include/boost/process/v2.hpp @@ -0,0 +1,19 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_HPP +#define BOOST_PROCESS_V2_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif //BOOST_PROCESS_V2_HPP diff --git a/include/boost/process/v2/cstring_ref.hpp b/include/boost/process/v2/cstring_ref.hpp new file mode 100644 index 000000000..72c544487 --- /dev/null +++ b/include/boost/process/v2/cstring_ref.hpp @@ -0,0 +1,232 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_CSTRING_REF_HPP +#define BOOST_PROCESS_V2_CSTRING_REF_HPP + +#include + +#if defined(BOOST_PROCESS_V2_STANDALONE) +#include +#else +#include +#endif + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +namespace detail +{ + +BOOST_CONSTEXPR static const char* null_char_(char) {return "";} +BOOST_CONSTEXPR static const wchar_t* null_char_(wchar_t) {return L"";} +BOOST_CONSTEXPR static const char16_t* null_char_(char16_t) {return u"";} +BOOST_CONSTEXPR static const char32_t* null_char_(char32_t) {return U"";} + +#if defined(BOOST_PROCESS_V2_HAS_CHAR8_T) +BOOST_CONSTEXPR static const char8_t* null_char_(char8_t) {return u8"";} +#endif + +} + +#if defined(BOOST_PROCESS_V2_STANDALONE) +using std::basic_string_view; +using std:: string_view; +using std:: wstring_view; +#else +using boost::basic_string_view; +using boost:: string_view; +using boost:: wstring_view; +#endif + + +/// Small wrapper for a null-terminated string that can be directly passed to C APIS +/** This ref can only be modified by moving the front pointer. It does not store the + * size, but can detect values that can directly be passed to system APIs. + * + * It can be constructed from a `char*` pointer or any class that has a `c_str()` + * member function, e.g. std::string or boost::static_string. + * + */ +template> +struct basic_cstring_ref +{ + using value_type = CharT; + using traits_type = Traits; + + BOOST_CONSTEXPR basic_cstring_ref() noexcept : view_(detail::null_char_(value_type{})) {} + BOOST_CONSTEXPR basic_cstring_ref(std::nullptr_t) = delete; + + BOOST_CONSTEXPR basic_cstring_ref( const value_type* s ) : view_(s) {} + + template().c_str())>::type + >::value>::type> + BOOST_CONSTEXPR basic_cstring_ref(Source && src) : view_(src.c_str()) {} + + BOOST_CONSTEXPR typename basic_string_view::const_pointer c_str() const BOOST_NOEXCEPT + { + return this->data(); + } + + using string_view_type = basic_string_view; + constexpr operator string_view_type() const {return view_;} + + using pointer = CharT *; + using const_pointer = const CharT *; + using reference = CharT &; + using const_reference = const CharT &; + using const_iterator = const_pointer; + using iterator = const_iterator; + using const_reverse_iterator = typename std::reverse_iterator; + using reverse_iterator = typename std::reverse_iterator; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + static BOOST_CONSTEXPR size_type npos = -1; + + BOOST_CONSTEXPR const_iterator begin() const BOOST_NOEXCEPT {return view_;}; + BOOST_CONSTEXPR const_iterator end() const BOOST_NOEXCEPT {return view_ + length();}; + BOOST_CONSTEXPR const_iterator cbegin() const BOOST_NOEXCEPT {return view_;}; + BOOST_CONSTEXPR const_iterator cend() const BOOST_NOEXCEPT {return view_ + length();}; + BOOST_CONSTEXPR const_reverse_iterator rbegin() const BOOST_NOEXCEPT {return reverse_iterator(view_ + length());}; + BOOST_CONSTEXPR const_reverse_iterator rend() const BOOST_NOEXCEPT {return reverse_iterator(view_);}; + BOOST_CONSTEXPR const_reverse_iterator crbegin() const BOOST_NOEXCEPT {return reverse_iterator(view_ + length());}; + BOOST_CONSTEXPR const_reverse_iterator crend() const BOOST_NOEXCEPT {return reverse_iterator(view_);}; + + BOOST_CONSTEXPR size_type size() const BOOST_NOEXCEPT {return length(); } + BOOST_CONSTEXPR size_type length() const BOOST_NOEXCEPT {return traits_type::length(view_); } + BOOST_CONSTEXPR size_type max_size() const BOOST_NOEXCEPT {return (std::numeric_limits::max)() / sizeof(CharT); } + BOOST_ATTRIBUTE_NODISCARD BOOST_CONSTEXPR bool empty() const BOOST_NOEXCEPT {return *view_ == *detail::null_char_(CharT{}); } + + BOOST_CONSTEXPR const_reference operator[](size_type pos) const {return view_[pos] ;} + BOOST_CXX14_CONSTEXPR const_reference at(size_type pos) const + { + if (pos >= size()) + throw std::out_of_range("cstring-view out of range"); + return view_[pos]; + } + BOOST_CONSTEXPR const_reference front() const {return *view_;} + BOOST_CONSTEXPR const_reference back() const {return view_[length() - 1];} + BOOST_CONSTEXPR const_pointer data() const BOOST_NOEXCEPT {return view_;} + BOOST_CXX14_CONSTEXPR void remove_prefix(size_type n) {view_ = view_ + n;} + void swap(basic_cstring_ref& s) BOOST_NOEXCEPT {std::swap(view_, s.view_);} + + size_type copy(value_type* s, size_type n, size_type pos = 0) const + { + return traits_type::copy(s, view_ + pos, n) - view_; + } + BOOST_CONSTEXPR basic_cstring_ref substr(size_type pos = 0) const + { + return basic_cstring_ref(view_ + pos); + } + + BOOST_CONSTEXPR string_view_type substr(size_type pos, size_type length) const + { + return string_view_type(view_).substr(pos, length); + } + + BOOST_CXX14_CONSTEXPR int compare(basic_cstring_ref x) const BOOST_NOEXCEPT + { + auto idx = 0u; + for (; view_[idx] != null_char_()[0] && x[idx] != null_char_()[0]; idx++) + if (!traits_type::eq(view_[idx], x[idx])) + return traits_type::lt(view_[idx], x[idx]) ? -1 : 1; + + return traits_type::to_int_type(view_[idx]) - + traits_type::to_int_type(x[idx]); // will compare to null char of either. + } + + BOOST_CXX14_CONSTEXPR bool starts_with(string_view_type x) const BOOST_NOEXCEPT + { + if (x.empty()) + return true; + + auto idx = 0u; + for (; view_[idx] != null_char_()[0] && idx < x.size(); idx++) + if (!traits_type::eq(view_[idx], x[idx])) + return false; + + return idx == x.size() || view_[idx] != null_char_()[0]; + } + BOOST_CONSTEXPR bool starts_with(value_type x) const BOOST_NOEXCEPT + { + return traits_type::eq(view_[0], x); + } + + BOOST_CXX14_CONSTEXPR size_type find( CharT ch, size_type pos = 0 ) const BOOST_NOEXCEPT + { + for (auto p = view_ + pos; *p != *null_char_(); p++) + if (traits_type::eq(*p, ch)) + return p - view_; + return npos; + } + + + friend BOOST_CXX14_CONSTEXPR bool operator==(basic_cstring_ref x, basic_cstring_ref y) BOOST_NOEXCEPT + { + std::size_t idx = 0u; + for (idx = 0u; x[idx] != null_char_()[0] && y[idx] != null_char_()[0]; idx++) + if (!traits_type::eq(x[idx], y[idx])) + return false; + return x[idx] == y[idx]; + } + friend BOOST_CXX14_CONSTEXPR bool operator!=(basic_cstring_ref x, basic_cstring_ref y) BOOST_NOEXCEPT + { + std::size_t idx = 0u; + for (idx = 0u; x[idx] != null_char_()[0] && + y[idx] != null_char_()[0]; idx++) + if (!traits_type::eq(x[idx], y[idx])) + return true; + return x[idx] != y[idx]; + } + friend BOOST_CXX14_CONSTEXPR bool operator< (basic_cstring_ref x, basic_cstring_ref y) BOOST_NOEXCEPT {return x.compare(y) < 0;} + friend BOOST_CXX14_CONSTEXPR bool operator> (basic_cstring_ref x, basic_cstring_ref y) BOOST_NOEXCEPT {return x.compare(y) > 0;} + friend BOOST_CXX14_CONSTEXPR bool operator<=(basic_cstring_ref x, basic_cstring_ref y) BOOST_NOEXCEPT {return x.compare(y) <= 0;} + friend BOOST_CXX14_CONSTEXPR bool operator>=(basic_cstring_ref x, basic_cstring_ref y) BOOST_NOEXCEPT {return x.compare(y) >= 0;} + + // modifiers + void clear() BOOST_NOEXCEPT { view_ = null_char_(); } // Boost extension + + std::basic_string to_string() const { + return std::basic_string(begin(), end()); + } + + template + std::basic_string to_string(const Allocator& a) const { + return std::basic_string(begin(), end(), a); + } + + private: + BOOST_CONSTEXPR static const_pointer null_char_() {return detail::null_char_(CharT{});} + const_pointer view_; +}; + +template +inline std::basic_ostream& +operator<<(std::basic_ostream& os, + const basic_cstring_ref& str) +{ + return os << static_cast>(str); +} + +template +std::size_t hash_value(basic_string_view s) { + return boost::hash_range(s.begin(), s.end()); +} + +using cstring_ref = basic_cstring_ref; +using wcstring_ref = basic_cstring_ref; +using u16cstring_ref = basic_cstring_ref; +using u32cstring_ref = basic_cstring_ref; + +#if defined(BOOST_PROCESS_V2_HAS_CHAR8_T) +using u8cstring_ref = basic_cstring_ref; +#endif + +BOOST_PROCESS_V2_END_NAMESPACE + +#endif //BOOST_PROCESS_V2_CSTRING_REF_HPP diff --git a/include/boost/process/v2/default_launcher.hpp b/include/boost/process/v2/default_launcher.hpp new file mode 100644 index 000000000..42a26b724 --- /dev/null +++ b/include/boost/process/v2/default_launcher.hpp @@ -0,0 +1,66 @@ +// +// boost/process/v2/default_launcher.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2022 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_PROCESS_V2_DEFAULT_LAUNCHER_HPP +#define BOOST_PROCESS_V2_DEFAULT_LAUNCHER_HPP + +#include + +#if defined(BOOST_PROCESS_V2_WINDOWS) +#include +#else +#if defined(BOOST_PROCESS_V2_PDFORK) +#include +#else +#include +#endif + +#endif + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +#if defined(GENERATING_DOCUMENTATION) + +/// The default launcher for processes. +/** This launcher will be used by process if a + * process is launched through the constructor: + * + * @code {.cpp} + * process proc("test", {}); + * // equivalent to + * process prod = default_launcher()("test", {}); + * @endcode + * + */ + +typedef implementation_defined default_process_launcher; + +#else +#if defined(BOOST_PROCESS_V2_WINDOWS) +typedef windows::default_launcher default_process_launcher; +#else +#if defined(BOOST_PROCESS_V2_PDFORK) +typedef posix::pdfork_launcher default_process_launcher; +#else +typedef posix::default_launcher default_process_launcher; +#endif +#endif + + +#endif + + +BOOST_PROCESS_V2_END_NAMESPACE + +#if defined(BOOST_PROCESS_V2_HEADER_ONLY) +#include +#endif + +#endif //BOOST_PROCESS_V2_DEFAULT_LAUNCHER_HPP \ No newline at end of file diff --git a/include/boost/process/v2/detail/config.hpp b/include/boost/process/v2/detail/config.hpp new file mode 100644 index 000000000..7e4fe58f4 --- /dev/null +++ b/include/boost/process/v2/detail/config.hpp @@ -0,0 +1,155 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_DETAIL_CONFIG_HPP +#define BOOST_PROCESS_V2_DETAIL_CONFIG_HPP + +#if defined(BOOST_PROCESS_V2_STANDALONE) + +#define BOOST_PROCESS_V2_ASIO_NAMESPACE ::asio +#define BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(Sig) ASIO_COMPLETION_TOKEN_FOR(Sig) +#define BOOST_PROCESS_V2_DEFAULT_COMPLETION_TOKEN_TYPE(Executor) ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(Executor) +#define BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(Token, Signature) ASIO_INITFN_AUTO_RESULT_TYPE(Token, Signature) +#define BOOST_PROCESS_V2_DEFAULT_COMPLETION_TOKEN(Executor) ASIO_DEFAULT_COMPLETION_TOKEN(Executor) + + +#include +#include +#include +#include +#include + +#if defined(ASIO_WINDOWS) +#define BOOST_PROCESS_V2_WINDOWS 1 + +// Windows: suppress definition of "min" and "max" macros. +#if !defined(NOMINMAX) +# define NOMINMAX 1 +#endif +#endif + +#if defined(ASIO_HAS_UNISTD_H) +#define BOOST_PROCESS_V2_POSIX 1 +#endif + +#define BOOST_PROCESS_V2_BEGIN_NAMESPACE namespace process_v2 { +#define BOOST_PROCESS_V2_END_NAMESPACE } +#define BOOST_PROCESS_V2_NAMESPACE process_v2 + +#else + +#define BOOST_PROCESS_V2_ASIO_NAMESPACE ::boost::asio +#define BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(Sig) BOOST_ASIO_COMPLETION_TOKEN_FOR(Sig) +#define BOOST_PROCESS_V2_DEFAULT_COMPLETION_TOKEN_TYPE(Executor) BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(Executor) +#define BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(Token, Signature) BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(Token, Signature) +#define BOOST_PROCESS_V2_DEFAULT_COMPLETION_TOKEN(Executor) BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(Executor) + + +#include +#include +#include +#include +#include + +#if defined(BOOST_WINDOWS_API) +#define BOOST_PROCESS_V2_WINDOWS 1 + +// Windows: suppress definition of "min" and "max" macros. +#if !defined(NOMINMAX) +# define NOMINMAX 1 +#endif + +#endif + +#if defined(BOOST_POSIX_API) +#define BOOST_PROCESS_V2_POSIX 1 +#endif + +#if !defined(BOOST_PROCESS_V2_WINDOWS) && !defined(BOOST_POSIX_API) +#error Unsupported operating system +#endif + +#if defined(BOOST_PROCESS_USE_STD_FS) +#include +#include +#else +#include +#include +#include +#endif + +#define BOOST_PROCESS_V2_BEGIN_NAMESPACE namespace boost { namespace process { namespace v2 { +#define BOOST_PROCESS_V2_END_NAMESPACE } } } +#define BOOST_PROCESS_V2_NAMESPACE boost::process::v2 + +#endif + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +#if defined(BOOST_PROCESS_STANDALONE) + +using std::error_code ; +using std::error_category ; +using std::system_category ; +using std::system_error ; +namespace filesystem = std::filesystem; +using std::quoted; +using std::optional; + +#else + +using boost::system::error_code ; +using boost::system::error_category ; +using boost::system::system_category ; +using boost::system::system_error ; +using boost::io::quoted; +using boost::optional; + +#ifdef BOOST_PROCESS_USE_STD_FS +namespace filesystem = std::filesystem; +#else +namespace filesystem = boost::filesystem; +#endif + +#endif + +BOOST_PROCESS_V2_END_NAMESPACE + +#ifndef BOOST_PROCESS_V2_HEADER_ONLY +# ifndef BOOST_PROCESS_V2_SEPARATE_COMPILATION +# define BOOST_PROCESS_V2_HEADER_ONLY 1 +# endif +#endif + +#if BOOST_PROCESS_V2_DOXYGEN +# define BOOST_PROCESS_V2_DECL +#elif defined(BOOST_PROCESS_V2_HEADER_ONLY) +# define BOOST_PROCESS_V2_DECL inline +#else +# define BOOST_PROCESS_V2_DECL +#endif + +#if defined(BOOST_PROCESS_V2_POSIX) + +#if defined(__linux__) && !defined(BOOST_PROCESS_V2_DISABLE_PIDFD_OPEN) + +#include + +#if defined(SYS_pidfd_open) +#define BOOST_PROCESS_V2_PIDFD_OPEN 1 +#define BOOST_PROCESS_V2_HAS_PROCESS_HANDLE 1 +#endif +#endif + +#if defined(__FreeBSD__) && !defined(BOOST_PROCESS_V2_DISABLE_PDFORK) +#define BOOST_PROCESS_V2_PDFORK 1 +#define BOOST_PROCESS_V2_HAS_PROCESS_HANDLE 1 +#endif +#else +#define BOOST_PROCESS_V2_HAS_PROCESS_HANDLE 1 +#endif + + + +#endif //BOOST_PROCESS_V2_DETAIL_CONFIG_HPP diff --git a/include/boost/process/v2/detail/environment_posix.hpp b/include/boost/process/v2/detail/environment_posix.hpp new file mode 100644 index 000000000..237a87395 --- /dev/null +++ b/include/boost/process/v2/detail/environment_posix.hpp @@ -0,0 +1,80 @@ +// +// process/environment/detail/environment_posix.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2021 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_PROCESS_V2_DETAIL_ENVIRONMENT_POSIX_HPP +#define BOOST_PROCESS_V2_DETAIL_ENVIRONMENT_POSIX_HPP + +#include +#include + +#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__APPLE__) || defined(__MACH__) +extern "C" { extern char **environ; } +#endif + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +namespace environment +{ + +using char_type = char; + +template +using key_char_traits = std::char_traits; + +template +using value_char_traits = std::char_traits; + + +constexpr char_type equality_sign = '='; +constexpr char_type delimiter = ':'; + +namespace detail +{ + +BOOST_PROCESS_V2_DECL +basic_cstring_ref> +get(basic_cstring_ref> key, error_code & ec); + +BOOST_PROCESS_V2_DECL +void set(basic_cstring_ref> key, + basic_cstring_ref> value, + error_code & ec); + +BOOST_PROCESS_V2_DECL +void unset(basic_cstring_ref> key, + error_code & ec); +} + + +using native_handle_type = const char * const *; +using native_iterator = native_handle_type; + +namespace detail +{ + +BOOST_PROCESS_V2_DECL native_handle_type load_native_handle(); +struct native_handle_deleter +{ + void operator()(native_handle_type) const {} +}; + +BOOST_PROCESS_V2_DECL native_iterator next(native_handle_type nh); +BOOST_PROCESS_V2_DECL native_iterator find_end(native_handle_type nh); +inline const char_type * dereference(native_iterator iterator) {return *iterator;} + +BOOST_PROCESS_V2_DECL bool is_executable(const filesystem::path & pth, error_code & ec); + +} + +} + +BOOST_PROCESS_V2_END_NAMESPACE + +#endif \ No newline at end of file diff --git a/include/boost/process/v2/detail/environment_win.hpp b/include/boost/process/v2/detail/environment_win.hpp new file mode 100644 index 000000000..3ddd419e8 --- /dev/null +++ b/include/boost/process/v2/detail/environment_win.hpp @@ -0,0 +1,212 @@ +// +// process/environment/detail/environment_win.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2021 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_PROCESS_V2_DETAIL_ENVIRONMENT_WIN_HPP +#define BOOST_PROCESS_V2_DETAIL_ENVIRONMENT_WIN_HPP + +#include +#include + +#include + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +namespace environment +{ + +using char_type = wchar_t; + +template +struct key_char_traits +{ + typedef Char char_type; + typedef typename std::char_traits::int_type int_type; + typedef typename std::char_traits::off_type off_type; + typedef typename std::char_traits::pos_type pos_type; + typedef typename std::char_traits::state_type state_type; + + BOOST_CONSTEXPR static char to_lower(char c) {return std::tolower(to_int_type(c));} + BOOST_CONSTEXPR static wchar_t to_lower(wchar_t c) {return std::towlower(to_int_type(c));} + + BOOST_CONSTEXPR static int_type to_lower(int_type i, char ) {return std::tolower(i);} + BOOST_CONSTEXPR static int_type to_lower(int_type i, wchar_t) {return std::towlower(i);} + + + BOOST_CONSTEXPR static + void assign(char_type& c1, const char_type& c2) BOOST_NOEXCEPT + { + c1 = c2; + } + + BOOST_CONSTEXPR static + bool eq(char_type c1, char_type c2) BOOST_NOEXCEPT + { + return to_lower(c1) == to_lower(c2); + } + + BOOST_CONSTEXPR static + bool lt(char_type c1, char_type c2) BOOST_NOEXCEPT + { + return to_lower(c1) < to_lower(c2); + } + + BOOST_CONSTEXPR static + int compare(const char_type* s1, const char_type* s2, size_t n) BOOST_NOEXCEPT + { + auto itrs = std::mismatch(s1, s1 + n, s2, &eq); + if (itrs.first == (s1 + n)) + return 0; + auto c1 = to_lower(*itrs.first); + auto c2 = to_lower(*itrs.second); + + return (c1 < c2 ) ? -1 : 1; + } + + BOOST_CONSTEXPR static size_t length(const char* s) BOOST_NOEXCEPT { return std::strlen(s); } + BOOST_CONSTEXPR static size_t length(const wchar_t* s) BOOST_NOEXCEPT { return std::wcslen(s); } + + BOOST_CONSTEXPR static + const char_type* find(const char_type* s, size_t n, const char_type& a) BOOST_NOEXCEPT + { + const char_type u = to_lower(a); + return std::find_if(s, s + n, [u](char_type c){return to_lower(c) == u;}); + } + + BOOST_CONSTEXPR static + char_type* move(char_type* s1, const char_type* s2, size_t n) BOOST_NOEXCEPT + { + if (s1 < s2) + return std::move(s2, s2 + n, s1); + else + return std::move_backward(s2, s2 + n, s1); + } + + BOOST_CONSTEXPR static + char_type* copy(char_type* s1, const char_type* s2, size_t n) BOOST_NOEXCEPT + { + return std::copy(s2, s2 + n, s1); + } + + BOOST_CONSTEXPR static + char_type* assign(char_type* s, size_t n, char_type a) BOOST_NOEXCEPT + { + std::fill(s, s + n, a); + return s +n; + } + + BOOST_CONSTEXPR static + int_type not_eof(int_type c) BOOST_NOEXCEPT + { + return eq_int_type(c, eof()) ? ~eof() : c; + } + + BOOST_CONSTEXPR static + char_type to_char_type(int_type c) BOOST_NOEXCEPT + { + return char_type(c); + } + + BOOST_CONSTEXPR static + int_type to_int_type(char c) BOOST_NOEXCEPT + { + return int_type((unsigned char)c); + } + + BOOST_CONSTEXPR static + int_type to_int_type(wchar_t c) BOOST_NOEXCEPT + { + return int_type((wchar_t)c); + } + + BOOST_CONSTEXPR static + bool eq_int_type(int_type c1, int_type c2) BOOST_NOEXCEPT + { + return to_lower(c1, char_type()) == to_lower(c2, char_type()); + } + + BOOST_CONSTEXPR static inline int_type eof() BOOST_NOEXCEPT + { + return int_type(EOF); + } +}; + +namespace detail +{ + + +template +std::size_t hash_step(std::size_t prev, Char c, key_char_traits) +{ + return prev ^ (key_char_traits::to_lower(c) << 1); +} + + +} + +template +using value_char_traits = std::char_traits; + +BOOST_CONSTEXPR static char_type equality_sign = L'='; +BOOST_CONSTEXPR static char_type delimiter = L';'; + +using native_handle_type = wchar_t*; +using native_iterator = const wchar_t*; + +namespace detail +{ + +BOOST_PROCESS_V2_DECL +std::basic_string> get( + basic_cstring_ref> key, + error_code & ec); + +BOOST_PROCESS_V2_DECL +void set(basic_cstring_ref> key, + basic_cstring_ref> value, + error_code & ec); + +BOOST_PROCESS_V2_DECL +void unset(basic_cstring_ref> key, + error_code & ec); + + +BOOST_PROCESS_V2_DECL +std::basic_string> get( + basic_cstring_ref> key, + error_code & ec); + +BOOST_PROCESS_V2_DECL +void set(basic_cstring_ref> key, + basic_cstring_ref> value, + error_code & ec); + +BOOST_PROCESS_V2_DECL +void unset(basic_cstring_ref> key, + error_code & ec); + +BOOST_PROCESS_V2_DECL native_handle_type load_native_handle(); +struct native_handle_deleter +{ + native_handle_deleter() = default; + native_handle_deleter(const native_handle_deleter& ) = default; + BOOST_PROCESS_V2_DECL void operator()(native_handle_type nh) const; + +}; + +inline const char_type * dereference(native_iterator iterator) {return iterator;} +BOOST_PROCESS_V2_DECL native_iterator next(native_iterator nh); +BOOST_PROCESS_V2_DECL native_iterator find_end(native_handle_type nh); +BOOST_PROCESS_V2_DECL bool is_executable(const filesystem::path & pth, error_code & ec); +} + +} +BOOST_PROCESS_V2_END_NAMESPACE + +#endif //BOOST_PROCESS_V2_DETAIL_ENVIRONMENT_WIN_HPP diff --git a/include/boost/process/v2/detail/impl/environment.ipp b/include/boost/process/v2/detail/impl/environment.ipp new file mode 100644 index 000000000..3dc1b6615 --- /dev/null +++ b/include/boost/process/v2/detail/impl/environment.ipp @@ -0,0 +1,18 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_DETAIL_IMPL_ENVIRONMENT_IPP +#define BOOST_PROCESS_V2_DETAIL_IMPL_ENVIRONMENT_IPP + +#include + +#if defined(BOOST_PROCESS_V2_WINDOWS) +#include +#elif defined(BOOST_PROCESS_V2_POSIX) +#include +#else +#error Operating System not supported. +#endif + +#endif //BOOST_PROCESS_V2_DETAIL_IMPL_ENVIRONMENT_IPP diff --git a/include/boost/process/v2/detail/impl/environment_posix.ipp b/include/boost/process/v2/detail/impl/environment_posix.ipp new file mode 100644 index 000000000..7d9ad564a --- /dev/null +++ b/include/boost/process/v2/detail/impl/environment_posix.ipp @@ -0,0 +1,81 @@ +// +// process/this_process/detail/environment_posix.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2021 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_PROCESS_V2_DETAIL_ENVIRONMENT_WIN_HPP +#define BOOST_PROCESS_V2_DETAIL_ENVIRONMENT_WIN_HPP + +#include +#include +#include +#include + +#include +#include + + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +namespace environment +{ +namespace detail +{ + +basic_cstring_ref> get( + basic_cstring_ref> key, + error_code & ec) +{ + auto res = ::getenv(key.c_str()); + if (res == nullptr) + { + ec.assign(ENOENT, system_category()); + return {}; + } + return res; +} + +void set(basic_cstring_ref> key, + basic_cstring_ref> value, + error_code & ec) +{ + if (::setenv(key.c_str(), value.c_str(), true)) + ec = ::BOOST_PROCESS_V2_NAMESPACE::detail::get_last_error(); +} + +void unset(basic_cstring_ref> key, error_code & ec) +{ + if (::unsetenv(key.c_str())) + ec = ::BOOST_PROCESS_V2_NAMESPACE::detail::get_last_error(); +} + + +native_handle_type load_native_handle() { return ::environ; } + + +native_iterator next(native_iterator nh) +{ + return nh + 1; +} + +native_iterator find_end(native_handle_type nh) +{ + while (*nh != nullptr) + nh++; + return nh; +} +bool is_executable(const filesystem::path & p, error_code & ec) +{ + return filesystem::is_regular_file(p, ec) && (::access(p.c_str(), X_OK) == 0); +} + +} +} +BOOST_PROCESS_V2_END_NAMESPACE + +#endif //BOOST_PROCESS_V2_DETAIL_ENVIRONMENT_WIN_HPP diff --git a/include/boost/process/v2/detail/impl/environment_win.ipp b/include/boost/process/v2/detail/impl/environment_win.ipp new file mode 100644 index 000000000..37e72dc26 --- /dev/null +++ b/include/boost/process/v2/detail/impl/environment_win.ipp @@ -0,0 +1,142 @@ +// +// process/this_process/detail/environment_win.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2021 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_PROCESS_V2_DETAIL_IMPL_ENVIRONMENT_WIN_IPP +#define BOOST_PROCESS_V2_DETAIL_IMPL_ENVIRONMENT_WIN_IPP + + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +namespace environment +{ +namespace detail +{ + +std::basic_string> get( + basic_cstring_ref> key, + error_code & ec) +{ + std::basic_string> buf; + + std::size_t size = 0u; + do + { + buf.resize(buf.size() + 4096); + size = ::GetEnvironmentVariableW(key.c_str(), &buf.front(), static_cast(buf.size())); + } + while (size == buf.size()); + + buf.resize(size); + + if (buf.size() == 0) + ec = ::BOOST_PROCESS_V2_NAMESPACE::detail::get_last_error(); + + return buf; +} + +void set(basic_cstring_ref> key, + basic_cstring_ref> value, + error_code & ec) +{ + if (!::SetEnvironmentVariableW(key.c_str(), value.c_str())) + ec = ::BOOST_PROCESS_V2_NAMESPACE::detail::get_last_error(); +} + +void unset(basic_cstring_ref> key, + error_code & ec) +{ + if (!::SetEnvironmentVariableW(key.c_str(), nullptr)) + ec = ::BOOST_PROCESS_V2_NAMESPACE::detail::get_last_error(); +} + + +std::basic_string> get( + basic_cstring_ref> key, + error_code & ec) +{ + std::basic_string> buf; + + std::size_t size = 0u; + do + { + buf.resize(buf.size() + 4096); + size = ::GetEnvironmentVariableA(key.c_str(), &buf.front(), static_cast(buf.size())); + } + while (size == buf.size()); + + buf.resize(size); + + if (buf.size() == 0) + ec = ::BOOST_PROCESS_V2_NAMESPACE::detail::get_last_error(); + + return buf; +} + +void set(basic_cstring_ref> key, + basic_cstring_ref> value, + error_code & ec) +{ + if (!::SetEnvironmentVariableA(key.c_str(), value.c_str())) + ec = ::BOOST_PROCESS_V2_NAMESPACE::detail::get_last_error(); +} + +void unset(basic_cstring_ref> key, + error_code & ec) +{ + if (!::SetEnvironmentVariableA(key.c_str(), nullptr)) + ec = ::BOOST_PROCESS_V2_NAMESPACE::detail::get_last_error(); +} + + +native_handle_type load_native_handle() { return ::GetEnvironmentStringsW(); } +void native_handle_deleter::operator()(native_handle_type nh) const +{ + ::FreeEnvironmentStringsW(nh); +} + +native_iterator next(native_iterator nh) +{ + while (*nh != L'\0') + nh++; + return ++nh; +} + + +native_iterator find_end(native_handle_type nh) +{ + while ((*nh != L'\0') || (*std::next(nh) != L'\0')) + nh++; + return ++nh; +} + +bool is_executable(const filesystem::path & pth, error_code & ec) +{ + return filesystem::is_regular_file(pth, ec) && SHGetFileInfoW(pth.native().c_str(), 0,0,0, SHGFI_EXETYPE); +} + +} +} +BOOST_PROCESS_V2_END_NAMESPACE + +#endif //BOOST_PROCESS_V2_DETAIL_IMPL_ENVIRONMENT_WIN_IPP diff --git a/include/boost/process/v2/detail/impl/last_error.ipp b/include/boost/process/v2/detail/impl/last_error.ipp new file mode 100644 index 000000000..f9c294c25 --- /dev/null +++ b/include/boost/process/v2/detail/impl/last_error.ipp @@ -0,0 +1,48 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_DETAIL_IMPL_LAST_ERROR_IPP +#define BOOST_PROCESS_V2_DETAIL_IMPL_LAST_ERROR_IPP + +#include +#include + +#if defined(BOOST_PROCESS_V2_WINDOWS) +#include +#else +#include +#endif + +BOOST_PROCESS_V2_BEGIN_NAMESPACE +namespace detail +{ + +error_code get_last_error() +{ +#if defined(BOOST_PROCESS_V2_WINDOWS) + return error_code(::GetLastError(), system_category()); +#else + return error_code(errno, system_category()); +#endif + +} + +void throw_last_error() +{ + throw system_error(get_last_error()); +} +void throw_last_error(const char * msg) +{ + throw system_error(get_last_error(), msg); +} +void throw_last_error(const std::string & msg) +{ + throw system_error(get_last_error(), msg); +} + + +} +BOOST_PROCESS_V2_END_NAMESPACE + +#endif //BOOST_PROCESS_V2_DETAIL_IMPL_LAST_ERROR_IPP \ No newline at end of file diff --git a/include/boost/process/v2/detail/impl/process_handle_windows.ipp b/include/boost/process/v2/detail/impl/process_handle_windows.ipp new file mode 100644 index 000000000..bbc324bb8 --- /dev/null +++ b/include/boost/process/v2/detail/impl/process_handle_windows.ipp @@ -0,0 +1,126 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_DETAIL_IMPL_PROCESS_HANDLE_WINDOWS_IPP +#define BOOST_PROCESS_V2_DETAIL_IMPL_PROCESS_HANDLE_WINDOWS_IPP + +#include +#include +#include +#include + +#include + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +namespace detail +{ + +void get_exit_code_( + HANDLE handle, + native_exit_code_type & exit_code, + error_code & ec) +{ + if (!::GetExitCodeProcess(handle, &exit_code)) + ec = detail::get_last_error(); +} + + +HANDLE open_process_(DWORD pid) +{ + auto proc = OpenProcess(PROCESS_TERMINATE | SYNCHRONIZE, FALSE, pid); + if (proc == nullptr) + detail::throw_last_error("open_process()"); + return proc; +} + + +void terminate_if_running_(HANDLE handle) +{ + DWORD exit_code = 0u; + if (handle == INVALID_HANDLE_VALUE) + return ; + if (::GetExitCodeProcess(handle, &exit_code)) + if (exit_code == STILL_ACTIVE) + ::TerminateProcess(handle, 260); +} + +bool check_handle_(HANDLE handle, error_code & ec) +{ + if (handle == INVALID_HANDLE_VALUE) + { + ec.assign(ERROR_INVALID_HANDLE_STATE, system_category()); + return false; + } + return true; +} + +bool check_pid_(pid_type pid_, error_code & ec) +{ + if (pid_ == 0) + { + ec.assign(ERROR_INVALID_HANDLE_STATE, system_category()); + return false; + } + return true; +} + +struct enum_windows_data_t +{ + error_code &ec; + pid_type pid; +}; + +static BOOL CALLBACK enum_window(HWND hwnd, LPARAM param) + { + auto data = reinterpret_cast(param); + DWORD pid{0u}; + GetWindowThreadProcessId(hwnd, &pid); + + if (pid != data->pid) + return TRUE; + + LRESULT res = ::SendMessageW(hwnd, WM_CLOSE, 0, 0); + if (!res) + data->ec = detail::get_last_error(); + return res != 0; + } + +void request_exit_(pid_type pid_, error_code & ec) +{ + enum_windows_data_t data{ec, pid_}; + + if (!::EnumWindows(enum_window, reinterpret_cast(&data))) + ec = detail::get_last_error(); +} + +void interrupt_(pid_type pid_, error_code & ec) +{ + if (!::GenerateConsoleCtrlEvent(CTRL_C_EVENT, pid_)) + ec = detail::get_last_error(); +} + +void terminate_(HANDLE handle, error_code & ec, DWORD & exit_status) +{ + if (!::TerminateProcess(handle, 260)) + ec = detail::get_last_error(); +} + +void check_running_(HANDLE handle, error_code & ec, DWORD & exit_status) +{ + if (!::GetExitCodeProcess(handle, &exit_status)) + ec = detail::get_last_error(); +} + + +#if !defined(BOOST_PROCESS_V2_HEADER_ONLY) +template struct basic_process_handle_win<>; +#endif + +} + + +BOOST_PROCESS_V2_END_NAMESPACE + +#endif //BOOST_PROCESS_V2_DETAIL_IMPL_PROCESS_HANDLE_WINDOWS_IPP diff --git a/include/boost/process/v2/detail/impl/throw_error.ipp b/include/boost/process/v2/detail/impl/throw_error.ipp new file mode 100644 index 000000000..663bb1674 --- /dev/null +++ b/include/boost/process/v2/detail/impl/throw_error.ipp @@ -0,0 +1,31 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_DETAIL_IMPL_THROW_ERROR_IPP +#define BOOST_PROCESS_V2_DETAIL_IMPL_THROW_ERROR_IPP + +#include +#include +#include + +BOOST_PROCESS_V2_BEGIN_NAMESPACE +namespace detail +{ + +void do_throw_error(const error_code& err) +{ + system_error e(err); + throw_exception(e); +} + +void do_throw_error(const error_code& err, const char* location) +{ + system_error e(err, location); + throw_exception(e); +} + +} +BOOST_PROCESS_V2_END_NAMESPACE + +#endif //BOOST_PROCESS_V2_DETAIL_IMPL_THROW_ERROR_IPP diff --git a/include/boost/process/v2/detail/impl/utf8.ipp b/include/boost/process/v2/detail/impl/utf8.ipp new file mode 100644 index 000000000..22623df69 --- /dev/null +++ b/include/boost/process/v2/detail/impl/utf8.ipp @@ -0,0 +1,379 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_DETAIL_IMPL_UTF8_HPP +#define BOOST_PROCESS_V2_DETAIL_IMPL_UTF8_HPP + +#include +#include +#include +#include + +#if defined(BOOST_PROCESS_V2_WINDOWS) +#include +#endif + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +namespace detail +{ + +#if defined(BOOST_PROCESS_V2_WINDOWS) + +inline void handle_error(error_code & ec) +{ + const auto err = ::GetLastError(); + switch (err) + { + case ERROR_INSUFFICIENT_BUFFER: + ec.assign(error::insufficient_buffer, error::utf8_category); + break; + case ERROR_NO_UNICODE_TRANSLATION: + ec.assign(error::invalid_character, error::utf8_category); + break; + default: + ec.assign(err, system_category()); + } +} + +std::size_t size_as_utf8(const wchar_t * in, std::size_t size, error_code & ec) +{ + auto res = WideCharToMultiByte( + CP_UTF8, // CodePage, + 0, // dwFlags, + in, // lpWideCharStr, + static_cast(size), // cchWideChar, + nullptr, // lpMultiByteStr, + 0, // cbMultiByte, + nullptr, // lpDefaultChar, + FALSE); // lpUsedDefaultChar + + if (res == 0u) + handle_error(ec); + return static_cast(res); +} + +std::size_t size_as_wide(const char * in, std::size_t size, error_code & ec) +{ + auto res = ::MultiByteToWideChar( + CP_UTF8, // CodePage + 0, // dwFlags + in, // lpMultiByteStr + static_cast(size), // cbMultiByte + nullptr, // lpWideCharStr + 0); // cchWideChar + if (res == 0u) + handle_error(ec); + + return static_cast(res); +} + +std::size_t convert_to_utf8(const wchar_t *in, std::size_t size, char * out, + std::size_t max_size, error_code & ec) +{ + auto res = ::WideCharToMultiByte( + CP_UTF8, // CodePage + 0, // dwFlags + in, // lpWideCharStr + static_cast(size), // cchWideChar + out, // lpMultiByteStr + static_cast(max_size), // cbMultiByte + nullptr, // lpDefaultChar + FALSE); // lpUsedDefaultChar + if (res == 0u) + handle_error(ec); + + return static_cast(res); +} + +std::size_t convert_to_wide(const char *in, std::size_t size, wchar_t * out, + std::size_t max_size, error_code & ec) +{ + auto res = ::MultiByteToWideChar( + CP_UTF8, // CodePage + 0, // dwFlags + in, // lpMultiByteStr + static_cast(size), // cbMultiByte + out, // lpWideCharStr + static_cast(max_size)); // cchWideChar + if (res == 0u) + handle_error(ec); + + return static_cast(res); +} + +#else + + +template +inline int get_cont_octet_out_count_impl(wchar_t word) { + if (word < 0x80) { + return 0; + } + if (word < 0x800) { + return 1; + } + return 2; +} + +template<> +inline int get_cont_octet_out_count_impl<4>(wchar_t word) { + if (word < 0x80) { + return 0; + } + if (word < 0x800) { + return 1; + } + + // Note that the following code will generate warnings on some platforms + // where wchar_t is defined as UCS2. The warnings are superfluous as the + // specialization is never instantitiated with such compilers, but this + // can cause problems if warnings are being treated as errors, so we guard + // against that. Including as we do + // should be enough to get WCHAR_MAX defined. +#if !defined(WCHAR_MAX) +# error WCHAR_MAX not defined! +#endif + // cope with VC++ 7.1 or earlier having invalid WCHAR_MAX +#if defined(_MSC_VER) && _MSC_VER <= 1310 // 7.1 or earlier + return 2; +#elif WCHAR_MAX > 0x10000 + + if (word < 0x10000) { + return 2; + } + if (word < 0x200000) { + return 3; + } + if (word < 0x4000000) { + return 4; + } + return 5; + +#else + return 2; +#endif +} + +inline int get_cont_octet_out_count(wchar_t word) +{ + return detail::get_cont_octet_out_count_impl(word); +} + +// copied from boost/detail/utf8_codecvt_facet.ipp +// Copyright (c) 2001 Ronald Garcia, Indiana University (garcia@osl.iu.edu) +// Andrew Lumsdaine, Indiana University (lums@osl.iu.edu). + +inline unsigned int get_octet_count(unsigned char lead_octet) +{ + // if the 0-bit (MSB) is 0, then 1 character + if (lead_octet <= 0x7f) return 1; + + // Otherwise the count number of consecutive 1 bits starting at MSB +// assert(0xc0 <= lead_octet && lead_octet <= 0xfd); + + if (0xc0 <= lead_octet && lead_octet <= 0xdf) return 2; + else if (0xe0 <= lead_octet && lead_octet <= 0xef) return 3; + else if (0xf0 <= lead_octet && lead_octet <= 0xf7) return 4; + else if (0xf8 <= lead_octet && lead_octet <= 0xfb) return 5; + else return 6; +} + +inline bool invalid_continuing_octet(unsigned char octet_1) { + return (octet_1 < 0x80|| 0xbf< octet_1); +} + +inline unsigned int get_cont_octet_count(unsigned char lead_octet) +{ + return get_octet_count(lead_octet) - 1; +} + +inline const wchar_t * get_octet1_modifier_table() noexcept +{ + static const wchar_t octet1_modifier_table[] = { + 0x00, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc + }; + return octet1_modifier_table; +} + + +std::size_t size_as_utf8(const wchar_t * in, std::size_t size, error_code & ec) +{ + std::size_t res = 0u; + const auto from_end = in + size; + for (auto from = in; from != from_end; from++) + res += get_cont_octet_out_count(*from) + 1; + return res; +} + +std::size_t size_as_wide(const char * in, std::size_t size, error_code & ec) +{ + const auto from = in; + const auto from_end = from + size; + const char * from_next = from; + for (std::size_t char_count = 0u; from_next < from_end; ++char_count) { + unsigned int octet_count = get_octet_count(*from_next); + // The buffer may represent incomplete characters, so terminate early if one is found + if (octet_count > static_cast(from_end - from_next)) + break; + from_next += octet_count; + } + + return from_next - from; +} + +std::size_t convert_to_utf8(const wchar_t * in, std::size_t size, + char * out, std::size_t max_size, error_code & ec) +{ + + const wchar_t * from = in; + const wchar_t * from_end = from + size; + const wchar_t * & from_next = from; + char * to = out; + char * to_end = out + max_size; + char * & to_next = to; + + const wchar_t * const octet1_modifier_table = get_octet1_modifier_table(); + wchar_t max_wchar = (std::numeric_limits::max)(); + while (from != from_end && to != to_end) { + + // Check for invalid UCS-4 character + if (*from > max_wchar) { + from_next = from; + to_next = to; + ec.assign(error::invalid_character, error::get_utf8_category()); + return 0u; + } + + int cont_octet_count = get_cont_octet_out_count(*from); + + // RG - comment this formula better + int shift_exponent = cont_octet_count * 6; + + // Process the first character + *to++ = static_cast(octet1_modifier_table[cont_octet_count] + + (unsigned char)(*from / (1 << shift_exponent))); + + // Process the continuation characters + // Invariants: At the start of the loop: + // 1) 'i' continuing octets have been generated + // 2) '*to' points to the next location to place an octet + // 3) shift_exponent is 6 more than needed for the next octet + int i = 0; + while (i != cont_octet_count && to != to_end) { + shift_exponent -= 6; + *to++ = static_cast(0x80 + ((*from / (1 << shift_exponent)) % (1 << 6))); + ++i; + } + // If we filled up the out buffer before encoding the character + if (to == to_end && i != cont_octet_count) { + from_next = from; + to_next = to - (i + 1); + ec.assign(error::insufficient_buffer, error::get_utf8_category()); + return 0u; + } + ++from; + } + from_next = from; + to_next = to; + + // Were we done or did we run out of destination space + if (from != from_end) + ec.assign(error::insufficient_buffer, error::get_utf8_category()); + + return to_next - out; +} + +inline bool invalid_leading_octet(unsigned char octet_1) { + return (0x7f < octet_1 && octet_1 < 0xc0) || + (octet_1 > 0xfd); +} + +std::size_t convert_to_wide(const char * in, std::size_t size, + wchar_t * out, std::size_t max_size, error_code & ec) +{ + const char * from = in; + const char * from_end = from + size; + const char * & from_next = from; + wchar_t * to = out; + wchar_t * to_end = out + max_size; + wchar_t * & to_next = to; + + // Basic algorithm: The first octet determines how many + // octets total make up the UCS-4 character. The remaining + // "continuing octets" all begin with "10". To convert, subtract + // the amount that specifies the number of octets from the first + // octet. Subtract 0x80 (1000 0000) from each continuing octet, + // then mash the whole lot together. Note that each continuing + // octet only uses 6 bits as unique values, so only shift by + // multiples of 6 to combine. + const wchar_t * const octet1_modifier_table = detail::get_octet1_modifier_table(); + while (from != from_end && to != to_end) { + + // Error checking on the first octet + if (invalid_leading_octet(*from)) { + from_next = from; + to_next = to; + ec.assign(error::invalid_character, error::get_utf8_category()); + return 0u; + } + + // The first octet is adjusted by a value dependent upon + // the number of "continuing octets" encoding the character + const int cont_octet_count = get_cont_octet_count(*from); + + // The unsigned char conversion is necessary in case char is + // signed (I learned this the hard way) + wchar_t ucs_result = + (unsigned char)(*from++) - octet1_modifier_table[cont_octet_count]; + + // Invariants: + // 1) At the start of the loop, 'i' continuing characters have been + // processed + // 2) *from points to the next continuing character to be processed. + int i = 0; + while (i != cont_octet_count && from != from_end) { + + // Error checking on continuing characters + if (invalid_continuing_octet(*from)) { + from_next = from; + to_next = to; + ec.assign(error::invalid_character, error::get_utf8_category()); + return 0u; + } + + ucs_result *= (1 << 6); + + // each continuing character has an extra (10xxxxxx)b attached to + // it that must be removed. + ucs_result += (unsigned char)(*from++) - 0x80; + ++i; + } + + // If the buffer ends with an incomplete unicode character... + if (from == from_end && i != cont_octet_count) { + // rewind "from" to before the current character translation + from_next = from - (i + 1); + to_next = to; + ec.assign(error::insufficient_buffer, error::get_utf8_category()); + return 0u; + } + *to++ = ucs_result; + } + from_next = from; + to_next = to; + + if (from != from_end) + ec.assign(error::insufficient_buffer, error::get_utf8_category()); + + return to_next - out; +} + +#endif + +} + +BOOST_PROCESS_V2_END_NAMESPACE + +#endif //BOOST_PROCESS_V2_DETAIL_IMPL_UTF8_HPP diff --git a/include/boost/process/v2/detail/last_error.hpp b/include/boost/process/v2/detail/last_error.hpp new file mode 100644 index 000000000..05e09ee0a --- /dev/null +++ b/include/boost/process/v2/detail/last_error.hpp @@ -0,0 +1,29 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_DETAIL_LAST_ERROR_HPP +#define BOOST_PROCESS_V2_DETAIL_LAST_ERROR_HPP + +#include + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +namespace detail { + +BOOST_PROCESS_V2_DECL error_code get_last_error(); +BOOST_PROCESS_V2_DECL void throw_last_error(); +BOOST_PROCESS_V2_DECL void throw_last_error(const char * msg); +BOOST_PROCESS_V2_DECL void throw_last_error(const std::string & msg); + +} +BOOST_PROCESS_V2_END_NAMESPACE + +#if defined(BOOST_PROCESS_V2_HEADER_ONLY) + +#include + +#endif + + +#endif //BOOST_PROCESS_V2_DETAIL_LAST_ERROR_HPP diff --git a/include/boost/process/v2/detail/process_handle_fd.hpp b/include/boost/process/v2/detail/process_handle_fd.hpp new file mode 100644 index 000000000..868542661 --- /dev/null +++ b/include/boost/process/v2/detail/process_handle_fd.hpp @@ -0,0 +1,318 @@ + +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_DETAIL_PROCESS_HANDLE_FD_HPP +#define BOOST_PROCESS_V2_DETAIL_PROCESS_HANDLE_FD_HPP + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#if defined(BOOST_PROCESS_V2_STANDALONE) +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +namespace detail +{ + +template +struct basic_process_handle_fd +{ + using native_handle_type = int; + + typedef Executor executor_type; + + executor_type get_executor() + { return descriptor_.get_executor(); } + + /// Rebinds the process_handle to another executor. + template + struct rebind_executor + { + /// The socket type when rebound to the specified executor. + typedef basic_process_handle_fd other; + }; + + template + basic_process_handle_fd(ExecutionContext &context, + typename std::enable_if< + std::is_convertible::value>::type * = nullptr) + : pid_(-1), descriptor_(context) + { + } + + basic_process_handle_fd(executor_type executor) + : pid_(-1), descriptor_(executor) + { + } + + basic_process_handle_fd(executor_type executor, pid_type pid) + : pid_(pid), descriptor_(executor, syscall(SYS_pidfd_open, pid, 0)) + { + } + + basic_process_handle_fd(executor_type executor, pid_type pid, native_handle_type process_handle) + : pid_(pid), descriptor_(executor, process_handle) + { + } + + basic_process_handle_fd(basic_process_handle_fd &&handle) + : pid_(handle.pid_), descriptor_(std::move(handle.descriptor_)) + { + handle.pid_ = -1; + } + + template + basic_process_handle_fd(basic_process_handle_fd &&handle) + : pid_(handle.pid_), descriptor_(std::move(handle.descriptor_)) + { + handle.pid_ = -1; + } + + + basic_process_handle_fd& operator=(basic_process_handle_fd &&handle) + { + pid_ = handle.pid_; + descriptor_ = std::move(handle.descriptor_); + handle.pid_ = -1; + return *this; + } + + pid_type id() const + { return pid_; } + + void terminate_if_running(error_code &) + { + if (pid_ <= 0) + return; + if (::waitpid(pid_, nullptr, WNOHANG) == 0) + { + ::kill(pid_, SIGKILL); + ::waitpid(pid_, nullptr, 0); + } + } + + void terminate_if_running() + { + if (pid_ <= 0) + return; + if (::waitpid(pid_, nullptr, WNOHANG) == 0) + { + ::kill(pid_, SIGKILL); + ::waitpid(pid_, nullptr, 0); + } + } + + void wait(native_exit_code_type &exit_status, error_code &ec) + { + if (pid_ <= 0) + return; + while (::waitpid(pid_, &exit_status, 0) < 0) + { + if (errno != EINTR) + { + ec = get_last_error(); + break; + } + } + } + + void wait(native_exit_code_type &exit_status) + { + if (pid_ <= 0) + return; + error_code ec; + wait(exit_status, ec); + if (ec) + detail::throw_error(ec, "wait(pid)"); + } + + void interrupt(error_code &ec) + { + if (pid_ <= 0) + return; + if (::kill(pid_, SIGINT) == -1) + ec = get_last_error(); + } + + void interrupt() + { + if (pid_ <= 0) + return; + error_code ec; + interrupt(ec); + if (ec) + detail::throw_error(ec, "interrupt"); + } + + void request_exit(error_code &ec) + { + if (pid_ <= 0) + return; + if (::kill(pid_, SIGTERM) == -1) + ec = get_last_error(); + } + + void request_exit() + { + if (pid_ <= 0) + return; + error_code ec; + request_exit(ec); + if (ec) + detail::throw_error(ec, "request_exit"); + } + + void terminate(native_exit_code_type &exit_status, error_code &ec) + { + if (pid_ <= 0) + return; + if (::kill(pid_, SIGKILL) == -1) + ec = get_last_error(); + } + + void terminate(native_exit_code_type &exit_status) + { + if (pid_ <= 0) + return; + error_code ec; + terminate(exit_status, ec); + if (ec) + detail::throw_error(ec, "terminate"); + } + + bool running(native_exit_code_type &exit_code, error_code & ec) + { + if (pid_ <= 0) + return false; + int code = 0; + int res = ::waitpid(pid_, &code, WNOHANG); + if (res == -1) + ec = get_last_error(); + else if (res == 0) + return true; + else + ec.clear(); + + if (res == 0) + return true; + else + { + exit_code = code; + return false; + } + } + + bool running(native_exit_code_type &exit_code) + { + if (pid_ <= 0) + return false; + + error_code ec; + bool res = running(exit_code, ec); + if (ec) + detail::throw_error(ec, "is_running"); + return res; + } + + bool is_open() const + { + return pid_ != -1; + } + + template + BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(WaitHandler, void (error_code, native_exit_code_type)) + async_wait(WaitHandler &&handler BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)) + { + return BOOST_PROCESS_V2_ASIO_NAMESPACE::async_compose( + async_wait_op_{descriptor_, pid_}, handler, descriptor_); + } + + private: + template + friend + struct basic_process_handle_fd; + pid_type pid_ = -1; + BOOST_PROCESS_V2_ASIO_NAMESPACE::posix::basic_stream_descriptor descriptor_; + + struct async_wait_op_ + { + BOOST_PROCESS_V2_ASIO_NAMESPACE::posix::basic_descriptor &descriptor; + pid_type pid_; + + template + void operator()(Self &&self) + { + error_code ec; + native_exit_code_type exit_code{}; + int wait_res = -1; + if (pid_ <= 0) // error, complete early + ec = BOOST_PROCESS_V2_ASIO_NAMESPACE::error::bad_descriptor; + else + { + wait_res = ::waitpid(pid_, &exit_code, WNOHANG); + if (wait_res == -1) + ec = get_last_error(); + } + + + if (!ec && (wait_res == 0)) + { + descriptor.async_wait( + BOOST_PROCESS_V2_ASIO_NAMESPACE::posix::descriptor_base::wait_read, std::move(self)); + return; + } + + struct completer + { + error_code ec; + native_exit_code_type code; + typename std::decay::type self; + + void operator()() + { + self.complete(ec, code); + } + }; + BOOST_PROCESS_V2_ASIO_NAMESPACE::post(descriptor.get_executor(), + completer{ec, exit_code, std::move(self)}); + + } + + template + void operator()(Self &&self, error_code ec, int = 0) + { + native_exit_code_type exit_code{}; + if (!ec) + if (::waitpid(pid_, &exit_code, 0) == -1) + ec = get_last_error(); + std::move(self).complete(ec, exit_code); + } + }; +}; +} + +BOOST_PROCESS_V2_END_NAMESPACE + +#endif //BOOST_PROCESS_HANDLE_FD_OR_SIGNAL_HPP diff --git a/include/boost/process/v2/detail/process_handle_fd_or_signal.hpp b/include/boost/process/v2/detail/process_handle_fd_or_signal.hpp new file mode 100644 index 000000000..c91b31535 --- /dev/null +++ b/include/boost/process/v2/detail/process_handle_fd_or_signal.hpp @@ -0,0 +1,346 @@ + +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_DETAIL_PROCESS_HANDLE_FD_OR_SIGNAL_HPP +#define BOOST_PROCESS_V2_DETAIL_PROCESS_HANDLE_FD_OR_SIGNAL_HPP + +#include + +#include +#include + +#include +#include +#include +#include + +#if defined(BOOST_PROCESS_V2_STANDALONE) +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#endif + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +namespace detail +{ + +template +struct basic_process_handle_fd_or_signal +{ + using native_handle_type = int; + + typedef Executor executor_type; + + executor_type get_executor() + { return signal_set_.get_executor(); } + + /// Rebinds the process_handle to another executor. + template + struct rebind_executor + { + /// The socket type when rebound to the specified executor. + typedef basic_process_handle_fd_or_signal other; + }; + + template + basic_process_handle_fd_or_signal(ExecutionContext &context, + typename std::enable_if< + std::is_convertible::value + >::type * = nullptr) + : pid_(-1), descriptor_(context) + { + } + + template + basic_process_handle_fd_or_signal(ExecutionContext &context, + pid_type pid, + typename std::enable_if< + std::is_convertible::value + >::type * = nullptr) + : pid_(pid), descriptor_(context) + { + } + + template + basic_process_handle_fd_or_signal(ExecutionContext &context, + pid_type pid, native_handle_type process_handle, + typename std::enable_if< + std::is_convertible::value + >::type * = nullptr) + : pid_(pid), descriptor_(context, process_handle) + { + } + + + basic_process_handle_fd_or_signal(Executor executor) + : pid_(-1), descriptor_(executor) + { + } + + basic_process_handle_fd_or_signal(Executor executor, pid_type pid) + : pid_(pid), descriptor_(executor) + { + } + + basic_process_handle_fd_or_signal(Executor executor, pid_type pid, native_handle_type process_handle) + : pid_(pid), descriptor_(executor, process_handle) + { + } + + + basic_process_handle_fd_or_signal(basic_process_handle_fd_or_signal &&handle) + : pid_(handle.pid_), descriptor_(std::move(handle.descriptor_)) + { + handle.pid_ = -1; + } + // Warn: does not change the executor of the signal-set. + basic_process_handle_fd_or_signal& operator=(basic_process_handle_fd_or_signal &&handle) + { + pid_ = handle.pid_; + descriptor_ = std::move(handle.descriptor_); + handle.pid_ = -1; + return *this; + } + + + template + basic_process_handle_fd_or_signal(basic_process_handle_fd_or_signal &&handle) + : pid_(handle.pid_), descriptor_(std::move(handle.descriptor_)) + { + handle.pid_ = -1; + } + + pid_type id() const + { return pid_; } + + void terminate_if_running(error_code &) + { + if (pid_ <= 0) + return; + if (::waitpid(pid_, nullptr, WNOHANG) == 0) + { + ::kill(pid_, SIGKILL); + ::waitpid(pid_, nullptr, 0); + } + } + + void terminate_if_running() + { + if (pid_ <= 0) + return; + if (::waitpid(pid_, nullptr, WNOHANG) == 0) + { + ::kill(pid_, SIGKILL); + ::waitpid(pid_, nullptr, 0); + } + } + + void wait(native_exit_code_type &exit_status, error_code &ec) + { + if (pid_ <= 0) + return; + while (::waitpid(pid_, &exit_status, 0) < 0) + { + if (errno != EINTR) + { + ec = get_last_error(); + break; + } + } + + } + + void wait(native_exit_code_type &exit_status) + { + if (pid_ <= 0) + return; + error_code ec; + wait(exit_status, ec); + if (ec) + detail::throw_error(ec, "wait(pid)"); + } + + void interrupt(error_code &ec) + { + if (pid_ <= 0) + return; + if (::kill(pid_, SIGINT) == -1) + ec = get_last_error(); + } + + void interrupt() + { + if (pid_ <= 0) + return; + error_code ec; + interrupt(ec); + if (ec) + detail::throw_error(ec, "interrupt"); + } + + void request_exit(error_code &ec) + { + if (pid_ <= 0) + return; + if (::kill(pid_, SIGTERM) == -1) + ec = get_last_error(); + } + + void request_exit() + { + if (pid_ <= 0) + return; + error_code ec; + request_exit(ec); + if (ec) + detail::throw_error(ec, "request_exit"); + } + + void terminate(native_exit_code_type &exit_status, error_code &ec) + { + if (pid_ <= 0) + return; + if (::kill(pid_, SIGKILL) == -1) + ec = get_last_error(); + } + + void terminate(native_exit_code_type &exit_status) + { + if (pid_ <= 0) + return; + error_code ec; + terminate(exit_status, ec); + if (ec) + detail::throw_error(ec, "terminate"); + } + + bool running(native_exit_code_type &exit_code, error_code & ec) + { + if (pid_ <= 0) + return false; + int code = 0; + int res = ::waitpid(pid_, &code, WNOHANG); + if (res == -1) + ec = get_last_error(); + else + ec.clear(); + + if (process_is_running(res)) + return true; + else + { + exit_code = code; + return false; + } + } + + bool running(native_exit_code_type &exit_code) + { + if (pid_ <= 0) + return false; + + error_code ec; + bool res = running(exit_code, ec); + if (ec) + detail::throw_error(ec, "is_running"); + return res; + } + + bool is_open() const + { + return pid_ != -1; + } + + template + BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(WaitHandler, void (error_code, native_exit_code_type)) + async_wait(WaitHandler &&handler BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)) + { + return BOOST_PROCESS_V2_ASIO_NAMESPACE::async_compose( + async_wait_op_{descriptor_, signal_set_, pid_}, handler, descriptor_); + } + + private: + template + friend + struct basic_process_handle_fd_or_signal; + pid_type pid_ = -1; + BOOST_PROCESS_V2_ASIO_NAMESPACE::posix::basic_stream_descriptor descriptor_; + BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_signal_set signal_set_{descriptor_.get_executor(), SIGCHLD}; + + struct async_wait_op_ + { + BOOST_PROCESS_V2_ASIO_NAMESPACE::posix::basic_descriptor &descriptor; + BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_signal_set &handle; + pid_type pid_; + bool needs_post = true; + + template + void operator()(Self &&self, error_code ec = {}, int = 0) + { + native_exit_code_type exit_code{}; + int wait_res = -1; + if (pid_ <= 0) // error, complete early + ec = BOOST_PROCESS_V2_ASIO_NAMESPACE::error::bad_descriptor; + else + { + wait_res = ::waitpid(pid_, &exit_code, WNOHANG); + if (wait_res == -1) + ec = get_last_error(); + } + + if (!ec && (wait_res == 0)) + { + needs_post = false; + if (descriptor.is_open()) + descriptor.async_wait( + BOOST_PROCESS_V2_ASIO_NAMESPACE::posix::descriptor_base::wait_read, + std::move(self)); + else + handle.async_wait(std::move(self)); + return; + } + + struct completer + { + error_code ec; + native_exit_code_type code; + typename std::decay::type self; + + void operator()() + { + self.complete(ec, code); + } + }; + + const auto exec = self.get_executor(); + completer cpl{ec, exit_code, std::move(self)}; + if (needs_post) + BOOST_PROCESS_V2_ASIO_NAMESPACE::post(exec, std::move(cpl)); + else + BOOST_PROCESS_V2_ASIO_NAMESPACE::dispatch(exec, std::move(cpl)); + + } + }; +}; +} + +BOOST_PROCESS_V2_END_NAMESPACE + +#endif //BOOST_PROCESS_HANDLE_FD_OR_SIGNAL_HPP diff --git a/include/boost/process/v2/detail/process_handle_signal.hpp b/include/boost/process/v2/detail/process_handle_signal.hpp new file mode 100644 index 000000000..cf042c40d --- /dev/null +++ b/include/boost/process/v2/detail/process_handle_signal.hpp @@ -0,0 +1,314 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_DETAIL_PROCESS_HANDLE_SIGNAL_HPP +#define BOOST_PROCESS_V2_DETAIL_PROCESS_HANDLE_SIGNAL_HPP + +#include + +#include +#include + +#include +#include +#include +#include + +#if defined(BOOST_PROCESS_V2_STANDALONE) +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +namespace detail +{ + +template +struct basic_process_handle_signal +{ + struct native_handle_type + { + native_handle_type() = delete; + native_handle_type(const native_handle_type & ) = delete; + ~native_handle_type() = delete; + }; + + typedef Executor executor_type; + + executor_type get_executor() + { return signal_set_.get_executor(); } + + /// Rebinds the process_handle to another executor. + template + struct rebind_executor + { + /// The socket type when rebound to the specified executor. + typedef basic_process_handle_signal other; + }; + + template + basic_process_handle_signal(ExecutionContext &context, + typename std::enable_if< + std::is_convertible::value + >::type * = nullptr) + : pid_(-1), signal_set_(context, SIGCHLD) + { + } + + basic_process_handle_signal(Executor executor) + : pid_(-1), signal_set_(executor, SIGCHLD) + { + } + + basic_process_handle_signal(Executor executor, pid_type pid) + : pid_(pid), signal_set_(executor, SIGCHLD) + { + } + + basic_process_handle_signal(basic_process_handle_signal && handle) + : pid_(handle.pid_), signal_set_(handle.signal_set_.get_executor(), SIGCHLD) + { + handle.pid_ = -1; + } + + basic_process_handle_signal& operator=(basic_process_handle_signal && handle) + { + pid_ = handle.id(); + signal_set_.~basic_signal_set(); + using ss = BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_signal_set; + new (&signal_set_) ss(handle.get_executor(), SIGCHLD); + handle.pid_ = -1; + return *this; + } + + + template + basic_process_handle_signal(basic_process_handle_signal && handle) + : pid_(handle.pid_), signal_set_(Executor1(handle.signal_set_.get_executor()), SIGCHLD) + { + handle.pid_ = -1; + } + + pid_type id() const + { return pid_; } + + void terminate_if_running(error_code &) + { + terminate_if_running(); + } + + void terminate_if_running() + { + if (pid_ <= 0) + return ; + if (::waitpid(pid_, nullptr, WNOHANG) == 0) + { + ::kill(pid_, SIGKILL); + ::waitpid(pid_, nullptr, 0); + } + } + + void wait(native_exit_code_type &exit_status, error_code &ec) + { + if (pid_ <= 0) + return ; + while (::waitpid(pid_, &exit_status, 0) < 0) + { + if (errno != EINTR) + { + ec = get_last_error(); + break; + } + } + } + + void wait(native_exit_code_type &exit_status) + { + if (pid_ <= 0) + return ; + error_code ec; + wait(exit_status, ec); + if (ec) + detail::throw_error(ec, "wait(pid)"); + } + + void interrupt(error_code &ec) + { + if (pid_ <= 0) + return ; + if (::kill(pid_, SIGTERM) == -1) + ec = get_last_error(); + } + + void interrupt() + { + if (pid_ <= 0) + return ; + error_code ec; + interrupt(ec); + if (ec) + detail::throw_error(ec, "interrupt"); + } + + void request_exit(error_code &ec) + { + if (pid_ <= 0) + return ; + if (::kill(pid_, SIGTERM) == -1) + ec = get_last_error(); + } + + void request_exit() + { + if (pid_ <= 0) + return ; + error_code ec; + request_exit(ec); + if (ec) + detail::throw_error(ec, "request_exit"); + } + + void terminate(native_exit_code_type &exit_status, error_code &ec) + { + if (pid_ <= 0) + return ; + if (::kill(pid_, SIGKILL) == -1) + ec = get_last_error(); + } + + void terminate(native_exit_code_type &exit_status) + { + if (pid_ <= 0) + return ; + error_code ec; + terminate(exit_status, ec); + if (ec) + detail::throw_error(ec, "terminate"); + } + + bool running(native_exit_code_type &exit_code, error_code & ec) + { + if (pid_ <= 0) + return false; + int code = 0; + int res = ::waitpid(pid_, &code, WNOHANG); + if (res == -1) + ec = get_last_error(); + + if (res == 0) + return true; + else + { + exit_code = code; + return false; + } + } + + bool running(native_exit_code_type &exit_code) + { + if (pid_ <= 0) + return false; + + error_code ec; + bool res = running(exit_code, ec); + if (ec) + detail::throw_error(ec, "is_running"); + return res; + } + + bool is_open() const + { + return pid_ != -1; + } + + template + BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(WaitHandler, void (error_code, native_exit_code_type)) + async_wait(WaitHandler &&handler BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)) + { + return BOOST_PROCESS_V2_ASIO_NAMESPACE::async_compose( + async_wait_op_{signal_set_, pid_}, handler, signal_set_); + } + + private: + template + friend struct basic_process_handle_signal; + pid_type pid_ = -1; + BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_signal_set signal_set_; + + struct async_wait_op_ + { + BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_signal_set &handle; + pid_type pid_; + + template + void operator()(Self &&self) + { + handle.async_wait(std::move(self)); + handle.cancel(); + // we cancel so we end up on the signal-sets executor + } + + template + void operator()(Self &&self, error_code ec, int sig) + { + if (ec == BOOST_PROCESS_V2_ASIO_NAMESPACE::error::operation_aborted && + self.get_cancellation_state().cancelled() + == BOOST_PROCESS_V2_ASIO_NAMESPACE::cancellation_type::none) + ec.clear(); + + native_exit_code_type exit_code = -1; + int wait_res = -1; + + if (pid_ <= 0) // error, complete early + ec = BOOST_PROCESS_V2_ASIO_NAMESPACE::error::bad_descriptor; + else if (!ec) + { + wait_res = ::waitpid(pid_, &exit_code, WNOHANG); + if (wait_res == -1) + ec = get_last_error(); + } + + if (!ec && (wait_res == 0)) + { + handle.async_wait(std::move(self)); + return ; + } + + struct completer + { + error_code ec; + native_exit_code_type code; + typename std::decay::type self; + + void operator()() + { + self.complete(ec, code); + } + }; + + const auto exec = self.get_executor(); + BOOST_PROCESS_V2_ASIO_NAMESPACE::dispatch(exec, completer{ec, exit_code, std::move(self)}); + } + }; +}; + +} + + +BOOST_PROCESS_V2_END_NAMESPACE + + +#endif //BOOST_PROCESS_V2_DETAIL_PROCESS_HANDLE_SIGNAL_HPP diff --git a/include/boost/process/v2/detail/process_handle_windows.hpp b/include/boost/process/v2/detail/process_handle_windows.hpp new file mode 100644 index 000000000..bd8441ab7 --- /dev/null +++ b/include/boost/process/v2/detail/process_handle_windows.hpp @@ -0,0 +1,279 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_DETAIL_PROCESS_HANDLE_WINDOWS_HPP +#define BOOST_PROCESS_V2_DETAIL_PROCESS_HANDLE_WINDOWS_HPP + +#include + +#include +#include +#include + +#if defined(BOOST_PROCESS_V2_STANDALONE) +#include +#include +#include +#else +#include +#include +#include +#endif + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +namespace detail +{ + +BOOST_PROCESS_V2_DECL void get_exit_code_( void * handle, native_exit_code_type & exit_code, error_code & ec); +BOOST_PROCESS_V2_DECL void * open_process_(pid_type pid); +BOOST_PROCESS_V2_DECL void terminate_if_running_(void * handle); +BOOST_PROCESS_V2_DECL bool check_handle_(void* handle, error_code & ec); +BOOST_PROCESS_V2_DECL bool check_pid_(pid_type pid_, error_code & ec); +BOOST_PROCESS_V2_DECL void interrupt_(pid_type pid_, error_code & ec); +BOOST_PROCESS_V2_DECL void terminate_(void * handle, error_code & ec, native_exit_code_type & exit_code); +BOOST_PROCESS_V2_DECL void request_exit_(pid_type pid_, error_code & ec); +BOOST_PROCESS_V2_DECL void check_running_(void* handle, error_code & ec, native_exit_code_type & exit_status); + +template +struct basic_process_handle_win +{ + typedef BOOST_PROCESS_V2_ASIO_NAMESPACE::windows::basic_object_handle handle_type; + typedef typename handle_type::native_handle_type native_handle_type; + + typedef Executor executor_type; + + executor_type get_executor() + { return handle_.get_executor(); } + + /// Rebinds the process_handle to another executor. + template + struct rebind_executor + { + /// The socket type when rebound to the specified executor. + typedef basic_process_handle_win other; + }; + + template + basic_process_handle_win(ExecutionContext &context, + typename std::enable_if< + std::is_convertible::value + >::type = 0) + : pid_(0), handle_(context) + { + } + + basic_process_handle_win(Executor executor) + : pid_(0), handle_(executor) + { + } + + basic_process_handle_win(Executor executor, pid_type pid) + : pid_(pid), handle_(executor, detail::open_process_(pid)) + { + } + + basic_process_handle_win(Executor executor, pid_type pid, native_handle_type process_handle) + : pid_(pid), handle_(executor, process_handle) + { + } + + + template + basic_process_handle_win(basic_process_handle_win && handle) + : pid_(handle.pid_), handle_(handle.handle_.get_executor()) + { + } + + basic_process_handle_win(basic_process_handle_win && handle) + { + pid_ = handle.id(); + handle_ = std::move(handle.handle_); + handle.pid_ = static_cast(-1); + } + + basic_process_handle_win& operator=(basic_process_handle_win && handle) + { + pid_ = handle.pid_; + handle_ = std::mopve(handle_)) + handle.pid_ = static_cast(-1); + return *this; + } + + ~basic_process_handle_win() + { + if (handle_.is_open()) + { + error_code ec; + handle_.close(ec); + } + } + + native_handle_type native_handle() + { return handle_.native_handle(); } + + pid_type id() const + { return pid_; } + + void terminate_if_running(error_code &) + { + detail::terminate_if_running_(handle_.native_handle()); + } + + void terminate_if_running() + { + detail::terminate_if_running_(handle_.native_handle()); + } + + void wait(native_exit_code_type &exit_status, error_code &ec) + { + if (!detail::check_handle_(handle_.native_handle(), ec)) + return; + + handle_.wait(ec); + if (!ec) + detail::get_exit_code_(handle_.native_handle(), exit_status, ec); + } + + + void wait(native_exit_code_type &exit_status) + { + error_code ec; + wait(exit_status, ec); + if (ec) + detail::throw_error(ec, "wait(pid)"); + } + + void interrupt(error_code &ec) + { + if (!detail::check_pid_(pid_, ec)) + return; + + detail::interrupt_(pid_, ec); + } + + void interrupt() + { + error_code ec; + interrupt(ec); + if (ec) + detail::throw_error(ec, "interrupt"); + } + + void request_exit(error_code &ec) + { + if (!detail::check_pid_(pid_, ec)) + return; + + detail::request_exit_(pid_, ec); + } + + void request_exit() + { + error_code ec; + request_exit(ec); + if (ec) + detail::throw_error(ec, "request_exit"); + } + + void terminate(native_exit_code_type &exit_status, error_code &ec) + { + if (!detail::check_handle_(handle_.native_handle(), ec)) + return; + + detail::terminate_(handle_.native_handle(), ec, exit_status); + if (!ec) + wait(exit_status, ec); + + } + + void terminate(native_exit_code_type &exit_status) + { + error_code ec; + terminate(exit_status, ec); + if (ec) + detail::throw_error(ec, "terminate"); + } + + bool running(native_exit_code_type &exit_code, error_code & ec) + { + if (!detail::check_handle_(handle_.native_handle(), ec)) + return false; + + native_exit_code_type code; + //single value, not needed in the winapi. + detail::check_running_(handle_.native_handle(), ec, code); + if (ec) + return false; + + if (process_is_running(code)) + return true; + else + { + exit_code = code; + return false; + } + } + + bool running(native_exit_code_type &exit_code) + { + error_code ec; + bool res = running(exit_code, ec); + if (ec) + detail::throw_error(ec, "is_running"); + return res; + } + + bool is_open() const + { + return handle_.is_open(); + } + + template + BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(WaitHandler, void (error_code, native_exit_code_type)) + async_wait(WaitHandler &&handler BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)) + { + return BOOST_PROCESS_V2_ASIO_NAMESPACE::async_compose( + async_wait_op_{handle_}, handler, handle_ + ); + } + template + friend struct basic_process_handle_win; + private: + pid_type pid_; + handle_type handle_; + + struct async_wait_op_ + { + handle_type &handle; + + template + void operator()(Self &&self) + { + handle.async_wait(std::move(self)); + } + + template + void operator()(Self &&self, error_code ec) + { + native_exit_code_type exit_code{}; + if (!ec) + detail::get_exit_code_(handle.native_handle(), exit_code, ec); + std::move(self).complete(ec, exit_code); + } + }; +}; + +#if !defined(BOOST_PROCESS_V2_HEADER_ONLY) +extern template struct basic_process_handle_win<>; +#endif + +} + +BOOST_PROCESS_V2_END_NAMESPACE + + +#endif //BOOST_PROCESS_V2_DETAIL_PROCESS_HANDLE_WINDOWS_HPP diff --git a/include/boost/process/v2/detail/throw_error.hpp b/include/boost/process/v2/detail/throw_error.hpp new file mode 100644 index 000000000..4ddb682ce --- /dev/null +++ b/include/boost/process/v2/detail/throw_error.hpp @@ -0,0 +1,38 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_DETAIL_THROW_ERROR_HPP +#define BOOST_PROCESS_V2_DETAIL_THROW_ERROR_HPP + +#include + +BOOST_PROCESS_V2_BEGIN_NAMESPACE +namespace detail +{ + +BOOST_PROCESS_V2_DECL void do_throw_error(const error_code& err); +BOOST_PROCESS_V2_DECL void do_throw_error(const error_code& err, const char* location); + +inline void throw_error(const error_code& err) +{ + if (err) + do_throw_error(err); +} + +inline void throw_error(const error_code& err, const char* location) +{ + if (err) + do_throw_error(err, location); +} + +} +BOOST_PROCESS_V2_END_NAMESPACE + +#if defined(BOOST_PROCESS_V2_HEADER_ONLY) + +#include + +#endif + +#endif //BOOST_PROCESS_V2_DETAIL_THROW_ERROR_HPP diff --git a/include/boost/process/v2/detail/throw_exception.hpp b/include/boost/process/v2/detail/throw_exception.hpp new file mode 100644 index 000000000..0ef29d08f --- /dev/null +++ b/include/boost/process/v2/detail/throw_exception.hpp @@ -0,0 +1,39 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_DETAIL_THROW_EXCEPTION_HPP +#define BOOST_PROCESS_V2_DETAIL_THROW_EXCEPTION_HPP + +#include + +#if !defined(BOOST_PROCESS_V2_STANDALONE) + +#include + +#endif + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +namespace detail +{ + +#if defined(BOOST_PROCESS_V2_STANDALONE) + +template +inline void throw_exception(const Exception& e) +{ + throw e; +} + +#else + +using boost::throw_exception; + +#endif + +} + +BOOST_PROCESS_V2_END_NAMESPACE + +#endif //BOOST_PROCESS_V2_DETAIL_THROW_EXCEPTION_HPP diff --git a/include/boost/process/v2/detail/utf8.hpp b/include/boost/process/v2/detail/utf8.hpp new file mode 100644 index 000000000..edceeb056 --- /dev/null +++ b/include/boost/process/v2/detail/utf8.hpp @@ -0,0 +1,95 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_DETAIL_UTF8_HPP +#define BOOST_PROCESS_V2_DETAIL_UTF8_HPP + +#include +#include + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +namespace detail +{ + +BOOST_PROCESS_V2_DECL std::size_t size_as_utf8(const wchar_t * in, std::size_t size, error_code & ec); +BOOST_PROCESS_V2_DECL std::size_t size_as_wide(const char * in, std::size_t size, error_code & ec); + +BOOST_PROCESS_V2_DECL std::size_t convert_to_utf8(const wchar_t * in, std::size_t size, + char * out, std::size_t max_size, error_code & ec); +BOOST_PROCESS_V2_DECL std::size_t convert_to_wide(const char * in, std::size_t size, + wchar_t * out, std::size_t max_size, error_code & ec); + +template, + typename Allocator = std::allocator, typename CharIn, + typename = typename std::enable_if::value>::type> +BOOST_PROCESS_V2_DECL +std::basic_string conv_string( + const CharIn * data, std::size_t size, + const Allocator allocator = Allocator{}) +{ + return std::basic_string(data, size, allocator); +} + + +template, + typename Allocator = std::allocator, + typename = typename std::enable_if::value>::type> +BOOST_PROCESS_V2_DECL +std::basic_string conv_string( + const wchar_t * data, std::size_t size, + const Allocator allocator = Allocator{}) +{ + error_code ec; + const auto req_size = size_as_utf8(data, size, ec); + if (ec) + detail::throw_error(ec, "size_as_utf8"); + + std::basic_string res(allocator); + res.resize(req_size); + + auto res_size = convert_to_utf8(data, size, &res.front(), req_size, ec); + if (ec) + detail::throw_error(ec, "convert_to_utf8"); + + res.resize(res_size); + return res; +} + +template, + typename Allocator = std::allocator, + typename = typename std::enable_if::value>::type> +BOOST_PROCESS_V2_DECL +std::basic_string conv_string( + const char * data, std::size_t size, + const Allocator allocator = Allocator{}) +{ + error_code ec; + const auto req_size = size_as_wide(data, size, ec); + if (ec) + detail::throw_error(ec, "size_as_wide"); + + std::basic_string res(allocator); + res.resize(req_size); + + auto res_size = convert_to_wide(data, size, &res.front(), req_size, ec); + if (ec) + detail::throw_error(ec, "convert_to_wide"); + + res.resize(res_size); + return res; +} + +} + +BOOST_PROCESS_V2_END_NAMESPACE + +#if defined(BOOST_PROCESS_V2_HEADER_ONLY) + +#include + +#endif + + +#endif //BOOST_PROCESS_V2_DETAIL_UTF8_HPP diff --git a/include/boost/process/v2/environment.hpp b/include/boost/process/v2/environment.hpp new file mode 100644 index 000000000..2016a6d9a --- /dev/null +++ b/include/boost/process/v2/environment.hpp @@ -0,0 +1,1892 @@ +// +// process/environment.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2021 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_PROCESS_V2_ENVIRONMENT_HPP +#define BOOST_PROCESS_V2_ENVIRONMENT_HPP + +#include +#include +#include +#include +#include +#include + +#if !defined(GENERATING_DOCUMENTATION) +#if defined(BOOST_PROCESS_V2_WINDOWS) +#include +#else +#include +#endif +#endif + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +/// Namespace for information and functions regarding the calling process. +namespace environment +{ + +#if defined(GENERATING_DOCUMENTATION) + +/// A char traits type that reflects the OS rules for string representing environment keys. +/** Can be an alias of std::char_traits. May only be defined for `char` and `wchar_t`. + * + * Windows treats keys as case-insensitive yet perserving. The char traits are made to reflect + * that behaviour. +*/ +tempalte +using key_char_traits = implementation_defined ; + +/// A char traits type that reflects the OS rules for string representing environment values. +/** Can be an alias of std::char_traits. May only be defined for `char` and `wchar_t`. +*/ +tempalte +using value_char_traits = implementation_defined ; + +/// The character type used by the environment. Either `char` or `wchar_t`. +using char_type = implementation_defined ; + +/// The equal character in an environment string used to separate key and value. +constexpr char_type equality_sign = implementation_defined; + +/// The delimiter in environemtn lists. Commonly used by the `PATH` variable. +constexpr char_type delimiter = implementation_defined; + +/// The native handle of an environment. Note that this can be an owning pointer and is generally not thread safe. +using native_handle = implementation_defined; + + +#endif + + +/// The iterator used by a value or value_view to iterator through environments that are lists. +struct value_iterator +{ + using string_view_type = basic_string_view>; + using difference_type = int; + using reference = string_view_type; + using iterator_category = std::forward_iterator_tag; + + value_iterator & operator++() + { + const auto delim = view_.find(delimiter); + if (delim != string_view_type::npos) + view_ = view_.substr(delim + 1); + else + view_ = view_.substr(view_.size()); + return *this; + } + + value_iterator operator++(int) + { + auto last = *this; + ++(*this); + return last; + } + string_view_type operator*() const + { + const auto delim = view_.find(delimiter); + if (delim == string_view_type::npos) + return view_; + else + return view_.substr(0, delim); + } + + value_iterator() = default; + value_iterator(const value_iterator & ) = default; + value_iterator(string_view_type view, std::size_t offset = 0u) : view_(view.substr(offset)) + { + } + + friend bool operator==(const value_iterator & l, const value_iterator & r) { return l.view_ == r.view_; } + friend bool operator!=(const value_iterator & l, const value_iterator & r) { return l.view_ != r.view_; } + + private: + string_view_type view_; +}; + +/// A view type for a key of an environment +struct key_view +{ + using value_type = char_type; + using traits_type = key_char_traits; + using string_view_type = basic_string_view; + using string_type = std::basic_string>; + + key_view() noexcept = default; + key_view( const key_view& p ) = default; + key_view( key_view&& p ) noexcept = default; + template::value>::type> + key_view( const Source& source ) : value_(source) {} + key_view( const char_type * p) : value_(p) {} + key_view( char_type * p) : value_(p) {} + + ~key_view() = default; + + key_view& operator=( const key_view& p ) = default; + key_view& operator=( key_view&& p ) noexcept = default; + key_view& operator=( string_view_type source ) + { + value_ = source; + return *this; + } + + void swap( key_view& other ) noexcept + { + std::swap(value_, other.value_); + } + + string_view_type native() const noexcept {return value_;} + + operator string_view_type() const {return native();} + + int compare( const key_view& p ) const noexcept {return value_.compare(p.value_);} + int compare( string_view_type str ) const {return value_.compare(str);} + int compare( const value_type* s ) const {return value_.compare(s);} + + template< class CharT, class Traits = std::char_traits, + class Alloc = std::allocator > + std::basic_string + basic_string( const Alloc& alloc = Alloc()) const + { + return BOOST_PROCESS_V2_NAMESPACE::detail::conv_string( + value_.data(), value_.size(), alloc); + } + + std::string string() const {return basic_string();} + std::wstring wstring() const {return basic_string();} + + string_type native_string() const + { + return basic_string>(); + } + + friend bool operator==(key_view l, key_view r) { return l.value_ == r.value_; } + friend bool operator!=(key_view l, key_view r) { return l.value_ != r.value_; } + friend bool operator<=(key_view l, key_view r) { return l.value_ <= r.value_; } + friend bool operator>=(key_view l, key_view r) { return l.value_ >= r.value_; } + friend bool operator< (key_view l, key_view r) { return l.value_ < r.value_; } + friend bool operator> (key_view l, key_view r) { return l.value_ > r.value_; } + + bool empty() const {return value_.empty(); } + + template< class CharT, class Traits > + friend std::basic_ostream& + operator<<( std::basic_ostream& os, const key_view& p ) + { + os << BOOST_PROCESS_V2_NAMESPACE::quoted(p.basic_string()); + return os; + } + + template< class CharT, class Traits > + friend std::basic_istream& + operator>>( std::basic_istream& is, key_view& p ) + { + std::basic_string t; + is >> BOOST_PROCESS_V2_NAMESPACE::quoted(t); + p = t; + return is; + } + const value_type * data() const {return value_.data(); } + std::size_t size() const {return value_.size(); } + private: + string_view_type value_; +}; + +/// A view for a value in an environment +struct value_view +{ + using value_type = char_type; + using string_view_type = basic_cstring_ref>; + using string_type = std::basic_string>; + using traits_type = value_char_traits; + + value_view() noexcept = default; + value_view( const value_view& p ) = default; + value_view( value_view&& p ) noexcept = default; + template::value>::type> + value_view( const Source& source ) : value_(source) {} + value_view( const char_type * p) : value_(p) {} + value_view( char_type * p) : value_(p) {} + + ~value_view() = default; + + value_view& operator=( const value_view& p ) = default; + value_view& operator=( value_view&& p ) noexcept = default; + value_view& operator=( string_view_type source ) + { + value_ = source; + return *this; + } + + void swap( value_view& other ) noexcept + { + std::swap(value_, other.value_); + } + + string_view_type native() const noexcept {return value_;} + + operator string_view_type() const {return native();} + operator typename string_view_type::string_view_type() const {return value_; } + + int compare( const value_view& p ) const noexcept {return value_.compare(p.value_);} + int compare( string_view_type str ) const {return value_.compare(str);} + int compare( const value_type* s ) const {return value_.compare(s);} + + template< class CharT, class Traits = std::char_traits, + class Alloc = std::allocator > + std::basic_string + basic_string( const Alloc& alloc = Alloc() ) const + { + return BOOST_PROCESS_V2_NAMESPACE::detail::conv_string( + value_.data(), value_.size(), alloc); + } + + std::string string() const {return basic_string();} + std::wstring wstring() const {return basic_string();} + + string_type native_string() const + { + return basic_string>(); + } + + bool empty() const {return value_.empty(); } + + friend bool operator==(value_view l, value_view r) { return l.value_ == r.value_; } + friend bool operator!=(value_view l, value_view r) { return l.value_ != r.value_; } + friend bool operator<=(value_view l, value_view r) { return l.value_ <= r.value_; } + friend bool operator>=(value_view l, value_view r) { return l.value_ >= r.value_; } + friend bool operator< (value_view l, value_view r) { return l.value_ < r.value_; } + friend bool operator> (value_view l, value_view r) { return l.value_ > r.value_; } + + + template< class CharT, class Traits > + friend std::basic_ostream& + operator<<( std::basic_ostream& os, const value_view& p ) + { + os << BOOST_PROCESS_V2_NAMESPACE::quoted(p.basic_string()); + return os; + } + + template< class CharT, class Traits > + friend std::basic_istream& + operator>>( std::basic_istream& is, value_view& p ) + { + std::basic_string t; + is >> BOOST_PROCESS_V2_NAMESPACE::quoted(t); + p = t; + return is; + } + value_iterator begin() const {return value_iterator(value_.data());} + value_iterator end() const {return value_iterator(value_.data() , value_.size());} + + const char_type * c_str() {return value_.c_str(); } + const value_type * data() const {return value_.data(); } + std::size_t size() const {return value_.size(); } + + private: + string_view_type value_; +}; + +/// A view for a key value pair in an environment +struct key_value_pair_view +{ + using value_type = char_type; + using string_type = std::basic_string; + using string_view_type = basic_cstring_ref; + using traits_type = std::char_traits; + + key_value_pair_view() noexcept = default; + key_value_pair_view( const key_value_pair_view& p ) = default; + key_value_pair_view( key_value_pair_view&& p ) noexcept = default; + template::value>::type> + key_value_pair_view( const Source& source ) : value_(source) {} + + key_value_pair_view( const char_type * p) : value_(p) {} + key_value_pair_view( char_type * p) : value_(p) {} + + + ~key_value_pair_view() = default; + + key_value_pair_view& operator=( const key_value_pair_view& p ) = default; + key_value_pair_view& operator=( key_value_pair_view&& p ) noexcept = default; + + void swap( key_value_pair_view& other ) noexcept + { + std::swap(value_, other.value_); + } + + string_view_type native() const noexcept {return value_;} + + operator string_view_type() const {return native();} + operator typename string_view_type::string_view_type() const {return value_; } + + int compare( key_value_pair_view p ) const noexcept + { + const auto c = key().compare(p.key()); + if (c != 0) + return c; + return value().compare(p.value()); + } + int compare( const string_type& str ) const + { + return compare(key_value_pair_view(str)); + } + int compare( string_view_type str ) const + { + string_type st(str.data(), str.size()); + return compare(st); + } + int compare( const value_type* s ) const + { + return compare(key_value_pair_view(s)); + } + + template< class CharT, class Traits = std::char_traits, class Alloc = std::allocator > + std::basic_string + basic_string( const Alloc& alloc = Alloc()) const + { + return BOOST_PROCESS_V2_NAMESPACE::detail::conv_string(value_.begin(), value_.size(), alloc); + } + + std::string string() const {return basic_string();} + std::wstring wstring() const {return basic_string();} + + string_type native_string() const + { + return basic_string(); + } + + bool empty() const {return value_.empty(); } + + key_view key() const + { + auto eq = value_.find(equality_sign); + if (eq == 0) + { + auto eq2 = value_.find(equality_sign, 1); + if (eq2 != string_type::npos) + eq = eq2; + } + const auto res = native().substr(0, eq == string_view_type::npos ? value_.size() : eq); + return key_view::string_view_type(res.data(), res.size()); + } + value_view value() const + { + auto eq = value_.find(equality_sign); + if (eq == 0) + { + auto eq2 = value_.find(equality_sign, 1); + if (eq2 != string_type::npos) + eq = eq2; + } + return environment::value_view(native().substr(eq + 1)); + } + + friend bool operator==(key_value_pair_view l, key_value_pair_view r) { return l.compare(r) == 0; } + friend bool operator!=(key_value_pair_view l, key_value_pair_view r) { return l.compare(r) != 0; } + friend bool operator<=(key_value_pair_view l, key_value_pair_view r) { return l.compare(r) <= 0; } + friend bool operator>=(key_value_pair_view l, key_value_pair_view r) { return l.compare(r) >= 0; } + friend bool operator< (key_value_pair_view l, key_value_pair_view r) { return l.compare(r) < 0; } + friend bool operator> (key_value_pair_view l, key_value_pair_view r) { return l.compare(r) > 0; } + + + template< class CharT, class Traits > + friend std::basic_ostream& + operator<<( std::basic_ostream& os, const key_value_pair_view& p ) + { + os << BOOST_PROCESS_V2_NAMESPACE::quoted(p.basic_string()); + return os; + } + + template< class CharT, class Traits > + friend std::basic_istream& + operator>>( std::basic_istream& is, key_value_pair_view& p ) + { + std::basic_string t; + is >> BOOST_PROCESS_V2_NAMESPACE::quoted(t); + p = t; + return is; + } + + template + inline auto get() const -> typename conditional::type; + const value_type * c_str() const noexcept + { + return value_.data(); + } + const value_type * data() const {return value_.data(); } + std::size_t size() const {return value_.size(); } + + private: + + string_view_type value_; +}; + +template<> +inline key_view key_value_pair_view::get<0u>() const +{ + return key(); +} + +template<> +inline value_view key_value_pair_view::get<1u>() const +{ + return value(); +} + +namespace detail +{ + +template +std::size_t hash_step(std::size_t prev, Char c, std::char_traits) +{ + return prev ^ (c << 1); +} + +} + +inline std::size_t hash_value(const key_view & value) +{ + std::size_t hash = 0u; + for (auto c = value.data(); *c != *v2::detail::null_char_(*c); c++) + hash = detail::hash_step(hash, *c, key_view::traits_type{}); + return hash ; +} + + +inline std::size_t hash_value(const BOOST_PROCESS_V2_NAMESPACE::environment::value_view & value) +{ + std::size_t hash = 0u; + for (auto c = value.data(); *c != *v2::detail::null_char_(*c); c++) + hash = detail::hash_step(hash, *c, value_view::traits_type{}); + return hash ; +} + + +inline std::size_t hash_value(const BOOST_PROCESS_V2_NAMESPACE::environment::key_value_pair_view & value) +{ + std::size_t hash = 0u; + for (auto c = value.data(); *c != *v2::detail::null_char_(*c); c++) + hash = detail::hash_step(hash, *c, key_value_pair_view::traits_type{}); + return hash ; +} + +/// A class representing a key within an environment. +struct key +{ + using value_type = char_type; + using traits_type = key_char_traits; + using string_type = std::basic_string; + using string_view_type = basic_string_view; + + key() noexcept = default; + key( const key& p ) = default; + key( key&& p ) noexcept = default; + key( const string_type& source ) : value_(source) {} + key( string_type&& source ) : value_(std::move(source)) {} + key( const value_type * raw ) : value_(raw) {} + key( value_type * raw ) : value_(raw) {} + + explicit key(key_view kv) : value_(kv.native_string()) {} + + + template< class Source > + key( const Source& source, + decltype(source.data()) = nullptr, + decltype(source.size()) = 0u) + : value_( + BOOST_PROCESS_V2_NAMESPACE::detail::conv_string( + source.data(), source.size())) + { + } + + key(const typename conditional::value, wchar_t, char>::type * raw) + : value_(BOOST_PROCESS_V2_NAMESPACE::detail::conv_string( + raw, std::char_traits::type>::type>::length(raw))) + { + } + + template< class InputIt > + key( InputIt first, InputIt last) + : key(std::basic_string::type>::value_type>(first, last)) + { + } + + ~key() = default; + + key& operator=( const key& p ) = default; + key& operator=( key&& p ) noexcept = default; + key& operator=( string_type&& source ) + { + value_ = std::move(source); + return *this; + } + template< class Source > + key& operator=( const Source& source ) + { + value_ = BOOST_PROCESS_V2_NAMESPACE::detail::conv_string(source.data(), source.size()); + return *this; + } + + key& assign( string_type&& source ) + { + value_ = std::move(source); + return *this; + } + template< class Source > + key& assign( const Source& source ) + { + value_ = BOOST_PROCESS_V2_NAMESPACE::detail::conv_string(source.data(), source.size()); + return *this; + } + + template< class InputIt > + key& assign( InputIt first, InputIt last ) + { + + return assign(std::basic_string::type>::value_type>(first, last)); + } + + void clear() {value_.clear();} + + void swap( key& other ) noexcept + { + std::swap(value_, other.value_); + } + + const value_type* c_str() const noexcept {return value_.c_str();} + const string_type& native() const noexcept {return value_;} + string_view_type native_view() const noexcept {return value_;} + + operator string_type() const {return native();} + operator string_view_type() const {return native_view();} + + int compare( const key& p ) const noexcept {return value_.compare(p.value_);} + int compare( const string_type& str ) const {return value_.compare(str);} + int compare( string_view_type str ) const {return -str.compare(value_);} + int compare( const value_type* s ) const {return value_.compare(s);} + + template< class CharT, class Traits = std::char_traits, + class Alloc = std::allocator > + std::basic_string + basic_string( const Alloc& alloc = Alloc()) const + { + return BOOST_PROCESS_V2_NAMESPACE::detail::conv_string( + value_.data(), value_.size(), alloc); + } + + + std::string string() const {return basic_string();} + std::wstring wstring() const {return basic_string();} + + const string_type & native_string() const + { + return value_; + } + + bool empty() const {return value_.empty(); } + + friend bool operator==(const key & l, const key & r) { return l.value_ == r.value_; } + friend bool operator!=(const key & l, const key & r) { return l.value_ != r.value_; } + friend bool operator<=(const key & l, const key & r) { return l.value_ <= r.value_; } + friend bool operator>=(const key & l, const key & r) { return l.value_ >= r.value_; } + friend bool operator< (const key & l, const key & r) { return l.value_ < r.value_; } + friend bool operator> (const key & l, const key & r) { return l.value_ > r.value_; } + + template< class CharT, class Traits > + friend std::basic_ostream& + operator<<( std::basic_ostream& os, const key& p ) + { + os << BOOST_PROCESS_V2_NAMESPACE::quoted(p.basic_string()); + return os; + } + + template< class CharT, class Traits > + friend std::basic_istream& + operator>>( std::basic_istream& is, key& p ) + { + std::basic_string t; + is >> BOOST_PROCESS_V2_NAMESPACE::quoted(t); + p = t; + return is; + } + const value_type * data() const {return value_.data(); } + std::size_t size() const {return value_.size(); } + + private: + string_type value_; +}; + +#if !defined(GENERATING_DOCUMENTATION) + +template +typename std::enable_if< + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value) + || + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value), + bool>::type +operator==(const T &l, const U & r) { return key_view(l) == key_view(r); } + +template +typename std::enable_if< + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value) + || + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value), + bool>::type +operator!=(const T &l, const U & r) { return key_view(l) != key_view(r); } + +template +typename std::enable_if< + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value) + || + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value), + bool>::type +operator<=(const T &l, const U & r) { return key_view(l) <= key_view(r); } + +template +typename std::enable_if< + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value) + || + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value), + bool>::type +operator <(const T &l, const U & r) { return key_view(l) < key_view(r); } + +template +typename std::enable_if< + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value) + || + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value), + bool>::type +operator>=(const T &l, const U & r) { return key_view(l) >= key_view(r); } + +template +typename std::enable_if< + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value) + || + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value), + bool>::type +operator >(const T &l, const U & r) { return key_view(l) > key_view(r); } + +#else + + +bool operator==(const value_view &, const value_view); +bool operator!=(const value_view &, const value_view); +bool operator<=(const value_view &, const value_view); +bool operator< (const value_view &, const value_view); +bool operator> (const value_view &, const value_view); +bool operator>=(const value_view &, const value_view); + +#endif + + +struct value +{ + using value_type = char_type; + using traits_type = value_char_traits; + using string_type = std::basic_string; + using string_view_type = basic_cstring_ref; + + value() noexcept = default; + value( const value& p ) = default; + + value( const string_type& source ) : value_(source) {} + value( string_type&& source ) : value_(std::move(source)) {} + value( const value_type * raw ) : value_(raw) {} + value( value_type * raw ) : value_(raw) {} + + explicit value(value_view kv) : value_(kv.c_str()) {} + + template< class Source > + value( const Source& source, + decltype(source.data()) = nullptr, + decltype(source.size()) = 0u) + : value_(BOOST_PROCESS_V2_NAMESPACE::detail::conv_string( + source.data(), source.size())) + { + } + + value(const typename conditional::value, wchar_t, char>::type * raw) + : value_(BOOST_PROCESS_V2_NAMESPACE::detail::conv_string( + raw, std::char_traits::type>::type>::length(raw))) + { + } + + template< class InputIt > + value( InputIt first, InputIt last) + : value(std::basic_string::type>::value_type>(first, last)) + { + } + + ~value() = default; + + value& operator=( const value& p ) = default; + value& operator=( value&& p ) noexcept = default; + value& operator=( string_type&& source ) + { + value_ = std::move(source); + return *this; + } + template< class Source > + value& operator=( const Source& source ) + { + value_ = BOOST_PROCESS_V2_NAMESPACE::detail::conv_string( + source.data(), source.size); + return *this; + } + + value& assign( string_type&& source ) + { + value_ = std::move(source); + return *this; + } + template< class Source > + value& assign( const Source& source ) + { + value_ = BOOST_PROCESS_V2_NAMESPACE::detail::conv_string( + source.data(), source.size()); + return *this; + } + + template< class InputIt > + value& assign( InputIt first, InputIt last ) + { + return assign(std::basic_string::type>::value_type>(first, last)); + } + + void push_back(const value & sv) + { + (value_ += delimiter) += sv; + } + + void clear() {value_.clear();} + + void swap( value& other ) noexcept + { + std::swap(value_, other.value_); + } + + const value_type* c_str() const noexcept {return value_.c_str();} + const string_type& native() const noexcept {return value_;} + string_view_type native_view() const noexcept {return value_;} + + operator string_type() const {return native();} + operator string_view_type() const {return native_view();} + operator typename string_view_type::string_view_type() const {return value_; } + + int compare( const value& p ) const noexcept {return value_.compare(p.value_);} + int compare( const string_type& str ) const {return value_.compare(str);} + int compare( string_view_type str ) const {return -str.compare(value_);} + int compare( const value_type* s ) const {return value_.compare(s);} + + template< class CharT, class Traits = std::char_traits, + class Alloc = std::allocator > + std::basic_string + basic_string( const Alloc& alloc = Alloc()) const + { + return BOOST_PROCESS_V2_NAMESPACE::detail::conv_string( + value_.data(), value_.size(),alloc); + } + + std::string string() const {return basic_string();} + std::wstring wstring() const {return basic_string();} + + + const string_type & native_string() const + { + return value_; + } + + bool empty() const {return value_.empty(); } + + friend bool operator==(const value & l, const value & r) { return l.value_ == r.value_; } + friend bool operator!=(const value & l, const value & r) { return l.value_ != r.value_; } + friend bool operator<=(const value & l, const value & r) { return l.value_ <= r.value_; } + friend bool operator>=(const value & l, const value & r) { return l.value_ >= r.value_; } + friend bool operator< (const value & l, const value & r) { return l.value_ < r.value_; } + friend bool operator> (const value & l, const value & r) { return l.value_ > r.value_; } + + template< class CharT, class Traits > + friend std::basic_ostream& + operator<<( std::basic_ostream& os, const value& p ) + { + os << BOOST_PROCESS_V2_NAMESPACE::quoted(p.basic_string()); + return os; + } + + template< class CharT, class Traits > + friend std::basic_istream& + operator>>( std::basic_istream& is, value& p ) + { + std::basic_string t; + is >> BOOST_PROCESS_V2_NAMESPACE::quoted(t); + p = t; + return is; + } + + value_iterator begin() const {return value_iterator(value_.data());} + value_iterator end() const {return value_iterator(value_.data(), value_.size());} + const value_type * data() const {return value_.data(); } + std::size_t size() const {return value_.size(); } + + private: + string_type value_; +}; + + +#if !defined(GENERATING_DOCUMENTATION) + +template +typename std::enable_if< + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value) + || + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value), + bool>::type +operator==(const T &l, const U & r) { return value_view(l) == value_view(r); } + +template +typename std::enable_if< + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value) + || + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value), + bool>::type +operator!=(const T &l, const U & r) { return value_view(l) != value_view(r); } + +template +typename std::enable_if< + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value) + || + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value), + bool>::type +operator<=(const T &l, const U & r) { return value_view(l) <= value_view(r); } + +template +typename std::enable_if< + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value) + || + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value), + bool>::type +operator <(const T &l, const U & r) { return value_view(l) < value_view(r); } + +template +typename std::enable_if< + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value) + || + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value), + bool>::type +operator>=(const T &l, const U & r) { return value_view(l) >= value_view(r); } + +template +typename std::enable_if< + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value) + || + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value), + bool>::type +operator >(const T &l, const U & r) { return value_view(l) > value_view(r); } + +#else + +bool operator==(const value_view &, const value_view); +bool operator!=(const value_view &, const value_view); +bool operator<=(const value_view &, const value_view); +bool operator< (const value_view &, const value_view); +bool operator> (const value_view &, const value_view); +bool operator>=(const value_view &, const value_view); + +#endif + +struct key_value_pair +{ + using value_type = char_type; + using traits_type = std::char_traits; + using string_type = std::basic_string; + using string_view_type = basic_cstring_ref; + + key_value_pair() noexcept = default; + key_value_pair( const key_value_pair& p ) = default; + key_value_pair( key_value_pair&& p ) noexcept = default; + key_value_pair(key_view key, value_view value) : value_(key.basic_string() + equality_sign + + value.basic_string()) {} + + key_value_pair(key_view key, std::initializer_list> values) + { + const auto sz = std::accumulate(values.begin(), values.end(), + key.size(), [](std::size_t sz, const basic_string_view & str) { return sz + str.size() + 1;}); + + value_.reserve(sz); + value_.append(key.data(), key.size()); + value_ += equality_sign; + for (auto & value : values) + { + if (value_.back() != equality_sign) + value_ += delimiter; + value_.append(value.data(), value.size()); + } + } + + key_value_pair( const string_type& source ) : value_(source) {} + key_value_pair( string_type&& source ) : value_(std::move(source)) {} + key_value_pair( const value_type * raw ) : value_(raw) {} + key_value_pair( value_type * raw ) : value_(raw) {} + + explicit key_value_pair(key_value_pair_view kv) : value_(kv.c_str()) {} + + template< class Source > + key_value_pair( const Source& source, + decltype(source.data()) = nullptr, + decltype(source.size()) = 0u) + : value_(BOOST_PROCESS_V2_NAMESPACE::detail::conv_string( + source.data(), source.size())) + { + } + + template< typename Key, + typename Value > + key_value_pair( + const std::pair & kv/*, + typename std::enable_if::value && + std::is_constructible::value + >::type = 0*/) : value_(((struct key)(kv.first)).string() + equality_sign + ((struct value)(kv.second)).string()) + {} + + key_value_pair(const typename conditional::value, wchar_t, char>::type * raw) + : value_(BOOST_PROCESS_V2_NAMESPACE::detail::conv_string( + raw, + std::char_traits::type>::type>::length(raw))) + { + } + + template< class InputIt , typename std::iterator_traits::iterator_category> + key_value_pair( InputIt first, InputIt last ) + : key_value_pair(std::basic_string::type>::value_type>(first, last)) + { + } + + ~key_value_pair() = default; + + key_value_pair& operator=( const key_value_pair& p ) = default; + key_value_pair& operator=( key_value_pair&& p ) noexcept = default; + key_value_pair& operator=( string_type&& source ) + { + value_ = std::move(source); + return *this; + } + template< class Source > + key_value_pair& operator=( const Source& source ) + { + value_ = BOOST_PROCESS_V2_NAMESPACE::detail::conv_string( + source.data(), source.size()); + return *this; + } + + key_value_pair& assign( string_type&& source ) + { + value_ = std::move(source); + return *this; + } + template< class Source > + key_value_pair& assign( const Source& source ) + { + value_ = BOOST_PROCESS_V2_NAMESPACE::detail::conv_string( + source.data(), source.size()); + return *this; + } + + + template< class InputIt > + key_value_pair& assign( InputIt first, InputIt last ) + { + return assign(std::basic_string::type>::value_type>(first, last)); + } + + void clear() {value_.clear();} + + void swap( key_value_pair& other ) noexcept + { + std::swap(value_, other.value_); + } + + const value_type* c_str() const noexcept {return value_.c_str();} + const string_type& native() const noexcept {return value_;} + string_view_type native_view() const noexcept {return value_;} + + operator string_type() const {return native();} + operator string_view_type() const {return native_view();} + operator key_value_pair_view() const {return native_view();} + + int compare( const key_value_pair& p ) const noexcept + { + return key_value_pair_view(*this).compare(key_value_pair_view(p)); + } + + int compare( const string_type& str ) const + { + return key_value_pair_view(*this).compare(str); + } + int compare( string_view_type str ) const + { + return key_value_pair_view(*this).compare(str); + } + int compare( const value_type* s ) const + { + return key_value_pair_view(*this).compare(s); + } + + template< class CharT, class Traits = std::char_traits, class Alloc = std::allocator > + std::basic_string + basic_string( const Alloc& alloc = Alloc() ) const + { + return BOOST_PROCESS_V2_NAMESPACE::detail::conv_string(value_.data(), value_.size(), alloc); + } + + std::string string() const {return basic_string();} + std::wstring wstring() const {return basic_string();} + + const string_type & native_string() const + { + return value_; + } + + friend bool operator==(const key_value_pair & l, const key_value_pair & r) { return l.compare(r) == 0; } + friend bool operator!=(const key_value_pair & l, const key_value_pair & r) { return l.compare(r) != 0; } + friend bool operator<=(const key_value_pair & l, const key_value_pair & r) { return l.compare(r) <= 0; } + friend bool operator>=(const key_value_pair & l, const key_value_pair & r) { return l.compare(r) >= 0; } + friend bool operator< (const key_value_pair & l, const key_value_pair & r) { return l.compare(r) < 0; } + friend bool operator> (const key_value_pair & l, const key_value_pair & r) { return l.compare(r) > 0; } + + bool empty() const {return value_.empty(); } + + struct key_view key() const + { + auto eq = value_.find(equality_sign); + if (eq == 0) + { + auto eq2 = value_.find(equality_sign, 1); + if (eq2 != string_type::npos) + eq = eq2; + } + const auto k = native_view().substr(0, eq); + + return BOOST_PROCESS_V2_NAMESPACE::environment::key_view::string_view_type (k.data(), k.size()); + } + struct value_view value() const + { + auto eq = value_.find(equality_sign); + if (eq == 0) + { + auto eq2 = value_.find(equality_sign, 1); + if (eq2 != string_type::npos) + eq = eq2; + } + return value_view::string_view_type(native_view().substr(eq + 1)); + } + + template< class CharT, class Traits > + friend std::basic_ostream& + operator<<( std::basic_ostream& os, const key_value_pair& p ) + { + os << BOOST_PROCESS_V2_NAMESPACE::quoted(p.basic_string()); + return os; + } + + template< class CharT, class Traits > + friend std::basic_istream& + operator>>( std::basic_istream& is, key_value_pair& p ) + { + is >> BOOST_PROCESS_V2_NAMESPACE::quoted(p.value_); + return is; + } + + const value_type * data() const {return value_.data(); } + std::size_t size() const {return value_.size(); } + + template + inline auto get() const + -> typename conditional::type; + +private: + string_type value_; +}; + +#if !defined(GENERATING_DOCUMENTATION) + +template +typename std::enable_if< + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value) + || + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value), + bool>::type +operator==(const T &l, const U & r) { return key_value_pair_view(l) == key_value_pair_view(r); } + +template +typename std::enable_if< + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value) + || + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value), + bool>::type +operator!=(const T &l, const U & r) { return key_value_pair_view(l) != key_value_pair_view(r); } + +template +typename std::enable_if< + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value) + || + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value), + bool>::type +operator<=(const T &l, const U & r) { return key_value_pair_view(l) <= key_value_pair_view(r); } + +template +typename std::enable_if< + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value) + || + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value), + bool>::type +operator <(const T &l, const U & r) { return key_value_pair_view(l) < key_value_pair_view(r); } + +template +typename std::enable_if< + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value) + || + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value), + bool>::type +operator>=(const T &l, const U & r) { return key_value_pair_view(l) >= key_value_pair_view(r); } + +template +typename std::enable_if< + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value) + || + ((std::is_same::value || std::is_same::value) && + std::is_convertible::value), + bool>::type +operator >(const T &l, const U & r) { return key_value_pair_view(l) > key_value_pair_view(r); } + +#else + +bool operator==(const key_value_pair_view &, const key_value_pair_view); +bool operator!=(const key_value_pair_view &, const key_value_pair_view); +bool operator<=(const key_value_pair_view &, const key_value_pair_view); +bool operator< (const key_value_pair_view &, const key_value_pair_view); +bool operator> (const key_value_pair_view &, const key_value_pair_view); +bool operator>=(const key_value_pair_view &, const key_value_pair_view); + +#endif + + +template<> +inline key_view key_value_pair::get<0u>() const +{ + return key(); +} + +template<> +inline value_view key_value_pair::get<1u>() const +{ + return value(); +} + +} +BOOST_PROCESS_V2_END_NAMESPACE + +namespace std +{ + +template<> +class tuple_size : integral_constant {}; + +template<> +class tuple_element<0u, BOOST_PROCESS_V2_NAMESPACE::environment::key_value_pair> +{ + public: + using type = BOOST_PROCESS_V2_NAMESPACE::environment::key_view; +}; + +template<> +class tuple_element<1u, BOOST_PROCESS_V2_NAMESPACE::environment::key_value_pair> +{ + public: + using type = BOOST_PROCESS_V2_NAMESPACE::environment::value_view; +}; + +template +inline auto get(const BOOST_PROCESS_V2_NAMESPACE::environment::key_value_pair & kvp) + -> typename std::tuple_element::type +{ + return kvp.get(); +} + +template<> +class tuple_size : integral_constant {}; + +template<> +class tuple_element<0u, BOOST_PROCESS_V2_NAMESPACE::environment::key_value_pair_view> +{ + public: + using type = BOOST_PROCESS_V2_NAMESPACE::environment::key_view; +}; + +template<> +class tuple_element<1u, BOOST_PROCESS_V2_NAMESPACE::environment::key_value_pair_view> +{ + public: + using type = BOOST_PROCESS_V2_NAMESPACE::environment::value_view; +}; + +template +inline auto get(BOOST_PROCESS_V2_NAMESPACE::environment::key_value_pair_view kvp) + -> typename std::tuple_element::type +{ + return kvp.get(); +} + +} + +BOOST_PROCESS_V2_BEGIN_NAMESPACE +namespace environment +{ + + +/// A view object for the current environment of this process. +/** + * The view might (windows) or might not (posix) be owning; + * if it owns it will deallocate the on destruction, like a unique_ptr. + * + * Note that accessing the environment in this way is not thread-safe. + * + * @code + * + * void dump_my_env(current_view env = current()) + * { + * for (auto & [k, v] : env) + * std::cout << k.string() << " = " << v.string() << std::endl; + * } + * + * @endcode + * + * + */ +struct current_view +{ + using native_handle_type = environment::native_handle_type; + using value_type = key_value_pair_view; + + current_view() = default; + current_view(current_view && nt) = default; + + native_handle_type native_handle() { return handle_.get(); } + + struct iterator + { + using value_type = key_value_pair_view; + using difference_type = int; + using reference = key_value_pair_view; + using pointer = key_value_pair_view; + using iterator_category = std::forward_iterator_tag; + + iterator() = default; + iterator(const iterator & ) = default; + iterator(const native_iterator &native_handle) : iterator_(native_handle) {} + + iterator & operator++() + { + iterator_ = detail::next(iterator_); + return *this; + } + + iterator operator++(int) + { + auto last = *this; + iterator_ = detail::next(iterator_); + return last; + } + key_value_pair_view operator*() const + { + return detail::dereference(iterator_); + } + + friend bool operator==(const iterator & l, const iterator & r) {return l.iterator_ == r.iterator_;} + friend bool operator!=(const iterator & l, const iterator & r) {return l.iterator_ != r.iterator_;} + + private: + environment::native_iterator iterator_; + }; + + iterator begin() const {return iterator(handle_.get());} + iterator end() const {return iterator(detail::find_end(handle_.get()));} + + private: + + std::unique_ptr::type, + detail::native_handle_deleter> handle_{environment::detail::load_native_handle()}; +}; + +/// Obtain a handle to the current environment +inline current_view current() {return current_view();} + +namespace detail +{ + +template +auto find_key(Environment & env, key_view ky) + -> typename std::enable_if::value, value_view>::type +{ + const auto itr = std::find_if(std::begin(env), std::end(env), + [&](key_value_pair_view vp) + { + auto tmp = std::get<0>(vp) == ky; + if (tmp) + return true; + else + return false; + }); + + if (itr != std::end(env)) + return key_value_pair_view(*itr).value(); + else + return {}; +} + +template +auto find_key(Environment & env, key_view ky) + -> typename std::enable_if< + !std::is_convertible::value && + std::is_convertible::value, + value>::type +{ + const auto itr = std::find_if(std::begin(env), std::end(env), + [&](key_value_pair vp) + { + auto tmp = std::get<0>(vp) == ky; + if (tmp) + return true; + else + return false; + }); + if (itr != std::end(env)) + return key_value_pair(*itr).value(); + else + return {}; +} + + +} + + +/// Find the home folder in an environment-like type. +/** + * @param env The environment to search. Defaults to the current environment of this process + * + * The environment type passed in must be a range with value T that fulfills the following requirements: + * + * For `T value` + * + * - std::get<0>(value) must return a type comparable to `key_view`. + * - std::get<1>(value) must return a type convertible to filesystem::path. + * + * @return A filesystem::path to the home directory or an empty path if it cannot be found. + * + */ +template +inline filesystem::path home(Environment && env = current()) +{ +#if defined(ASIO_WINDOWS) + return detail::find_key(env, L"HOMEDRIVE") + detail::find_key(env, L"HOMEPATH").native_string(); +#else + return detail::find_key(env, "HOME").native_string(); +#endif +} + +/// Find the executable `name` in an environment-like type. +/** + * @param env The environment to search. Defaults to the current environment of this process + * + * The environment type passed in must be a range with value T that fulfills the following requirements: + * + * For `T value` + * + * - std::get<0>(value) must return a type comparable to `key_view`. + * - std::get<1>(value) must return a type convertible to `value_view`. + * + * + * @return A filesystem::path to the executable or an empty path if it cannot be found. + * + */ +template +inline BOOST_PROCESS_V2_NAMESPACE::filesystem::path find_executable( + BOOST_PROCESS_V2_NAMESPACE::filesystem::path name, + Environment && env = current()) +{ + +#if defined(BOOST_PROCESS_V2_WINDOWS) + auto path = detail::find_key(env, L"PATH"); + auto pathext = detail::find_key(env, L"PATHEXT"); + for (auto pp_view : path) + { + // first check if it has the extension already + BOOST_PROCESS_V2_NAMESPACE::filesystem::path full_nm(name); + BOOST_PROCESS_V2_NAMESPACE::filesystem::path pp(pp_view.begin(), pp_view.end()); + auto p = pp / nm; + error_code ec; + + if (detail::is_executable(p, ec) && !ec) + return p; + + for (auto ext : pathext) + { + ec.clear(); + BOOST_PROCESS_V2_NAMESPACE::filesystem::path nm(name); + nm.concat(ext.begin(), ext.end()); + + auto p = pp / nm; + + if (detail::is_executable(p, ec) && !ec) + return p; + } + } +#else + for (auto pp_view : detail::find_key(env, "PATH")) + { + auto p = BOOST_PROCESS_V2_NAMESPACE::filesystem::path(pp_view.begin(), pp_view.end()) / name; + error_code ec; + bool is_exec = detail::is_executable(p, ec); + if (!ec && is_exec) + return p; + } +#endif + return {}; +} + +/// Get an environment variable from the current process. +inline value get(const key & k, error_code & ec) { return detail::get(k.c_str(), ec);} +/// Throwing @overload value get(const key & k, error_code & ec) +inline value get(const key & k) +{ + error_code ec; + auto tmp = detail::get(k.c_str(), ec); + BOOST_PROCESS_V2_NAMESPACE::detail::throw_error(ec, "environment::get"); + return tmp; +} + +/// Disambiguating @overload value get(const key & k, error_code & ec) +inline value get(basic_cstring_ref> k, error_code & ec) +{ + return detail::get(k, ec); +} +/// Disambiguating @overload value get(const key & k) +inline value get(basic_cstring_ref> k) +{ + error_code ec; + auto tmp = detail::get(k, ec); + BOOST_PROCESS_V2_NAMESPACE::detail::throw_error(ec, "environment::get"); + return tmp; +} + + +/// Disambiguating @overload value get(const key & k, error_code & ec) +inline value get(const char_type * c, error_code & ec) { return detail::get(c, ec);} +/// Disambiguating @overload value get(const key & k) +inline value get(const char_type * c) +{ + error_code ec; + auto tmp = detail::get(c, ec); + BOOST_PROCESS_V2_NAMESPACE::detail::throw_error(ec, "environment::get"); + return tmp; +} + +/// Set an environment variable for the current process. +inline void set(const key & k, value_view vw, error_code & ec) { detail::set(k, vw, ec);} +/// Throwing @overload void set(const key & k, value_view vw, error_code & ec) +inline void set(const key & k, value_view vw) +{ + error_code ec; + detail::set(k, vw, ec); + BOOST_PROCESS_V2_NAMESPACE::detail::throw_error(ec, "environment::set"); +} + +/// Disambiguating @overload void set(const key & k, value_view vw, error_code & ec) +inline void set(basic_cstring_ref> k, value_view vw, error_code & ec) { detail::set(k, vw, ec);} +/// Disambiguating @overload void set(const key & k, value_view vw, error_code & ec) +inline void set(basic_cstring_ref> k, value_view vw) +{ + error_code ec; + detail::set(k, vw, ec); + BOOST_PROCESS_V2_NAMESPACE::detail::throw_error(ec, "environment::set"); +} + + +/// Disambiguating @overload void set(const key & k, value_view vw, error_code & ec) +inline void set(const char_type * k, value_view vw, error_code & ec) { detail::set(k, vw, ec);} +/// Disambiguating @overload void set(const key & k, value_view vw, error_code & ec) +inline void set(const char_type * k, value_view vw) +{ + error_code ec; + detail::set(k, vw, ec); + BOOST_PROCESS_V2_NAMESPACE::detail::throw_error(ec, "environment::set"); +} + +/// Disambiguating @overload void set(const key & k, value_view vw, error_code & ec) +template::value>::type> +inline void set(const key & k, const Char * vw, error_code & ec) +{ + value val{vw}; + detail::set(k, val, ec); +} +/// Disambiguating @overload void set(const key & k, value_view vw, error_code & ec) +template::value>::type> +inline void set(const key & k, const Char * vw) +{ + error_code ec; + value val{vw}; + detail::set(k, val, ec); + BOOST_PROCESS_V2_NAMESPACE::detail::throw_error(ec, "environment::set"); +} + +/// Disambiguating @overload void set(const key & k, value_view vw, error_code & ec) +template::value>::type> +inline void set(basic_cstring_ref> k, const Char * vw, error_code & ec) +{ + value val{vw}; + detail::set(k, val, ec); +} + +/// Disambiguating @overload void set(const key & k, value_view vw, error_code & ec) +template::value>::type> +inline void set(basic_cstring_ref> k, const Char * vw) +{ + error_code ec; + value val{vw}; + detail::set(k, val, ec); + BOOST_PROCESS_V2_NAMESPACE::detail::throw_error(ec, "environment::set"); +} + +/// Disambiguating @overload void set(const key & k, value_view vw, error_code & ec) +template::value>::type> +inline void set(const char_type * k, const Char * vw, error_code & ec) +{ + value val{vw}; + detail::set(k, val, ec); +} + +/// Disambiguating @overload void set(const key & k, value_view vw, error_code & ec) +template::value>::type> +inline void set(const char_type * k, const Char * vw) +{ + error_code ec; + value val{vw}; + detail::set(k, val, ec); + BOOST_PROCESS_V2_NAMESPACE::detail::throw_error(ec, "environment::set"); +} + + +/// Remove an environment variable from the current process. +inline void unset(const key & k, error_code & ec) { detail::unset(k, ec);} +/// Throwing @overload void unset(const key & k, error_code & ec) +inline void unset(const key & k) +{ + error_code ec; + detail::unset(k, ec); + BOOST_PROCESS_V2_NAMESPACE::detail::throw_error(ec, "environment::unset"); +} + +/// Disambiguating @overload void unset(const key & k, error_code & ec) +inline void unset(basic_cstring_ref> k, error_code & ec) +{ + detail::unset(k, ec); +} + +/// Disambiguating @overload void unset(const key & k, error_code & ec) +inline void unset(basic_cstring_ref> k) +{ + error_code ec; + detail::unset(k, ec); + BOOST_PROCESS_V2_NAMESPACE::detail::throw_error(ec, "environment::unset"); +} + +/// Disambiguating @overload void unset(const key & k, error_code & ec) +inline void unset(const char_type * c, error_code & ec) { detail::unset(c, ec);} + +/// Disambiguating @overload void unset(const key & k, error_code & ec) +inline void unset(const char_type * c) +{ + error_code ec; + detail::unset(c, ec); + BOOST_PROCESS_V2_NAMESPACE::detail::throw_error(ec, "environment::unset"); +} +} + +// sub process environment stuff + +#if defined(BOOST_PROCESS_V2_WINDOWS) +namespace windows { struct default_launcher ;} +#else +namespace posix { struct default_launcher ;} +#endif + +/// Initializer for the environment of sub process. +/** + * This will set the environment in a subprocess: + * + * @code {.cpp} + * + * process proc{executor, find_executable("printenv"), {"foo"}, process_environment{"foo=bar"}}; + * @endcode + * + * The environment initializer will persist it's state, so that it can + * be used multiple times. Do however note the the Operating System is + * allowed to modify the internal state. + * + * @code {.cpp} + * auto exe = find_executable("printenv"); + * process_environment env = {"FOO=BAR", "BAR=FOO"}; + * + * process proc1(executor, exe, {"FOO"}, env); + * process proc2(executor, exe, {"BAR"}, env); + * @endcode + * + * + */ +struct process_environment +{ + +#if defined(BOOST_PROCESS_V2_WINDOWS) + + + template + void build_env(Args && args, string_view rs) + { + std::size_t length = 0u; + for (string_view v : args) + length += detail::size_as_wide(v.data(), v.size(), ec) + 1u; + + if (ec) + return; + length ++ ; + + unicode_env.resize(length); + + auto itr = &unicode_env.front(); + for (string_view v : args) + { + itr += detail::convert_to_wide( + v.data(), v.size(), + itr, &unicode_env.back() - itr, + ec); + if (ec) + break; + *(itr++) = '\0'; + } + unicode_env.back() = '\0'; + } + template + void build_env(Args && args, wstring_view rs) + { + std::size_t length = 0u; + for (const auto & v : std::forward(args)) + length += v.size() + 1u; + + length ++ ; + + unicode_env.resize(length); + + auto itr = unicode_env.begin(); + for (wstring_view v : args ) + { + itr = std::copy(v.begin(), v.end(), itr); + *(itr++) = L'\0'; + } + unicode_env.back() = L'\0'; + } + + + process_environment(std::initializer_list sv) { build_env(sv, ""); } + process_environment(std::initializer_list sv) { build_env(sv, L""); } + + template + process_environment(Args && args) + { + if (std::begin(args) != std::end(args)) + build_env(std::forward(args), *std::begin(args)); + } + + error_code error() {return ec;} + error_code ec; + std::vector unicode_env; + + + error_code on_setup(windows::default_launcher & launcher, + const filesystem::path &, const std::wstring &); + +#else + + template + static + std::vector build_env(Args && args, + typename std::enable_if< + std::is_convertible< + decltype(*std::begin(std::declval())), + cstring_ref>::value>::type * = nullptr) + { + std::vector env; + for (auto && e : args) + env.push_back(e.c_str()); + + env.push_back(nullptr); + return env; + } + + template + std::vector build_env(Args && args, + typename std::enable_if< + !std::is_convertible< + decltype(*std::begin(std::declval())), + cstring_ref>::value>::type * = nullptr) + { + std::vector env; + + for (auto && arg: std::forward(args)) + env_buffer.emplace_back(arg); + + for (auto && e : env_buffer) + env.push_back(e.c_str()); + env.push_back(nullptr); + return env; + } + + + process_environment(std::initializer_list sv) : env{build_env(sv)} { } + + template + process_environment(Args && args) : env(build_env(std::forward(args))) + { + } + + + error_code on_setup(posix::default_launcher & launcher, + const filesystem::path &, const char * const *); + + std::vector env_buffer; + std::vector env; + +#endif + +}; + + + +BOOST_PROCESS_V2_END_NAMESPACE + + +namespace std +{ + +template<> +struct hash +{ + std::size_t operator()( BOOST_PROCESS_V2_NAMESPACE::environment::key_view kv) const noexcept + { + return BOOST_PROCESS_V2_NAMESPACE::environment::hash_value(kv); + } +}; + + +template<> +struct hash +{ + std::size_t operator()( BOOST_PROCESS_V2_NAMESPACE::environment::value_view kv) const noexcept + { + return BOOST_PROCESS_V2_NAMESPACE::environment::hash_value(kv); + } +}; + +template<> +struct hash +{ + std::size_t operator()( BOOST_PROCESS_V2_NAMESPACE::environment::key_value_pair_view kv) const noexcept + { + return BOOST_PROCESS_V2_NAMESPACE::environment::hash_value(kv); + } +}; + + +template<> +struct hash +{ + std::size_t operator()( BOOST_PROCESS_V2_NAMESPACE::environment::key_view kv) const noexcept + { + return BOOST_PROCESS_V2_NAMESPACE::environment::hash_value(kv); + } +}; + + +template<> +struct hash +{ + std::size_t operator()( BOOST_PROCESS_V2_NAMESPACE::environment::value_view kv) const noexcept + { + return BOOST_PROCESS_V2_NAMESPACE::environment::hash_value(kv); + } +}; + +template<> +struct hash +{ + std::size_t operator()( BOOST_PROCESS_V2_NAMESPACE::environment::key_value_pair_view kv) const noexcept + { + return BOOST_PROCESS_V2_NAMESPACE::environment::hash_value(kv); + } +}; + +} + + + +#if defined(BOOST_PROCESS_V2_HEADER_ONLY) + +#include + +#endif + +#endif //BOOST_PROCESS_V2_ENVIRONMENT_HPP diff --git a/include/boost/process/v2/error.hpp b/include/boost/process/v2/error.hpp new file mode 100644 index 000000000..621f032d3 --- /dev/null +++ b/include/boost/process/v2/error.hpp @@ -0,0 +1,50 @@ +// Copyright (c) 2021 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_ERROR_HPP +#define BOOST_PROCESS_V2_ERROR_HPP + +#include + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +namespace error +{ + +/// Errors used for utf8 <-> UCS-2 conversions. +enum utf8_conv_error +{ + insufficient_buffer = 1, + invalid_character, +}; + +extern BOOST_PROCESS_V2_DECL const error_category& get_utf8_category(); +static const error_category& utf8_category = get_utf8_category(); + +extern BOOST_PROCESS_V2_DECL const error_category& get_exit_code_category(); + +/// An error category that can be used to interpret exit codes of subprocesses. +/** Currently not used by boost.process, but it might be in the future. + * + * void run_my_process(filesystem::path pt, error_code & ec) + * { + * process proc(pt, {}); + * proc.wait(); + * ec.assign(proc.native_exit_code(), error::get_exit_code_category()); + * } + * + * */ +static const error_category& exit_code_category = get_exit_code_category(); + +} + +BOOST_PROCESS_V2_END_NAMESPACE + +#if defined(BOOST_PROCESS_V2_HEADER_ONLY) + +#include + +#endif + +#endif //BOOST_PROCESS_V2_ERROR_HPP diff --git a/include/boost/process/v2/execute.hpp b/include/boost/process/v2/execute.hpp new file mode 100644 index 000000000..10a203a24 --- /dev/null +++ b/include/boost/process/v2/execute.hpp @@ -0,0 +1,120 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_EXECUTE_HPP +#define BOOST_PROCESS_V2_EXECUTE_HPP + +#include + +#if defined(BOOST_PROCESS_V2_STANDALONE) +#include +#else +#include +#endif + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + + +/** + * @brief Run a process and wait for it to complete. + * + * @tparam Executor The asio executor of the process handle + * @param proc The process to be run. + * @return int The exit code of the process + * @exception system_error An error that might have occured during the wait. + */ +template +inline int execute(basic_process proc) +{ + return proc.wait(); +} + +/** \overload int execute(const basic_process proc) */ +template +inline int execute(basic_process proc, error_code & ec) +{ + return proc.wait(ec); +} + +namespace detail +{ + +template +struct execute_op +{ + std::unique_ptr> proc; + + struct cancel + { + using cancellation_type = BOOST_PROCESS_V2_ASIO_NAMESPACE::cancellation_type; + basic_process * proc; + cancel(basic_process * proc) : proc(proc) {} + + void operator()(cancellation_type tp) + { + error_code ign; + if ((tp & cancellation_type::total) != cancellation_type::none) + proc->interrupt(ign); + else if ((tp & cancellation_type::partial) != cancellation_type::none) + proc->request_exit(ign); + else if ((tp & cancellation_type::terminal) != cancellation_type::none) + proc->terminate(ign); + } + }; + + template + void operator()(Self && self) + { + self.reset_cancellation_state(); + BOOST_PROCESS_V2_ASIO_NAMESPACE::cancellation_slot s = self.get_cancellation_state().slot(); + if (s.is_connected()) + s.emplace(proc.get()); + + auto pro_ = proc.get(); + pro_->async_wait( + BOOST_PROCESS_V2_ASIO_NAMESPACE::bind_cancellation_slot( + BOOST_PROCESS_V2_ASIO_NAMESPACE::cancellation_slot(), + std::move(self))); + } + + template + void operator()(Self && self, error_code ec, int res) + { + self.get_cancellation_state().slot().clear(); + self.complete(ec, res); + } +}; + +} + +/// Execute a process asynchronously +/** This function asynchronously for a process to complete. + * + * Cancelling the execution will signal the child process to exit + * with the following intepretations: + * + * - cancellation_type::total -> interrupt + * - cancellation_type::partial -> request_exit + * - cancellation_type::terminal -> terminate + * + * It is to note that `async_execute` will us the lowest seelected cancellation + * type. A subprocess might ignore anything not terminal. + */ +template +inline +BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(WaitHandler, void (error_code, int)) +async_execute(basic_process proc, + WaitHandler && handler BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(Executor)) +{ + std::unique_ptr> pro_(new basic_process(std::move(proc))); + auto exec = proc.get_executor(); + return BOOST_PROCESS_V2_ASIO_NAMESPACE::async_compose( + detail::execute_op{std::move(pro_)}, handler, exec); +} + +BOOST_PROCESS_V2_END_NAMESPACE + +#endif //BOOST_PROCESS_V2_EXECUTE_HPP diff --git a/include/boost/process/v2/exit_code.hpp b/include/boost/process/v2/exit_code.hpp new file mode 100644 index 000000000..5af4615b1 --- /dev/null +++ b/include/boost/process/v2/exit_code.hpp @@ -0,0 +1,90 @@ +// +// process/exit_code.hpp +// ~~~~~~~~~~~~~~ +// +// Copyright (c) 2022 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_PROCESS_V2_EXIT_CODE_HPP +#define BOOST_PROCESS_V2_EXIT_CODE_HPP + +#include + +#if defined(BOOST_PROCESS_V2_POSIX) +#include +#endif + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +#if defined(GENERATING_DOCUMENTATION) + +/// The native exit-code type, usually an integral value +/** The OS may have a value different from `int` to represent + * the exit codes of subprocesses. It might also + * contain additional information. + */ +typedef implementation_defined native_exit_code_type; + + +/// Check if the native exit code indicates the process is still running +bool process_is_running(native_exit_code_type code); + +/// Obtain the portable part of the exit code, i.e. what the subprocess has returned from main. +int evaluate_exit_code(native_exit_code_type code); + + +#else + +#if defined(BOOST_PROCESS_V2_WINDOWS) + +typedef unsigned long native_exit_code_type; + +namespace detail +{ +constexpr native_exit_code_type still_active = 259u; +} + +inline bool process_is_running(native_exit_code_type code) +{ + return code == detail::still_active; +} + +inline int evaluate_exit_code(native_exit_code_type code) +{ + return static_cast(code); +} + +#else + +typedef int native_exit_code_type; + +namespace detail +{ +constexpr native_exit_code_type still_active = 0x7f; +} + +inline bool process_is_running(int code) +{ + return !WIFEXITED(code) && !WIFSIGNALED(code); +} + +inline int evaluate_exit_code(int code) +{ + if (WIFEXITED(code)) + return WEXITSTATUS(code); + else if (WIFSIGNALED(code)) + return WTERMSIG(code); + else + return code; +} + +#endif + +#endif + +BOOST_PROCESS_V2_END_NAMESPACE + +#endif //BOOST_PROCESS_V2_EXIT_CODE_HPP \ No newline at end of file diff --git a/include/boost/process/v2/impl/default_launcher.ipp b/include/boost/process/v2/impl/default_launcher.ipp new file mode 100644 index 000000000..26a897940 --- /dev/null +++ b/include/boost/process/v2/impl/default_launcher.ipp @@ -0,0 +1,24 @@ +// +// boost/process/v2/default_launcher.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2022 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_PROCESS_V2_IMPL_DEFAULT_LAUNCHER_IPP +#define BOOST_PROCESS_V2_IMPL_DEFAULT_LAUNCHER_IPP + +#include + +#if defined(BOOST_PROCESS_V2_WINDOWS) +#include +#else +#include +#endif + + + +#endif //BOOST_PROCESS_V2_IMPL_DEFAULT_LAUNCHER_IPP \ No newline at end of file diff --git a/include/boost/process/v2/impl/environment.ipp b/include/boost/process/v2/impl/environment.ipp new file mode 100644 index 000000000..d756bf1ab --- /dev/null +++ b/include/boost/process/v2/impl/environment.ipp @@ -0,0 +1,47 @@ +// +// boost/process/v2/impl/environment.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2022 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_PROCESS_V2_IMPL_ENVIRONMENT_IPP +#define BOOST_PROCESS_V2_IMPL_ENVIRONMENT_IPP + +#include +#include +#include + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +#if defined(BOOST_PROCESS_V2_WINDOWS) + +error_code process_environment::on_setup(windows::default_launcher & launcher, const filesystem::path &, const std::wstring &) +{ + if (!unicode_env.empty() && !ec) + { + launcher.creation_flags |= CREATE_UNICODE_ENVIRONMENT ; + launcher.environment = unicode_env.data(); + } + + return ec; +}; + +#else + +error_code process_environment::on_setup(posix::default_launcher & launcher, const filesystem::path &, const char * const *) +{ + launcher.env = env.data(); + return error_code{}; +}; + +#endif + + +BOOST_PROCESS_V2_END_NAMESPACE + + +#endif //BOOST_PROCESS_V2_IMPL_ENVIRONMENT_IPP \ No newline at end of file diff --git a/include/boost/process/v2/impl/error.ipp b/include/boost/process/v2/impl/error.ipp new file mode 100644 index 000000000..a5924df78 --- /dev/null +++ b/include/boost/process/v2/impl/error.ipp @@ -0,0 +1,206 @@ +// Copyright (c) 2021 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_IMPL_ERROR_IPP +#define BOOST_PROCESS_V2_IMPL_ERROR_IPP + +#include +#include +#include + +#include + +#if defined(BOOST_PROCESS_V2_POSIX) +#include +#endif +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +namespace error +{ + +namespace detail +{ + +struct utf8_category final : public error_category +{ + utf8_category() : error_category(0xDAEDu) {} + + const char* name() const noexcept + { + return "process.v2.utf8"; + } + std::string message(int value) const + { + switch (static_cast(value)) + { + case utf8_conv_error::insufficient_buffer: + return "A supplied buffer size was not large enough"; + case utf8_conv_error::invalid_character: + return "Invalid characters were found in a string."; + default: + return "process.v2.utf8 error"; + } + } +}; + + +struct exit_code_category final : public error_category +{ + exit_code_category() : error_category(0xDAEEu) {} + + const char* name() const noexcept + { + return "process.v2.exit_code"; + } + std::string message(int status) const + { + switch (status) + { + case v2::detail::still_active: + return "still-active"; + case EXIT_SUCCESS: + return "exit_success"; + case EXIT_FAILURE: + return "exit_failure"; + default: +#if defined(BOOST_PROCESS_V2_POSIX) + if (WIFCONTINUED(status)) + return "continued"; + switch (WTERMSIG(status)) + { +# if defined(SIGABRT) + case SIGABRT: return "SIGABRT: Abort signal from abort(3)"; +# endif +# if defined(SIGALRM) + case SIGALRM: return "SIGALRM: Timer signal from alarm(2)"; +# endif +# if defined(SIGBUS) + case SIGBUS: return "SIGBUS: Bus error (bad memory access)"; +# endif +# if defined(SIGCHLD) + case SIGCHLD: return "SIGCHLD: Child stopped or terminated"; +# endif +# if defined(SIGCONT) + case SIGCONT: return "SIGCONT: Continue if stopped"; +# endif +# if defined(SIGEMT) + case SIGEMT: return "SIGEMT: Emulator trap"; +# endif +# if defined(SIGFPE) + case SIGFPE: return "SIGFPE: Floating-point exception"; +# endif +# if defined(SIGHUP) + case SIGHUP: return "SIGHUP: Hangup detected on controlling terminal"; +# endif +# if defined(SIGILL) + case SIGILL: return "SIGILL: Illegal Instruction"; +# endif +# if defined(SIGINFO) + case SIGINFO: return "SIGINFO: A synonym for SIGPWR"; +# endif +# if defined(SIGINT) + case SIGINT: return "SIGINT: Interrupt from keyboard"; +# endif +# if defined(SIGIO) + case SIGIO: return "SIGIO: I/O now possible (4.2BSD)"; +# endif +# if defined(SIGKILL) + case SIGKILL: return "SIGKILL: Kill signal"; +# endif +# if defined(SIGLOST) + case SIGLOST: return "SIGLOST: File lock lost (unused)"; +# endif +# if defined(SIGPIPE) + case SIGPIPE: return "SIGPIPE: Broken pipe: write to pipe with no"; +# endif +# if defined(SIGPOLL) && !defined(SIGIO) + case SIGPOLL: return "SIGPOLL: Pollable event (Sys V);"; +# endif +# if defined(SIGPROF) + case SIGPROF: return "SIGPROF: Profiling timer expired"; +# endif +# if defined(SIGPWR) + case SIGPWR: return "SIGPWR: Power failure (System V)"; +# endif +# if defined(SIGQUIT) + case SIGQUIT: return "SIGQUIT: Quit from keyboard"; +# endif +# if defined(SIGSEGV) + case SIGSEGV: return "SIGSEGV: Invalid memory reference"; +# endif +# if defined(SIGSTKFLT) + case SIGSTKFLT: return "SIGSTKFLT: Stack fault on coprocessor (unused)"; +# endif +# if defined(SIGSTOP) + case SIGSTOP: return "SIGSTOP: Stop process"; +# endif +# if defined(SIGTSTP) + case SIGTSTP: return "SIGTSTP: Stop typed at terminal"; +# endif +# if defined(SIGSYS) + case SIGSYS: return "SIGSYS: Bad system call (SVr4);"; +# endif +# if defined(SIGTERM) + case SIGTERM: return "SIGTERM: Termination signal"; +# endif +# if defined(SIGTRAP) + case SIGTRAP: return "SIGTRAP: Trace/breakpoint trap"; +# endif +# if defined(SIGTTIN) + case SIGTTIN: return "SIGTTIN: Terminal input for background process"; +# endif +# if defined(SIGTTOU) + case SIGTTOU: return "SIGTTOU: Terminal output for background process"; +# endif +# if defined(SIGURG) + case SIGURG: return "SIGURG: Urgent condition on socket (4.2BSD)"; +# endif +# if defined(SIGUSR1) + case SIGUSR1: return "SIGUSR1: User-defined signal 1"; +# endif +# if defined(SIGUSR2) + case SIGUSR2: return "SIGUSR2: User-defined signal 2"; +# endif +# if defined(SIGVTALRM) + case SIGVTALRM: return "SIGVTALRM: Virtual alarm clock (4.2BSD)"; +# endif +# if defined(SIGXCPU) + case SIGXCPU: return "SIGXCPU: CPU time limit exceeded (4.2BSD);"; +# endif +# if defined(SIGXFSZ) + case SIGXFSZ: return "SIGXFSZ: File size limit exceeded (4.2BSD);"; +# endif +# if defined(SIGWINCH) + case SIGWINCH: return "SIGWINCH: Window resize signal (4.3BSD, Sun)"; +# endif + default: return "Unknown signal"; + } +#endif + return "exited with other error"; + } + } +}; + + + + +} // namespace detail + +BOOST_PROCESS_V2_DECL const error_category& get_utf8_category() +{ + static detail::utf8_category instance; + return instance; +} + +BOOST_PROCESS_V2_DECL const error_category& get_exit_code_category() +{ + static detail::exit_code_category instance; + return instance; +} + +} + +BOOST_PROCESS_V2_END_NAMESPACE + +#endif //BOOST_PROCESS_V2_IMPL_ERROR_IPP diff --git a/include/boost/process/v2/impl/pid.ipp b/include/boost/process/v2/impl/pid.ipp new file mode 100644 index 000000000..1ac40acb6 --- /dev/null +++ b/include/boost/process/v2/impl/pid.ipp @@ -0,0 +1,27 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_IMPL_PID_IPP +#define BOOST_PROCESS_V2_IMPL_PID_IPP + +#include +#include + +#if defined(BOOST_PROCESS_V2_WINDOWS) +#include +#else +#include +#endif + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +#if defined(BOOST_PROCESS_V2_WINDOWS) +pid_type current_pid() {return ::GetCurrentProcessId();} +#else +pid_type current_pid() {return ::getpid();} +#endif + +BOOST_PROCESS_V2_END_NAMESPACE + +#endif //BOOST_PROCESS_V2_IMPL_PID_IPP diff --git a/include/boost/process/v2/impl/process_handle.ipp b/include/boost/process/v2/impl/process_handle.ipp new file mode 100644 index 000000000..813395c21 --- /dev/null +++ b/include/boost/process/v2/impl/process_handle.ipp @@ -0,0 +1,17 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_IMPL_PROCESS_HANDLE_IPP +#define BOOST_PROCESS_V2_IMPL_PROCESS_HANDLE_IPP + +#include + +#if defined(BOOST_PROCESS_V2_WINDOWS) +#include +#else + + +#endif + +#endif //BOOST_PROCESS_V2_IMPL_PROCESS_HANDLE_IPP diff --git a/include/boost/process/v2/pid.hpp b/include/boost/process/v2/pid.hpp new file mode 100644 index 000000000..48a5e143f --- /dev/null +++ b/include/boost/process/v2/pid.hpp @@ -0,0 +1,41 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_PID_HPP +#define BOOST_PROCESS_V2_PID_HPP + +#include + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +#if defined(GENERATING_DOCUMENTATION) + +//An integral type representing a process id. +typedef implementation_defined pid_type; + + +#else + +#if defined(BOOST_PROCESS_V2_WINDOWS) + +typedef unsigned long pid_type; + +#else + +typedef int pid_type; + +#endif +#endif + +/// Get the process id of the current process. +BOOST_PROCESS_V2_DECL pid_type current_pid(); + +BOOST_PROCESS_V2_END_NAMESPACE + +#if defined(BOOST_PROCESS_V2_HEADER_ONLY) +#include +#endif + + +#endif //BOOST_PROCESS_V2_PID_HPP diff --git a/include/boost/process/v2/popen.hpp b/include/boost/process/v2/popen.hpp new file mode 100644 index 000000000..290582a5b --- /dev/null +++ b/include/boost/process/v2/popen.hpp @@ -0,0 +1,421 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_POPEN_HPP +#define BOOST_PROCESS_V2_POPEN_HPP + +#include +#include + +#if defined(BOOST_PROCESS_V2_STANDALONE) +#include +#include +#include +#else +#include +#include +#include +#endif + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +/// A subprocess with automatically assigned pipes. +/** The purpose os the popen is to provide a convenient way + * to use the stdin & stdout of a process. + * + * @code {.cpp} + * popen proc(executor, find_executable("addr2line"), {argv[0]}); + * asio::write(proc, asio::buffer("main\n")); + * std::string line; + * asio::read_until(proc, asio::dynamic_buffer(line), '\n'); + * @endcode + * + * + * Popen can be used as a stream object in other protocols. + */ +template +struct basic_popen : basic_process +{ + /// The executor of the process + using executor_type = Executor; + + /// Rebinds the popen type to another executor. + template + struct rebind_executor + { + /// The pipe type when rebound to the specified executor. + typedef basic_popen other; + }; + + /// Move construct a popen + basic_popen(basic_popen &&) = default; + /// Move assign a popen + basic_popen& operator=(basic_popen &&) = default; + + /// Move construct a popen and change the executor type. + template + basic_popen(basic_popen&& lhs) + : basic_process(std::move(lhs)), + stdin_(std::move(lhs.stdin_)), stdout_(std::move(lhs.stdout_)) + { + } + + /// Create a closed process handle + explicit basic_popen(executor_type exec) : basic_process{std::move(exec)} {} + + /// Create a closed process handle + template + explicit basic_popen(ExecutionContext & context, + typename std::enable_if< + is_convertible::value, void *>::type = nullptr) + : basic_process{context} + { + } + + + + /// Construct a child from a property list and launch it using the default process launcher. + template + explicit basic_popen( + executor_type executor, + const filesystem::path& exe, + std::initializer_list args, + Inits&&... inits) + : basic_process(executor) + { + *static_cast*>(this) = + default_process_launcher()( + this->get_executor(), exe, args, + std::forward(inits)..., + process_stdio{stdin_, stdout_} + ); + + } + + /// Construct a child from a property list and launch it using the default process launcher. + template + explicit basic_popen( + executor_type executor, + const filesystem::path& exe, + std::initializer_list args, + Inits&&... inits) + : basic_process(executor) + { + *static_cast*>(this) = + default_process_launcher()( + this->get_executor(), exe, args, + std::forward(inits)..., + process_stdio{stdin_, stdout_} + ); + } + + /// Construct a child from a property list and launch it using the default process launcher. + template + explicit basic_popen( + executor_type executor, + const filesystem::path& exe, + Args&& args, Inits&&... inits) + : basic_process(executor) + { + *static_cast*>(this) = + default_process_launcher()( + std::move(executor), exe, args, + std::forward(inits)..., + process_stdio{stdin_, stdout_} + ); + } + + /// Construct a child from a property list and launch it using the default process launcher. + template + explicit basic_popen( + ExecutionContext & context, + typename std::enable_if< + std::is_convertible::value, + const filesystem::path&>::type exe, + std::initializer_list args, + Inits&&... inits) + : basic_process(context) + { + *static_cast*>(this) = + default_process_launcher()( + this->get_executor(), exe, args, + std::forward(inits)..., + process_stdio{stdin_, stdout_} + ); + } + + /// Construct a child from a property list and launch it using the default process launcher. + template + explicit basic_popen( + ExecutionContext & context, + typename std::enable_if< + std::is_convertible::value, + const filesystem::path&>::type exe, + Args&& args, Inits&&... inits) + : basic_process(context) + { + *static_cast*>(this) = + default_process_launcher()( + this->get_executor(), exe, args, + std::forward(inits)..., + process_stdio{stdin_, stdout_} + ); + } + + /// The type used for stdin on the parent process side. + using stdin_type = BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_writable_pipe; + /// The type used for stdout on the parent process side. + using stdout_type = BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_readable_pipe; + + /// Get the stdin pipe. + stdin_type & get_stdin() {return stdin_; } + /// Get the stdout pipe. + stdout_type & get_stdout() {return stdout_; } + + /// Get the stdin pipe. + const stdin_type & get_stdin() const {return stdin_; } + /// Get the stdout pipe. + const stdout_type & get_stdout() const {return stdout_; } + + /// Write some data to the pipe. + /** + * This function is used to write data to the pipe. The function call will + * block until one or more bytes of the data has been written successfully, + * or until an error occurs. + * + * @param buffers One or more data buffers to be written to the pipe. + * + * @returns The number of bytes written. + * + * @throws boost::system::system_error Thrown on failure. An error code of + * boost::asio::error::eof indicates that the connection was closed by the + * subprocess. + * + * @note The write_some operation may not transmit all of the data to the + * peer. Consider using the @ref write function if you need to ensure that + * all data is written before the blocking operation completes. + * + * @par Example + * To write a single data buffer use the @ref buffer function as follows: + * @code + * pipe.write_some(boost::asio::buffer(data, size)); + * @endcode + * See the @ref buffer documentation for information on writing multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + std::size_t write_some(const ConstBufferSequence& buffers) + { + return stdin_.write_some(buffers); + } + + /// Write some data to the pipe. + /** + * This function is used to write data to the pipe. The function call will + * block until one or more bytes of the data has been written successfully, + * or until an error occurs. + * + * @param buffers One or more data buffers to be written to the pipe. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns The number of bytes written. Returns 0 if an error occurred. + * + * @note The write_some operation may not transmit all of the data to the + * subprocess. Consider using the @ref write function if you need to ensure that + * all data is written before the blocking operation completes. + */ + template + std::size_t write_some(const ConstBufferSequence& buffers, + boost::system::error_code& ec) + { + return stdin_.write_some(buffers, ec); + } + + /// Start an asynchronous write. + /** + * This function is used to asynchronously write data to the pipe. It is an + * initiating function for an @ref asynchronous_operation, and always returns + * immediately. + * + * @param buffers One or more data buffers to be written to the pipe. + * Although the buffers object may be copied as necessary, ownership of the + * underlying memory blocks is retained by the caller, which must guarantee + * that they remain valid until the completion handler is called. + * + * @param token The @ref completion_token that will be used to produce a + * completion handler, which will be called when the write completes. + * Potential completion tokens include @ref use_future, @ref use_awaitable, + * @ref yield_context, or a function object with the correct completion + * signature. The function signature of the completion handler must be: + * @code void handler( + * const boost::system::error_code& error, // Result of operation. + * std::size_t bytes_transferred // Number of bytes written. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the completion handler will not be invoked from within this function. + * On immediate completion, invocation of the handler will be performed in a + * manner equivalent to using boost::asio::post(). + * + * @par Completion Signature + * @code void(boost::system::error_code, std::size_t) @endcode + * + * @note The write operation may not transmit all of the data to the peer. + * Consider using the @ref async_write function if you need to ensure that all + * data is written before the asynchronous operation completes. + * + * @par Example + * To write a single data buffer use the @ref buffer function as follows: + * @code + * popen.async_write_some(boost::asio::buffer(data, size), handler); + * @endcode + * See the @ref buffer documentation for information on writing multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(WriteToken, + void (boost::system::error_code, std::size_t)) + async_write_some(const ConstBufferSequence& buffers, + BOOST_ASIO_MOVE_ARG(WriteToken) token + BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)) + { + return stdin_.async_write_some(buffers, std::forward(token)); + } + + + /// Read some data from the pipe. + /** + * This function is used to read data from the pipe. The function call will + * block until one or more bytes of data has been read successfully, or until + * an error occurs. + * + * @param buffers One or more buffers into which the data will be read. + * + * @returns The number of bytes read. + * + * @throws boost::system::system_error Thrown on failure. An error code of + * boost::asio::error::eof indicates that the connection was closed by the + * peer. + * + * @note The read_some operation may not read all of the requested number of + * bytes. Consider using the @ref read function if you need to ensure that + * the requested amount of data is read before the blocking operation + * completes. + * + * @par Example + * To read into a single data buffer use the @ref buffer function as follows: + * @code + * basic_readable_pipe.read_some(boost::asio::buffer(data, size)); + * @endcode + * See the @ref buffer documentation for information on reading into multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + std::size_t read_some(const MutableBufferSequence& buffers) + { + return stdout_.read_some(buffers); + } + + /// Read some data from the pipe. + /** + * This function is used to read data from the pipe. The function call will + * block until one or more bytes of data has been read successfully, or until + * an error occurs. + * + * @param buffers One or more buffers into which the data will be read. + * + * @param ec Set to indicate what error occurred, if any. + * + * @returns The number of bytes read. Returns 0 if an error occurred. + * + * @note The read_some operation may not read all of the requested number of + * bytes. Consider using the @ref read function if you need to ensure that + * the requested amount of data is read before the blocking operation + * completes. + */ + template + std::size_t read_some(const MutableBufferSequence& buffers, + boost::system::error_code& ec) + { + return stdout_.read_some(buffers, ec); + } + + /// Start an asynchronous read. + /** + * This function is used to asynchronously read data from the pipe. It is an + * initiating function for an @ref asynchronous_operation, and always returns + * immediately. + * + * @param buffers One or more buffers into which the data will be read. + * Although the buffers object may be copied as necessary, ownership of the + * underlying memory blocks is retained by the caller, which must guarantee + * that they remain valid until the completion handler is called. + * + * @param token The @ref completion_token that will be used to produce a + * completion handler, which will be called when the read completes. + * Potential completion tokens include @ref use_future, @ref use_awaitable, + * @ref yield_context, or a function object with the correct completion + * signature. The function signature of the completion handler must be: + * @code void handler( + * const boost::system::error_code& error, // Result of operation. + * std::size_t bytes_transferred // Number of bytes read. + * ); @endcode + * Regardless of whether the asynchronous operation completes immediately or + * not, the completion handler will not be invoked from within this function. + * On immediate completion, invocation of the handler will be performed in a + * manner equivalent to using boost::asio::post(). + * + * @par Completion Signature + * @code void(boost::system::error_code, std::size_t) @endcode + * + * @note The read operation may not read all of the requested number of bytes. + * Consider using the @ref async_read function if you need to ensure that the + * requested amount of data is read before the asynchronous operation + * completes. + * + * @par Example + * To read into a single data buffer use the @ref buffer function as follows: + * @code + * basic_readable_pipe.async_read_some( + * boost::asio::buffer(data, size), handler); + * @endcode + * See the @ref buffer documentation for information on reading into multiple + * buffers in one go, and how to use it with arrays, boost::array or + * std::vector. + */ + template + BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(ReadToken, + void (boost::system::error_code, std::size_t)) + async_read_some(const MutableBufferSequence& buffers, + BOOST_ASIO_MOVE_ARG(ReadToken) token + BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)) + { + return stdout_.async_read_some(buffers, std::forward(token)); + } + + + + private: + stdin_type stdin_ {basic_process::get_executor()}; + stdout_type stdout_{basic_process::get_executor()}; +}; + +/// A popen object with the default executor. +using popen = basic_popen<>; + +BOOST_PROCESS_V2_END_NAMESPACE + +#endif //BOOST_PROCESS_V2_POPEN_HPP diff --git a/include/boost/process/v2/posix/bind_fd.hpp b/include/boost/process/v2/posix/bind_fd.hpp new file mode 100644 index 000000000..bb0ab08b9 --- /dev/null +++ b/include/boost/process/v2/posix/bind_fd.hpp @@ -0,0 +1,109 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_POSIX_BIND_FD_HPP +#define BOOST_PROCESS_V2_POSIX_BIND_FD_HPP + +#include +#include + +#include + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +namespace posix +{ + +/// Utility class to bind a file descriptor to an explicit file descriptor for the child process. +struct bind_fd +{ + int target; + int fd; + bool fd_needs_closing{false}; + + ~bind_fd() + { + if (fd_needs_closing) + ::close(fd); + } + + bind_fd() = delete; + /// Inherit file descriptor with the same value. + /** + * This will pass descriptor 42 as 42 to the child process: + * @code + * process p{"test", {}, posix::bind_fd(42)}; + * @endcode + */ + bind_fd(int target) : target(target), fd(target) {} + + /// Inherit an asio io-object as a given file descriptor to the child process. + /** + * This will pass the tcp::socket, as 42 to the child process: + * @code + * extern tcp::socket sock; + * process p{"test", {}, posix::bind_fd(42, sock)}; + * @endcode + */ + + template + bind_fd(int target, Stream && str, decltype(std::declval().native_handle()) = -1) + : bind_fd(target, str.native_handle()) + {} + + /// Inherit a `FILE` as a given file descriptor to the child process. + /** + * This will pass the given `FILE*`, as 42 to the child process: + * @code + * process p{"test", {}, posix::bind_fd(42, stderr)}; + * @endcode + */ + bind_fd(int target, FILE * f) : bind_fd(target, fileno(f)) {} + + /// Inherit a file descriptor with as a differnet value. + /** + * This will pass 24 as 42 to the child process: + * @code + * process p{"test", {}, posix::bind_fd(42, 24)}; + * @endcode + */ + bind_fd(int target, int fd) : target(target), fd(fd) {} + + /// Inherit a null device as a set descriptor. + /** + * This will pass 24 as 42 to the child process: + * @code + * process p{"test", {}, posix::bind_fd(42, nullptr)}; + * @endcode + */ + bind_fd(int target, std::nullptr_t) : bind_fd(target, filesystem::path("/dev/null")) {} + + /// Inherit a newly openedfile as a set descriptor. + /** + * This will pass 24 as 42 to the child process: + * @code + * process p{"test", {}, posix::bind_fd(42, "extra-output.txt")}; + * @endcode + */ + bind_fd(int target, const filesystem::path & pth, int flags = O_RDWR | O_CREAT) + : target(target), fd(::open(pth.c_str(), flags, 0660)), fd_needs_closing(true) + { + } + + /// Implementation of the initialization function. + error_code on_exec_setup(posix::default_launcher & launcher, const filesystem::path &, const char * const *) + { + if (::dup2(fd, target) == -1) + return error_code(errno, system_category()); + + launcher.fd_whitelist.push_back(target); + return error_code (); + } +}; + +} + +BOOST_PROCESS_V2_END_NAMESPACE + +#endif //BOOST_PROCESS_V2_POSIX_BIND_FD_HPP diff --git a/include/boost/process/v2/posix/default_launcher.hpp b/include/boost/process/v2/posix/default_launcher.hpp new file mode 100644 index 000000000..0486f0696 --- /dev/null +++ b/include/boost/process/v2/posix/default_launcher.hpp @@ -0,0 +1,518 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_POSIX_DEFAULT_LAUNCHER +#define BOOST_PROCESS_V2_POSIX_DEFAULT_LAUNCHER + +#include +#include +#include +#include + +#if defined(BOOST_PROCESS_V2_STANDALONE) +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +#include +#include + + +#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__APPLE__) || defined(__MACH__) +extern "C" { extern char **environ; } +#endif + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +template +struct basic_process; + +namespace posix +{ + + +namespace detail +{ + +struct base {}; +struct derived : base {}; + +template +inline error_code invoke_on_setup(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + Init && init, base && ) +{ + return error_code{}; +} + +template +inline auto invoke_on_setup(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + Init && init, derived && ) + -> decltype(init.on_setup(launcher, executable, cmd_line)) +{ + return init.on_setup(launcher, executable, cmd_line); +} + +template +inline error_code on_setup(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line)) +{ + return error_code{}; +} + +template +inline error_code on_setup(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + Init1 && init1, Inits && ... inits) +{ + auto ec = invoke_on_setup(launcher, executable, cmd_line, init1, derived{}); + if (ec) + return ec; + else + return on_setup(launcher, executable, cmd_line, inits...); +} + + +template +inline void invoke_on_error(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + const error_code & ec, Init && init, base && ) +{ +} + +template +inline auto invoke_on_error(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + const error_code & ec, Init && init, derived && ) +-> decltype(init.on_error(launcher, ec, executable, cmd_line, ec)) +{ + init.on_error(launcher, executable, cmd_line, ec); +} + +template +inline void on_error(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + const error_code & ec) +{ +} + +template +inline void on_error(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + const error_code & ec, + Init1 && init1, Inits && ... inits) +{ + invoke_on_error(launcher, executable, cmd_line, ec, init1, derived{}); + on_error(launcher, executable, cmd_line, ec, inits...); +} + +template +inline void invoke_on_success(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + Init && init, base && ) +{ +} + +template +inline auto invoke_on_success(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + Init && init, derived && ) +-> decltype(init.on_success(launcher, executable, cmd_line)) +{ + init.on_success(launcher, executable, cmd_line); +} + +template +inline void on_success(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line)) +{ +} + +template +inline void on_success(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + Init1 && init1, Inits && ... inits) +{ + invoke_on_success(launcher, executable, cmd_line, init1, derived{}); + on_success(launcher, executable, cmd_line, inits...); +} + +template +inline void invoke_on_fork_error(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + const error_code & ec, Init && init, base && ) +{ +} + +template +inline auto invoke_on_fork_error(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + const error_code & ec, Init && init, derived && ) +-> decltype(init.on_fork_error(launcher, ec, executable, cmd_line, ec)) +{ + init.on_fork_error(launcher, executable, cmd_line, ec); +} + +template +inline void on_fork_error(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + const error_code & ec) +{ +} + +template +inline void on_fork_error(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + const error_code & ec, + Init1 && init1, Inits && ... inits) +{ + invoke_on_fork_error(launcher, executable, cmd_line, ec, init1, derived{}); + on_fork_error(launcher, executable, cmd_line, ec, inits...); +} + + + +template +inline void invoke_on_fork_success(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + Init && init, base && ) +{ + +} + +template +inline auto invoke_on_fork_success(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + Init && init, derived && ) +-> decltype(init.on_fork_success(launcher, executable, cmd_line)) +{ + init.on_fork_success(launcher, executable, cmd_line); +} + +template +inline void on_fork_success(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line)) +{ +} + +template +inline void on_fork_success(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + Init1 && init1, Inits && ... inits) +{ + invoke_on_fork_success(launcher, executable, cmd_line, init1, derived{}); + on_fork_success(launcher, executable, cmd_line, inits...); +} + + +template +inline error_code invoke_on_exec_setup(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + Init && init, base && ) +{ + return error_code{}; +} + +template +inline auto invoke_on_exec_setup(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + Init && init, derived && ) +-> decltype(init.on_exec_setup(launcher, executable, cmd_line)) +{ + return init.on_exec_setup(launcher, executable, cmd_line); +} + +template +inline error_code on_exec_setup(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line)) +{ + return error_code{}; +} + +template +inline error_code on_exec_setup(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + Init1 && init1, Inits && ... inits) +{ + auto ec = invoke_on_exec_setup(launcher, executable, cmd_line, init1, derived{}); + if (ec) + return ec; + else + return on_exec_setup(launcher, executable, cmd_line, inits...); +} + + + +template +inline void invoke_on_exec_error(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + const error_code & ec, Init && init, base && ) +{ +} + +template +inline auto invoke_on_exec_error(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + const error_code & ec, Init && init, derived && ) +-> decltype(init.on_exec_error(launcher, ec, executable, cmd_line, ec)) +{ + init.on_exec_error(launcher, executable, cmd_line, ec); +} + +template +inline void on_exec_error(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + const error_code & ec) +{ +} + +template +inline void on_exec_error(Launcher & launcher, const filesystem::path &executable, + const char * const * (&cmd_line), + const error_code & ec, + Init1 && init1, Inits && ... inits) +{ + invoke_on_exec_error(launcher, executable, cmd_line, ec, init1, derived{}); + on_exec_error(launcher, executable, cmd_line, ec, inits...); +} +} + +/// The default launcher for processes on windows. +struct default_launcher +{ + /// The pointer to the environment forwarded to the subprocess. + const char * const * env = ::environ; + /// The pid of the subprocess - will be assigned after fork. + int pid = -1; + + /// The whitelist for file descriptors. + std::vector fd_whitelist = {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}; + + default_launcher() = default; + + template + auto operator()(ExecutionContext & context, + const typename std::enable_if::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + error_code ec; + auto proc = (*this)(context, ec, executable, std::forward(args), std::forward(inits)...); + + if (ec) + asio::detail::throw_error(ec, "default_launcher"); + + return proc; + } + + + template + auto operator()(ExecutionContext & context, + error_code & ec, + const typename std::enable_if::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + return (*this)(context.get_executor(), executable, std::forward(args), std::forward(inits)...); + } + + template + auto operator()(Executor exec, + const typename std::enable_if< + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor::value || + BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + error_code ec; + auto proc = (*this)(std::move(exec), ec, executable, std::forward(args), std::forward(inits)...); + + if (ec) + asio::detail::throw_error(ec, "default_launcher"); + + return proc; + } + + template + auto operator()(Executor exec, + error_code & ec, + const typename std::enable_if< + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor::value || + BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + auto argv = this->build_argv_(executable, std::forward(args)); + { + pipe_guard pg; + if (::pipe(pg.p)) + { + ec.assign(errno, system_category()); + return basic_process{exec}; + } + if (::fcntl(pg.p[1], F_SETFD, FD_CLOEXEC)) + { + ec.assign(errno, system_category()); + return basic_process{exec}; + } + ec = detail::on_setup(*this, executable, argv, inits ...); + if (ec) + { + detail::on_error(*this, executable, argv, ec, inits...); + return basic_process(exec); + } + + auto & ctx = BOOST_PROCESS_V2_ASIO_NAMESPACE::query( + exec, BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::context); + ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_prepare); + pid = ::fork(); + if (pid == -1) + { + ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_parent); + detail::on_fork_error(*this, executable, argv, ec, inits...); + detail::on_error(*this, executable, argv, ec, inits...); + + ec.assign(errno, system_category()); + return basic_process{exec}; + } + else if (pid == 0) + { + ::close(pg.p[0]); + ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_child); + ec = detail::on_exec_setup(*this, executable, argv, inits...); + if (!ec) + { + fd_whitelist.push_back(pg.p[1]); + close_all_fds(ec); + } + if (!ec) + ::execve(executable.c_str(), const_cast(argv), const_cast(env)); + + ignore_unused(::write(pg.p[1], &errno, sizeof(int))); + ec.assign(errno, system_category()); + detail::on_exec_error(*this, executable, argv, ec, inits...); + ::exit(EXIT_FAILURE); + return basic_process{exec}; + } + + ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_parent); + ::close(pg.p[1]); + pg.p[1] = -1; + int child_error{0}; + int count = -1; + while ((count = ::read(pg.p[0], &child_error, sizeof(child_error))) == -1) + { + int err = errno; + if ((err != EAGAIN) && (err != EINTR)) + { + ec.assign(err, system_category()); + break; + } + } + if (count != 0) + ec.assign(child_error, system_category()); + + if (ec) + { + detail::on_error(*this, executable, argv, ec, inits...); + return basic_process{exec}; + } + } + basic_process proc(exec, pid); + detail::on_success(*this, executable, argv, ec, inits...); + return proc; + + } + protected: + + void ignore_unused(std::size_t ) {} + void close_all_fds(error_code & ec) + { + std::sort(fd_whitelist.begin(), fd_whitelist.end()); + detail::close_all(fd_whitelist, ec); + fd_whitelist = {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}; + } + + struct pipe_guard + { + int p[2]; + pipe_guard() : p{-1,-1} {} + + ~pipe_guard() + { + if (p[0] != -1) + ::close(p[0]); + if (p[1] != -1) + ::close(p[1]); + } + }; + + //if we need to allocate something + std::vector argv_buffer_; + std::vector argv_; + + template + const char * const * build_argv_(const filesystem::path & pt, const Args & args, + typename std::enable_if< + std::is_convertible< + decltype(*std::begin(std::declval())), + cstring_ref>::value>::type * = nullptr) + { + const auto arg_cnt = std::distance(std::begin(args), std::end(args)); + argv_.reserve(arg_cnt + 2); + argv_.push_back(pt.native().data()); + for (auto && arg : args) + argv_.push_back(arg.c_str()); + + argv_.push_back(nullptr); + return argv_.data(); + } + + template + const char * const * build_argv_(const filesystem::path & pt, const Args & args, + typename std::enable_if< + !std::is_convertible< + decltype(*std::begin(std::declval())), + cstring_ref>::value>::type * = nullptr) + { + const auto arg_cnt = std::distance(std::begin(args), std::end(args)); + argv_.reserve(arg_cnt + 2); + argv_buffer_.reserve(arg_cnt); + argv_.push_back(pt.native().data()); + + using char_type = typename decay()))[0])>::type; + + for (basic_string_view arg : args) + argv_buffer_.push_back(v2::detail::conv_string(arg.data(), arg.size())); + + for (auto && arg : argv_buffer_) + argv_.push_back(arg.c_str()); + + argv_.push_back(nullptr); + return argv_.data(); + } +}; + + +} + +BOOST_PROCESS_V2_END_NAMESPACE + +#endif //BOOST_PROCESS_V2_POSIX_DEFAULT_LAUNCHER diff --git a/include/boost/process/v2/posix/detail/close_handles.hpp b/include/boost/process/v2/posix/detail/close_handles.hpp new file mode 100644 index 000000000..86c1e9d0f --- /dev/null +++ b/include/boost/process/v2/posix/detail/close_handles.hpp @@ -0,0 +1,29 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_POSIX_DETAIL_CLOSE_HANDLES_HPP +#define BOOST_PROCESS_V2_POSIX_DETAIL_CLOSE_HANDLES_HPP + +#include +#include + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +namespace posix +{ + +namespace detail +{ + +// whitelist must be ordered +BOOST_PROCESS_V2_DECL void close_all(const std::vector & whitelist, + error_code & ec); + +} + +} + +BOOST_PROCESS_V2_END_NAMESPACE + +#endif //BOOST_PROCESS_V2_POSIX_DETAIL_CLOSE_HANDLES_HPP diff --git a/include/boost/process/v2/posix/detail/close_handles.ipp b/include/boost/process/v2/posix/detail/close_handles.ipp new file mode 100644 index 000000000..a74409e6d --- /dev/null +++ b/include/boost/process/v2/posix/detail/close_handles.ipp @@ -0,0 +1,194 @@ +// +// boost/process/v2/poxix/detail/close_handles.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2022 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_PROCESS_V2_POSIX_DETAIL_CLOSE_HANDLES_IPP +#define BOOST_PROCESS_V2_POSIX_DETAIL_CLOSE_HANDLES_IPP + +#include +#include +#include +// linux has close_range since 5.19 + + +#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__) + +// https://www.freebsd.org/cgi/man.cgi?query=close_range&apropos=0&sektion=0&manpath=FreeBSD+13.1-RELEASE+and+Ports&arch=default&format=html +// https://man.netbsd.org/closefrom.3 +// __FreeBSD__ +// +// gives us +// +// int closefrom(int fd); +// int close_range(u_int lowfd, u_int highfd, int flags); + +#include +#define BOOST_PROCESS_V2_HAS_CLOSE_RANGE_AND_CLOSEFROM 1 + +#elif defined(__sun) + +/*https://docs.oracle.com/cd/E36784_01/html/E36874/closefrom-3c.html + +int fdwalk(int (*func)(void *, int), void *cd); +*/ + +#include +#define BOOST_PROCESS_V2_HAS_PDFORK 1 + +#elif defined(__linux__) + +#include + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,11,0) + +// https://man7.org/linux/man-pages/man2/close_range.2.html +#include +#define BOOST_PROCESS_V2_HAS_CLOSE_RANGE 1 +#else + +#include + +#endif + +#else + +#include + +#endif + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +namespace posix +{ + +namespace detail +{ + +#if defined(BOOST_PROCESS_V2_HAS_PDFORK) + +void close_all(const std::vector & whitelist, error_code & ec) +{ + fdwalk(+[](void * p, int fd) + { + const auto & wl = *static_cast*>(p); + if (std::find(wl.begin(), wl.end(), fd) == wl.end()) + return ::close(fd); + else + return 0; + }, const_cast(static_cast(&whitelist)) ); + ec = BOOST_PROCESS_V2_NAMESPACE::detail::get_last_error(); +} + +#elif defined(BOOST_PROCESS_V2_HAS_CLOSE_RANGE_AND_CLOSEFROM) + +// freeBSD impl - whitelist must be ordered +void close_all(const std::vector & whitelist, error_code & ec) +{ + //the most common scenario is whitelist = {0,1,2} + if (!whitelist.empty()) + { + if (whitelist.front() != 0) + ::close_range(0, whitelist.front() - 1, 0); + + for (std::size_t idx = 0u; + idx < (whitelist.size() - 1u); + idx++) + { + const auto mine = whitelist[idx]; + const auto next = whitelist[idx]; + if ((mine + 1) != next && (mine != next)) + { + ::close_range(mine + 1, next - 1, 0); + } + } + + ::closefrom(whitelist.back() + 1); + } + else + ::closefrom(0); +} + +#elif defined(BOOST_PROCESS_V2_HAS_CLOSE_RANGE) + + +// linux impl - whitelist must be ordered +void close_all(const std::vector & whitelist, error_code & ec) +{ +// https://patchwork.kernel.org/project/linux-fsdevel/cover/20200602204219.186620-1-christian.brauner@ubuntu.com/ + //the most common scenario is whitelist = {0,1,2} + if (!whitelist.empty()) + { + if (whitelist.front() != 0) + ::close_range(0, whitelist.front() - 1, CLOSE_RANGE_UNSHARE); + + for (std::size_t idx = 0u; + idx < (whitelist.size() - 1u); + idx++) + { + const auto mine = whitelist[idx]; + const auto next = whitelist[idx]; + if ((mine + 1) != next && (mine != next)) + { + ::close_range(mine + 1, next - 1, CLOSE_RANGE_UNSHARE); + } + } + + ::close_range(whitelist.back() + 1, std::numeric_limits::max(), CLOSE_RANGE_UNSHARE); + } + else + ::close_range(0, std::numeric_limits::max(), CLOSE_RANGE_UNSHARE); +} + +#else + +// default one +void close_all(const std::vector & whitelist, error_code & ec) +{ + std::unique_ptr dir{::opendir("/dev/fd"), +[](DIR* p){::closedir(p);}}; + if (dir.get() == nullptr) + { + ec = BOOST_PROCESS_V2_NAMESPACE::detail::get_last_error(); + return ; + } + + auto dir_fd = ::dirfd(dir.get()); + if (dir_fd == -1) + { + ec = BOOST_PROCESS_V2_NAMESPACE::detail::get_last_error(); + return ; + } + + struct ::dirent * ent_p; + + while ((ent_p = ::readdir(dir.get())) != nullptr) + { + if (ent_p->d_name[0] == '.') + continue; + + const auto conv = std::atoi(ent_p->d_name); + if (conv == 0 && (ent_p->d_name[0] != '0' && ent_p->d_name[1] != '\0')) + continue; + + if (conv == dir_fd + || (std::find(whitelist.begin(), whitelist.end(), conv) != whitelist.end())) + continue; + + ::close(conv); + } +} + +#endif + +} + +} + +BOOST_PROCESS_V2_END_NAMESPACE + +#endif //BOOST_PROCESS_V2_POSIX_DETAIL_CLOSE_HANDLES_IPP \ No newline at end of file diff --git a/include/boost/process/v2/posix/fork_and_forget_launcher.hpp b/include/boost/process/v2/posix/fork_and_forget_launcher.hpp new file mode 100644 index 000000000..1b92b5d7e --- /dev/null +++ b/include/boost/process/v2/posix/fork_and_forget_launcher.hpp @@ -0,0 +1,135 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_POSIX_FORK_AND_FORGET_LAUNCHER_HPP +#define BOOST_PROCESS_V2_POSIX_FORK_AND_FORGET_LAUNCHER_HPP + +#include + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +namespace posix +{ + +/// A posix fork launcher that ignores errors after `fork`. +struct fork_and_forget_launcher : default_launcher +{ + fork_and_forget_launcher() = default; + + template + auto operator()(ExecutionContext & context, + const typename std::enable_if::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + error_code ec; + auto proc = (*this)(context, ec, executable, std::forward(args), std::forward(inits)...); + + if (ec) + asio::detail::throw_error(ec, "fork_and_forget_launcher"); + + return proc; + } + + + template + auto operator()(ExecutionContext & context, + error_code & ec, + const typename std::enable_if::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + return (*this)(context.get_executor(), executable, std::forward(args), std::forward(inits)...); + } + + template + auto operator()(Executor exec, + const typename std::enable_if< + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor::value || + BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + error_code ec; + auto proc = (*this)(std::move(exec), ec, executable, std::forward(args), std::forward(inits)...); + + if (ec) + asio::detail::throw_error(ec, "fork_and_forget_launcher"); + + return proc; + } + + template + auto operator()(Executor exec, + error_code & ec, + const typename std::enable_if< + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor::value || + BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + auto argv = this->build_argv_(executable, std::forward(args)); + { + ec = detail::on_setup(*this, executable, argv, inits ...); + if (ec) + { + detail::on_error(*this, executable, argv, ec, inits...); + return basic_process(exec); + } + + auto & ctx = BOOST_PROCESS_V2_ASIO_NAMESPACE::query( + exec, BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::context); + ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_prepare); + pid = ::fork(); + if (pid == -1) + { + ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_parent); + detail::on_fork_error(*this, executable, argv, ec, inits...); + detail::on_error(*this, executable, argv, ec, inits...); + + ec.assign(errno, system_category()); + return basic_process{exec}; + } + else if (pid == 0) + { + ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_child); + + ec = detail::on_exec_setup(*this, executable, argv, inits...); + if (!ec) + close_all_fds(ec); + if (!ec) + ::execve(executable.c_str(), const_cast(argv), const_cast(env)); + + ec.assign(errno, system_category()); + detail::on_exec_error(*this, executable, argv, ec, inits...); + ::exit(EXIT_FAILURE); + return basic_process{exec}; + } + ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_parent); + if (ec) + { + detail::on_error(*this, executable, argv, ec, inits...); + return basic_process{exec}; + } + } + basic_process proc(exec, pid); + detail::on_success(*this, executable, argv, ec, inits...); + return proc; + + } +}; + + +} + +BOOST_PROCESS_V2_END_NAMESPACE + + +#endif //BOOST_PROCESS_V2_POSIX_FORK_AND_FORGET_LAUNCHER_HPP diff --git a/include/boost/process/v2/posix/pdfork_launcher.hpp b/include/boost/process/v2/posix/pdfork_launcher.hpp new file mode 100644 index 000000000..98985995e --- /dev/null +++ b/include/boost/process/v2/posix/pdfork_launcher.hpp @@ -0,0 +1,171 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_POSIX_PDFORK_LAUNCHER_HPP +#define BOOST_PROCESS_V2_POSIX_PDFORK_LAUNCHER_HPP + +#include + +#include +#include + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +namespace posix +{ + +/// A launcher using `pdfork`. Default on FreeBSD +struct pdfork_launcher : default_launcher +{ + /// The file descriptor of the subprocess. Set after fork. + int fd; + pdfork_launcher() = default; + + template + auto operator()(ExecutionContext & context, + const typename std::enable_if::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + error_code ec; + auto proc = (*this)(context, ec, executable, std::forward(args), std::forward(inits)...); + + if (ec) + asio::detail::throw_error(ec, "pdfork_launcher"); + + return proc; + } + + + template + auto operator()(ExecutionContext & context, + error_code & ec, + const typename std::enable_if::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + return (*this)(context.get_executor(), executable, std::forward(args), std::forward(inits)...); + } + + template + auto operator()(Executor exec, + const typename std::enable_if< + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor::value || + BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + error_code ec; + auto proc = (*this)(std::move(exec), ec, executable, std::forward(args), std::forward(inits)...); + + if (ec) + asio::detail::throw_error(ec, "pdfork_launcher"); + + return proc; + } + + template + auto operator()(Executor exec, + error_code & ec, + const typename std::enable_if< + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor::value || + BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + auto argv = this->build_argv_(executable, std::forward(args)); + { + pipe_guard pg; + if (::pipe(pg.p)) + { + ec.assign(errno, system_category()); + return basic_process{exec}; + } + if (::fcntl(pg.p[1], F_SETFD, FD_CLOEXEC)) + { + ec.assign(errno, system_category()); + return basic_process{exec}; + } + ec = detail::on_setup(*this, executable, argv, inits ...); + if (ec) + { + detail::on_error(*this, executable, argv, ec, inits...); + return basic_process(exec); + } + + auto & ctx = BOOST_PROCESS_V2_ASIO_NAMESPACE::query( + exec, BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::context); + ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_prepare); + pid = ::pdfork(&fd, PD_DAEMON | PD_CLOEXEC); + if (pid == -1) + { + ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_parent); + detail::on_fork_error(*this, executable, argv, ec, inits...); + detail::on_error(*this, executable, argv, ec, inits...); + + ec.assign(errno, system_category()); + return basic_process{exec}; + } + else if (pid == 0) + { + ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_child); + ::close(pg.p[0]); + + ec = detail::on_exec_setup(*this, executable, argv, inits...); + if (!ec) + { + fd_whitelist.push_back(pg.p[1]); + close_all_fds(ec); + } + if (!ec) + ::execve(executable.c_str(), const_cast(argv), const_cast(env)); + + default_launcher::ignore_unused(::write(pg.p[1], &errno, sizeof(int))); + ec.assign(errno, system_category()); + detail::on_exec_error(*this, executable, argv, ec, inits...); + ::exit(EXIT_FAILURE); + return basic_process{exec}; + } + ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_parent); + ::close(pg.p[1]); + pg.p[1] = -1; + int child_error{0}; + int count = -1; + while ((count = ::read(pg.p[0], &child_error, sizeof(child_error))) == -1) + { + int err = errno; + if ((err != EAGAIN) && (err != EINTR)) + { + ec.assign(err, system_category()); + break; + } + } + if (count != 0) + ec.assign(child_error, system_category()); + + if (ec) + { + detail::on_error(*this, executable, argv, ec, inits...); + return basic_process{exec}; + } + } + basic_process proc(exec, pid, fd); + detail::on_success(*this, executable, argv, ec, inits...); + return proc; + } +}; + + +} + +BOOST_PROCESS_V2_END_NAMESPACE + + +#endif //BOOST_PROCESS_V2_POSIX_PDFORK_LAUNCHER_HPP diff --git a/include/boost/process/v2/posix/vfork_launcher.hpp b/include/boost/process/v2/posix/vfork_launcher.hpp new file mode 100644 index 000000000..fde3842a5 --- /dev/null +++ b/include/boost/process/v2/posix/vfork_launcher.hpp @@ -0,0 +1,136 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_POSIX_VFORK_LAUNCHER_HPP +#define BOOST_PROCESS_V2_POSIX_VFORK_LAUNCHER_HPP + +#include +#include + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +namespace posix +{ + + +/// A launcher using vfork instead of fork. +struct vfork_launcher : default_launcher +{ + vfork_launcher() = default; + + template + auto operator()(ExecutionContext & context, + const typename std::enable_if::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + error_code ec; + auto proc = (*this)(context, ec, executable, std::forward(args), std::forward(inits)...); + + if (ec) + asio::detail::throw_error(ec, "default_launcher"); + + return proc; + } + + + template + auto operator()(ExecutionContext & context, + error_code & ec, + const typename std::enable_if::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + return (*this)(context.get_executor(), executable, std::forward(args), std::forward(inits)...); + } + + template + auto operator()(Executor exec, + const typename std::enable_if< + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor::value || + BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + error_code ec; + auto proc = (*this)(std::move(exec), ec, executable, std::forward(args), std::forward(inits)...); + + if (ec) + asio::detail::throw_error(ec, "default_launcher"); + + return proc; + } + + template + auto operator()(Executor exec, + error_code & ec, + const typename std::enable_if< + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor::value || + BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + auto argv = this->build_argv_(executable, std::forward(args)); + + ec = detail::on_setup(*this, executable, argv, inits ...); + if (ec) + { + detail::on_error(*this, executable, argv, ec, inits...); + return basic_process(exec); + } + + auto & ctx = BOOST_PROCESS_V2_ASIO_NAMESPACE::query( + exec, BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::context); + ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_prepare); + pid = ::vfork(); + if (pid == -1) + { + ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_parent); + detail::on_fork_error(*this, executable, argv, ec, inits...); + detail::on_error(*this, executable, argv, ec, inits...); + + ec.assign(errno, system_category()); + return basic_process{exec}; + } + else if (pid == 0) + { + ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_child); + ec = detail::on_exec_setup(*this, executable, argv, inits...); + if (!ec) + close_all_fds(ec); + if (!ec) + ::execve(executable.c_str(), const_cast(argv), const_cast(env)); + + ec.assign(errno, system_category()); + detail::on_exec_error(*this, executable, argv, ec, inits...); + ::exit(EXIT_FAILURE); + return basic_process{exec}; + } + ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_parent); + + if (ec) + { + detail::on_error(*this, executable, argv, ec, inits...); + return basic_process{exec}; + } + + basic_process proc(exec, pid); + detail::on_success(*this, executable, argv, ec, inits...); + return proc; + + } +}; + + +} + +BOOST_PROCESS_V2_END_NAMESPACE + +#endif //BOOST_PROCESS_V2_POSIX_VFORK_LAUNCHER_HPP diff --git a/include/boost/process/v2/process.hpp b/include/boost/process/v2/process.hpp new file mode 100644 index 000000000..7acd4d327 --- /dev/null +++ b/include/boost/process/v2/process.hpp @@ -0,0 +1,369 @@ +// Copyright (c) 2021 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// +// process.hpp +// ~~~~~~~~~~~~~~ +// + +#ifndef BOOST_PROCESS_V2_PROCESS_HPP +#define BOOST_PROCESS_V2_PROCESS_HPP + +#include +#include +#include +#include +#include + +#if defined(BOOST_PROCESS_V2_STANDALONE) +#include +#include +#else +#include +#include +#endif + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +/// A class managing a subprocess +/* A `basic_process` object manages a subprocess; it tracks the status and exit-code, + * and will terminate the process on destruction if `detach` was not called. +*/ +template +struct basic_process +{ + /// The executor of the process + using executor_type = Executor; + /// Get the executor of the process + executor_type get_executor() {return process_handle_.get_executor();} + + /// The non-closing handle type + using handle_type = basic_process_handle; + + /// Get the underlying non-closing handle + handle_type & handle() { return process_handle_; } + + /// Get the underlying non-closing handle + const handle_type & handle() const { return process_handle_; } + + /// Provides access to underlying operating system facilities + using native_handle_type = typename handle_type::native_handle_type; + + /// Rebinds the process_handle to another executor. + template + struct rebind_executor + { + /// The socket type when rebound to the specified executor. + typedef basic_process other; + }; + + /** An empty process is similar to a default constructed thread. It holds an empty + handle and is a place holder for a process that is to be launched later. */ + basic_process() = default; + + basic_process(const basic_process&) = delete; + basic_process& operator=(const basic_process&) = delete; + + /// Move construct the process. It will be detached from `lhs`. + basic_process(basic_process&& lhs) = default; + + /// Move assign a process. It will be detached from `lhs`. + basic_process& operator=(basic_process&& lhs) = default; + + /// Move construct and rebind the executor. + template + basic_process(basic_process&& lhs) + : process_handle_(std::move(lhs.process_handle_)), + exit_status_{lhs.exit_status_} + { + } + + /// Construct a child from a property list and launch it using the default launcher.. + template + explicit basic_process( + executor_type executor, + const filesystem::path& exe, + std::initializer_list args, + Inits&&... inits) + : basic_process(default_process_launcher()(std::move(executor), exe, args, std::forward(inits)...)) + { + } + /// Construct a child from a property list and launch it using the default launcher.. + template + explicit basic_process( + executor_type executor, + const filesystem::path& exe, + std::initializer_list args, + Inits&&... inits) + : basic_process(default_process_launcher()(std::move(executor), exe, args, std::forward(inits)...)) + { + } + + /// Construct a child from a property list and launch it using the default launcher.. + template + explicit basic_process( + executor_type executor, + const filesystem::path& exe, + Args&& args, Inits&&... inits) + : basic_process(default_process_launcher()(std::move(executor), exe, + std::forward(args), std::forward(inits)...)) + { + } + + /// Construct a child from a property list and launch it using the default launcher.. + template + explicit basic_process( + ExecutionContext & context, + typename std::enable_if< + std::is_convertible::value, + const filesystem::path&>::type exe, + std::initializer_list args, + Inits&&... inits) + : basic_process(default_process_launcher()(executor_type(context.get_executor()), + exe, args, std::forward(inits)...)) + { + } + /// Construct a child from a property list and launch it using the default launcher. + template + explicit basic_process( + ExecutionContext & context, + typename std::enable_if< + std::is_convertible::value, + const filesystem::path&>::type exe, + Args&& args, Inits&&... inits) + : basic_process(default_process_launcher()(executor_type(context.get_executor()), + exe, std::forward(args), std::forward(inits)...)) + { + } + + /// Attach to an existing process + explicit basic_process(executor_type exec, pid_type pid) : process_handle_(std::move(exec), pid) {} + + /// Attach to an existing process and the internal handle + explicit basic_process(executor_type exec, pid_type pid, native_handle_type native_handle) + : process_handle_(std::move(exec), pid, native_handle) {} + + /// Create an invalid handle + explicit basic_process(executor_type exec) : process_handle_{std::move(exec)} {} + + /// Attach to an existing process + template + explicit basic_process(ExecutionContext & context, pid_type pid, + typename std::enable_if< + std::is_convertible::value, void *>::type = nullptr) + : process_handle_(context, pid) {} + + /// Attach to an existing process and the internal handle + template + explicit basic_process(ExecutionContext & context, pid_type pid, native_handle_type native_handle, + typename std::enable_if< + std::is_convertible::value, void *>::type = nullptr) + : process_handle_(context, pid, native_handle) {} + + /// Create an invalid handle + template + explicit basic_process(ExecutionContext & context, + typename std::enable_if< + is_convertible::value, void *>::type = nullptr) + : process_handle_(context) {} + + + + /// Destruct the handle and terminate the process if it wasn't detached. + ~basic_process() + { + process_handle_.terminate_if_running(); + } + + /// Sends the process a signal to ask for an interrupt, which the process may interpret as a shutdown. + /** Maybe be ignored by the subprocess. */ + void interrupt() + { + error_code ec; + interrupt(ec); + if (ec) + throw system_error(ec, "interrupt failed"); + + } + /// Throwing @overload void interrupt() + void interrupt(error_code & ec) + { + process_handle_.interrupt(ec); + } + + /// Throwing @overload void request_exit(error_code & ec) + void request_exit() + { + error_code ec; + request_exit(ec); + if (ec) + throw system_error(ec, "request_exit failed"); + } + /// Sends the process a signal to ask for a graceful shutdown. Maybe be ignored by the subprocess. + void request_exit(error_code & ec) + { + process_handle_.request_exit(ec); + } + + /// Throwing @overload void terminate(native_exit_code_type &exit_code, error_code & ec) + void terminate() + { + error_code ec; + terminate(ec); + if (ec) + detail::throw_error(ec, "terminate failed"); + } + /// Unconditionally terminates the process and stores the exit code in exit_status. + void terminate(error_code & ec) + { + process_handle_.terminate(exit_status_, ec); + } + + /// Throwing @overload wait(error_code & ec) + int wait() + { + error_code ec; + if (running(ec)) + wait(ec); + if (ec) + detail::throw_error(ec, "wait failed"); + return exit_code(); + } + /// Waits for the process to exit, store the exit code internall and return it. + int wait(error_code & ec) + { + if (running(ec)) + process_handle_.wait(exit_status_, ec); + return exit_code(); + } + + /// Detach the process. + handle_type detach() + { +#if defined(BOOST_PROCESS_V2_STANDALONE) + return std::exchange(process_handle_, get_executor()); +#else + return boost::exchange(process_handle_, get_executor()); +#endif + } + // Get the native + native_handle_type native_handle() {return process_handle_.native_handle(); } + int exit_code() const + { + return evaluate_exit_code(exit_status_); + } + + /// Get the id of the process; + pid_type id() const {return process_handle_.id();} + + /// The native handle of the process. + /** This might be undefined on posix systems that only support signals */ + native_exit_code_type native_exit_code() const + { + return exit_status_; + } + /// Checks if the current process is running. + /** If it has already completed the exit code will be stored internally + * and can be obtained by calling `exit_code. + */ + bool running() + { + error_code ec; + native_exit_code_type exit_code{}; + auto r = process_handle_.running(exit_code, ec); + if (!ec && !r) + exit_status_ = exit_code; + else + detail::throw_error(ec, "running failed"); + + return r; + } + + /// Throwing @overload bool running(error_code & ec) + bool running(error_code & ec) noexcept + { + native_exit_code_type exit_code{}; + auto r = process_handle_.running(exit_code, ec); + if (!ec && !r) + exit_status_ = exit_code; + return r; + } + + /// Check if the process is referring to an existing process. + /** Note that this might be a process that already exited.*/ + bool is_open() const { return process_handle_.is_open(); } + + /// Asynchronously wait for the process to exit and deliver the portable exit-code in the completion handler. + template + BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(WaitHandler, void (error_code, int)) + async_wait(WaitHandler && handler BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)) + { + return BOOST_PROCESS_V2_ASIO_NAMESPACE::async_compose( + async_wait_op_{process_handle_, exit_status_}, handler, process_handle_); + } + +private: + template + friend struct basic_process; + + basic_process_handle process_handle_; + native_exit_code_type exit_status_{detail::still_active}; + + + struct async_wait_op_ + { + basic_process_handle & handle; + native_exit_code_type & res; + + template + void operator()(Self && self) + { + if (!process_is_running(res)) + { + struct completer + { + int code; + typename std::decay::type self; + void operator()() + { + self.complete(error_code{}, evaluate_exit_code(code)); + } + }; + + BOOST_PROCESS_V2_ASIO_NAMESPACE::post(handle.get_executor(), + completer{res, std::move(self)}); + } + else + handle.async_wait(std::move(self)); + } + + template + void operator()(Self && self, error_code ec, native_exit_code_type code) + { + if (!ec && process_is_running(code)) + handle.async_wait(std::move(self)); + else + { + if (!ec) + res = code; + std::move(self).complete(ec, evaluate_exit_code(code)); + } + } + }; +}; + +/// Process with the default executor. +typedef basic_process<> process; + +BOOST_PROCESS_V2_END_NAMESPACE + + +#endif //BOOST_PROCESS_V2_PROCESS_HPP \ No newline at end of file diff --git a/include/boost/process/v2/process_handle.hpp b/include/boost/process/v2/process_handle.hpp new file mode 100644 index 000000000..04ea6e62c --- /dev/null +++ b/include/boost/process/v2/process_handle.hpp @@ -0,0 +1,169 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_PROCESS_HANDLE_HPP +#define BOOST_PROCESS_V2_PROCESS_HANDLE_HPP + +#include + +#if defined(BOOST_PROCESS_V2_WINDOWS) +#include +#else + +#if defined(BOOST_PROCESS_V2_PIDFD_OPEN) +#include +#elif defined(BOOST_PROCESS_V2_PDFORK) +#include +#else +// with asio support we could use EVFILT_PROC:NOTE_EXIT as well. +#include +#endif +#endif + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + + +#if defined(GENERATING_DOCUMENTATION) +/** A process handle is an unmanaged version of a process. + * This means it does not terminate the proces on destruction and + * will not keep track of the exit-code. + * + * Note that the exit code might be discovered early, during a call to `running`. + * Thus it can only be discovered that process has exited already. + */ +template +struct basic_process_handle +{ + /// The native handle of the process. + /** This might be undefined on posix systems that only support signals */ + using native_handle_type = implementation_defined; + + /// The executor_type of the process_handle + using executor_type = Executor; + + /// Getter for the executor + executor_type get_executor(); + + /// Rebinds the process_handle to another executor. + template + struct rebind_executor + { + /// The socket type when rebound to the specified executor. + typedef basic_process_handle other; + }; + + + /// Construct a basic_process_handle from an execution_context. + /** + * @tparam ExecutionContext The context must fulfill the asio::execution_context requirements + */ + template + basic_process_handle(ExecutionContext &context); + + /// Construct an empty process_handle from an executor. + basic_process_handle(executor_type executor); + + /// Construct an empty process_handle from an executor and bind it to a pid. + /** On NON-linux posix systems this call is not able to obtain a file-descriptor and will thus + * rely on signals. + */ + basic_process_handle(executor_type executor, pid_type pid); + + /// Construct an empty process_handle from an executor and bind it to a pid and the native-handle + /** On some non-linux posix systems this overload is not present. + */ + basic_process_handle(executor_type executor, pid_type pid, native_handle_type process_handle); + + /// Move construct and rebind the executor. + template + basic_process_handle(basic_process_handle &&handle); + + /// Get the id of the process + pid_type id() const + { return pid_; } + + /// Terminate the process if it's still running and ignore the result + void terminate_if_running(error_code &); + + /// Throwing @overload void terminate_if_running(error_code & ec; + void terminate_if_running(); + /// wait for the process to exit and store the exit code in exit_status. + void wait(native_exit_code_type &exit_status, error_code &ec); + /// Throwing @overload wait(native_exit_code_type &exit_code, error_code & ec) + void wait(native_exit_code_type &exit_status); + + /// Sends the process a signal to ask for an interrupt, which the process may interpret as a shutdown. + /** Maybe be ignored by the subprocess. */ + void interrupt(error_code &ec); + + /// Throwing @overload void interrupt() + void interrupt(); + + /// Sends the process a signal to ask for a graceful shutdown. Maybe be ignored by the subprocess. + void request_exit(error_code &ec); + + /// Throwing @overload void request_exit(error_code & ec) + void request_exit() + + /// Unconditionally terminates the process and stores the exit code in exit_status. + void terminate(native_exit_code_type &exit_status, error_code &ec);\ + /// Throwing @overload void terminate(native_exit_code_type &exit_code, error_code & ec) + void terminate(native_exit_code_type &exit_status);/ + + /// Checks if the current process is running. + /**If it has already completed, it assigns the exit code to `exit_code`. + */ + bool running(native_exit_code_type &exit_code, error_code &ec); + /// Throwing @overload bool running(native_exit_code_type &exit_code, error_code & ec) + bool running(native_exit_code_type &exit_code); + + /// Check if the process handle is referring to an existing process. + bool is_open() const; + + /// Asynchronously wait for the process to exit and deliver the native exit-code in the completion handler. + template + BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(WaitHandler, void (error_code, native_exit_code_type)) + async_wait(WaitHandler &&handler BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)); + +}; + + +#else +#if defined(BOOST_PROCESS_V2_WINDOWS) +template +using basic_process_handle = detail::basic_process_handle_win; +#else + +#if defined(BOOST_PROCESS_V2_PIDFD_OPEN) +template +using basic_process_handle = detail::basic_process_handle_fd; +#elif defined(BOOST_PROCESS_V2_PDFORK) +template +using basic_process_handle = detail::basic_process_handle_fd_or_signal; +#else + +template +using basic_process_handle = detail::basic_process_handle_signal; + +#endif +#endif + +/// Process handle with the default executor. +using process_handle = basic_process_handle<>; + +#endif + +BOOST_PROCESS_V2_END_NAMESPACE + +#if defined(BOOST_PROCESS_V2_HEADER_ONLY) + +#include + +#endif + + + + +#endif //BOOST_PROCESS_V2_PROCESS_HANDLE_HPP diff --git a/include/boost/process/v2/src.hpp b/include/boost/process/v2/src.hpp new file mode 100644 index 000000000..60c505ce6 --- /dev/null +++ b/include/boost/process/v2/src.hpp @@ -0,0 +1,26 @@ +// Copyright (c) 2021 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_PROCESS_V2_SRC_HPP +#define BOOST_PROCESS_V2_SRC_HPP + +#define BOOST_PROCESS_V2_SOURCE + +#include + +#if defined(BOOST_PROCESS_V2_HEADER_ONLY) +# error Do not compile Beast library source with BOOST_BEAST_HEADER_ONLY defined +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif //BOOST_PROCESS_V2_SRC_HPP diff --git a/include/boost/process/v2/start_dir.hpp b/include/boost/process/v2/start_dir.hpp new file mode 100644 index 000000000..1410d7a2e --- /dev/null +++ b/include/boost/process/v2/start_dir.hpp @@ -0,0 +1,50 @@ +// +// process/start_dir.hpp +// ~~~~~~~~ +// +// Copyright (c) 2021 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_PROCESS_v2_START_DIR_HPP +#define BOOST_PROCESS_v2_START_DIR_HPP + +#include +#include + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +/// Initializer for the starting directory of a subprocess to be launched. +struct process_start_dir +{ + filesystem::path start_dir; + + process_start_dir(filesystem::path start_dir) : start_dir(std::move(start_dir)) + { + } + +#if defined(BOOST_PROCESS_V2_WINDOWS) + error_code on_setup(windows::default_launcher & launcher, + const filesystem::path &, const std::wstring &) + { + launcher.current_directory = start_dir; + return error_code {}; + }; + +#else + error_code on_exec_setup(posix::default_launcher & launcher, + const filesystem::path &, const char * const *) + { + if (::chdir(start_dir.c_str()) == -1) + return detail::get_last_error(); + else + return error_code (); + } +#endif + +}; + +BOOST_PROCESS_V2_END_NAMESPACE + +#endif // BOOST_PROCESS_v2_START_DIR_HPP \ No newline at end of file diff --git a/include/boost/process/v2/stdio.hpp b/include/boost/process/v2/stdio.hpp new file mode 100644 index 000000000..5e0320eef --- /dev/null +++ b/include/boost/process/v2/stdio.hpp @@ -0,0 +1,305 @@ +// +// process/stdio.hpp +// ~~~~~~~~ +// +// Copyright (c) 2021 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_PROCESS_V2_STDIO_HPP +#define BOOST_PROCESS_V2_STDIO_HPP + +#include +#include + +#if defined(BOOST_PROCESS_V2_STANDALONE) +#include +#else +#include +#endif + +#if defined(BOOST_PROCESS_V2_POSIX) +#include +#include +#include +#endif + +BOOST_PROCESS_V2_BEGIN_NAMESPACE +namespace detail +{ +#if defined(BOOST_PROCESS_V2_WINDOWS) + +extern "C" intptr_t _get_osfhandle(int fd); + +struct handle_closer +{ + handle_closer() = default; + handle_closer(bool close) : close(close) {} + handle_closer(DWORD flags) : close(false), flags{flags} {} + + + void operator()(HANDLE h) const + { + if (close) + ::CloseHandle(h); + else if (flags != 0xFFFFFFFFu) + ::SetHandleInformation(h, 0xFFFFFFFFu, flags); + + } + + bool close{false}; + DWORD flags{0xFFFFFFFFu}; +}; + +template +struct process_io_binding +{ + HANDLE prepare() + { + auto hh = h.get(); + ::SetHandleInformation(hh, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); + return hh; + } + + std::unique_ptr h{::GetStdHandle(Io), false}; + + static DWORD get_flags(HANDLE h) + { + DWORD res; + if (!::GetHandleInformation(h, &res)) + detail::throw_last_error("get_flags"); + return res; + } + + process_io_binding() = default; + + template + process_io_binding(Stream && str, decltype(std::declval().native_handle()) = nullptr) + : process_io_binding(str.native_handle()) + {} + + process_io_binding(FILE * f) : process_io_binding(_get_osfhandle(_fileno(f))) {} + process_io_binding(HANDLE h) : h{h, get_flags(h)} {} + process_io_binding(std::nullptr_t) : process_io_binding(filesystem::path("NUL")) {} + process_io_binding(const filesystem::path & pth) + : h(::CreateFileW( + pth.c_str(), + Io == STD_INPUT_HANDLE ? GENERIC_READ : GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + nullptr + ), true) + { + } + + + template + process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_readable_pipe & readable_pipe, + typename std::enable_if::type = 0) + { + BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::native_pipe_handle p[2]; + BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::create_pipe(p, ec); + if (ec) + return ; + h = std::unique_ptr{p[1], true}; + readable_pipe.assign(p[0], ec); + } + + + template + process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_writable_pipe & writable_pipe, + typename std::enable_if::type = 0) + { + BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::native_pipe_handle p[2]; + BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::create_pipe(p, ec); + if (ec) + return ; + h = std::unique_ptr{p[0], true}; + writable_pipe.assign(p[1], ec); + } +}; + +typedef process_io_binding process_input_binding; +typedef process_io_binding process_output_binding; +typedef process_io_binding process_error_binding; + +#else + +template +struct process_io_binding +{ + constexpr static int target = Target; + int fd{target}; + bool fd_needs_closing{false}; + error_code ec; + + ~process_io_binding() + { + if (fd_needs_closing) + ::close(fd); + } + + process_io_binding() = default; + + template + process_io_binding(Stream && str, decltype(std::declval().native_handle()) = -1) + : process_io_binding(str.native_handle()) + {} + + process_io_binding(FILE * f) : process_io_binding(fileno(f)) {} + process_io_binding(int fd) : fd(fd) {} + process_io_binding(std::nullptr_t) : process_io_binding(filesystem::path("/dev/null")) {} + process_io_binding(const filesystem::path & pth) + : fd(::open(pth.c_str(), + Target == STDIN_FILENO ? O_RDONLY : (O_WRONLY | O_CREAT), + 0660)), fd_needs_closing(true) + { + } + + template + process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_readable_pipe & readable_pipe, + typename std::enable_if::type = 0) + { + BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::native_pipe_handle p[2]; + BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::create_pipe(p, ec); + if (ec) + return ; + fd = p[1]; + if (::fcntl(p[0], F_SETFD, FD_CLOEXEC) == -1) + { + ec = detail::get_last_error(); + return ; + } + fd_needs_closing = true; + readable_pipe.assign(p[0], ec); + } + + + template + process_io_binding(BOOST_PROCESS_V2_ASIO_NAMESPACE::basic_writable_pipe & writable_pipe, + typename std::enable_if::type = 0) + { + BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::native_pipe_handle p[2]; + BOOST_PROCESS_V2_ASIO_NAMESPACE::detail::create_pipe(p, ec); + if (ec) + return ; + fd = p[0]; + if (::fcntl(p[1], F_SETFD, FD_CLOEXEC) == -1) + { + ec = detail::get_last_error(); + return ; + } + fd_needs_closing = true; + writable_pipe.assign(p[1], ec); + } + + error_code on_setup(posix::default_launcher &, + const filesystem::path &, const char * const *) + { + return ec; + } + + error_code on_exec_setup(posix::default_launcher & launcher, + const filesystem::path &, const char * const *) + { + if (::dup2(fd, target) == -1) + return get_last_error(); + else + return error_code(); + } +}; + +typedef process_io_binding process_input_binding; +typedef process_io_binding process_output_binding; +typedef process_io_binding process_error_binding; + +#endif + +} + + +/// The initializer for the stdio of a subprocess +/** The subprocess initializer has three members: + * + * - in for stdin + * - out for stdout + * - err for stderr + * + * If the initializer is present all three will be set for the subprocess. + * By default they will inherit the stdio handles from the parent process. + * This means that this will forward stdio to the subprocess: + * + * @code {.cpp} + * asio::io_context ctx; + * v2::process proc(ctx, "/bin/bash", {}, v2::process_stdio{}); + * @endcode + * + * No constructors are provided in order to support designated initializers + * in later version of C++. + * + * * @code {.cpp} + * asio::io_context ctx; + * /// C++17 + * v2::process proc17(ctx, "/bin/bash", {}, v2::process_stdio{.stderr=nullptr}); + * /// C++11 & C++14 + * v2::process proc17(ctx, "/bin/bash", {}, v2::process_stdio{ {}, {}, nullptr}); + * stdin ^ ^ stderr + * @endcode + * + * Valid initializers for any stdio are: + * + * - `std::nullptr_t` assigning a null-device + * - `FILE*` any open file, including `stdin`, `stdout` and `stderr` + * - a filesystem::path, which will open a readable or writable depending on the direction of the stream + * - `native_handle` any native file handle (`HANDLE` on windows) or file descriptor (`int` on posix) + * - any io-object with a .native_handle() function that is comptaiblie with the above. E.g. a asio::ip::tcp::socket + * - an asio::basic_writeable_pipe for stdin or asio::basic_readable_pipe for stderr/stdout. + * + * + */ +struct process_stdio +{ + detail::process_input_binding in; + detail::process_output_binding out; + detail::process_error_binding err; + +#if defined(BOOST_PROCESS_V2_WINDOWS) + error_code on_setup(windows::default_launcher & launcher, const filesystem::path &, const std::wstring &) + { + + launcher.startup_info.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; + launcher.startup_info.StartupInfo.hStdInput = in.prepare(); + launcher.startup_info.StartupInfo.hStdOutput = out.prepare(); + launcher.startup_info.StartupInfo.hStdError = err.prepare(); + launcher.inherit_handles = true; + return error_code {}; + }; +#else + error_code on_exec_setup(posix::default_launcher & launcher, const filesystem::path &, const char * const *) + { + if (::dup2(in.fd, in.target) == -1) + return error_code(errno, system_category()); + + if (::dup2(out.fd, out.target) == -1) + return error_code(errno, system_category()); + + if (::dup2(err.fd, err.target) == -1) + return error_code(errno, system_category()); + + + launcher.fd_whitelist.push_back(STDIN_FILENO); + launcher.fd_whitelist.push_back(STDOUT_FILENO); + launcher.fd_whitelist.push_back(STDERR_FILENO); + + return error_code {}; + }; +#endif + +}; + +BOOST_PROCESS_V2_END_NAMESPACE + +#endif // BOOST_PROCESS_V2_STDIO_HPP \ No newline at end of file diff --git a/include/boost/process/v2/windows/as_user_launcher.hpp b/include/boost/process/v2/windows/as_user_launcher.hpp new file mode 100644 index 000000000..cf1c8b712 --- /dev/null +++ b/include/boost/process/v2/windows/as_user_launcher.hpp @@ -0,0 +1,136 @@ +// +// boost/process/v2/windows/default_launcher.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2022 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_PROCESS_V2_WINDOWS_AS_USER_LAUNCHER_HPP +#define BOOST_PROCESS_V2_WINDOWS_AS_USER_LAUNCHER_HPP + +#include +#include + +BOOST_PROCESS_V2_BEGIN_NAMESPACE +namespace windows +{ + +/// A windows launcher using CreateProcessAsUser instead of CreateProcess +struct as_user_launcher : default_launcher +{ + /// The token to be used in CreateProcessAsUser. + HANDLE token; + as_user_launcher(HANDLE token = INVALID_HANDLE_VALUE) : token(token) {} + + template + auto operator()(ExecutionContext & context, + const typename std::enable_if::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + error_code ec; + auto proc = (*this)(context, ec, executable, std::forward(args), std::forward(inits)...); + + if (ec) + asio::detail::throw_error(ec, "as_user_launcher"); + + return proc; + } + + + template + auto operator()(ExecutionContext & context, + error_code & ec, + const typename std::enable_if::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + return (*this)(context.get_executor(), executable, std::forward(args), std::forward(inits)...); + } + + template + auto operator()(Executor exec, + const typename std::enable_if< + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor::value || + BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + error_code ec; + auto proc = (*this)(std::move(exec), ec, executable, std::forward(args), std::forward(inits)...); + + if (ec) + asio::detail::throw_error(ec, "as_user_launcher"); + + return proc; + } + + template + auto operator()(Executor exec, + error_code & ec, + const typename std::enable_if< + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor::value || + BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + auto command_line = this->build_command_line(executable, args); + + ec = detail::on_setup(*this, executable, command_line, inits...); + if (ec) + { + detail::on_error(*this, executable, command_line, ec, inits...); + return basic_process(exec); + } + auto ok = ::CreateProcessAsUserW( + token, + executable.empty() ? nullptr : executable.c_str(), + command_line.empty() ? nullptr : &command_line.front(), + process_attributes, + thread_attributes, + inherit_handles ? TRUE : FALSE, + creation_flags, + environment, + current_directory.empty() ? nullptr : current_directory.c_str(), + &startup_info.StartupInfo, + &process_information); + + + if (ok == 0) + { + ec = detail::get_last_error(); + detail::on_error(*this, executable, command_line, ec, inits...); + + if (process_information.hProcess != INVALID_HANDLE_VALUE) + ::CloseHandle(process_information.hProcess); + if (process_information.hThread != INVALID_HANDLE_VALUE) + ::CloseHandle(process_information.hThread); + + return basic_process(exec); + } else + { + detail::on_success(*this, executable, command_line, inits...); + + if (process_information.hThread != INVALID_HANDLE_VALUE) + ::CloseHandle(process_information.hThread); + + return basic_process(exec, + this->process_information.dwProcessId, + this->process_information.hProcess); + } + } +}; + +} +BOOST_PROCESS_V2_END_NAMESPACE + +#endif // BOOST_PROCESS_V2_WINDOWS_AS_USER_LAUNCHER_HPP \ No newline at end of file diff --git a/include/boost/process/v2/windows/creation_flags.hpp b/include/boost/process/v2/windows/creation_flags.hpp new file mode 100644 index 000000000..89f08d819 --- /dev/null +++ b/include/boost/process/v2/windows/creation_flags.hpp @@ -0,0 +1,45 @@ +// +// boost/process/v2/windows/default_launcher.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2022 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_PROCESS_V2_WINDOWS_CREATION_FLAGS_HPP +#define BOOST_PROCESS_V2_WINDOWS_CREATION_FLAGS_HPP + +#include + +BOOST_PROCESS_V2_BEGIN_NAMESPACE +namespace windows +{ + + +/// An initializers to add to the dwFlags in the startup-info +/** + * @tparam Flags The flags to be set. + */ +template +struct process_creation_flags +{ + constexpr process_creation_flags () {} + + error_code on_setup(windows::default_launcher & launcher, + const filesystem::path &, + const std::wstring &) const + { + launcher.startup_info.StartupInfo.dwFlags |= Flags; + return error_code {}; + }; +}; + +/// A flag to create a new process group. Necessary to allow interupts for the subproces. +constexpr static process_creation_flags create_new_process_group; + +} +BOOST_PROCESS_V2_END_NAMESPACE + +#endif // BOOST_PROCESS_V2_WINDOWS_CREATION_FLAGS_HPP \ No newline at end of file diff --git a/include/boost/process/v2/windows/default_launcher.hpp b/include/boost/process/v2/windows/default_launcher.hpp new file mode 100644 index 000000000..aa8d47891 --- /dev/null +++ b/include/boost/process/v2/windows/default_launcher.hpp @@ -0,0 +1,411 @@ +// +// boost/process/v2/windows/default_launcher.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2022 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_PROCESS_V2_WINDOWS_DEFAULT_LAUNCHER_HPP +#define BOOST_PROCESS_V2_WINDOWS_DEFAULT_LAUNCHER_HPP + +#include +#include +#include +#include +#include + +#include +#include + +#if defined(BOOST_PROCESS_V2_STANDALONE) +#include +#include +#include +#else +#include +#include +#include +#endif + +BOOST_PROCESS_V2_BEGIN_NAMESPACE + +template +struct basic_process; + +namespace detail +{ + +struct base {}; +struct derived : base {}; + +template +inline error_code invoke_on_setup(Launcher & launcher, const filesystem::path &executable, std::wstring &cmd_line, + Init && init, base && ) +{ + return error_code{}; +} + +template +inline auto invoke_on_setup(Launcher & launcher, const filesystem::path &executable, std::wstring &cmd_line, + Init && init, derived && ) +-> decltype(init.on_setup(launcher, executable, cmd_line)) +{ + return init.on_setup(launcher, executable, cmd_line); +} + +template +inline std::false_type probe_on_setup( + Launcher & launcher, Init && init, base && ); + +template +inline auto probe_on_setup(Launcher & launcher, Init && init, derived && ) + -> std::is_same(), std::declval()))>; + +template +using has_on_setup = decltype(probe_on_setup(std::declval(), std::declval(), derived{})); + +template +inline error_code on_setup(Launcher & launcher, const filesystem::path &executable, std::wstring &cmd_line) +{ + return error_code{}; +} + +template +inline error_code on_setup(Launcher & launcher, const filesystem::path &executable, std::wstring &cmd_line, + Init1 && init1, Inits && ... inits) +{ + auto ec = invoke_on_setup(launcher, executable, cmd_line, init1, derived{}); + if (ec) + return ec; + else + return on_setup(launcher, executable, cmd_line, inits...); +} + + +template +inline void invoke_on_error(Launcher & launcher, const filesystem::path &executable, std::wstring &cmd_line, + const error_code & ec, Init && init, base && ) +{ +} + +template +inline auto invoke_on_error(Launcher & launcher, const filesystem::path &executable, std::wstring &cmd_line, + const error_code & ec, Init && init, derived && ) +-> decltype(init.on_error(launcher, ec, executable, cmd_line, ec)) +{ + init.on_error(launcher, executable, cmd_line, ec); +} + + +template +inline std::false_type probe_on_error( + Launcher & launcher, Init && init, base && ); + +template +inline auto probe_on_error(Launcher & launcher, Init && init, derived && ) + -> std::is_same(), std::declval(), std::declval()))>; + +template +using has_on_error = decltype(probe_on_error(std::declval(), std::declval(), derived{})); + + +template +inline void on_error(Launcher & launcher, const filesystem::path &executable, std::wstring &cmd_line, + const error_code & ec) +{ +} + +template +inline void on_error(Launcher & launcher, const filesystem::path &executable, std::wstring &cmd_line, + const error_code & ec, + Init1 && init1, + Inits && ... inits) +{ + invoke_on_error(launcher, executable, cmd_line, ec, init1, derived{}); + on_error(launcher, executable, cmd_line, ec, inits...); +} + +template +inline void invoke_on_success(Launcher & launcher, const filesystem::path &executable, std::wstring &cmd_line, + Init && init, base && ) +{ +} + +template +inline auto invoke_on_success(Launcher & launcher, const filesystem::path &executable, std::wstring &cmd_line, + Init && init, derived && ) + -> decltype(init.on_success(launcher, executable, cmd_line)) +{ + init.on_success(launcher, executable, cmd_line); +} + +template +inline std::false_type probe_on_success( + Launcher & launcher, Init && init, base && ); + +template +inline auto probe_on_success(Launcher & launcher, Init && init, derived && ) + -> std::is_same(), std::declval()))>; + +template +using has_on_success = decltype(probe_on_success(std::declval(), std::declval(), derived{})); + +template +inline void on_success(Launcher & launcher, const filesystem::path &executable, std::wstring &cmd_line) +{ +} + +template +inline void on_success(Launcher & launcher, const filesystem::path &executable, std::wstring &cmd_line, + Init1 && init1, Inits && ... inits) +{ + invoke_on_success(launcher, executable, cmd_line, init1, derived{}); + on_success(launcher, executable, cmd_line, inits...); +} + +template +struct is_initializer : std::integral_constant::value || + has_on_error::value || + has_on_success::value> +{ +}; + +template +struct all_are_initializers; + +template +struct all_are_initializers : std::true_type {}; + + +template +struct all_are_initializers : is_initializer {}; + +template +struct all_are_initializers + : std::integral_constant::value && all_are_initializers::value> +{ +}; + + +} + +template +struct basic_process; + +namespace windows +{ + +/// The default launcher for processes on windows. +struct default_launcher +{ + //// The process_attributes passed to CreateProcess + SECURITY_ATTRIBUTES * process_attributes = nullptr; + //// The thread_attributes passed to CreateProcess + SECURITY_ATTRIBUTES * thread_attributes = nullptr; + /// The bInheritHandles option. Needs to be set to true by any initializers using handles. + bool inherit_handles = false; + /// The creation flags of the process. Initializers may add to them; extended startupinfo is assumed. + DWORD creation_flags{EXTENDED_STARTUPINFO_PRESENT}; + /// A pointer to the subprocess environment. + void * environment = nullptr; + /// The startup director. An empty path will get ignored. + filesystem::path current_directory{}; + + /// The full startup info passed to CreateProcess + STARTUPINFOEXW startup_info{{sizeof(STARTUPINFOEXW), nullptr, nullptr, nullptr, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, nullptr, + INVALID_HANDLE_VALUE, + INVALID_HANDLE_VALUE, + INVALID_HANDLE_VALUE}, + nullptr}; + /// The process_information that gets assigned after a call to CreateProcess + PROCESS_INFORMATION process_information{nullptr, nullptr, 0,0}; + + template + using enable_init = typename std::enable_if< + detail::all_are_initializers::value, + basic_process>::type; + + default_launcher() = default; + + template + auto operator()(ExecutionContext & context, + const typename std::enable_if::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> enable_init + { + error_code ec; + auto proc = (*this)(context, ec, executable, std::forward(args), std::forward(inits)...); + + if (ec) + asio::detail::throw_error(ec, "default_launcher"); + + return proc; + } + + + template + auto operator()(ExecutionContext & context, + error_code & ec, + const typename std::enable_if::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> enable_init + { + return (*this)(context.get_executor(), ec, executable, std::forward(args), std::forward(inits)...); + } + + template + auto operator()(Executor exec, + const typename std::enable_if< + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor::value + || BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> enable_init + { + error_code ec; + auto proc = (*this)(std::move(exec), ec, executable, std::forward(args), std::forward(inits)...); + + if (ec) + asio::detail::throw_error(ec, "default_launcher"); + + return proc; + } + + template + auto operator()(Executor exec, + error_code & ec, + const typename std::enable_if< + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor::value || + BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> enable_init + { + auto command_line = this->build_command_line(executable, std::forward(args)); + + ec = detail::on_setup(*this, executable, command_line, inits...); + if (ec) + { + detail::on_error(*this, executable, command_line, ec, inits...); + return basic_process(exec); + } + + auto ok = ::CreateProcessW( + executable.empty() ? nullptr : executable.c_str(), + command_line.empty() ? nullptr : &command_line.front(), + process_attributes, + thread_attributes, + inherit_handles ? TRUE : FALSE, + creation_flags, + environment, + current_directory.empty() ? nullptr : current_directory.c_str(), + &startup_info.StartupInfo, + &process_information); + + auto ec__ = detail::get_last_error(); + if (ok == 0) + { + ec = detail::get_last_error(); + detail::on_error(*this, executable, command_line, ec, inits...); + + if (process_information.hProcess != INVALID_HANDLE_VALUE) + ::CloseHandle(process_information.hProcess); + if (process_information.hThread != INVALID_HANDLE_VALUE) + ::CloseHandle(process_information.hThread); + + return basic_process(exec); + } + else + { + detail::on_success(*this, executable, command_line, inits...); + + if (process_information.hThread != INVALID_HANDLE_VALUE) + ::CloseHandle(process_information.hThread); + + return basic_process(exec, + this->process_information.dwProcessId, + this->process_information.hProcess); + } + } + + BOOST_PROCESS_V2_DECL static + std::size_t escaped_argv_length(basic_string_view ws); + BOOST_PROCESS_V2_DECL static + std::size_t escape_argv_string(wchar_t * itr, std::size_t max_size, + basic_string_view ws); + + + + + template + static std::wstring build_command_line_impl( + const filesystem::path & pt, + const Argv & argv, + basic_string_view args) + { + std::size_t req_size = std::accumulate( + std::begin(argv), std::end(argv), escaped_argv_length(pt.native()), + [](std::size_t sz, basic_string_view arg) -> std::size_t + { + return sz + 1u + escaped_argv_length(arg); + }); + + std::wstring res; + res.resize(req_size, L' '); + + wchar_t * itr = &res.front(); + itr += escape_argv_string(itr, res.size(), pt.native()); + for (const auto & a : argv) + { + itr++; + itr += escape_argv_string(itr, std::distance(itr, &res.back() + 1), a); + } + return res; + } + + template + static std::wstring build_command_line_impl( + const filesystem::path & pt, + const Argv & argv, + basic_string_view args) + { + std::vector argw; + argw.resize(std::distance(std::begin(argv), std::end(argv))); + std::transform(std::begin(argv), std::end(argv), argw.begin(), + [](basic_string_view arg) + { + return detail::conv_string(arg.data(), arg.size()); + }); + return build_command_line_impl(pt, argw, L""); + } + + template()))> + static std::wstring build_command_line(const filesystem::path & pt, const Args & args) + { + if (std::begin(args) == std::end(args)) + return pt.native(); + + return build_command_line_impl(pt, args, *std::begin(args)); + } + +}; + + +} +BOOST_PROCESS_V2_END_NAMESPACE + +#if defined(BOOST_PROCESS_V2_HEADER_ONLY) +#include +#endif + +#endif //BOOST_PROCESS_V2_WINDOWS_DEFAULT_LAUNCHER_HPP \ No newline at end of file diff --git a/include/boost/process/v2/windows/impl/default_launcher.ipp b/include/boost/process/v2/windows/impl/default_launcher.ipp new file mode 100644 index 000000000..fa6947562 --- /dev/null +++ b/include/boost/process/v2/windows/impl/default_launcher.ipp @@ -0,0 +1,80 @@ +// +// boost/process/v2/windows/impl/default_launcher.ipp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2022 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#ifndef BOOST_PROCESS_V2_WINDOWS_IMPL_DEFAULT_LAUNCHER_IPP +#define BOOST_PROCESS_V2_WINDOWS_IMPL_DEFAULT_LAUNCHER_IPP + +#include +#include + +BOOST_PROCESS_V2_BEGIN_NAMESPACE +namespace windows +{ + + std::size_t default_launcher::escaped_argv_length(basic_string_view ws) + { + if (ws.empty()) + return 2u; // just quotes + + constexpr static auto space = L' '; + constexpr static auto quote = L'"'; + + const auto has_space = ws.find(space) != basic_string_view::npos; + const auto quoted = (ws.front() == quote) && (ws.back() == quote); + const auto needs_escape = has_space && !quoted ; + + if (!needs_escape) + return ws.size(); + else + return ws.size() + std::count(ws.begin(), ws.end(), quote) + 2u; + } + + + std::size_t default_launcher::escape_argv_string(wchar_t * itr, std::size_t max_size, + basic_string_view ws) + { + const auto sz = escaped_argv_length(ws); + if (sz > max_size) + return 0u; + if (ws.empty()) + { + itr[0] = L'"'; + itr[1] = L'"'; + return 2u; + } + + const auto has_space = ws.find(L' ') != basic_string_view::npos; + const auto quoted = (ws.front() == L'"') && (ws.back() == L'"'); + const auto needs_escape = has_space && !quoted; + + if (!needs_escape) + return std::copy(ws.begin(), ws.end(), itr) - itr; + + if (sz < (2u + ws.size())) + return 0u; + + const auto end = itr + sz; + const auto begin = itr; + *(itr ++) = L'"'; + for (auto wc : ws) + { + if (wc == L'"') + *(itr++) = L'\\'; + *(itr++) = wc; + } + + *(itr ++) = L'"'; + return itr - begin; + } + +} +BOOST_PROCESS_V2_END_NAMESPACE + + +#endif //BOOST_PROCESS_V2_WINDOWS_IMPL_DEFAULT_LAUNCHER_IPP \ No newline at end of file diff --git a/include/boost/process/v2/windows/show_window.hpp b/include/boost/process/v2/windows/show_window.hpp new file mode 100644 index 000000000..1c75b79b8 --- /dev/null +++ b/include/boost/process/v2/windows/show_window.hpp @@ -0,0 +1,53 @@ +// +// boost/process/v2/windows/default_launcher.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2022 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_PROCESS_V2_WINDOWS_SHOW_WINDOW_HPP +#define BOOST_PROCESS_V2_WINDOWS_SHOW_WINDOW_HPP + +#include + +BOOST_PROCESS_V2_BEGIN_NAMESPACE +namespace windows +{ + +/// A templated initializer to add wShowWindow flags. +template +struct process_show_window +{ + constexpr process_show_window() {} + + error_code on_setup(windows::default_launcher & launcher, + const filesystem::path &, + const std::wstring &) const + { + launcher.startup_info.StartupInfo.dwFlags |= STARTF_USESHOWWINDOW; + launcher.startup_info.StartupInfo.wShowWindow |= Flags; + + return error_code {}; + }; +}; + +///Hides the window and activates another window. +constexpr static process_show_window show_window_hide; +///Activates the window and displays it as a maximized window. +constexpr static process_show_window show_window_maximized; +///Activates the window and displays it as a minimized window. +constexpr static process_show_window show_window_minimized; +///Displays the window as a minimized window. This value is similar to `minimized`, except the window is not activated. +constexpr static process_show_window show_window_minimized_not_active; +///Displays a window in its most recent size and position. This value is similar to show_normal`, except that the window is not activated. +constexpr static process_show_window show_window_not_active; +///Activates and displays a window. If the window is minimized or maximized, the system restores it to its original size and position. An application should specify this flag when displaying the window for the first time. +constexpr static process_show_window show_window_normal; + +} +BOOST_PROCESS_V2_END_NAMESPACE + +#endif // BOOST_PROCESS_V2_WINDOWS_SHOW_WINDOW_HPP \ No newline at end of file diff --git a/include/boost/process/v2/windows/with_logon_launcher.hpp b/include/boost/process/v2/windows/with_logon_launcher.hpp new file mode 100644 index 000000000..c18a02631 --- /dev/null +++ b/include/boost/process/v2/windows/with_logon_launcher.hpp @@ -0,0 +1,140 @@ +// +// boost/process/v2/windows/default_launcher.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2022 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_PROCESS_V2_WINDOWS_WITH_LOGON_LAUNCHER_HPP +#define BOOST_PROCESS_V2_WINDOWS_WITH_LOGON_LAUNCHER_HPP + +#include + +BOOST_PROCESS_V2_BEGIN_NAMESPACE +namespace windows +{ + +/// A windows launcher using CreateProcessWithLogon instead of CreateProcess +struct with_logon_launcher : default_launcher +{ + std::wstring username, domain, password; + DWORD logon_flags{0u}; + + with_logon_launcher(std::wstring username = L"", + std::wstring password = L"", + std::wstring domain = L"", + DWORD logon_flags = 0u) : + username(std::move(username)), + password(std::move(password)), + domain(std::move(domain)), + logon_flags(logon_flags) + { + } + + + template + auto operator()(ExecutionContext & context, + error_code & ec, + const typename std::enable_if::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + return (*this)(context.get_executor(), ec, executable, std::forward(args), std::forward(inits)...); + } + + + template + auto operator()(ExecutionContext & context, + const typename std::enable_if::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + return (*this)(context.get_executor(), executable, std::forward(args), std::forward(inits)...); + } + + template + auto operator()(Executor exec, + const typename std::enable_if< + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor::value || + BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + error_code ec; + auto proc = (*this)(std::move(exec), ec, executable, std::forward(args), std::forward(inits)...); + + if (ec) + asio::detail::throw_error(ec, "with_logon_launcher"); + + return proc; + } + + template + auto operator()(Executor exec, + error_code & ec, + const typename std::enable_if< + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor::value || + BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + auto command_line = this->build_command_line(executable, args); + + ec = detail::on_setup(*this, executable, command_line, inits...); + if (ec) + { + detail::on_error(*this, executable, command_line, ec, inits...); + return basic_process(exec); + } + auto ok = ::CreateProcessWithLogonW( + username.c_str(), + domain.empty() ? nullptr : domain.c_str(), + password.c_str(), + logon_flags, + executable.empty() ? nullptr : executable.c_str(), + command_line.empty() ? nullptr : &command_line.front(), + creation_flags, + environment, + current_directory.empty() ? nullptr : current_directory.c_str(), + &startup_info.StartupInfo, + &process_information); + + + if (ok == 0) + { + ec = detail::get_last_error(); + detail::on_error(*this, executable, command_line, ec, inits...); + + if (process_information.hProcess != INVALID_HANDLE_VALUE) + ::CloseHandle(process_information.hProcess); + if (process_information.hThread != INVALID_HANDLE_VALUE) + ::CloseHandle(process_information.hThread); + + return basic_process(exec); + } else + { + detail::on_success(*this, executable, command_line, inits...); + + if (process_information.hThread != INVALID_HANDLE_VALUE) + ::CloseHandle(process_information.hThread); + + return basic_process(exec, + this->process_information.dwProcessId, + this->process_information.hProcess); + } + } +}; + +} +BOOST_PROCESS_V2_END_NAMESPACE + +#endif // BOOST_PROCESS_V2_WINDOWS_WITH_LOGON_LAUNCHER_HPP \ No newline at end of file diff --git a/include/boost/process/v2/windows/with_token_launcher.hpp b/include/boost/process/v2/windows/with_token_launcher.hpp new file mode 100644 index 000000000..c3e8383c5 --- /dev/null +++ b/include/boost/process/v2/windows/with_token_launcher.hpp @@ -0,0 +1,135 @@ +// +// boost/process/v2/windows/default_launcher.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2022 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_PROCESS_V2_WINDOWS_WITH_TOKEN_LAUNCHER_HPP +#define BOOST_PROCESS_V2_WINDOWS_WITH_TOKEN_LAUNCHER_HPP + +#include + +BOOST_PROCESS_V2_BEGIN_NAMESPACE +namespace windows +{ + +/// A windows launcher using CreateProcessWithToken instead of CreateProcess +struct with_token_launcher : default_launcher +{ + HANDLE token; + DWORD logon_flags; + with_token_launcher(HANDLE token = INVALID_HANDLE_VALUE, + DWORD logon_flags = 0u) : token(token), logon_flags(logon_flags) {} + + + template + auto operator()(ExecutionContext & context, + const typename std::enable_if::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + error_code ec; + auto proc = (*this)(context, ec, executable, std::forward(args), std::forward(inits)...); + + if (ec) + asio::detail::throw_error(ec, "with_token_launcher"); + + return proc; + } + + + template + auto operator()(ExecutionContext & context, + error_code & ec, + const typename std::enable_if::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + return (*this)(context.get_executor(), ec, executable, std::forward(args), std::forward(inits)...); + } + + template + auto operator()(Executor exec, + const typename std::enable_if< + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor::value || + BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + error_code ec; + auto proc = (*this)(std::move(exec), ec, executable, std::forward(args), std::forward(inits)...); + + if (ec) + asio::detail::throw_error(ec, "with_token_launcher"); + + return proc; + } + + template + auto operator()(Executor exec, + error_code & ec, + const typename std::enable_if< + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor::value || + BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor::value, + filesystem::path >::type & executable, + Args && args, + Inits && ... inits ) -> basic_process + { + auto command_line = this->build_command_line(executable, args); + + ec = detail::on_setup(*this, executable, command_line, inits...); + if (ec) + { + detail::on_error(*this, executable, command_line, ec, inits...); + return basic_process(exec); + } + auto ok = ::CreateProcessWithTokenW( + token, + logon_flags, + executable.empty() ? nullptr : executable.c_str(), + command_line.empty() ? nullptr : &command_line.front(), + creation_flags, + environment, + current_directory.empty() ? nullptr : current_directory.c_str(), + &startup_info.StartupInfo, + &process_information); + + + if (ok == 0) + { + ec = detail::get_last_error(); + detail::on_error(*this, executable, command_line, ec, inits...); + + if (process_information.hProcess != INVALID_HANDLE_VALUE) + ::CloseHandle(process_information.hProcess); + if (process_information.hThread != INVALID_HANDLE_VALUE) + ::CloseHandle(process_information.hThread); + + return basic_process(exec); + } else + { + detail::on_success(*this, executable, command_line, inits...); + + if (process_information.hThread != INVALID_HANDLE_VALUE) + ::CloseHandle(process_information.hThread); + + return basic_process(exec, + this->process_information.dwProcessId, + this->process_information.hProcess); + } + } +}; + +} +BOOST_PROCESS_V2_END_NAMESPACE + +#endif // BOOST_PROCESS_V2_WINDOWS_WITH_TOKEN_LAUNCHER_HPP \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 000000000..7867c8adf --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,76 @@ +enable_testing() + + +add_executable(boost_process_sparring_partner sparring_partner.cpp ) +target_link_libraries(boost_process_sparring_partner Boost::program_options Boost::filesystem Boost::iostreams) + +add_executable(boost_process_exit_argc exit_argc.cpp) + +add_executable(boost_process_sub_launch sub_launcher.cpp) +target_link_libraries(boost_process_sub_launch Boost::program_options Boost::filesystem Boost::iostreams Boost::system) + +function(process_standalone_test name ) + add_executable(boost_process_${name} ${name}.cpp) + target_link_libraries(boost_process_${name} Boost::system Boost::filesystem) + add_test(NAME boost_process_${name} COMMAND $ ) +endfunction() + +process_standalone_test(environment) +process_standalone_test(async_pipe) +process_standalone_test(pipe) + +function(process_sub_launch_test name ) + add_executable(boost_process_${name} ${name}.cpp) + target_link_libraries(boost_process_${name} Boost::system Boost::filesystem Boost::thread) + add_test(NAME boost_process_${name} COMMAND $ $ ) +endfunction() + +process_sub_launch_test(group) +process_sub_launch_test(group_wait) + +function(process_sparring_partner_launch name ) + add_executable(boost_process_${name} ${name}.cpp) + target_link_libraries(boost_process_${name} Boost::system Boost::filesystem Boost::thread) + add_test(NAME boost_process_${name} COMMAND $ $ ) +endfunction() + +process_sparring_partner_launch(async) +process_sparring_partner_launch(async_fut) +process_sparring_partner_launch(args_handling) +process_sparring_partner_launch(args_cmd) +process_sparring_partner_launch(wargs_cmd) +process_sparring_partner_launch(bind_stderr) +process_sparring_partner_launch(bind_stdin) +process_sparring_partner_launch(bind_stdin_stdout) +process_sparring_partner_launch(bind_stdout) +process_sparring_partner_launch(bind_stdout_stderr) +process_sparring_partner_launch(pipe_fwd) +process_sparring_partner_launch(cmd_test) +process_sparring_partner_launch(close_stderr) +process_sparring_partner_launch(close_stdin) +process_sparring_partner_launch(close_stdout) +process_sparring_partner_launch(error) +process_sparring_partner_launch(exit_code) +process_sparring_partner_launch(extensions) +process_sparring_partner_launch(env) +process_sparring_partner_launch(limit_fd) +process_sparring_partner_launch(run_exe) +process_sparring_partner_launch(run_exe_path) +process_sparring_partner_launch(search_path) +process_sparring_partner_launch(shell) +process_sparring_partner_launch(shell_path) +process_sparring_partner_launch(system_test1) +process_sparring_partner_launch(system_test2) +process_sparring_partner_launch(spawn) +process_sparring_partner_launch(start_dir) +process_sparring_partner_launch(terminate) +process_sparring_partner_launch(throw_on_error) +process_sparring_partner_launch(wait) +process_sparring_partner_launch(wait_for) +process_sparring_partner_launch(on_exit) +process_sparring_partner_launch(on_exit2) +process_sparring_partner_launch(on_exit3) +process_sparring_partner_launch(posix_specific) +process_sparring_partner_launch(windows_specific) + +add_subdirectory(v2) \ No newline at end of file diff --git a/test/Jamfile.jam b/test/Jamfile.jam index 329634cc0..7d2c3db8d 100644 --- a/test/Jamfile.jam +++ b/test/Jamfile.jam @@ -70,7 +70,7 @@ rule test-options ( name ) test-suite bare : [ run environment.cpp system filesystem : [ test-options environment ] ] - [ run async_pipe.cpp system filesystem : [ test-options async_pipe ] ] + [ run async_pipe.cpp system filesystem : [ test-options async_pipe ] : : darwin:no ] [ run pipe.cpp system filesystem : [ test-options pipe ] ] [ compile no_ansi_apps.cpp ] [ compile-fail spawn_fail.cpp ] @@ -100,12 +100,12 @@ test-suite with-valgrind : [ run env.cpp program_options system filesystem : [ test-options env ] : sparring_partner ] [ run group.cpp system thread filesystem : [ test-options group ] : sub_launch ] [ run group.cpp system thread filesystem : [ test-options group ] : sub_launch : no windows:yes BOOST_USE_WINDOWS_H=1 : group-windows-h ] - [ run group_wait.cpp system thread filesystem : [ test-options group_wait ] : sparring_partner ] + [ run group_wait.cpp system thread filesystem : [ test-options group_wait ] : sparring_partner : darwin:no ] [ run limit_fd.cpp program_options system filesystem : [ test-options limit_fd ] : sparring_partner ] [ run run_exe.cpp filesystem : : sparring_partner ] [ run run_exe_path.cpp filesystem : [ test-options run_exe_path ] : sparring_partner ] [ run search_path.cpp filesystem system : [ test-options search_path ] : : windows:shell32 ] - [ run shell.cpp filesystem system : [ test-options shell ] : sparring_partner ] + [ run shell.cpp filesystem system : [ test-options shell ] : sparring_partner : darwin:no ] [ run shell_path.cpp filesystem system : [ test-options shell_path ] ] [ run system_test1.cpp filesystem system : [ test-options system_test1 ] : sparring_partner ] [ run system_test2.cpp filesystem system : [ test-options system_test2 ] : sparring_partner ] @@ -125,10 +125,11 @@ test-suite with-valgrind : test-suite without-valgrind : [ run async_system_future.cpp filesystem system coroutine : [ test-options async_system_future ] : sparring_partner : static msvc:/bigobj ] [ run async_system_stackful.cpp filesystem system coroutine : [ test-options async_system_stackful ] : sparring_partner : static msvc:/bigobj ] - [ run async_system_stackful_error.cpp filesystem system coroutine : [ test-options async_system_stackful_error ] : sparring_partner : static msvc:/bigobj ] +# [ run async_system_stackful_error.cpp filesystem system coroutine : [ test-options async_system_stackful_error ] : sparring_partner : static msvc:/bigobj ] [ run async_system_stackful_except.cpp filesystem system coroutine : [ test-options async_system_stackful_except ] : sparring_partner : static msvc:/bigobj ] [ run async_system_stackless.cpp filesystem system coroutine : [ test-options async_system_stackless ] : sparring_partner : static msvc:/bigobj ] [ run vfork.cpp system filesystem : [ test-options vfork ] : sparring_partner : no linux:yes ] ; +alias v2-tests : v2//standalone v2//with_target ; \ No newline at end of file diff --git a/test/args_handling.cpp b/test/args_handling.cpp index 699659367..4377a836c 100644 --- a/test/args_handling.cpp +++ b/test/args_handling.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include #include #include @@ -26,7 +26,7 @@ BOOST_AUTO_TEST_CASE(implicit_args_fs_path) { using boost::unit_test::framework::master_test_suite; - boost::filesystem::path exe = master_test_suite().argv[1]; + boost::process::filesystem::path exe = master_test_suite().argv[1]; std::error_code ec; bp::child c( @@ -62,7 +62,7 @@ BOOST_AUTO_TEST_CASE(explicit_args_fs_path) { using boost::unit_test::framework::master_test_suite; - boost::filesystem::path exe = master_test_suite().argv[1]; + boost::process::filesystem::path exe = master_test_suite().argv[1]; std::error_code ec; bp::child c( diff --git a/test/async.cpp b/test/async.cpp index e12f4bf53..4b5957eee 100644 --- a/test/async.cpp +++ b/test/async.cpp @@ -147,8 +147,8 @@ BOOST_AUTO_TEST_CASE(async_wait_different_contexts, *boost::unit_test::timeout(1 timeout2.async_wait([&](boost::system::error_code ec){if (!ec) io_context2.stop();}); std::error_code ec; - bool exit_called_for_c1 = false; - int exit_code_c1 = 0; + std::atomic exit_called_for_c1 {false}; + std::atomic exit_code_c1 {0}; bp::child c1( master_test_suite().argv[1], "test", "--exit-code", "1", @@ -164,8 +164,8 @@ BOOST_AUTO_TEST_CASE(async_wait_different_contexts, *boost::unit_test::timeout(1 ); BOOST_REQUIRE(!ec); - bool exit_called_for_c2 = false; - int exit_code_c2 = 0; + std::atomic exit_called_for_c2 {false}; + std::atomic exit_code_c2{0}; bp::child c2( master_test_suite().argv[1], "test", "--exit-code", "2", "--wait", "4", @@ -174,7 +174,8 @@ BOOST_AUTO_TEST_CASE(async_wait_different_contexts, *boost::unit_test::timeout(1 bp::on_exit([&](int exit, const std::error_code& ec_in) { BOOST_CHECK(!exit_called_for_c2); - exit_code_c2 = exit; exit_called_for_c2=true; + exit_code_c2 = exit; + exit_called_for_c2=true; BOOST_CHECK(!ec_in); timeout2.cancel(); }) diff --git a/test/async_pipe.cpp b/test/async_pipe.cpp index ef6cab4be..0507dbd7a 100644 --- a/test/async_pipe.cpp +++ b/test/async_pipe.cpp @@ -120,7 +120,7 @@ BOOST_AUTO_TEST_CASE(move_pipe) ap_inv.close(); const auto ap3 = std::move(ap_inv); } - +/* { //copy an a closed pipe BOOST_TEST_CHECKPOINT("Copy assign"); @@ -139,7 +139,7 @@ BOOST_AUTO_TEST_CASE(move_pipe) BOOST_TEST_CHECKPOINT("Copy construct"); bp::async_pipe ap4{ap_inv}; } - +*/ } diff --git a/test/bind_stderr.cpp b/test/bind_stderr.cpp index 6f0c11d16..08762c005 100644 --- a/test/bind_stderr.cpp +++ b/test/bind_stderr.cpp @@ -22,7 +22,8 @@ #include #include -#include +#include +#include #include #include @@ -37,7 +38,7 @@ typedef boost::asio::windows::stream_handle pipe_end; typedef boost::asio::posix::stream_descriptor pipe_end; #endif -namespace fs = boost::filesystem; +namespace fs = boost::process::filesystem; namespace bp = boost::process; BOOST_AUTO_TEST_SUITE( bind_stderr ); @@ -148,7 +149,7 @@ BOOST_AUTO_TEST_CASE(file_io, *boost::unit_test::timeout(2)) is >> s; BOOST_CHECK_EQUAL(s, "hello"); } - boost::filesystem::remove(pth); + boost::process::filesystem::remove(pth); } diff --git a/test/bind_stdin.cpp b/test/bind_stdin.cpp index 6f03326e6..afd81d027 100644 --- a/test/bind_stdin.cpp +++ b/test/bind_stdin.cpp @@ -19,7 +19,8 @@ #include #include -#include +#include +#include #include #include @@ -41,7 +42,7 @@ typedef boost::asio::posix::stream_descriptor pipe_end; #endif -namespace fs = boost::filesystem; +namespace fs = boost::process::filesystem; namespace bp = boost::process; BOOST_AUTO_TEST_CASE(sync_io, *boost::unit_test::timeout(10)) @@ -177,7 +178,7 @@ BOOST_AUTO_TEST_CASE(file_io, *boost::unit_test::timeout(2)) bp::ipstream is; { - boost::filesystem::ofstream fs(pth); + boost::process::filesystem::ofstream fs(pth); fs << 321 << std::endl; fs << 1.2345 << std::endl; fs << "some_string" << std::endl; @@ -205,7 +206,7 @@ BOOST_AUTO_TEST_CASE(file_io, *boost::unit_test::timeout(2)) BOOST_CHECK_EQUAL(s, "abcsome_string"); c.wait(); - boost::filesystem::remove(pth); + boost::process::filesystem::remove(pth); } BOOST_AUTO_TEST_CASE(file_io_C, *boost::unit_test::timeout(2)) @@ -220,7 +221,7 @@ BOOST_AUTO_TEST_CASE(file_io_C, *boost::unit_test::timeout(2)) bp::ipstream is; { - boost::filesystem::ofstream fs(pth); + boost::process::filesystem::ofstream fs(pth); fs << 321 << std::endl; fs << 1.2345 << std::endl; fs << "some_string" << std::endl; @@ -254,7 +255,7 @@ BOOST_AUTO_TEST_CASE(file_io_C, *boost::unit_test::timeout(2)) BOOST_CHECK_EQUAL(s, "abcsome_string"); c.wait(); - boost::filesystem::remove(pth); + boost::process::filesystem::remove(pth); } BOOST_AUTO_TEST_SUITE_END(); \ No newline at end of file diff --git a/test/bind_stdin_stdout.cpp b/test/bind_stdin_stdout.cpp index 1f71e44bd..c2ee29280 100644 --- a/test/bind_stdin_stdout.cpp +++ b/test/bind_stdin_stdout.cpp @@ -10,6 +10,7 @@ #define BOOST_TEST_MAIN #define BOOST_TEST_IGNORE_SIGCHLD #include +#include #include #include diff --git a/test/bind_stdout.cpp b/test/bind_stdout.cpp index 6798bea5d..da7fe7765 100644 --- a/test/bind_stdout.cpp +++ b/test/bind_stdout.cpp @@ -10,6 +10,7 @@ #define BOOST_TEST_MAIN #define BOOST_TEST_IGNORE_SIGCHLD #include +#include #include @@ -23,7 +24,7 @@ #include #include -#include +#include #include #include @@ -40,7 +41,7 @@ typedef boost::asio::posix::stream_descriptor pipe_end; BOOST_AUTO_TEST_SUITE( bind_stdout ); -namespace fs = boost::filesystem; +namespace fs = boost::process::filesystem; namespace bp = boost::process; BOOST_AUTO_TEST_CASE(sync_io, *boost::unit_test::timeout(5)) @@ -164,7 +165,7 @@ BOOST_AUTO_TEST_CASE(file_io, *boost::unit_test::timeout(2)) is >> s; BOOST_CHECK_EQUAL(s, "hello"); } - boost::filesystem::remove(pth); + boost::process::filesystem::remove(pth); } diff --git a/test/bind_stdout_stderr.cpp b/test/bind_stdout_stderr.cpp index a10f0a59b..d108ba327 100644 --- a/test/bind_stdout_stderr.cpp +++ b/test/bind_stdout_stderr.cpp @@ -10,6 +10,7 @@ #define BOOST_TEST_MAIN #define BOOST_TEST_IGNORE_SIGCHLD #include +#include #include #include diff --git a/test/cmd_test.cpp b/test/cmd_test.cpp index 98b84df6d..47ca034b9 100644 --- a/test/cmd_test.cpp +++ b/test/cmd_test.cpp @@ -16,7 +16,7 @@ #include #include -#include +#include #include #include @@ -27,7 +27,7 @@ #include namespace bp = boost::process; -namespace fs = boost::filesystem; +namespace fs = boost::process::filesystem; BOOST_AUTO_TEST_CASE(excplicit) diff --git a/test/group_wait.cpp b/test/group_wait.cpp index 5f6c55730..9d911fe5d 100644 --- a/test/group_wait.cpp +++ b/test/group_wait.cpp @@ -7,6 +7,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + #define BOOST_TEST_MAIN #define BOOST_TEST_IGNORE_SIGCHLD #include @@ -31,13 +32,15 @@ namespace bp = boost::process; + + BOOST_AUTO_TEST_CASE(wait_group_test, *boost::unit_test::timeout(5)) { std::atomic done{false}; std::thread thr{ [&] { - for (int i = 0; i < 50 && !done.load(); i++) + for (int i = 0; i < 100 && !done.load(); i++) std::this_thread::sleep_for(std::chrono::milliseconds(100)); BOOST_REQUIRE(done.load()); }}; @@ -100,7 +103,7 @@ BOOST_AUTO_TEST_CASE(wait_group_test_timeout, *boost::unit_test::timeout(15)) bp::child c1( master_test_suite().argv[1], - "--wait", "1", + "--wait", "2", g, ec ); @@ -119,7 +122,7 @@ BOOST_AUTO_TEST_CASE(wait_group_test_timeout, *boost::unit_test::timeout(15)) BOOST_REQUIRE(c1.in_group()); BOOST_REQUIRE(c2.in_group()); - BOOST_CHECK(!g.wait_for(std::chrono::seconds(2), ec)); + BOOST_CHECK(!g.wait_for(std::chrono::milliseconds(2500), ec)); BOOST_CHECK_MESSAGE(!ec, std::to_string(ec.value()) + " == " + ec.message()); BOOST_CHECK(!c1.running()); @@ -132,4 +135,7 @@ BOOST_AUTO_TEST_CASE(wait_group_test_timeout, *boost::unit_test::timeout(15)) done.store(true); thr.join(); -} \ No newline at end of file +} + + + diff --git a/test/limit_fd.cpp b/test/limit_fd.cpp index c2807e939..aa391756b 100644 --- a/test/limit_fd.cpp +++ b/test/limit_fd.cpp @@ -16,7 +16,7 @@ #include #include -#include +#include #include #include @@ -27,9 +27,12 @@ #if defined(BOOST_WINDOWS_API) #include #include + +#elif defined(__APPLE__) +#include #endif -namespace fs = boost::filesystem; +namespace fs = boost::process::filesystem; namespace bp = boost::process; namespace bt = boost::this_process; @@ -98,7 +101,8 @@ BOOST_AUTO_TEST_CASE(leak_test, *boost::unit_test::timeout(5)) int event_fd =::eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); BOOST_CHECK(!bt::is_stream_handle(event_fd , ec)); BOOST_CHECK_MESSAGE(!ec, ec.message()); #endif - int dir_fd = ::dirfd(::opendir(".")); + auto od = ::opendir("."); + int dir_fd = ::dirfd(od); BOOST_CHECK(!bt::is_stream_handle(dir_fd , ec)); BOOST_CHECK_MESSAGE(!ec, ec.message()); #endif @@ -115,6 +119,9 @@ BOOST_AUTO_TEST_CASE(leak_test, *boost::unit_test::timeout(5)) BOOST_CHECK(bt::is_stream_handle(socket_to_handle(udp_socket.native_handle()), ec)); BOOST_CHECK_MESSAGE(!ec, ec.message()); BOOST_CHECK(bt::is_stream_handle(std::move(ap).sink(). native_handle(), ec)); BOOST_CHECK_MESSAGE(!ec, ec.message()); BOOST_CHECK(bt::is_stream_handle(std::move(ap).source().native_handle(), ec)); BOOST_CHECK_MESSAGE(!ec, ec.message()); +#if !defined( BOOST_WINDOWS_API ) + ::closedir(od); +#endif } struct on_setup_t @@ -154,7 +161,7 @@ BOOST_AUTO_TEST_CASE(iterate_handles, *boost::unit_test::timeout(5)) BOOST_CHECK_MESSAGE(!ec, ec.message()); - BOOST_CHECK_EQUAL(ret, 42u); + BOOST_CHECK_EQUAL(ret, 42); BOOST_CHECK_EQUAL(std::count(res.begin(), res.end(), p_in. native_sink()), 0u); BOOST_CHECK_EQUAL(std::count(res.begin(), res.end(), p_out.native_source()), 0u); } diff --git a/test/pipe.cpp b/test/pipe.cpp index cb0caf8dc..750373916 100644 --- a/test/pipe.cpp +++ b/test/pipe.cpp @@ -39,8 +39,7 @@ BOOST_AUTO_TEST_CASE(named, *boost::unit_test::timeout(2)) #if defined( BOOST_WINDOWS_API ) bp::pipe pipe("\\\\.\\pipe\\pipe_name"); #elif defined( BOOST_POSIX_API ) - const auto home_path = boost::this_process::environment()["HOME"].to_string(); - bp::pipe pipe(home_path + "/.boost_process_test_pipe"); + bp::pipe pipe("./.boost_process_test_pipe"); #endif std::string in = "xyz"; diff --git a/test/posix_specific.cpp b/test/posix_specific.cpp index b37cc9294..86140f584 100644 --- a/test/posix_specific.cpp +++ b/test/posix_specific.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include #include @@ -23,7 +23,7 @@ #include #include -namespace fs = boost::filesystem; +namespace fs = boost::process::filesystem; namespace bp = boost::process; BOOST_AUTO_TEST_CASE(bind_fd, *boost::unit_test::timeout(2)) diff --git a/test/run_exe_path.cpp b/test/run_exe_path.cpp index 118fda7dc..67a9c5585 100644 --- a/test/run_exe_path.cpp +++ b/test/run_exe_path.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include @@ -25,7 +25,7 @@ BOOST_AUTO_TEST_CASE(run_exe_success) { using boost::unit_test::framework::master_test_suite; - boost::filesystem::path exe = master_test_suite().argv[1]; + boost::process::filesystem::path exe = master_test_suite().argv[1]; std::error_code ec; bp::child c( @@ -38,7 +38,7 @@ BOOST_AUTO_TEST_CASE(run_exe_success) #if defined(BOOST_WINDOWS_API) BOOST_AUTO_TEST_CASE(run_exe_error) { - boost::filesystem::path exe = "doesnt-exist"; + boost::process::filesystem::path exe = "doesnt-exist"; std::error_code ec; bp::child c( diff --git a/test/search_path.cpp b/test/search_path.cpp index 2dfc6fa75..31f0844f6 100644 --- a/test/search_path.cpp +++ b/test/search_path.cpp @@ -10,11 +10,11 @@ #define BOOST_TEST_MAIN #include #include -#include +#include #include namespace bp = boost::process; -namespace fs = boost::filesystem; +namespace fs = boost::process::filesystem; BOOST_AUTO_TEST_CASE(search_path) { diff --git a/test/shell.cpp b/test/shell.cpp index ed4e48a3e..e0a97c958 100644 --- a/test/shell.cpp +++ b/test/shell.cpp @@ -7,6 +7,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + #define BOOST_TEST_MAIN #define BOOST_TEST_IGNORE_SIGCHLD #include @@ -23,6 +24,7 @@ namespace bp = boost::process; + BOOST_AUTO_TEST_CASE(shell_simple, *boost::unit_test::timeout(5)) { using boost::unit_test::framework::master_test_suite; @@ -48,6 +50,8 @@ BOOST_AUTO_TEST_CASE(shell_simple, *boost::unit_test::timeout(5)) BOOST_TEST_CHECKPOINT("Finished read"); BOOST_CHECK_EQUAL(s, "hello"); + c.wait(); + BOOST_CHECK_EQUAL(c.exit_code(), 0); } BOOST_AUTO_TEST_CASE(shell_error, *boost::unit_test::timeout(5)) @@ -61,3 +65,4 @@ BOOST_AUTO_TEST_CASE(shell_error, *boost::unit_test::timeout(5)) c2.wait(); BOOST_CHECK(c2.exit_code() != 0); } + diff --git a/test/shell_path.cpp b/test/shell_path.cpp index dce7cdeb2..9c2201c4a 100644 --- a/test/shell_path.cpp +++ b/test/shell_path.cpp @@ -10,7 +10,7 @@ #define BOOST_TEST_MAIN #include #include -#include +#include #include namespace bp = boost::process; @@ -18,13 +18,13 @@ namespace bp = boost::process; BOOST_AUTO_TEST_CASE(shell_set_on_error) { std::error_code ec; - boost::filesystem::path p = bp::shell(ec); + boost::process::filesystem::path p = bp::shell(ec); BOOST_CHECK(!ec); - BOOST_CHECK(boost::filesystem::exists(p)); + BOOST_CHECK(boost::process::filesystem::exists(p)); } BOOST_AUTO_TEST_CASE(shell_throw_on_error) { BOOST_CHECK_NO_THROW(bp::shell()); - BOOST_CHECK(boost::filesystem::exists(bp::shell())); + BOOST_CHECK(boost::process::filesystem::exists(bp::shell())); } diff --git a/test/sparring_partner.cpp b/test/sparring_partner.cpp index c628832a1..fe28c3cea 100644 --- a/test/sparring_partner.cpp +++ b/test/sparring_partner.cpp @@ -10,7 +10,7 @@ #define BOOST_USE_WINDOWS_H #include -#include +#include #include #include #include @@ -170,7 +170,7 @@ int main(int argc, char *argv[]) } else if (vm["pwd"].as()) { - std::cout << boost::filesystem::current_path().string() << std::endl; + std::cout << boost::process::filesystem::current_path().string() << std::endl; } else if (vm.count("query")) { diff --git a/test/spawn.cpp b/test/spawn.cpp index 25a2ecb3f..b823722aa 100644 --- a/test/spawn.cpp +++ b/test/spawn.cpp @@ -24,7 +24,7 @@ #include #include -#include +#include #include #include @@ -38,7 +38,7 @@ typedef boost::asio::windows::stream_handle pipe_end; typedef boost::asio::posix::stream_descriptor pipe_end; #endif -namespace fs = boost::filesystem; +namespace fs = boost::process::filesystem; namespace bp = boost::process; BOOST_AUTO_TEST_CASE(sync_spawn, *boost::unit_test::timeout(5)) diff --git a/test/spawn_fail.cpp b/test/spawn_fail.cpp index 3fa963ef8..199773e4e 100644 --- a/test/spawn_fail.cpp +++ b/test/spawn_fail.cpp @@ -20,7 +20,7 @@ #include #include -#include +#include #include #include @@ -34,7 +34,7 @@ typedef boost::asio::windows::stream_handle pipe_end; typedef boost::asio::posix::stream_descriptor pipe_end; #endif -namespace fs = boost::filesystem; +namespace fs = boost::process::filesystem; namespace bp = boost::process; int main() diff --git a/test/start_dir.cpp b/test/start_dir.cpp index 706c511c9..84c677aa6 100644 --- a/test/start_dir.cpp +++ b/test/start_dir.cpp @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include #include @@ -24,8 +24,8 @@ struct test_dir { std::string s_; test_dir(const std::string &s) : s_(s) - { BOOST_REQUIRE_NO_THROW(boost::filesystem::create_directory(s)); } - ~test_dir() { boost::filesystem::remove(s_); } + { BOOST_REQUIRE_NO_THROW(boost::process::filesystem::create_directory(s)); } + ~test_dir() { boost::process::filesystem::remove(s_); } }; BOOST_AUTO_TEST_CASE(start_in_dir) @@ -38,7 +38,7 @@ BOOST_AUTO_TEST_CASE(start_in_dir) std::error_code ec; bp::child c( - bp::exe=boost::filesystem::absolute(master_test_suite().argv[1]).string(), + bp::exe=boost::process::filesystem::absolute(master_test_suite().argv[1]).string(), bp::args +={"test", "--pwd"}, bp::start_dir = dir.s_, bp::std_out>is, @@ -49,8 +49,8 @@ BOOST_AUTO_TEST_CASE(start_in_dir) std::string s; std::getline(is, s); - auto path_read = boost::filesystem::absolute(boost::filesystem::path(s)).string(); - auto path_set = boost::filesystem::absolute(dir.s_).string(); + auto path_read = boost::process::filesystem::absolute(boost::process::filesystem::path(s)).string(); + auto path_set = boost::process::filesystem::absolute(dir.s_).string(); if (path_read.size() > path_set.size()) path_read.resize(path_set.size()); diff --git a/test/sub_launcher.cpp b/test/sub_launcher.cpp index c36490132..3579b3bf0 100644 --- a/test/sub_launcher.cpp +++ b/test/sub_launcher.cpp @@ -12,8 +12,8 @@ #include #include - #include +#include int main(int argc, char *argv[]) diff --git a/test/system_test1.cpp b/test/system_test1.cpp index 71aedd33f..3b45b9979 100644 --- a/test/system_test1.cpp +++ b/test/system_test1.cpp @@ -28,7 +28,7 @@ #include #include -#include +#include #include #include @@ -43,7 +43,7 @@ typedef boost::asio::windows::stream_handle pipe_end; typedef boost::asio::posix::stream_descriptor pipe_end; #endif -namespace fs = boost::filesystem; +namespace fs = boost::process::filesystem; namespace bp = boost::process; BOOST_AUTO_TEST_CASE(system_exit_code, *boost::unit_test::timeout(5)) diff --git a/test/system_test2.cpp b/test/system_test2.cpp index e5d670233..037d1799f 100644 --- a/test/system_test2.cpp +++ b/test/system_test2.cpp @@ -29,7 +29,7 @@ #include #include -#include +#include #include #include @@ -37,7 +37,7 @@ #include #include -namespace fs = boost::filesystem; +namespace fs = boost::process::filesystem; namespace bp = boost::process; BOOST_AUTO_TEST_CASE(explicit_async_io, *boost::unit_test::timeout(2)) diff --git a/test/v2/CMakeLists.txt b/test/v2/CMakeLists.txt new file mode 100644 index 000000000..937c78566 --- /dev/null +++ b/test/v2/CMakeLists.txt @@ -0,0 +1,33 @@ +enable_testing() + +add_library(boost_process_v2_test_impl test_impl.cpp) +target_link_libraries(boost_process_v2_test_impl Boost::unit_test_framework Boost::process) +target_compile_definitions(boost_process_v2_test_impl PUBLIC -DBOOST_PROCESS_V2_SEPARATE_COMPILATION=1) +target_include_directories(boost_process_v2_test_impl PUBLIC ../../include) + +function(boost_process_v2_standalone_test name) + add_executable(boost_process_v2_${name} ${name}.cpp) + target_link_libraries(boost_process_v2_${name} Boost::system Boost::filesystem boost_process_v2_test_impl) + add_test(NAME boost_process_v2_${name} COMMAND $ ) +endfunction() + +boost_process_v2_standalone_test(utf8) +boost_process_v2_standalone_test(cstring_ref) +boost_process_v2_standalone_test(pid) +boost_process_v2_standalone_test(environment) + +add_library(boost_process_v2_header_test header_1.cpp header_2.cpp) + +add_executable(boost_process_v2_test_target target.cpp) +target_link_libraries(boost_process_v2_test_target PUBLIC Boost::system) +target_include_directories(boost_process_v2_test_target PUBLIC ../../../..) + +function(boost_process_v2_test_with_target name) + add_executable(boost_process_v2_${name} ${name}.cpp) + target_link_libraries(boost_process_v2_${name} Boost::system Boost::filesystem boost_process_v2_test_impl) + add_dependencies(boost_process_v2_${name} boost_process_v2_test_target) + add_test(NAME boost_process_v2_${name} COMMAND $ + -- $) +endfunction() + +boost_process_v2_test_with_target(process) \ No newline at end of file diff --git a/test/v2/Jamfile.jam b/test/v2/Jamfile.jam new file mode 100644 index 000000000..fb4575dea --- /dev/null +++ b/test/v2/Jamfile.jam @@ -0,0 +1,62 @@ +# Copyright (c) 2022 Klemens D. Morgenstern +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + + +import os ; + +if [ os.name ] = NT +{ + lib ws2_32 ; + lib shell32 ; + lib Advapi32 ; + lib Ntdll ; + lib user32 ; +} + +project : requirements + BOOST_ASIO_NO_DEPRECATED + msvc:_SCL_SECURE_NO_WARNINGS + msvc:_CRT_SECURE_NO_DEPRECATE + msvc:/bigobj + windows:WIN32_LEAN_AND_MEAN + linux:-lpthread + NT,cw:ws2_32 + NT,gcc:ws2_32 + BOOST_PROCESS_V2_SEPARATE_COMPILATION=1 +; + +import testing ; + +alias filesystem : /boost//filesystem : static ; + +exe target : target.cpp : + off windows:shell32 + windows:Ntdll + ; + + +lib header_test : header_1.cpp header_2.cpp : + BOOST_PROCESS_V2_HEADER_ONLY=1 ; + +lib test_impl : test_impl.cpp filesystem : + BOOST_PROCESS_V2_SEPARATE_COMPILATION=1 + BOOST_TEST_IGNORE_SIGCHLD=1 + static + windows:shell32 + windows:user32 + ; + +test-suite standalone : + [ run utf8.cpp test_impl ] + [ run cstring_ref.cpp test_impl ] + [ run environment.cpp test_impl ] + [ run pid.cpp test_impl ] + ; + +test-suite with_target : + [ run process.cpp test_impl : --log_level=all --catch_system_errors=no -- : target ] + [ run windows.cpp test_impl : --log_level=all --catch_system_errors=no -- : target : no windows:yes windows:Advapi32 ] + ; + diff --git a/test/v2/cstring_ref.cpp b/test/v2/cstring_ref.cpp new file mode 100644 index 000000000..f2b82babc --- /dev/null +++ b/test/v2/cstring_ref.cpp @@ -0,0 +1,97 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + + +// Disable autolinking for unit tests. +#if !defined(BOOST_ALL_NO_LIB) +#define BOOST_ALL_NO_LIB 1 +#endif // !defined(BOOST_ALL_NO_LIB) + +// Test that header file is self-contained. +#include + +#include + +namespace bp2 = boost::process::v2; + +template struct bp2::basic_cstring_ref>; + +using char_type = bp2::basic_cstring_ref>::const_pointer; + +BOOST_AUTO_TEST_CASE(cstring_view_test) +{ + auto null = bp2::cstring_ref(); + BOOST_CHECK(null.empty()); + BOOST_CHECK_NE(null.c_str(), nullptr); + BOOST_CHECK_EQUAL(null.c_str()[0], '\0'); + BOOST_CHECK_EQUAL(null, ""); + auto *c = "foobar"; + bp2::cstring_ref cv = c; + + BOOST_CHECK_EQUAL(cv.c_str(), c); + + std::string s = "barfoo"; + + bp2::cstring_ref sv = s; + BOOST_CHECK(!s.empty()); + BOOST_CHECK_EQUAL(sv.c_str(), s.c_str()); + + BOOST_CHECK_EQUAL_COLLECTIONS(s.begin(), s.end(), sv.begin(), sv.end()); + BOOST_CHECK_EQUAL_COLLECTIONS(s.cbegin(), s.cend(), sv.cbegin(), sv.cend()); + BOOST_CHECK_EQUAL_COLLECTIONS(s.rbegin(), s.rend(), sv.rbegin(), sv.rend()); + BOOST_CHECK_EQUAL_COLLECTIONS(s.crbegin(), s.crend(), sv.crbegin(), sv.crend()); + + BOOST_CHECK_EQUAL(sv.size(), 6); + sv.remove_prefix(1); + BOOST_CHECK_EQUAL(sv.at(0), 'a'); + BOOST_CHECK_EQUAL(sv.at(4), 'o'); + BOOST_CHECK_THROW(sv.at(5), std::out_of_range); + BOOST_CHECK_EQUAL(sv.front(), 'a'); + BOOST_CHECK_EQUAL(sv.back(), 'o'); + + BOOST_CHECK_EQUAL(sv.length(), 5); + BOOST_CHECK_EQUAL(sv.c_str(), s.c_str() + 1); + BOOST_CHECK_EQUAL(sv.substr(2).c_str(), s.c_str() + 3); + + bp2::string_view ssv = sv; + BOOST_CHECK_EQUAL(ssv, "arfoo"); + + ssv = sv.substr(1, 3); + BOOST_CHECK_EQUAL(ssv, "rfo"); + + sv.swap(null); + + const bp2::cstring_ref cc = null; + BOOST_CHECK_EQUAL(cc.front(), 'a'); + BOOST_CHECK_EQUAL(cc.back(), 'o'); + BOOST_CHECK_GE(cc.max_size(), cc.size()); + + char out[2]; + cc.copy(out, 2, 1); + BOOST_CHECK_EQUAL(out[0], 'r'); + BOOST_CHECK_EQUAL(out[1], 'f'); + BOOST_CHECK(cc.starts_with('a')); + BOOST_CHECK(cc.starts_with("arf")); + BOOST_CHECK(cc == cc); + BOOST_CHECK(cc == null); + BOOST_CHECK(!(cc == sv)); + + BOOST_CHECK(!(cc != cc)); + BOOST_CHECK(!(cc != null)); + BOOST_CHECK(cc != sv); + + null.clear(); + BOOST_CHECK_EQUAL(null.to_string(), ""); + BOOST_CHECK_EQUAL(null.to_string(std::allocator()), ""); + + bp2::cstring_ref av = "aa", bv = "bb"; + BOOST_CHECK_LT(av, bv); + BOOST_CHECK_GT(bv, av); + + BOOST_CHECK_EQUAL(av.compare(av), 0); + BOOST_CHECK_LT(av.compare(bv), 0); + BOOST_CHECK_GT(bv.compare(av), 0); + +} diff --git a/test/v2/environment.cpp b/test/v2/environment.cpp new file mode 100644 index 000000000..168a0b97b --- /dev/null +++ b/test/v2/environment.cpp @@ -0,0 +1,171 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + + +// Disable autolinking for unit tests. +#if !defined(BOOST_ALL_NO_LIB) +#define BOOST_ALL_NO_LIB 1 +#endif // !defined(BOOST_ALL_NO_LIB) + +// Test that header file is self-contained. +#include + +#include + +#include + +namespace bp2 = boost::process::v2; +namespace bpe = boost::process::v2::environment; + + +BOOST_AUTO_TEST_CASE(environment) +{ + + for (auto && elem : bpe::get("PATH")) + BOOST_CHECK(std::find(elem.begin(), elem.end(), bpe::delimiter) == elem.end()); + + BOOST_CHECK(bpe::get("PATH").size() > 0); + + const auto key1 = "BP2_TEST_NAME_\320\240\320\230\320\221\320\220"; // РИБА +#if defined(BOOST_PROCESS_V2_WINDOWS) + const auto key2 = "BP2_TeSt_NamE_\321\200\320\270\320\261\320\260"; // риба +#else + const auto key2 = key1; +#endif + bp2::error_code ec; + bpe::unset(key1, ec); + bpe::unset(key2, ec); + ec.clear(); + + BOOST_CHECK_THROW(bpe::get(key1) , bp2::system_error); + bpe::get(key2, ec); + BOOST_CHECK(ec); + ec.clear(); + + bpe::set(key1, "some test string"); + BOOST_CHECK(bpe::get(key1) == "some test string"); + + bpe::set(key2, "some test string"); + + bpe::get(key1, ec); + BOOST_CHECK(!ec); + + BOOST_CHECK(bpe::get(key2) == "some test string"); + bpe::unset(key2); + + BOOST_CHECK_THROW(bpe::set("invalid=", "blablubb") , bp2::system_error); + BOOST_CHECK_THROW(bpe::get(key1) , bp2::system_error); + bpe::get(key2, ec); + BOOST_CHECK(ec); + ec.clear(); + + for (auto && ke : bpe::current()) + BOOST_CHECK_EQUAL(bpe::get(std::get<0>(ke)), std::get<1>(ke)); + + +#if defined(BOOST_PROCESS_V2_POSIX) + BOOST_CHECK_EQUAL(bpe::key("FOO"), bpe::key_view("FOO")); + BOOST_CHECK_EQUAL(bpe::key("FOO"), std::string("FOO")); + BOOST_CHECK_EQUAL(bpe::key_value_pair("FOO=BAR"), bpe::key_value_pair_view("FOO=BAR")); + BOOST_CHECK_EQUAL(bpe::key_value_pair("FOO", "BAR"), bpe::key_value_pair_view("FOO=BAR")); + + using sv = bpe::value::string_type; + std::string cmp = sv("FOO=X") + bpe::delimiter + sv("YY") + bpe::delimiter + sv("Z42"); + BOOST_CHECK_EQUAL(bpe::key_value_pair("FOO", {"X", "YY", "Z42"}), cmp); +#endif + + +#if defined(BOOST_PROCESS_V2_POSIX) + std::unordered_map custom_env = + { + {"HOME", "/home/byzantium"}, + {"HOMEDRIVE", "X:"}, + {"HOMEPATH", "\\users\\theodora"} + }; + std::vector custom_env2 = + { + "HOME=/home/byzantium", + "HOMEDRIVE=X:", + "HOMEPATH=\\users\\theodora" + }; + BOOST_CHECK_EQUAL(bpe::home(custom_env), "/home/byzantium"); + BOOST_CHECK_EQUAL(bpe::home(custom_env2), "/home/byzantium"); +#else + std::unordered_map custom_env = + { + L"HOME", L"/home/byzantium", + L"HOMEDRIVE", L"X:", + L"HOMEPATH", L"\\users\\theodora" + }; + + std::vector custom_env2 = + { + {L"HOME=/home/byzantium"}, + {L"HOMEDRIVE=X:"}, + {L"HOMEPATH=\\users\\theodora"} + }; + BOOST_CHECK_EQUAL(bpe::home(custom_env), L"X:\\Users\\theodora"); + BOOST_CHECK_EQUAL(bpe::home(custom_env2), L"X:\\Users\\theodora"); + +#endif + + bp2::process_environment env{custom_env }; + boost::ignore_unused(env); + + bp2::process_environment env2{custom_env2}; + boost::ignore_unused(env2); +} + + + +BOOST_AUTO_TEST_CASE(wenvironment) +{ + for (auto && elem : bpe::get(L"PATH")) + BOOST_CHECK(std::find(elem.begin(), elem.end(), bpe::delimiter) == elem.end()); + + BOOST_CHECK(bpe::get(L"PATH").size() > 0); + + const auto key1 = L"BP2_TEST_NAME_W_\u0420\u0418\u0411\u0410"; +#if defined(BOOST_PROCESS_V2_WINDOWS) + const auto key2 = L"BP2_TeSt_NamE_W_\u0440\u0438\u0431\u0430"; +#else + const auto key2 = key1; +#endif + + BOOST_CHECK_THROW(bpe::get(key1) , bp2::system_error); + bp2::error_code ec; + bpe::get(key2, ec); + BOOST_CHECK(ec); + + bpe::set(key1, L"some test string"); + BOOST_CHECK(bpe::get(key1) == L"some test string"); + BOOST_CHECK(bpe::get(key2) == L"some test string"); + bpe::unset(key2); + + BOOST_CHECK_THROW(bpe::set(L"invalid=", L"blablubb") , bp2::system_error); + BOOST_CHECK_THROW(bpe::get(key1) , bp2::system_error); + bpe::get(key2, ec); + BOOST_CHECK(ec); + + for (const auto ke : bpe::current()) + BOOST_CHECK_EQUAL(bpe::get(std::get<0>(ke)), std::get<1>(ke)); + +#if defined(BOOST_PROCESS_V2_WINDOWS) + BOOST_CHECK_EQUAL(bpe::key(L"FOO"), bpe::key_view(L"Foo")); + BOOST_CHECK(bpe::key(L"FOO") == std::wstring(L"Foo")); + BOOST_CHECK_EQUAL(bpe::key_value_pair(L"Foo=BAR"), bpe::key_value_pair_view(L"FOO=BAR")); + BOOST_CHECK_EQUAL(bpe::key_value_pair(L"Foo=BAR"), bpe::key_value_pair(L"FOO=BAR")); + BOOST_CHECK_EQUAL(bpe::key_value_pair_view(L"Foo=BAR"), bpe::key_value_pair_view(L"FOO=BAR")); + BOOST_CHECK_EQUAL(bpe::key_value_pair(L"Foo", L"BAR"), bpe::key_value_pair_view(L"FOO=BAR")); + + BOOST_CHECK_NE(bpe::key_value_pair(L"FOO=BAR"), bpe::key_value_pair_view(L"FOO=Bar")); + BOOST_CHECK_LT(bpe::key_value_pair(L"FOO=BAR"), bpe::key_value_pair_view(L"goo=Bar")); + BOOST_CHECK_NE(bpe::key_value_pair(L"FOO", L"BAR"), bpe::key_value_pair_view(L"FOO=Bar")); + + using sv = bpe::value::string_type; + std::wstring cmp = sv(L"FOO=X") + bpe::delimiter + sv(L"YY") + bpe::delimiter + sv(L"Z42"); + BOOST_CHECK(bpe::key_value_pair(L"FOO", {L"X", L"YY", L"Z42"}) == cmp); +#endif +} diff --git a/test/v2/header_1.cpp b/test/v2/header_1.cpp new file mode 100644 index 000000000..1ed397ce4 --- /dev/null +++ b/test/v2/header_1.cpp @@ -0,0 +1,6 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include \ No newline at end of file diff --git a/test/v2/header_2.cpp b/test/v2/header_2.cpp new file mode 100644 index 000000000..1ed397ce4 --- /dev/null +++ b/test/v2/header_2.cpp @@ -0,0 +1,6 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include \ No newline at end of file diff --git a/test/v2/pid.cpp b/test/v2/pid.cpp new file mode 100644 index 000000000..a0664bcc1 --- /dev/null +++ b/test/v2/pid.cpp @@ -0,0 +1,15 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include + +#include + + +BOOST_AUTO_TEST_CASE(test_pid) +{ + namespace bp2 = boost::process::v2; + BOOST_CHECK_NE(bp2::current_pid(), static_cast(0)); +} \ No newline at end of file diff --git a/test/v2/process.cpp b/test/v2/process.cpp new file mode 100644 index 000000000..d160b5dfa --- /dev/null +++ b/test/v2/process.cpp @@ -0,0 +1,473 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + + +// Disable autolinking for unit tests. +#if !defined(BOOST_ALL_NO_LIB) +#define BOOST_ALL_NO_LIB 1 +#endif // !defined(BOOST_ALL_NO_LIB) + +#if defined(BOOST_FILESYSTEM_DYN_LINK) +#undef BOOST_FILESYSTEM_DYN_LINK +#endif +#define BOOST_TEST_IGNORE_SIGCHLD 1 + +#if true //defined(BOOST_POSIX_API) +# include +#endif +// Test that header file is self-contained. +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace bpv = boost::process::v2; +namespace asio = boost::asio; + +#if defined(BOOST_PROCESS_V2_WINDOWS) +bpv::filesystem::path shell() +{ + return bpv::environment::find_executable("cmd"); +} + +bpv::filesystem::path closable() +{ + return bpv::environment::find_executable("notepad"); +} + +bpv::filesystem::path interruptable() +{ + return bpv::environment::find_executable("cmd"); +} +#else +bpv::filesystem::path shell() +{ + return bpv::environment::find_executable("sh"); +} +bpv::filesystem::path closable() +{ + return bpv::environment::find_executable("tee"); +} +bpv::filesystem::path interruptable() +{ + return bpv::environment::find_executable("tee"); +} +#endif + +BOOST_AUTO_TEST_SUITE(with_target); + +BOOST_AUTO_TEST_CASE(exit_code_sync) +{ + using boost::unit_test::framework::master_test_suite; + const auto pth = master_test_suite().argv[1]; + + boost::asio::io_context ctx; + + BOOST_CHECK_EQUAL(bpv::process(ctx, pth, {"exit-code", "0"}).wait(), 0); + BOOST_CHECK_EQUAL(bpv::execute(bpv::process(ctx, pth, {"exit-code", "1"})), 1); + std::vector args = {"exit-code", "2"}; + BOOST_CHECK_EQUAL(bpv::default_process_launcher()(ctx, pth, args).wait(), 2); + args[1] = "42"; + auto proc = bpv::default_process_launcher()(ctx, pth, args); + BOOST_CHECK_EQUAL(proc.wait(), 42); + printf("42: %d\n", proc.native_exit_code()); + + BOOST_CHECK_EQUAL(bpv::process(ctx, pth, {"sleep", "100"}).wait(), 0); + BOOST_CHECK_EQUAL(bpv::execute(bpv::process(ctx, pth, {"sleep", "100"})), 0); +} + +BOOST_AUTO_TEST_CASE(exit_code_async) +{ + using boost::unit_test::framework::master_test_suite; + printf("Running exit_code_async\n"); + auto & mm = master_test_suite(); + printf("Running exit_code_async %p\n", &mm); + printf("Args: '%d'\n", master_test_suite().argc); + printf("Exe '%s'\n", master_test_suite().argv[0]); + const auto pth = master_test_suite().argv[1]; + printf("Executing '%s'\n", pth); + + boost::asio::io_context ctx; + + int called = 0; + printf("Setting up processes\n"); + + bpv::process proc1(ctx, pth, {"exit-code", "0"}); + bpv::process proc3(ctx, pth, {"exit-code", "2"}); + bpv::process proc4(ctx, pth, {"exit-code", "42"}); + bpv::process proc5(ctx, pth, {"sleep", "100"}); + bpv::process proc6(ctx, pth, {"sleep", "50"}); + +#define CPL(Code) \ + [&](bpv::error_code ec, int e) \ + { \ + BOOST_CHECK_MESSAGE(!ec, ec.message()); \ + called++; \ + BOOST_CHECK_EQUAL(bpv::evaluate_exit_code(e), Code); \ + } + + printf("Waiting for processes\n"); + + proc1.async_wait(CPL(0)); + proc3.async_wait(CPL(2)); + proc4.async_wait(CPL(42)); + proc5.async_wait(CPL(0)); + proc6.async_wait(CPL(0)); + bpv::async_execute(bpv::process(ctx, pth, {"exit-code", "1"}),CPL(1)); + bpv::async_execute(bpv::process(ctx, pth, {"sleep", "100"}), CPL(0)); + +#undef CPL + + printf("Running\n"); + + ctx.run(); + BOOST_CHECK_EQUAL(called, 7); +} + + +BOOST_AUTO_TEST_CASE(terminate) +{ + asio::io_context ctx; + + auto sh = shell(); + + BOOST_CHECK_MESSAGE(!sh.empty(), sh); + bpv::process proc(ctx, sh, {}); + proc.terminate(); + proc.wait(); +} + +BOOST_AUTO_TEST_CASE(request_exit) +{ + asio::io_context ctx; + + auto sh = closable(); + BOOST_CHECK_MESSAGE(!sh.empty(), sh); + bpv::process proc(ctx, sh, {} +#if defined(ASIO_WINDOWS) + , asio::windows::show_window_minimized_not_active +#endif + ); + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + proc.request_exit(); + proc.wait(); +} + +BOOST_AUTO_TEST_CASE(interrupt) +{ + asio::io_context ctx; + + auto sh = interruptable(); + BOOST_CHECK_MESSAGE(!sh.empty(), sh); + bpv::process proc(ctx, sh, {} +#if defined(ASIO_WINDOWS) + , asio::windows::create_new_process_group +#endif + ); + proc.interrupt(); + proc.wait(); +} + +void trim_end(std::string & str) +{ + auto itr = std::find_if(str.rbegin(), str.rend(), &std::char_traits::not_eof); + str.erase(itr.base(), str.end()); +} + +BOOST_AUTO_TEST_CASE(print_args_out) +{ + using boost::unit_test::framework::master_test_suite; + const auto pth = master_test_suite().argv[1]; + + asio::io_context ctx; + + asio::readable_pipe rp{ctx}; + asio::writable_pipe wp{ctx}; + asio::connect_pipe(rp, wp); + + + bpv::process proc(ctx, pth, {"print-args", "foo", "bar"}, bpv::process_stdio{/*in*/{},/*out*/wp, /*err*/ nullptr}); + + wp.close(); + asio::streambuf st; + std::istream is{&st}; + bpv::error_code ec; + + auto sz = asio::read(rp, st, ec); + + BOOST_CHECK_NE(sz, 0u); + BOOST_CHECK_MESSAGE((ec == asio::error::broken_pipe) || (ec == asio::error::eof), ec.message()); + + std::string line; + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL(pth, line); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("print-args", line); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("foo", line); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("bar", line); + + + proc.wait(); + BOOST_CHECK(proc.exit_code() == 0); +} + + +BOOST_AUTO_TEST_CASE(print_args_err) +{ + using boost::unit_test::framework::master_test_suite; + const auto pth = master_test_suite().argv[1]; + + asio::io_context ctx; + + asio::readable_pipe rp{ctx}; + asio::writable_pipe wp{ctx}; + asio::connect_pipe(rp, wp); + + bpv::process proc(ctx, pth, {"print-args", "bar", "foo"}, bpv::process_stdio{/*in*/{}, /*.out= */ nullptr, /* .err=*/ wp}); + + wp.close(); + asio::streambuf st; + std::istream is{&st}; + bpv::error_code ec; + + auto sz = asio::read(rp, st, ec); + + BOOST_CHECK_NE(sz , 0u); + BOOST_CHECK_MESSAGE((ec == asio::error::broken_pipe) || (ec == asio::error::eof), ec.message()); + + std::string line; + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL(pth, line ); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("print-args", line); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("bar", line); + + BOOST_CHECK(std::getline(is, line)); + trim_end(line); + BOOST_CHECK_EQUAL("foo", line); + + + proc.wait(); + BOOST_CHECK_EQUAL(proc.exit_code(), 0); +} + +BOOST_AUTO_TEST_CASE(echo_file) +{ + using boost::unit_test::framework::master_test_suite; + const auto pth = master_test_suite().argv[1]; + + asio::io_context ctx; + + asio::readable_pipe rp{ctx}; + asio::writable_pipe wp{ctx}; + asio::connect_pipe(rp, wp); + + auto p = bpv::filesystem::temp_directory_path() / "asio-test-thingy.txt"; + + std::string test_data = "some ~~ test ~~ data"; + { + std::ofstream ofs{p.string()}; + ofs.write(test_data.data(), test_data.size()); + BOOST_CHECK(ofs); + } + + bpv::process proc(ctx, pth, {"echo"}, bpv::process_stdio{/*.in=*/p, /*.out=*/wp}); + wp.close(); + + std::string out; + bpv::error_code ec; + + auto sz = asio::read(rp, asio::dynamic_buffer(out), ec); + BOOST_CHECK(sz != 0); + BOOST_CHECK_MESSAGE((ec == asio::error::broken_pipe) || (ec == asio::error::eof), ec.message()); + BOOST_CHECK_MESSAGE(out == test_data, out); + + proc.wait(); + BOOST_CHECK_MESSAGE(proc.exit_code() == 0, proc.exit_code()); +} + +BOOST_AUTO_TEST_CASE(print_same_cwd) +{ + using boost::unit_test::framework::master_test_suite; + const auto pth = master_test_suite().argv[1]; + + asio::io_context ctx; + + asio::readable_pipe rp{ctx}; + + // default CWD + bpv::process proc(ctx, pth, {"print-cwd"}, bpv::process_stdio{/*.in=*/{},/*.out=*/rp}); + + std::string out; + bpv::error_code ec; + + auto sz = asio::read(rp, asio::dynamic_buffer(out), ec); + BOOST_CHECK(sz != 0); + BOOST_CHECK_MESSAGE((ec == asio::error::broken_pipe) || (ec == asio::error::eof), ec.message()); + BOOST_CHECK_MESSAGE(bpv::filesystem::path(out) == bpv::filesystem::current_path(), + bpv::filesystem::path(out) << " != " << bpv::filesystem::current_path()); + + proc.wait(); + BOOST_CHECK_MESSAGE(proc.exit_code() == 0, proc.exit_code()); +} + + +BOOST_AUTO_TEST_CASE(popen) +{ + using boost::unit_test::framework::master_test_suite; + const auto pth = master_test_suite().argv[1]; + + asio::io_context ctx; + + asio::readable_pipe rp{ctx}; + + + // default CWD + bpv::popen proc(ctx, pth, {"echo"}); + + asio::write(proc, asio::buffer("FOOBAR")); + + proc.get_stdin().close(); + + std::string res; + boost::system::error_code ec; + std::size_t n = asio::read(proc, asio::dynamic_buffer(res), ec); + res.resize(n - 1); + BOOST_CHECK_EQUAL(ec, asio::error::eof); + // remove EOF + BOOST_CHECK_EQUAL(res, "FOOBAR"); + + proc.wait(); + BOOST_CHECK_MESSAGE(proc.exit_code() == 0, proc.exit_code()); +} + +BOOST_AUTO_TEST_CASE(print_other_cwd) +{ + using boost::unit_test::framework::master_test_suite; + const auto pth = bpv::filesystem::absolute(master_test_suite().argv[1]); + + asio::io_context ctx; + + asio::readable_pipe rp{ctx}; + asio::writable_pipe wp{ctx}; + asio::connect_pipe(rp, wp); + + auto target = bpv::filesystem::canonical(bpv::filesystem::temp_directory_path()); + + // default CWD + bpv::process proc(ctx, pth, {"print-cwd"}, + bpv::process_stdio{/*.in=*/{}, /*.out=*/wp}, + bpv::process_start_dir(target)); + wp.close(); + + std::string out; + bpv::error_code ec; + + auto sz = asio::read(rp, asio::dynamic_buffer(out), ec); + BOOST_CHECK(sz != 0); + BOOST_CHECK_MESSAGE((ec == asio::error::broken_pipe) || (ec == asio::error::eof), ec.message()); + BOOST_CHECK_MESSAGE(bpv::filesystem::path(out) == target, + bpv::filesystem::path(out) << " != " << target); + + proc.wait(); + BOOST_CHECK_MESSAGE(proc.exit_code() == 0, proc.exit_code() << " from " << proc.native_exit_code()); +} + + +template +std::string read_env(const char * name, Inits && ... inits) +{ + using boost::unit_test::framework::master_test_suite; + const auto pth = bpv::filesystem::absolute(master_test_suite().argv[1]); + + + asio::io_context ctx; + + asio::readable_pipe rp{ctx}; + asio::writable_pipe wp{ctx}; + asio::connect_pipe(rp, wp); + + bpv::process proc(ctx, pth, {"print-env", name}, bpv::process_stdio{/*.in-*/{}, /*.out*/{wp}}, std::forward(inits)...); + + wp.close(); + + std::string out; + bpv::error_code ec; + + const auto sz = asio::read(rp, asio::dynamic_buffer(out), ec); + BOOST_CHECK_MESSAGE((ec == asio::error::broken_pipe) || (ec == asio::error::eof), ec.message()); + out.resize(sz); + trim_end(out); + printf("Read env (%ld) %s: '%s'\n", sz, name, out.c_str()); + + proc.wait(); + BOOST_CHECK_EQUAL(proc.exit_code(), 0); + + return out; +} + +BOOST_AUTO_TEST_CASE(environment) +{ + std::string path = ::getenv("PATH"); + BOOST_CHECK_EQUAL(read_env("PATH"), ::getenv("PATH")); + + auto c = bpv::environment::current(); + std::vector sub_env(c.begin(), c.end()); + + sub_env.push_back("FOOBAR=FOO-BAR"); + BOOST_CHECK_EQUAL("FOO-BAR", read_env("FOOBAR", bpv::process_environment{sub_env})); + + sub_env.push_back("XYZ=ZYX"); + auto itr = std::find_if(sub_env.begin(), sub_env.end(), [](const bpv::environment::key_value_pair & kv) {return kv.key() == "PATH";}); + path += static_cast(bpv::environment::delimiter); + path += "/bar/foo"; + bpv::environment::value pval = itr->value(); + pval.push_back("/bar/foo"); + *itr = bpv::environment::key_value_pair("PATH", pval); + BOOST_CHECK_EQUAL(path, read_env("PATH", bpv::process_environment{sub_env})); + +#if defined(BOOST_PROCESS_V2_WINDOWS) + std::wstring wpath = L"PATh=" + std::wstring(_wgetenv(L"PatH")); + BOOST_CHECK_EQUAL("FOO-BAR", read_env("FOOBAR", bpv::process_environment{L"FOOBAR=FOO-BAR", wpath.c_str()})); + wpath += bpv::environment::delimiter; + wpath += L"C:\\bar\\foo"; + BOOST_CHECK_EQUAL(wpath.substr(5), read_env("pATH", bpv::process_environment{wpath.c_str(), std::wstring(L"XYZ=ZYX")})); +#endif + + BOOST_CHECK_EQUAL(read_env("PATH", bpv::process_environment(bpv::environment::current())), ::getenv("PATH")); +} + + +BOOST_AUTO_TEST_SUITE_END(); + diff --git a/test/v2/target.cpp b/test/v2/target.cpp new file mode 100644 index 000000000..c4ccdab68 --- /dev/null +++ b/test/v2/target.cpp @@ -0,0 +1,85 @@ +#include +#include +#include +#include + +extern char **environ; + +#if defined(BOOST_PROCESS_V2_WINDOWS) +#include +#else +#include +#endif + + +int main(int argc, char * argv[]) +{ + std::string mode = argv[1]; + if (mode == "exit-code") + return std::stoi(argv[2]); + else if (mode == "sleep") + { + const auto delay = std::chrono::milliseconds(std::stoi(argv[2])); + std::this_thread::sleep_for(delay); + return 0; + } + else if (mode == "print-args") + for (auto i = 0; i < argc; i++) + { + std::cout << argv[i] << std::endl; + std::cerr << argv[i] << std::endl; + if (!std::cout || !std::cerr) + return 1; + } + else if (mode == "echo") + std::cout << std::cin.rdbuf(); + else if (mode == "print-cwd") + { +#if defined(BOOST_PROCESS_V2_WINDOWS) + wchar_t buf[65535]; + const auto sz = ::GetCurrentDirectoryW(sizeof(buf), buf); + std::wcout << boost::process::v2::wstring_view(buf, sz) << std::flush; +#else + char buf[65535]; + printf(::getcwd(buf, sizeof(buf))); +#endif + return 0; + } + else if (mode == "check-eof") + { + std::string st; + std::cin >> st; + return std::cin.eof() ? 0 : 1; + } + else if (mode == "print-env") + { + auto p = ::getenv(argv[2]); + if (p && *p) + printf("%s", p); + else + { + printf("Can't find %s in environment\n", argv[2]); + for (auto e = environ; e != nullptr; e++) + printf(" %s\n", *e); + return 3; + } + } +#if defined(BOOST_PROCESS_V2_WINDOWS) + else if (mode == "showwindow") + { + STARTUPINFO si; + GetStartupInfo(&si); + return static_cast(si.wShowWindow); + } + else if (mode == "creation-flags") + { + STARTUPINFO si; + GetStartupInfo(&si); + return static_cast(si.dwFlags); + } +#endif + else + return 34; + + return 0; +} \ No newline at end of file diff --git a/test/v2/test_impl.cpp b/test/v2/test_impl.cpp new file mode 100644 index 000000000..0cab70678 --- /dev/null +++ b/test/v2/test_impl.cpp @@ -0,0 +1,10 @@ +// Copyright (c) 2021 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#define BOOST_TEST_IGNORE_SIGCHLD +#define BOOST_TEST_MODULE process_v2_test +#include + +#include diff --git a/test/v2/utf8.cpp b/test/v2/utf8.cpp new file mode 100755 index 000000000..2abc04fff --- /dev/null +++ b/test/v2/utf8.cpp @@ -0,0 +1,40 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Disable autolinking for unit tests. +#if !defined(BOOST_ALL_NO_LIB) +#define BOOST_ALL_NO_LIB 1 +#endif // !defined(BOOST_ALL_NO_LIB) + +// Test that header file is self-contained. +#include + +#include + +BOOST_AUTO_TEST_CASE(test_codecvt) +{ + struct end_t + { + std::size_t operator()(const char * c) + { + return std::char_traits::length(c); + } + std::size_t operator()(const wchar_t * c) + { + return std::char_traits::length(c); + } + } end{}; + + + const char * in = "test-input-\320\240\320\230\320\221\320\220"; + const wchar_t * win_t = L"test-input-\u0420\u0418\u0411\u0410"; + + BOOST_CHECK(boost::process::v2::detail::conv_string ( in, end( in )) == in ); + BOOST_CHECK(boost::process::v2::detail::conv_string(win_t, end(win_t)) == win_t); + + BOOST_CHECK(boost::process::v2::detail::conv_string( in, end( in )) == win_t); + BOOST_CHECK(boost::process::v2::detail::conv_string (win_t, end(win_t)) == in ); + +} \ No newline at end of file diff --git a/test/v2/windows.cpp b/test/v2/windows.cpp new file mode 100644 index 000000000..9b6d3c61d --- /dev/null +++ b/test/v2/windows.cpp @@ -0,0 +1,113 @@ +// Copyright (c) 2022 Klemens D. Morgenstern +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + + +// Disable autolinking for unit tests. +#if !defined(BOOST_ALL_NO_LIB) +#define BOOST_ALL_NO_LIB 1 +#endif // !defined(BOOST_ALL_NO_LIB) + +#if defined(BOOST_FILESYSTEM_DYN_LINK) +#undef BOOST_FILESYSTEM_DYN_LINK +#endif + +// Test that header file is self-contained. +#include + + +#include +#include +#include +#include +#include + + +#include +#include + +#include +#include + +namespace bpv = boost::process::v2; +namespace asio = boost::asio; + +BOOST_AUTO_TEST_SUITE(windows); + + +BOOST_AUTO_TEST_CASE(show_window) +{ + using boost::unit_test::framework::master_test_suite; + asio::io_context ctx; + const auto pth = master_test_suite().argv[1]; + bpv::process proc{ctx, pth, {"showwindow"}}; + + BOOST_CHECK_EQUAL(proc.wait(), 0); + + proc = bpv::process{ctx, pth, {"showwindow"}, bpv::windows::show_window_minimized_not_active}; + BOOST_CHECK_EQUAL(proc.wait(), SW_SHOWMINNOACTIVE); + +} + +BOOST_AUTO_TEST_CASE(creation_flags) +{ + using boost::unit_test::framework::master_test_suite; + const auto pth = master_test_suite().argv[1]; + asio::io_context ctx; + bpv::process proc{ctx, pth, {"creation-flags"}}; + + BOOST_CHECK_EQUAL(proc.wait() & ~EXTENDED_STARTUPINFO_PRESENT, 0); + + proc = bpv::process{ctx, master_test_suite().argv[1], {"creation-flags"}, bpv::windows::process_creation_flags()}; + BOOST_CHECK(proc); + BOOST_CHECK_EQUAL(proc.wait() & ~EXTENDED_STARTUPINFO_PRESENT, STARTF_TITLEISAPPID); +} + +BOOST_AUTO_TEST_CASE(as_user_launcher) +{ + using boost::unit_test::framework::master_test_suite; + const auto pth = master_test_suite().argv[1]; + + asio::io_context ctx; + std::vector args = {"exit-code", "2"}; + + HANDLE token_handle = INVALID_HANDLE_VALUE; + BOOST_CHECK(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY, &token_handle) != 0); + bpv::error_code ec; + auto proc = bpv::windows::as_user_launcher(token_handle)(ctx, ec, pth, args); + BOOST_CHECK_EQUAL(proc.wait(), 2); +} + +BOOST_AUTO_TEST_CASE(with_logon_launcher) +{ + using boost::unit_test::framework::master_test_suite; + const auto pth = master_test_suite().argv[1]; + + asio::io_context ctx; + std::vector args = {"exit-code", "42"}; + + bpv::error_code ec; + auto proc = bpv::windows::with_logon_launcher(L"idiot", L"changeme")(ctx, ec, pth, args); + BOOST_CHECK_EQUAL(ec.value(), ERROR_INVALID_PARAMETER); +} + + +BOOST_AUTO_TEST_CASE(with_token_launcher) +{ + using boost::unit_test::framework::master_test_suite; + const auto pth = master_test_suite().argv[1]; + + asio::io_context ctx; + std::vector args = {"exit-code", "2"}; + + HANDLE token_handle = INVALID_HANDLE_VALUE; + BOOST_CHECK(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY, &token_handle) != 0); + bpv::error_code ec; + auto proc = bpv::windows::with_token_launcher(nullptr)(ctx, ec, pth, args); + BOOST_CHECK_EQUAL(ec.value(), ERROR_INVALID_PARAMETER); +} + + +BOOST_AUTO_TEST_SUITE_END(); + diff --git a/test/wait_for.cpp b/test/wait_for.cpp index 9ee58e495..daef5eab7 100644 --- a/test/wait_for.cpp +++ b/test/wait_for.cpp @@ -81,7 +81,7 @@ BOOST_AUTO_TEST_CASE(wait_until) auto now = std::chrono::system_clock::now(); auto t1 = now + std::chrono::milliseconds(400); - auto t2 = now + std::chrono::milliseconds(1200); + auto t2 = now + std::chrono::milliseconds(2000); BOOST_CHECK(!c.wait_until(t1)); BOOST_CHECK( c.wait_until(t2)); @@ -103,7 +103,7 @@ BOOST_AUTO_TEST_CASE(wait_until_ec) auto now = std::chrono::system_clock::now(); auto t1 = now + std::chrono::milliseconds(400); - auto t2 = now + std::chrono::milliseconds(1200); + auto t2 = now + std::chrono::milliseconds(2000); BOOST_CHECK(!c.wait_until(t1, ec)); BOOST_CHECK( c.wait_until(t2, ec));