Skip to content

Commit

Permalink
utils: adding subprocess utility
Browse files Browse the repository at this point in the history
  • Loading branch information
janweinstock committed Mar 2, 2025
1 parent eb8c6c7 commit aef0241
Show file tree
Hide file tree
Showing 7 changed files with 509 additions and 4 deletions.
4 changes: 3 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
##############################################################################

cmake_minimum_required(VERSION 3.11)
project(mwr VERSION 2025.02.28 LANGUAGES CXX)
project(mwr VERSION 2025.02.28 LANGUAGES C CXX)

set(MWR_LINTER "" CACHE STRING "Code linter to use")
set(MWR_COVERAGE OFF CACHE BOOL "Collect code coverage data")
Expand Down Expand Up @@ -67,6 +67,7 @@ if(MSVC)
target_sources(mwr PRIVATE ${src}/mwr/utils/library_win32.cpp)
target_sources(mwr PRIVATE ${src}/mwr/utils/memory_win32.cpp)
target_sources(mwr PRIVATE ${src}/mwr/utils/socket_win32.cpp)
target_sources(mwr PRIVATE ${src}/mwr/utils/subprocess_win32.cpp)
target_link_options(mwr PUBLIC /OPT:NOREF)
target_link_libraries(mwr PUBLIC Dbghelp.lib)
target_link_libraries(mwr PUBLIC ws2_32)
Expand All @@ -75,6 +76,7 @@ else()
target_sources(mwr PRIVATE ${src}/mwr/utils/library_posix.cpp)
target_sources(mwr PRIVATE ${src}/mwr/utils/memory_posix.cpp)
target_sources(mwr PRIVATE ${src}/mwr/utils/socket_posix.cpp)
target_sources(mwr PRIVATE ${src}/mwr/utils/subprocess_posix.cpp)
target_link_libraries(mwr PUBLIC -rdynamic)
if(NOT APPLE)
target_link_libraries(mwr PUBLIC rt stdc++fs)
Expand Down
1 change: 1 addition & 0 deletions include/mwr.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#include "mwr/utils/socket.h"
#include "mwr/utils/srec.h"
#include "mwr/utils/ihex.h"
#include "mwr/utils/subprocess.h"
#include "mwr/utils/terminal.h"
#include "mwr/utils/uimage.h"

Expand Down
66 changes: 66 additions & 0 deletions include/mwr/utils/subprocess.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/******************************************************************************
* *
* Copyright (C) 2025 MachineWare GmbH *
* All Rights Reserved *
* *
* This is work is licensed under the terms described in the LICENSE file *
* found in the root directory of this source tree. *
* *
******************************************************************************/

#ifndef MWR_UTILS_SUBPROCESS_H
#define MWR_UTILS_SUBPROCESS_H

#include "mwr/core/types.h"
#include "mwr/core/report.h"
#include "mwr/core/compiler.h"
#include "mwr/core/utils.h"

#include "mwr/stl/strings.h"
#include "mwr/stl/containers.h"

namespace mwr {

class subprocess
{
private:
#ifdef MWR_WINDOWS
void* m_handle;
void* m_stdin;
void* m_stdout;
void* m_stderr;
unsigned long m_pid;
#else
int m_pid;
int m_stdin;
int m_stdout;
int m_stderr;
#endif

public:
map<string, string> env;

#ifdef MWR_WINDOWS
unsigned long pid() const { return m_pid; }
void* native_handle() const { return m_handle; }
#else
int pid() const { return m_pid; }
int native_handle() const { return m_pid; }
#endif

subprocess();
virtual ~subprocess();

bool run(const string& path,
const vector<string>& args = vector<string>());

bool terminate();

bool write(const string& str);
bool read(char* buf, size_t buflen, bool use_stderr = false);
string read(bool use_stderr = false); // non-blocking
};

} // namespace mwr

