Replace cpp-subprocess with own linuxdeploy-subprocess

This commit is contained in:
TheAssassin 2020-08-07 15:39:32 +02:00
parent 4a0cfe323d
commit 59a8ec41ba
17 changed files with 69 additions and 191 deletions

3
.gitmodules vendored
View File

@ -1,6 +1,3 @@
[submodule "lib/cpp-subprocess"]
path = lib/cpp-subprocess
url = https://github.com/arun11299/cpp-subprocess.git
[submodule "lib/args"]
path = lib/args
url = https://github.com/Taywee/args.git

View File

@ -10,11 +10,6 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/Modules/
set(USE_SYSTEM_BOOST OFF CACHE BOOL "Set to ON to use system boost libraries instead of building up to date boost libraries from source")
set(USE_SYSTEM_CIMG ON CACHE BOOL "Set to OFF to use CImg library bundled in lib directory")
if (EXISTS "${PROJECT_SOURCE_DIR}/lib/cpp-subprocess/subprocess.hpp")
else()
message (FATAL_ERROR "Missing submodule(s), please 'git submodule update --init --recursive'...")
endif()
# support for ccache
# call CMake with -DUSE_CCACHE=ON to make use of it
set(USE_CCACHE ON CACHE BOOL "")

View File

@ -2,16 +2,18 @@
#include <set>
#include <string>
#include <vector>
#include <fcntl.h>
#include <poll.h>
// library headers
#include <boost/filesystem.hpp>
#include <fnmatch.h>
#include <subprocess.hpp>
#include <thread>
// local headers
#include "linuxdeploy/core/log.h"
#include "linuxdeploy/util/util.h"
#include "linuxdeploy/subprocess/process.h"
#pragma once
@ -46,8 +48,8 @@ namespace linuxdeploy {
private:
int getApiLevelFromExecutable() {
auto output = subprocess::check_output({pluginPath.c_str(), "--plugin-api-version"});
std::string stdoutOutput = output.buf.data();
const subprocess::subprocess proc({pluginPath.string(), "--plugin-api-version"});
const auto stdoutOutput = proc.check_output();
try {
auto apiLevel = std::stoi(stdoutOutput);
@ -63,9 +65,8 @@ namespace linuxdeploy {
// check whether plugin implements --plugin-type
try {
auto output = subprocess::check_output({pluginPath.c_str(), "--plugin-type"});
std::string stdoutOutput = output.buf.data();
const subprocess::subprocess proc({pluginPath.c_str(), "--plugin-type"});
const auto stdoutOutput = proc.check_output();
// the specification requires a single line, but we'll silently accept more than that, too
if (std::count(stdoutOutput.begin(), stdoutOutput.end(), '\n') >= 1) {
@ -76,7 +77,7 @@ namespace linuxdeploy {
else if (firstLine == "output")
type = OUTPUT_TYPE;
}
} catch (const subprocess::CalledProcessError&) {}
} catch (const std::logic_error&) {}
return type;
}
@ -127,8 +128,8 @@ namespace linuxdeploy {
template<int API_LEVEL>
int PluginBase<API_LEVEL>::run(const boost::filesystem::path& appDirPath) {
auto pluginPath = path();
auto args = {pluginPath.c_str(), "--appdir", appDirPath.string().c_str()};
const auto pluginPath = path();
const std::initializer_list<std::string> args = {pluginPath.string(), "--appdir", appDirPath.string()};
auto log = ldLog();
log << "Running process:";
@ -137,7 +138,7 @@ namespace linuxdeploy {
}
log << std::endl;
std::map<std::string, std::string> environmentVariables{};
subprocess::subprocess_env_map_t environmentVariables{};
if (this->pluginType() == PLUGIN_TYPE::INPUT_TYPE) {
// add $LINUXDEPLOY, which points to the current binary
@ -146,27 +147,22 @@ namespace linuxdeploy {
environmentVariables["LINUXDEPLOY"] = linuxdeploy::util::getOwnExecutablePath();
}
auto process = subprocess::Popen(
args,
subprocess::output{subprocess::PIPE},
subprocess::error{subprocess::PIPE},
subprocess::environment{environmentVariables}
);
subprocess::process process(args, environmentVariables);
std::vector<pollfd> pfds(2);
auto* opfd = &pfds[0];
auto* epfd = &pfds[1];
opfd->fd = fileno(process.output());
opfd->fd = process.stdout_fd();
opfd->events = POLLIN;
epfd->fd = fileno(process.error());
epfd->fd = process.stderr_fd();
epfd->events = POLLIN;
for (const auto& fd : {process.output(), process.error()}) {
auto flags = fcntl(fileno(fd), F_GETFL, 0);
for (const auto& fd : {process.stdout_fd(), process.stderr_fd()}) {
auto flags = fcntl(fd, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(fileno(fd), F_SETFL, flags);
fcntl(fd, F_SETFL, flags);
}
auto printOutput = [&pfds, opfd, epfd, this, &process]() {
@ -207,11 +203,10 @@ namespace linuxdeploy {
throw std::runtime_error("Buffer overflow");
while (true) {
auto bytesRead = fread(
auto bytesRead = read(
process.stdout_fd(),
reinterpret_cast<void*>(stdoutBuf.data() + stdoutBufSize),
sizeof(char),
static_cast<size_t>(stdoutBuf.size() - stdoutBufSize),
process.output()
static_cast<size_t>(stdoutBuf.size() - stdoutBufSize)
);
if (bytesRead == 0)
@ -230,11 +225,10 @@ namespace linuxdeploy {
throw std::runtime_error("Buffer overflow");
while (true) {
auto bytesRead = fread(
auto bytesRead = read(
process.stderr_fd(),
reinterpret_cast<void*>(stderrBuf.data() + stderrBufSize),
sizeof(char),
static_cast<size_t>(stderrBuf.size() - stderrBufSize),
process.error()
static_cast<size_t>(stderrBuf.size() - stderrBufSize)
);
if (bytesRead == 0)
@ -251,15 +245,13 @@ namespace linuxdeploy {
return true;
};
int retcode;
do {
if (!printOutput()) {
ldLog() << LD_ERROR << "Failed to communicate with process" << std::endl;
process.kill();
return -1;
}
} while ((retcode = process.poll()) < 0);
} while (process.poll());
if (!printOutput()) {
ldLog() << LD_ERROR << "Failed to communicate with process" << std::endl;
@ -267,6 +259,7 @@ namespace linuxdeploy {
return -1;
}
const auto retcode = process.close();
return retcode;
}
}

View File

@ -1,19 +0,0 @@
/**
* Wrapper for cpp-subprocess. Provides additional convenience functions.
*/
#pragma once
// library includes
#include <subprocess.hpp>
namespace linuxdeploy {
namespace util {
namespace subprocess {
using namespace ::subprocess;
// Reads output channels of existing Popen object into buffers and returns the contents
std::pair<std::string, std::string> check_output_error(Popen& proc);
}
}
}

View File

@ -1,10 +1,11 @@
// local includes
#include "misc.h"
#include "subprocess.h"
#include "linuxdeploy/util/assert.h"
#include "linuxdeploy/util/misc.h"
// import functions from misc module for convenience
namespace linuxdeploy {
namespace util {
using namespace misc;
using namespace assert;
}
}

View File

@ -1,10 +1,5 @@
include(CTest)
add_library(subprocess INTERFACE)
target_sources(subprocess INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/cpp-subprocess/subprocess.hpp)
target_include_directories(subprocess INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/cpp-subprocess)
target_compile_options(subprocess INTERFACE "-Wno-deprecated")
add_library(args INTERFACE)
target_sources(args INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/args/args.hxx)
target_include_directories(args INTERFACE args)

@ -1 +0,0 @@
Subproject commit 6931e3d69fb36e6eae099585646e54ac644bf99c

View File

@ -43,6 +43,7 @@ execute_process(
add_subdirectory(util)
add_subdirectory(plugin)
add_subdirectory(subprocess)
add_subdirectory(core)
add_executable(linuxdeploy main.cpp core.cpp)
@ -65,7 +66,7 @@ add_executable(plugin_test plugin_test_main.cpp)
target_link_libraries(plugin_test linuxdeploy_plugin)
set_target_properties(plugin_test PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin")
add_executable(appdir_test appdir_test_main.cpp)
add_executable(appdir_test appdir_test_main.cpp ../include/linuxdeploy/util/assert.h)
target_link_libraries(appdir_test linuxdeploy_core args)
target_include_directories(appdir_test PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/core)
set_target_properties(appdir_test PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin")

View File

@ -1,5 +1,6 @@
// system headers
#include <set>
#include <map>
#include <string>
#include <vector>
@ -7,7 +8,6 @@
#include <boost/filesystem.hpp>
#include <CImg.h>
#include <fnmatch.h>
#include <subprocess.hpp>
// local headers
@ -16,6 +16,7 @@
#include "linuxdeploy/core/log.h"
#include "linuxdeploy/desktopfile/desktopfileentry.h"
#include "linuxdeploy/util/util.h"
#include "linuxdeploy/subprocess/subprocess.h"
#include "copyright.h"
// auto-generated headers
@ -187,19 +188,15 @@ namespace linuxdeploy {
} else {
ldLog() << "Calling strip on library" << filePath << std::endl;
std::map<std::string, std::string> env;
subprocess::subprocess_env_map_t env;
env.insert(std::make_pair(std::string("LC_ALL"), std::string("C")));
subprocess::Popen proc(
{stripPath.c_str(), filePath.c_str()},
subprocess::output(subprocess::PIPE),
subprocess::error(subprocess::PIPE),
subprocess::environment(env)
);
subprocess::subprocess proc({stripPath, filePath.string()}, env);
std::string err = util::subprocess::check_output_error(proc).second;
const auto result = proc.run();
const auto& err = result.stderr_string();
if (proc.retcode() != 0 &&
if (result.exit_code() != 0 &&
!util::stringContains(err, "Not enough room for program headers")) {
ldLog() << LD_ERROR << "Strip call failed:" << err << std::endl;
success = false;

View File

@ -2,6 +2,7 @@
#include "copyright_dpkgquery.h"
#include "linuxdeploy/core/log.h"
#include "linuxdeploy/util/util.h"
#include "linuxdeploy/subprocess/subprocess.h"
namespace linuxdeploy {
namespace core {
@ -9,27 +10,16 @@ namespace linuxdeploy {
using namespace log;
std::vector<bf::path> DpkgQueryCopyrightFilesManager::getCopyrightFilesForPath(const bf::path& path) {
auto call = [](const std::initializer_list<const char*>& args) {
auto proc = subprocess::Popen(
args,
subprocess::output(subprocess::PIPE),
subprocess::error(subprocess::PIPE)
);
subprocess::subprocess proc{{"dpkg-query", "-S", path.c_str()}};
auto output = proc.communicate();
return std::make_pair(proc.retcode(), output.first);
};
auto result = proc.run();
auto dpkgQueryPackages = call({"dpkg-query", "-S", path.c_str()});
if (dpkgQueryPackages.first != 0
|| dpkgQueryPackages.second.buf.empty()
|| dpkgQueryPackages.second.buf.front() == '\0') {
if (result.exit_code() != 0 || result.stdout_contents().empty()) {
ldLog() << LD_WARNING << "Could not find copyright files for file" << path << "using dpkg-query" << std::endl;
return {};
}
auto packageName = util::split(util::splitLines(dpkgQueryPackages.second.buf.data())[0], ':')[0];
auto packageName = util::split(util::splitLines(result.stdout_string())[0], ':')[0];
if (!packageName.empty()) {
auto copyrightFilePath = bf::path("/usr/share/doc") / packageName / "copyright";

View File

@ -1,6 +1,3 @@
// library includes
#include <subprocess.hpp>
// local includes
#include "copyright.h"

View File

@ -2,16 +2,17 @@
#include <fstream>
#include <memory>
#include <utility>
#include <fcntl.h>
// library includes
#include <boost/regex.hpp>
#include <subprocess.hpp>
#include <sys/mman.h>
// local headers
#include "linuxdeploy/core/elf.h"
#include "linuxdeploy/core/log.h"
#include "linuxdeploy/util/util.h"
#include "linuxdeploy/subprocess/subprocess.h"
using namespace linuxdeploy::core::log;
@ -147,7 +148,7 @@ namespace linuxdeploy {
std::vector<bf::path> paths;
std::map<std::string, std::string> env;
subprocess::subprocess_env_map_t env;
env.insert(std::make_pair(std::string("LC_ALL"), std::string("C")));
// workaround for https://sourceware.org/bugzilla/show_bug.cgi?id=25263
@ -156,19 +157,14 @@ namespace linuxdeploy {
// note that this is just a bug in ldd, the linker has always worked as intended
const auto resolvedPath = bf::canonical(d->path);
subprocess::Popen lddProc(
{"ldd", resolvedPath.string().c_str()},
subprocess::output{subprocess::PIPE},
subprocess::error{subprocess::PIPE},
subprocess::environment(env)
);
subprocess::subprocess lddProc({"ldd", resolvedPath.string()}, env);
auto outputAndError = util::subprocess::check_output_error(lddProc);
const auto stdoutContents = lddProc.check_output();
const boost::regex expr(R"(\s*(.+)\s+\=>\s+(.+)\s+\((.+)\)\s*)");
boost::smatch what;
for (const auto& line : util::splitLines(outputAndError.first)) {
for (const auto& line : util::splitLines(stdoutContents)) {
if (boost::regex_search(line, what, expr)) {
auto libraryPath = what[2].str();
util::trim(libraryPath);
@ -195,30 +191,26 @@ namespace linuxdeploy {
const auto patchelfPath = PrivateData::getPatchelfPath();
try {
subprocess::Popen patchelfProc(
{patchelfPath.c_str(), "--print-rpath", d->path.c_str()},
subprocess::output(subprocess::PIPE),
subprocess::error(subprocess::PIPE)
);
subprocess::subprocess patchelfProc({patchelfPath, "--print-rpath", d->path.string()});
auto patchelfOutput = util::subprocess::check_output_error(patchelfProc);
auto& patchelfStdout = patchelfOutput.first;
auto& patchelfStderr = patchelfOutput.second;
const auto result = patchelfProc.run();
if (patchelfProc.retcode() != 0) {
if (result.exit_code() != 0) {
// if file is not an ELF executable, there is no need for a detailed error message
if (patchelfProc.retcode() == 1 && patchelfStderr.find("not an ELF executable")) {
if (result.exit_code() == 1 && result.stderr_string().find("not an ELF executable") != std::string::npos) {
return "";
} else {
ldLog() << LD_ERROR << "Call to patchelf failed:" << std::endl << patchelfStderr;
ldLog() << LD_ERROR << "Call to patchelf failed:" << std::endl << result.stderr_string();
return "";
}
}
util::trim(patchelfStdout, '\n');
util::trim(patchelfStdout);
auto stdoutContents = result.stdout_string();
return patchelfStdout;
util::trim(stdoutContents, '\n');
util::trim(stdoutContents);
return stdoutContents;
} catch (const std::exception&) {
return "";
}
@ -229,17 +221,12 @@ namespace linuxdeploy {
const auto patchelfPath = PrivateData::getPatchelfPath();
try {
subprocess::Popen patchelfProc(
{patchelfPath.c_str(), "--set-rpath", value.c_str(), d->path.c_str()},
subprocess::output(subprocess::PIPE),
subprocess::error(subprocess::PIPE)
);
subprocess::subprocess patchelfProc({patchelfPath.c_str(), "--set-rpath", value.c_str(), d->path.c_str()});
const auto patchelfOutput = util::subprocess::check_output_error(patchelfProc);
const auto& patchelfStderr = patchelfOutput.second;
const auto result = patchelfProc.run();
if (patchelfProc.retcode() != 0) {
ldLog() << LD_ERROR << "Call to patchelf failed:" << std::endl << patchelfStderr << std::endl;
if (result.exit_code() != 0) {
ldLog() << LD_ERROR << "Call to patchelf failed:" << std::endl << result.stderr_string() << std::endl;
return false;
}
} catch (const std::exception&) {

View File

@ -1,4 +1,4 @@
file(GLOB PLUGIN_HEADERS ${PROJECT_SOURCE_DIR}/include/linuxdeploy/plugin/*.h)
add_library(linuxdeploy_plugin STATIC plugin_type0.cpp plugin.cpp ${PLUGIN_HEADERS})
target_link_libraries(linuxdeploy_plugin PUBLIC linuxdeploy_core ${BOOST_LIBS} subprocess)
target_link_libraries(linuxdeploy_plugin PUBLIC linuxdeploy_core ${BOOST_LIBS} linuxdeploy_subprocess)

View File

@ -7,7 +7,6 @@
#include <boost/filesystem.hpp>
#include <boost/regex.hpp>
#include <fnmatch.h>
#include <subprocess.hpp>
// local headers
#include "linuxdeploy/core/log.h"

View File

@ -6,7 +6,6 @@
// library headers
#include <boost/filesystem.hpp>
#include <fnmatch.h>
#include <subprocess.hpp>
// local headers
#include "linuxdeploy/core/log.h"

View File

@ -1,7 +1,10 @@
cmake_minimum_required(VERSION 3.0)
file(GLOB HEADERS ${PROJECT_SOURCE_DIR}/include/linuxdeploy/util/*.h)
set(headers_dir ${PROJECT_SOURCE_DIR}/include/linuxdeploy/util/)
add_library(linuxdeploy_util STATIC subprocess.cpp ${HEADERS})
add_library(linuxdeploy_util INTERFACE)
target_sources(linuxdeploy_util INTERFACE
${headers_dir}/misc.h
${headers_dir}/util.h
)
target_include_directories(linuxdeploy_util INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/include)
target_link_libraries(linuxdeploy_util PUBLIC subprocess)

View File

@ -1,56 +0,0 @@
// global headers
#include <string>
#include <vector>
#include <unistd.h>
// local headers
#include "linuxdeploy/util/subprocess.h"
namespace linuxdeploy {
namespace util {
namespace subprocess {
std::pair<std::string, std::string> check_output_error(Popen& proc) {
std::vector<char> procStdout, procStderr;
// make file descriptors non-blocking
for (const auto& fd : {proc.output(), proc.error()}) {
auto flags = fcntl(fileno(fd), F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(fileno(fd), F_SETFL, flags);
}
while (true) {
constexpr auto bufSize = 4096;
std::vector<char> buf(bufSize, '\0');
for (auto& fd : {proc.output(), proc.error()}) {
auto& outBuf = fd == proc.output() ? procStdout : procStderr;
auto size = fread(buf.data(), sizeof(char), buf.size(), fd);
if (size < 0)
throw std::runtime_error("Error reading fd");
if (size == 0)
continue;
assert(size <= buf.size());
auto outBufSize = outBuf.size();
outBuf.reserve(outBufSize + size + 1);
std::copy(buf.begin(), buf.begin() + size, std::back_inserter(outBuf));
}
// make sure process exited and all data has been read from stdout and stderr
if (proc.poll() >= 0 && feof(proc.output()) != 0 && feof(proc.error()) != 0)
break;
}
std::string stdoutContents(procStdout.begin(), procStdout.end());
std::string stderrContents(procStderr.begin(), procStderr.end());
return std::make_pair(stdoutContents, stderrContents);
}
}
}
}