From 61d9e8699687342c2e32c32c8d4eb71760d5d290 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 26 Jun 2021 17:33:24 +0200 Subject: Use fork/exec instead of popen. Add Path class --- src/Exec.cpp | 173 ++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 123 insertions(+), 50 deletions(-) (limited to 'src/Exec.cpp') diff --git a/src/Exec.cpp b/src/Exec.cpp index 169858f..12e373d 100644 --- a/src/Exec.cpp +++ b/src/Exec.cpp @@ -3,6 +3,7 @@ #if OS_FAMILY == OS_FAMILY_POSIX #include +#include #endif using namespace std; @@ -13,60 +14,129 @@ const int BUFSIZE = 4096; namespace sibs { #if OS_FAMILY == OS_FAMILY_POSIX - Result exec(const _tinydir_char_t *cmd, bool print) + Result exec(const std::vector &args, bool print_instead_of_pipe) { char buffer[BUFSIZE]; std::string execStdout; - FILE *pipe = popen(cmd, "r"); - if(!pipe) - return Result::Err("popen() failed"); - while(!feof(pipe)) - { - if(fgets(buffer, BUFSIZE, pipe)) - { - int bytesRead = strlen(buffer); - execStdout.append(buffer, bytesRead); - if(print) - printf("%.*s", bytesRead, buffer); + if(args.empty()) + return Result::Err("exec requires at least one argument (the program name)"); + + std::vector exec_args; + for(const FileString &arg : args) { + exec_args.push_back(arg.c_str()); + } + exec_args.push_back(nullptr); + + int fd[2]; + if(!print_instead_of_pipe && pipe(fd) == -1) + return Result::Err(strerror(errno)); + + pid_t pid = fork(); + if(pid == -1) { + if(!print_instead_of_pipe) { + close(fd[0]); + close(fd[1]); } + return Result::Err("Failed to exec " + args[0] + " (failed to fork)"); + } else if(pid == 0) { // child + if(!print_instead_of_pipe) { + dup2(fd[1], STDOUT_FILENO); + close(fd[0]); + close(fd[1]); + } + execvp(exec_args[0], (char* const*)exec_args.data()); + perror("execvp"); + _exit(127); + } else { // parent + if(!print_instead_of_pipe) + close(fd[1]); } - int processCloseResult = pclose(pipe); - if(WIFEXITED(processCloseResult)) + if(!print_instead_of_pipe) { + for(;;) { + ssize_t bytes_read = read(fd[0], buffer, sizeof(buffer)); + if(bytes_read == 0) { + break; + } else if(bytes_read == -1) { + std::string err_msg = "Failed to read from pipe to program " + args[0] + ", error: " + strerror(errno); + kill(pid, SIGTERM); + close(fd[0]); + return Result::Err(err_msg); + } + + execStdout.append(buffer, bytes_read); + } + } + + int status = 0; + if(waitpid(pid, &status, 0) == -1) { + std::string err_msg = std::string("waitpid failed, error: ") + strerror(errno); + if(!print_instead_of_pipe) + close(fd[0]); + return Result::Err(err_msg); + } + if(!print_instead_of_pipe) + close(fd[0]); + + if(WIFEXITED(status)) { - int returned = WEXITSTATUS(processCloseResult); + int returned = WEXITSTATUS(status); ExecResult execResult; execResult.execStdout = move(execStdout); execResult.exitCode = returned; return Result::Ok(execResult); } - else if(WIFSIGNALED(processCloseResult)) + else if(WIFSIGNALED(status)) { - int signum = WSTOPSIG(processCloseResult); + int signum = WSTOPSIG(status); string errMsg = "Exited due to receiving signal "; errMsg += to_string(signum); return Result::Err(errMsg); } - else if(WIFSTOPPED(processCloseResult)) + else if(WIFSTOPPED(status)) { - int signum = WSTOPSIG(processCloseResult); + int signum = WSTOPSIG(status); string errMsg = "Stopped due to receiving signal "; errMsg += to_string(signum); return Result::Err(errMsg); } else { - string errMsg = "exec unexpected error on pclose: "; - errMsg += to_string(processCloseResult); + string errMsg = "exec unexpected error on waitpid: "; + errMsg += to_string(status); return Result::Err(errMsg); } } + #else + static FileString escape_arg(const FileString &arg) { + FileString escaped = TINYDIR_STRING("\""); + for(_tinydir_char_t c : arg) { + if(c == '"') { + escaped += TINYDIR_STRING("\"\""); + } else { + escaped += c; + } + } + escaped += TINYDIR_STRING("\""); + return escaped; + } + + static FileString command_list_to_command_string(const std::vector &args) { + FileString cmd; + for(size_t i = 0; i < args.size(); ++i) { + if(i > 0) + cmd += TINYDIR_STRING(" "); + cmd += escape_arg(args[i]); + } + return cmd; + } + // Currently stdout is read in text mode so \n is replaced with \r\n, should we read in binary mode instead? - Result exec(const _tinydir_char_t *cmd, bool print) + Result exec(const std::vector &args, bool print_instead_of_pipe) { - FileString cmdNonConst = cmd; + FileString cmdNonConst = command_list_to_command_string(args); std::string execStdout; SECURITY_ATTRIBUTES saAttr; @@ -77,15 +147,17 @@ namespace sibs HANDLE childReadHandle = nullptr; HANDLE childStdoutHandle = nullptr; - if (!CreatePipe(&childReadHandle, &childStdoutHandle, &saAttr, 0)) - { - string errMsg = "exec unexpected error: "; - errMsg += toUtf8(getLastErrorAsString()); - return Result::Err(errMsg); - } + if(!print_instead_of_pipe) { + if (!CreatePipe(&childReadHandle, &childStdoutHandle, &saAttr, 0)) + { + string errMsg = "exec unexpected error: "; + errMsg += toUtf8(getLastErrorAsString()); + return Result::Err(errMsg); + } - if (!SetHandleInformation(childReadHandle, HANDLE_FLAG_INHERIT, 0)) - goto cleanupAndExit; + if (!SetHandleInformation(childReadHandle, HANDLE_FLAG_INHERIT, 0)) + goto cleanupAndExit; + } PROCESS_INFORMATION piProcInfo; ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); @@ -94,7 +166,7 @@ namespace sibs ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); siStartInfo.cb = sizeof(STARTUPINFO); siStartInfo.hStdError = nullptr; - siStartInfo.hStdOutput = childStdoutHandle; + siStartInfo.hStdOutput = print_instead_of_pipe ? nullptr : childStdoutHandle; siStartInfo.hStdInput = nullptr; siStartInfo.dwFlags |= STARTF_USESTDHANDLES; @@ -103,19 +175,22 @@ namespace sibs if (!CreateProcessW(nullptr, (LPWSTR)cmdNonConst.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &siStartInfo, &piProcInfo)) goto cleanupAndExit; - CloseHandle(childStdoutHandle); - childStdoutHandle = nullptr; - DWORD bytesRead; - CHAR buffer[BUFSIZE]; - while (true) - { - BOOL bSuccess = ReadFile(childReadHandle, buffer, BUFSIZE, &bytesRead, nullptr); - if (!bSuccess || bytesRead == 0) - break; + if(!print_instead_of_pipe) { + CloseHandle(childStdoutHandle); + childStdoutHandle = nullptr; + + DWORD bytesRead; + CHAR buffer[BUFSIZE]; + while (true) + { + BOOL bSuccess = ReadFile(childReadHandle, buffer, BUFSIZE, &bytesRead, nullptr); + if (!bSuccess || bytesRead == 0) + break; - execStdout.append(buffer, bytesRead); - if (print) - printf("%.*s", bytesRead, buffer); + execStdout.append(buffer, bytesRead); + if (print) + printf("%.*s", bytesRead, buffer); + } } WaitForSingleObject(piProcInfo.hProcess, INFINITE); @@ -134,13 +209,11 @@ namespace sibs cleanupAndExit: string errMsg = "exec unexpected error: "; errMsg += toUtf8(getLastErrorAsString()); - CloseHandle(childReadHandle); - CloseHandle(childStdoutHandle); + if(childReadHandle) + CloseHandle(childReadHandle); + if(childStdoutHandle) + CloseHandle(childStdoutHandle); return Result::Err(errMsg); } #endif - Result exec(const FileString &cmd, bool print) - { - return exec(cmd.c_str(), print); - } } -- cgit v1.2.3