#endif
157 changes: 157 additions & 0 deletions src/mwr/utils/subprocess_posix.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/******************************************************************************
* *
* Copyright (C) 2025 MachineWare GmbH *
* All Rights Reserved *
* *
* This is work is licensed under the terms described in the LICENSE file *
* found in the root directory of this source tree. *
* *
******************************************************************************/

#include "mwr/utils/subprocess.h"

#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

namespace mwr {

subprocess::subprocess():
m_pid(-1), m_stdin(-1), m_stdout(-1), m_stderr(-1), env() {
}

subprocess::~subprocess() {
terminate();
}

static void set_o_nonblock(int fd, bool set) {
int flags = fcntl(fd, F_GETFL, 0);
if (set)
flags |= O_NONBLOCK;
else
flags &= ~O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
}

bool subprocess::run(const string& path, const vector<string>& args) {
int stdin_pipe[2];
int stdout_pipe[2];
int stderr_pipe[2];

if (pipe(stdin_pipe) < 0)
MWR_ERROR("error creating pipe: %s", strerror(errno));
if (pipe(stdout_pipe) < 0)
MWR_ERROR("error creating pipe: %s", strerror(errno));
if (pipe(stderr_pipe) < 0)
MWR_ERROR("error creating pipe: %s", strerror(errno));

int pid = fork();
if (pid < 0)
return false;

if (pid == 0) { // child process
dup2(stdin_pipe[0], STDIN_FILENO);
dup2(stdout_pipe[1], STDOUT_FILENO);
dup2(stderr_pipe[1], STDERR_FILENO);
close(stdin_pipe[0]);
close(stdin_pipe[1]);
close(stdout_pipe[0]);
close(stdout_pipe[1]);
close(stderr_pipe[0]);
close(stderr_pipe[1]);

vector<char*> argv;
argv.push_back(const_cast<char*>(path.c_str()));
for (auto& arg : args)
argv.push_back(const_cast<char*>(arg.c_str()));
argv.push_back(nullptr);

vector<char*> envp;
if (env.empty()) {
execv(path.c_str(), argv.data());
} else {
vector<string> env_strings(env.size());
for (auto& [name, value] : env)
env_strings.push_back(strcat(name, "=", value));
for (auto& s : env_strings)
envp.push_back(const_cast<char*>(s.c_str()));
envp.push_back(nullptr);
execve(path.c_str(), argv.data(), envp.data());
}

_exit(1);
} else { // parent process
close(stdout_pipe[1]);
close(stdin_pipe[0]);
m_stdin = stdin_pipe[1];
m_stdout = stdout_pipe[0];
m_stderr = stderr_pipe[0];
m_pid = pid;
return true;
}
}

bool subprocess::terminate() {
if (m_pid < 0)
return false;

if (kill(m_pid, SIGTERM))
return false;

int status;
waitpid(m_pid, &status, 0);

m_stdin = -1;
m_stdout = -1;
m_stderr = -1;
m_pid = -1;

return true;
}

bool subprocess::write(const string& s) {
if (m_stdin < 0)
return false;
if (s.empty())
return true;

ssize_t n = ::write(m_stdin, s.data(), s.length());
return n < 0 ? false : (size_t)n == s.length();
}

bool subprocess::read(char* buf, size_t buflen, bool use_stderr) {
int fd = use_stderr ? m_stderr : m_stdout;
if (fd < 0)
return false;

set_o_nonblock(fd, false);

size_t nread = 0;
while (nread < buflen) {
ssize_t res = ::read(fd, buf + nread, buflen - nread);
if (res < 0)
return false;
if (res == 0)
return false; // EOF
nread += (ssize_t)res;
}

return true;
}

string subprocess::read(bool use_stderr) {
int fd = use_stderr ? m_stderr : m_stdout;
if (fd < 0)
return "";

set_o_nonblock(fd, true);

char buf[1024] = {};
ssize_t n = ::read(fd, buf, sizeof(buf) - 1);
return n < 0 ? "" : buf;
}

} // namespace mwr
Loading

0 comments on commit aef0241

Please sign in to comment.