Commit 8552ae70 authored by Yann Garcia's avatar Yann Garcia
Browse files

Add Upper Tester for UERANSIM

parent 0354bd8f
Loading
Loading
Loading
Loading
+178 −0
Original line number Diff line number Diff line
#include "async_process.hh"

void async_process::start(const std::string& command, const std::vector<std::string>& args, Options opts) {
    // Sanity check
    if (running_) throw std::runtime_error("Process already running");

    opts_ = std::move(opts);

    if (pipe(stdoutPipe_.data()) < 0) throw std::runtime_error("pipe(stdout) failed");
    if (pipe(stderrPipe_.data()) < 0) throw std::runtime_error("pipe(stderr) failed");

    setNonBlocking(stdoutPipe_[0]);
    setNonBlocking(stderrPipe_[0]);

    if (opts_.useSudo) {
        if (pipe(stdinPipe_.data()) < 0) throw std::runtime_error("pipe(stdin) failed");
    }

    pid_ = fork();
    if (pid_ < 0) throw std::runtime_error("fork failed");

    if (pid_ == 0) {
        childMain(command, args);
        _exit(127);
    }

    running_ = true;
    exited_ = false;
    exitStatus_ = -1;

    if (opts_.onOutput) {
        reader_ = std::thread([this] { pumpOutput(); });
    }
}

bool async_process::pollExitStatus(int& status) {
    if (!running_) return false;
    int w = waitpid(pid_, &status, WNOHANG);
    if (w == 0) return false;
    if (w == pid_) {
        exited_ = true;
        exitStatus_ = status;
        running_ = false;
        return true;
    }
    return false;
}

int async_process::wait() {
    if (!running_ && !exitStatus_) {
        return -1;
    }

    if (!exited_) {
        int status = 0;
        if (waitpid(pid_, &status, 0) == pid_) {
            exited_ = true;
            exitStatus_ = status;
            running_ = false;
        }
    }
    if (reader_.joinable()) {
        reader_.join();
    }

    return exitStatus_;
}

void async_process::terminate(int signalNo) {
    if (pid_ > 0 && running_) {
        ::kill(pid_, signalNo);
    }
}

void async_process::setNonBlocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags < 0) throw std::runtime_error("fcntl(F_GETFL) failed");
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) throw std::runtime_error("fcntl(F_SETFL) failed");
}

void async_process::closeIfOpen(int fd) {
    if (fd >= 0) ::close(fd);
}

std::string async_process::drainFd(int fd) {
    std::string out;
    std::array<char, 4096> buf{};
    while (true) {
        ssize_t n = ::read(fd, buf.data(), buf.size());
        if (n > 0) {
            out.append(buf.data(), static_cast<size_t>(n));
        } else {
            break;
        }
    }
    return out;
}

void async_process::pumpOutput() {
    std::array<char, 4096> buf{};
    while (running_) {
        bool gotData = false;

        for (auto [fd, isErr] : {std::pair{stdoutPipe_[0], false}, std::pair{stderrPipe_[0], true}}) {
            while (true) {
                ssize_t n = ::read(fd, buf.data(), buf.size());
                if (n > 0) {
                    gotData = true;
                    if (opts_.onOutput) opts_.onOutput(std::string_view(buf.data(), static_cast<size_t>(n)), isErr);
                } else {
                    break;
                }
            }
        }

        int status = 0;
        if (pollExitStatus(status)) break;

        if (!gotData) {
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }

    while (true) {
        bool any = false;
        for (auto [fd, isErr] : {std::pair{stdoutPipe_[0], false}, std::pair{stderrPipe_[0], true}}) {
            while (true) {
                ssize_t n = ::read(fd, buf.data(), buf.size());
                if (n > 0) {
                    any = true;
                    if (opts_.onOutput) opts_.onOutput(std::string_view(buf.data(), static_cast<size_t>(n)), isErr);
                } else {
                    break;
                }
            }
        }
        if (!any) break;
    }
}

[[noreturn]] void async_process::childMain(const std::string& command, const std::vector<std::string>& args) {
    ::close(stdoutPipe_[0]);
    ::close(stderrPipe_[0]);
    if (opts_.useSudo) ::close(stdinPipe_[1]);

    if (::dup2(stdoutPipe_[1], STDOUT_FILENO) < 0) _exit(126);
    if (::dup2(stderrPipe_[1], STDERR_FILENO) < 0) _exit(126);

    if (opts_.useSudo) {
        if (::dup2(stdinPipe_[0], STDIN_FILENO) < 0) _exit(126);
    }

    ::close(stdoutPipe_[1]);
    ::close(stderrPipe_[1]);
    if (opts_.useSudo) ::close(stdinPipe_[0]);

    std::vector<char*> argv;
    argv.reserve(args.size() + (opts_.useSudo ? 4 : 2));

    if (opts_.useSudo) {
        argv.push_back(const_cast<char*>("sudo"));
        argv.push_back(const_cast<char*>("-S"));
        argv.push_back(const_cast<char*>("-p"));
        argv.push_back(const_cast<char*>(""));
    }

    argv.push_back(const_cast<char*>(command.c_str()));
    for (const auto& s : args) argv.push_back(const_cast<char*>(s.c_str()));
    argv.push_back(nullptr);

    if (opts_.useSudo && !opts_.sudoPassword.empty()) {
        ::write(STDIN_FILENO, opts_.sudoPassword.c_str(), opts_.sudoPassword.size());
        ::write(STDIN_FILENO, "\n", 1);
    }

    ::execvp(argv[0], argv.data());
    _exit(127);
}
+60 −0
Original line number Diff line number Diff line
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <signal.h>

#include <array>
#include <atomic>
#include <chrono>
#include <cstring>
#include <functional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <thread>
#include <vector>
#include <optional>

class async_process {
public:
    using OutputCallback = std::function<void(std::string_view, bool isStderr)>;

    struct Options {
        bool useSudo = false;
        std::string sudoPassword;   // only used if useSudo == true
        OutputCallback onOutput;    // optional callback for stdout/stderr chunks
    };

    async_process() = default;
    ~async_process() { terminate(); wait(); }

    async_process(const async_process&) = delete;
    async_process& operator=(const async_process&) = delete;
    void start(const std::string& command, const std::vector<std::string>& args, Options opts);
    bool pollExitStatus(int& status);
    int wait();
    void terminate(int signalNo = SIGTERM);

    inline bool running() const noexcept { return running_ && !exited_; };
    inline void killForce() { terminate(SIGKILL); };
    inline std::string readStdout() { return drainFd(stdoutPipe_[0]); };
    inline std::string readStderr() { return drainFd(stderrPipe_[0]); };
    inline pid_t pid() const noexcept { return pid_; };

private:
    pid_t pid_ = -1;
    bool running_ = false;
    bool exited_ = false;
    int exitStatus_;
    Options opts_;
    std::array<int, 2> stdoutPipe_{-1, -1};
    std::array<int, 2> stderrPipe_{-1, -1};
    std::array<int, 2> stdinPipe_{-1, -1};
    std::thread reader_;

    static void setNonBlocking(int fd);
    static void closeIfOpen(int fd);
    static std::string drainFd(int fd);
    void pumpOutput();
    [[noreturn]] void childMain(const std::string& command, const std::vector<std::string>& args);
};
 No newline at end of file
+1 −1
Original line number Diff line number Diff line
sources := helpers_externals.cc
sources := helpers_externals.cc async_process.cc
includes := .