Fix exception safety in subprocess

This commit is contained in:
Sebastian Messmer 2018-05-20 22:28:28 -07:00
parent aa4e3c7c08
commit 76dc5cbba8

View File

@ -26,44 +26,54 @@ constexpr const char* openmode = "r";
using std::string; using std::string;
namespace cpputils { namespace cpputils {
//TODO Exception safety
namespace { namespace {
FILE *_call(const string &command) { class SubprocessHandle final {
FILE *subprocess = popen(command.c_str(), openmode); public:
if (!subprocess) SubprocessHandle(const string &command)
{ : _subprocess(popen(command.c_str(), openmode)) {
throw std::runtime_error("Error starting subprocess "+command + ". Errno: " + std::to_string(errno)); if (!_subprocess) {
} throw std::runtime_error("Error starting subprocess " + command + ". Errno: " + std::to_string(errno));
return subprocess; }
} }
string _getOutput(FILE *subprocess) { ~SubprocessHandle() {
string output; if (_subprocess != nullptr) {
char buffer[1024]; close();
while(fgets(buffer, sizeof(buffer), subprocess) != nullptr) { }
output += buffer; }
}
return output; string getOutput() {
} string output;
char buffer[1024];
while (fgets(buffer, sizeof(buffer), _subprocess) != nullptr) {
output += buffer;
}
return output;
}
int close() {
auto returncode = pclose(_subprocess);
_subprocess = nullptr;
if (returncode == -1) {
throw std::runtime_error("Error calling pclose. Errno: " + std::to_string(errno));
}
if (!WIFEXITED(returncode)) {
// WEXITSTATUS is only valid if WIFEXITED is 0.
throw std::runtime_error("WIFEXITED returned " + std::to_string(WIFEXITED(returncode)));
}
return WEXITSTATUS(returncode);
}
private:
FILE *_subprocess;
};
int _close(FILE *subprocess) {
auto returncode = pclose(subprocess);
if(returncode == -1) {
throw std::runtime_error("Error calling pclose. Errno: " + std::to_string(errno));
}
if (!WIFEXITED(returncode)) {
// WEXITSTATUS is only valid if WIFEXITED is 0.
throw std::runtime_error("WIFEXITED returned " + std::to_string(WIFEXITED(returncode)));
}
return WEXITSTATUS(returncode);
}
} }
SubprocessResult Subprocess::call(const string &command) { SubprocessResult Subprocess::call(const string &command) {
FILE *subprocess = _call(command); SubprocessHandle subprocess(command);
string output = _getOutput(subprocess); string output = subprocess.getOutput();
int exitcode = _close(subprocess); int exitcode = subprocess.close();
return SubprocessResult {output, exitcode}; return SubprocessResult {output, exitcode};
} }