diff --git a/Makefile b/Makefile index b53cfb1..57813ce 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,9 @@ OBJFILES = \ gpg.o \ key.o \ util.o \ - parse_options.o + parse_options.o \ + coprocess.o \ + fhstream.o OBJFILES += crypto-openssl.o LDFLAGS += -lcrypto diff --git a/coprocess-unix.cpp b/coprocess-unix.cpp new file mode 100644 index 0000000..f9577e5 --- /dev/null +++ b/coprocess-unix.cpp @@ -0,0 +1,186 @@ +/* + * Copyright 2015 Andrew Ayer + * + * This file is part of git-crypt. + * + * git-crypt is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * git-crypt is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with git-crypt. If not, see . + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify the Program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, the licensors of the Program + * grant you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "coprocess.hpp" +#include "util.hpp" +#include +#include +#include + +static int execvp (const std::string& file, const std::vector& args) +{ + std::vector args_c_str; + args_c_str.reserve(args.size()); + for (std::vector::const_iterator arg(args.begin()); arg != args.end(); ++arg) { + args_c_str.push_back(arg->c_str()); + } + args_c_str.push_back(NULL); + return execvp(file.c_str(), const_cast(&args_c_str[0])); +} + +Coprocess::Coprocess () +{ + pid = -1; + stdin_pipe_reader = -1; + stdin_pipe_writer = -1; + stdin_pipe_ostream = NULL; + stdout_pipe_reader = -1; + stdout_pipe_writer = -1; + stdout_pipe_istream = NULL; +} + +Coprocess::~Coprocess () +{ + close_stdin(); + close_stdout(); +} + +std::ostream* Coprocess::stdin_pipe () +{ + if (!stdin_pipe_ostream) { + int fds[2]; + if (pipe(fds) == -1) { + throw System_error("pipe", "", errno); + } + stdin_pipe_reader = fds[0]; + stdin_pipe_writer = fds[1]; + stdin_pipe_ostream = new ofhstream(this, write_stdin); + } + return stdin_pipe_ostream; +} + +void Coprocess::close_stdin () +{ + delete stdin_pipe_ostream; + stdin_pipe_ostream = NULL; + if (stdin_pipe_writer != -1) { + close(stdin_pipe_writer); + stdin_pipe_writer = -1; + } + if (stdin_pipe_reader != -1) { + close(stdin_pipe_reader); + stdin_pipe_reader = -1; + } +} + +std::istream* Coprocess::stdout_pipe () +{ + if (!stdout_pipe_istream) { + int fds[2]; + if (pipe(fds) == -1) { + throw System_error("pipe", "", errno); + } + stdout_pipe_reader = fds[0]; + stdout_pipe_writer = fds[1]; + stdout_pipe_istream = new ifhstream(this, read_stdout); + } + return stdout_pipe_istream; +} + +void Coprocess::close_stdout () +{ + delete stdout_pipe_istream; + stdout_pipe_istream = NULL; + if (stdout_pipe_writer != -1) { + close(stdout_pipe_writer); + stdout_pipe_writer = -1; + } + if (stdout_pipe_reader != -1) { + close(stdout_pipe_reader); + stdout_pipe_reader = -1; + } +} + +void Coprocess::spawn (const std::vector& args) +{ + pid = fork(); + if (pid == -1) { + throw System_error("fork", "", errno); + } + if (pid == 0) { + if (stdin_pipe_writer != -1) { + close(stdin_pipe_writer); + } + if (stdout_pipe_reader != -1) { + close(stdout_pipe_reader); + } + if (stdin_pipe_reader != -1) { + dup2(stdin_pipe_reader, 0); + close(stdin_pipe_reader); + } + if (stdout_pipe_writer != -1) { + dup2(stdout_pipe_writer, 1); + close(stdout_pipe_writer); + } + + execvp(args[0], args); + perror(args[0].c_str()); + _exit(-1); + } + if (stdin_pipe_reader != -1) { + close(stdin_pipe_reader); + stdin_pipe_reader = -1; + } + if (stdout_pipe_writer != -1) { + close(stdout_pipe_writer); + stdout_pipe_writer = -1; + } +} + +int Coprocess::wait () +{ + int status = 0; + if (waitpid(pid, &status, 0) == -1) { + throw System_error("waitpid", "", errno); + } + return status; +} + +size_t Coprocess::write_stdin (void* handle, const void* buf, size_t count) +{ + const int fd = static_cast(handle)->stdin_pipe_writer; + ssize_t ret; + while ((ret = write(fd, buf, count)) == -1 && errno == EINTR); // restart if interrupted + if (ret < 0) { + throw System_error("write", "", errno); + } + return ret; +} + +size_t Coprocess::read_stdout (void* handle, void* buf, size_t count) +{ + const int fd = static_cast(handle)->stdout_pipe_reader; + ssize_t ret; + while ((ret = read(fd, buf, count)) == -1 && errno == EINTR); // restart if interrupted + if (ret < 0) { + throw System_error("read", "", errno); + } + return ret; +} diff --git a/coprocess-unix.hpp b/coprocess-unix.hpp new file mode 100644 index 0000000..f50f808 --- /dev/null +++ b/coprocess-unix.hpp @@ -0,0 +1,68 @@ +/* + * Copyright 2015 Andrew Ayer + * + * This file is part of git-crypt. + * + * git-crypt is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * git-crypt is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with git-crypt. If not, see . + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify the Program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, the licensors of the Program + * grant you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef GIT_CRYPT_COPROCESS_HPP +#define GIT_CRYPT_COPROCESS_HPP + +#include "fhstream.hpp" +#include +#include + +class Coprocess { + pid_t pid; + + int stdin_pipe_reader; + int stdin_pipe_writer; + ofhstream* stdin_pipe_ostream; + static size_t write_stdin (void*, const void*, size_t); + + int stdout_pipe_reader; + int stdout_pipe_writer; + ifhstream* stdout_pipe_istream; + static size_t read_stdout (void*, void*, size_t); + + Coprocess (const Coprocess&); // Disallow copy + Coprocess& operator= (const Coprocess&); // Disallow assignment +public: + Coprocess (); + ~Coprocess (); + + std::ostream* stdin_pipe (); + void close_stdin (); + + std::istream* stdout_pipe (); + void close_stdout (); + + void spawn (const std::vector&); + + int wait (); +}; + +#endif diff --git a/coprocess-win32.cpp b/coprocess-win32.cpp new file mode 100644 index 0000000..46e21d0 --- /dev/null +++ b/coprocess-win32.cpp @@ -0,0 +1,269 @@ +/* + * Copyright 2015 Andrew Ayer + * + * This file is part of git-crypt. + * + * git-crypt is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * git-crypt is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with git-crypt. If not, see . + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify the Program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, the licensors of the Program + * grant you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "coprocess-win32.hpp" +#include "util.hpp" + + +static void escape_cmdline_argument (std::string& cmdline, const std::string& arg) +{ + // For an explanation of Win32's arcane argument quoting rules, see: + // http://msdn.microsoft.com/en-us/library/17w5ykft%28v=vs.85%29.aspx + // http://msdn.microsoft.com/en-us/library/bb776391%28v=vs.85%29.aspx + // http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx + // http://blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx + cmdline.push_back('"'); + + std::string::const_iterator p(arg.begin()); + while (p != arg.end()) { + if (*p == '"') { + cmdline.push_back('\\'); + cmdline.push_back('"'); + ++p; + } else if (*p == '\\') { + unsigned int num_backslashes = 0; + while (p != arg.end() && *p == '\\') { + ++num_backslashes; + ++p; + } + if (p == arg.end() || *p == '"') { + // Backslashes need to be escaped + num_backslashes *= 2; + } + while (num_backslashes--) { + cmdline.push_back('\\'); + } + } else { + cmdline.push_back(*p++); + } + } + + cmdline.push_back('"'); +} + +static std::string format_cmdline (const std::vector& command) +{ + std::string cmdline; + for (std::vector::const_iterator arg(command.begin()); arg != command.end(); ++arg) { + if (arg != command.begin()) { + cmdline.push_back(' '); + } + escape_cmdline_argument(cmdline, *arg); + } + return cmdline; +} + +static HANDLE spawn_command (const std::vector& command, HANDLE stdin_handle, HANDLE stdout_handle, HANDLE stderr_handle) +{ + PROCESS_INFORMATION proc_info; + ZeroMemory(&proc_info, sizeof(proc_info)); + + STARTUPINFO start_info; + ZeroMemory(&start_info, sizeof(start_info)); + + start_info.cb = sizeof(STARTUPINFO); + start_info.hStdInput = stdin_handle ? stdin_handle : GetStdHandle(STD_INPUT_HANDLE); + start_info.hStdOutput = stdout_handle ? stdout_handle : GetStdHandle(STD_OUTPUT_HANDLE); + start_info.hStdError = stderr_handle ? stderr_handle : GetStdHandle(STD_ERROR_HANDLE); + start_info.dwFlags |= STARTF_USESTDHANDLES; + + std::string cmdline(format_cmdline(command)); + + if (!CreateProcessA(NULL, // application name (NULL to use command line) + const_cast(cmdline.c_str()), + NULL, // process security attributes + NULL, // primary thread security attributes + TRUE, // handles are inherited + 0, // creation flags + NULL, // use parent's environment + NULL, // use parent's current directory + &start_info, + &proc_info)) { + throw System_error("CreateProcess", cmdline, GetLastError()); + } + + CloseHandle(proc_info.hThread); + + return proc_info.hProcess; +} + + +Coprocess::Coprocess () +{ + proc_handle = NULL; + stdin_pipe_reader = NULL; + stdin_pipe_writer = NULL; + stdin_pipe_ostream = NULL; + stdout_pipe_reader = NULL; + stdout_pipe_writer = NULL; + stdout_pipe_istream = NULL; +} + +Coprocess::~Coprocess () +{ + close_stdin(); + close_stdout(); + if (proc_handle) { + CloseHandle(proc_handle); + } +} + +std::ostream* Coprocess::stdin_pipe () +{ + if (!stdin_pipe_ostream) { + SECURITY_ATTRIBUTES sec_attr; + + // Set the bInheritHandle flag so pipe handles are inherited. + sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); + sec_attr.bInheritHandle = TRUE; + sec_attr.lpSecurityDescriptor = NULL; + + // Create a pipe for the child process's STDIN. + if (!CreatePipe(&stdin_pipe_reader, &stdin_pipe_writer, &sec_attr, 0)) { + throw System_error("CreatePipe", "", GetLastError()); + } + + // Ensure the write handle to the pipe for STDIN is not inherited. + if (!SetHandleInformation(stdin_pipe_writer, HANDLE_FLAG_INHERIT, 0)) { + throw System_error("SetHandleInformation", "", GetLastError()); + } + + stdin_pipe_ostream = new ofhstream(this, write_stdin); + } + return stdin_pipe_ostream; +} + +void Coprocess::close_stdin () +{ + delete stdin_pipe_ostream; + stdin_pipe_ostream = NULL; + if (stdin_pipe_writer) { + CloseHandle(stdin_pipe_writer); + stdin_pipe_writer = NULL; + } + if (stdin_pipe_reader) { + CloseHandle(stdin_pipe_reader); + stdin_pipe_reader = NULL; + } +} + +std::istream* Coprocess::stdout_pipe () +{ + if (!stdout_pipe_istream) { + SECURITY_ATTRIBUTES sec_attr; + + // Set the bInheritHandle flag so pipe handles are inherited. + sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); + sec_attr.bInheritHandle = TRUE; + sec_attr.lpSecurityDescriptor = NULL; + + // Create a pipe for the child process's STDOUT. + if (!CreatePipe(&stdout_pipe_reader, &stdout_pipe_writer, &sec_attr, 0)) { + throw System_error("CreatePipe", "", GetLastError()); + } + + // Ensure the read handle to the pipe for STDOUT is not inherited. + if (!SetHandleInformation(stdout_pipe_reader, HANDLE_FLAG_INHERIT, 0)) { + throw System_error("SetHandleInformation", "", GetLastError()); + } + + stdout_pipe_istream = new ifhstream(this, read_stdout); + } + return stdout_pipe_istream; +} + +void Coprocess::close_stdout () +{ + delete stdout_pipe_istream; + stdout_pipe_istream = NULL; + if (stdout_pipe_writer) { + CloseHandle(stdout_pipe_writer); + stdout_pipe_writer = NULL; + } + if (stdout_pipe_reader) { + CloseHandle(stdout_pipe_reader); + stdout_pipe_reader = NULL; + } +} + +void Coprocess::spawn (const std::vector& args) +{ + proc_handle = spawn_command(args, stdin_pipe_reader, stdout_pipe_writer, NULL); + if (stdin_pipe_reader) { + CloseHandle(stdin_pipe_reader); + stdin_pipe_reader = NULL; + } + if (stdout_pipe_writer) { + CloseHandle(stdout_pipe_writer); + stdout_pipe_writer = NULL; + } +} + +int Coprocess::wait () +{ + if (WaitForSingleObject(proc_handle, INFINITE) == WAIT_FAILED) { + throw System_error("WaitForSingleObject", "", GetLastError()); + } + + DWORD exit_code; + if (!GetExitCodeProcess(proc_handle, &exit_code)) { + throw System_error("GetExitCodeProcess", "", GetLastError()); + } + + return exit_code; +} + +size_t Coprocess::write_stdin (void* handle, const void* buf, size_t count) +{ + DWORD bytes_written; + if (!WriteFile(static_cast(handle)->stdin_pipe_writer, buf, count, &bytes_written, NULL)) { + throw System_error("WriteFile", "", GetLastError()); + } + return bytes_written; +} + +size_t Coprocess::read_stdout (void* handle, void* buf, size_t count) +{ + // Note that ReadFile on a pipe may return with bytes_read==0 if the other + // end of the pipe writes zero bytes, so retry when this happens. + // When the other end of the pipe actually closes, ReadFile + // fails with ERROR_BROKEN_PIPE. + DWORD bytes_read; + do { + if (!ReadFile(static_cast(handle)->stdout_pipe_reader, buf, count, &bytes_read, NULL)) { + const DWORD read_error = GetLastError(); + if (read_error != ERROR_BROKEN_PIPE) { + throw System_error("ReadFile", "", read_error); + } + return 0; + } + } while (bytes_read == 0); + return bytes_read; +} diff --git a/coprocess-win32.hpp b/coprocess-win32.hpp new file mode 100644 index 0000000..532728d --- /dev/null +++ b/coprocess-win32.hpp @@ -0,0 +1,68 @@ +/* + * Copyright 2015 Andrew Ayer + * + * This file is part of git-crypt. + * + * git-crypt is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * git-crypt is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with git-crypt. If not, see . + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify the Program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, the licensors of the Program + * grant you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef GIT_CRYPT_COPROCESS_HPP +#define GIT_CRYPT_COPROCESS_HPP + +#include "fhstream.hpp" +#include +#include + +class Coprocess { + HANDLE proc_handle; + + HANDLE stdin_pipe_reader; + HANDLE stdin_pipe_writer; + ofhstream* stdin_pipe_ostream; + static size_t write_stdin (void*, const void*, size_t); + + HANDLE stdout_pipe_reader; + HANDLE stdout_pipe_writer; + ifhstream* stdout_pipe_istream; + static size_t read_stdout (void*, void*, size_t); + + Coprocess (const Coprocess&); // Disallow copy + Coprocess& operator= (const Coprocess&); // Disallow assignment +public: + Coprocess (); + ~Coprocess (); + + std::ostream* stdin_pipe (); + void close_stdin (); + + std::istream* stdout_pipe (); + void close_stdout (); + + void spawn (const std::vector&); + + int wait (); +}; + +#endif diff --git a/coprocess.cpp b/coprocess.cpp new file mode 100644 index 0000000..813cc2a --- /dev/null +++ b/coprocess.cpp @@ -0,0 +1,5 @@ +#ifdef _WIN32 +#include "coprocess-win32.cpp" +#else +#include "coprocess-unix.cpp" +#endif diff --git a/coprocess.hpp b/coprocess.hpp new file mode 100644 index 0000000..f8ac370 --- /dev/null +++ b/coprocess.hpp @@ -0,0 +1,5 @@ +#ifdef _WIN32 +#include "coprocess-win32.hpp" +#else +#include "coprocess-unix.hpp" +#endif diff --git a/fhstream.cpp b/fhstream.cpp new file mode 100644 index 0000000..8c8fc79 --- /dev/null +++ b/fhstream.cpp @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2012, 2015 Andrew Ayer + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Except as contained in this notice, the name(s) of the above copyright + * holders shall not be used in advertising or otherwise to promote the + * sale, use or other dealings in this Software without prior written + * authorization. + */ + +#include +#include // for std::min + +#include "fhstream.hpp" + +/* + * ofhstream + */ + +ofhbuf::ofhbuf (void* arg_handle, size_t (*arg_write_fun)(void*, const void*, size_t)) +: handle(arg_handle), + write_fun(arg_write_fun), + buffer(new char[default_buffer_size]), + buffer_size(default_buffer_size) +{ + reset_buffer(); +} + +ofhbuf::~ofhbuf () +{ + if (handle) { + try { + sync(); + } catch (...) { + // Ignore exception since we're in the destructor. + // To catch write errors, call sync() explicitly. + } + } + delete[] buffer; +} + +ofhbuf::int_type ofhbuf::overflow (ofhbuf::int_type c) +{ + const char* p = pbase(); + std::streamsize bytes_to_write = pptr() - p; + + if (!is_eof(c)) { + *pptr() = c; + ++bytes_to_write; + } + + while (bytes_to_write > 0) { + const size_t bytes_written = write_fun(handle, p, bytes_to_write); + bytes_to_write -= bytes_written; + p += bytes_written; + } + + reset_buffer(); + + return traits_type::to_int_type(0); +} + +int ofhbuf::sync () +{ + return !is_eof(overflow(traits_type::eof())) ? 0 : -1; +} + +std::streamsize ofhbuf::xsputn (const char* s, std::streamsize n) +{ + // Use heuristic to decide whether to write directly or just use buffer + // Write directly only if n >= MIN(4096, available buffer capacity) + // (this is similar to what basic_filebuf does) + + if (n < std::min(4096, epptr() - pptr())) { + // Not worth it to do a direct write + return std::streambuf::xsputn(s, n); + } + + // Before we can do a direct write of this string, we need to flush + // out the current contents of the buffer. + if (pbase() != pptr()) { + overflow(traits_type::eof()); // throws an exception or it succeeds + } + + // Now we can go ahead and write out the string. + size_t bytes_to_write = n; + + while (bytes_to_write > 0) { + const size_t bytes_written = write_fun(handle, s, bytes_to_write); + bytes_to_write -= bytes_written; + s += bytes_written; + } + + return n; // Return the total bytes written +} + +std::streambuf* ofhbuf::setbuf (char* s, std::streamsize n) +{ + if (s == 0 && n == 0) { + // Switch to unbuffered + // This won't take effect until the next overflow or sync + // (We defer it taking effect so that write errors can be properly reported) + // To cause it to take effect as soon as possible, we artificially reduce the + // size of the buffer so it has no space left. This will trigger an overflow + // on the next put. + std::streambuf::setp(pbase(), pptr()); + std::streambuf::pbump(pptr() - pbase()); + buffer_size = 1; + } + return this; +} + + + +/* + * ifhstream + */ + +ifhbuf::ifhbuf (void* arg_handle, size_t (*arg_read_fun)(void*, void*, size_t)) +: handle(arg_handle), + read_fun(arg_read_fun), + buffer(new char[default_buffer_size + putback_size]), + buffer_size(default_buffer_size) +{ + reset_buffer(0, 0); +} + +ifhbuf::~ifhbuf () +{ + delete[] buffer; +} + +ifhbuf::int_type ifhbuf::underflow () +{ + if (gptr() >= egptr()) { // A true underflow (no bytes in buffer left to read) + + // Move the putback_size most-recently-read characters into the putback area + size_t nputback = std::min(gptr() - eback(), putback_size); + std::memmove(buffer + (putback_size - nputback), gptr() - nputback, nputback); + + // Now read new characters from the file descriptor + const size_t nread = read_fun(handle, buffer + putback_size, buffer_size); + if (nread == 0) { + // EOF + return traits_type::eof(); + } + + // Reset the buffer + reset_buffer(nputback, nread); + } + + // Return the next character + return traits_type::to_int_type(*gptr()); +} + +std::streamsize ifhbuf::xsgetn (char* s, std::streamsize n) +{ + // Use heuristic to decide whether to read directly + // Read directly only if n >= bytes_available + 4096 + + std::streamsize bytes_available = egptr() - gptr(); + + if (n < bytes_available + 4096) { + // Not worth it to do a direct read + return std::streambuf::xsgetn(s, n); + } + + std::streamsize total_bytes_read = 0; + + // First, copy out the bytes currently in the buffer + std::memcpy(s, gptr(), bytes_available); + + s += bytes_available; + n -= bytes_available; + total_bytes_read += bytes_available; + + // Now do the direct read + while (n > 0) { + const size_t bytes_read = read_fun(handle, s, n); + if (bytes_read == 0) { + // EOF + break; + } + + s += bytes_read; + n -= bytes_read; + total_bytes_read += bytes_read; + } + + // Fill up the putback area with the most recently read characters + size_t nputback = std::min(total_bytes_read, putback_size); + std::memcpy(buffer + (putback_size - nputback), s - nputback, nputback); + + // Reset the buffer with no bytes available for reading, but with some putback characters + reset_buffer(nputback, 0); + + // Return the total number of bytes read + return total_bytes_read; +} + +std::streambuf* ifhbuf::setbuf (char* s, std::streamsize n) +{ + if (s == 0 && n == 0) { + // Switch to unbuffered + // This won't take effect until the next underflow (we don't want to + // lose what's currently in the buffer!) + buffer_size = 1; + } + return this; +} diff --git a/fhstream.hpp b/fhstream.hpp new file mode 100644 index 0000000..28bb1c7 --- /dev/null +++ b/fhstream.hpp @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2012, 2015 Andrew Ayer + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Except as contained in this notice, the name(s) of the above copyright + * holders shall not be used in advertising or otherwise to promote the + * sale, use or other dealings in this Software without prior written + * authorization. + */ + +#ifndef GIT_CRYPT_FHSTREAM_HPP +#define GIT_CRYPT_FHSTREAM_HPP + +#include +#include +#include + +/* + * ofhstream + */ +class ofhbuf : public std::streambuf { + enum { default_buffer_size = 8192 }; + + void* handle; + size_t (*write_fun)(void*, const void*, size_t); + char* buffer; + size_t buffer_size; + + inline void reset_buffer () + { + std::streambuf::setp(buffer, buffer + buffer_size - 1); + } + static inline bool is_eof (int_type ch) { return traits_type::eq_int_type(ch, traits_type::eof()); } + + // Disallow copy +#if __cplusplus >= 201103L /* C++11 */ + ofhbuf (const ofhbuf&) = delete; + ofhbuf& operator= (const ofhbuf&) = delete; +#else + ofhbuf (const ofhbuf&); + ofhbuf& operator= (const ofhbuf&); +#endif + +protected: + virtual int_type overflow (int_type ch =traits_type::eof()); + virtual int sync (); + virtual std::streamsize xsputn (const char*, std::streamsize); + virtual std::streambuf* setbuf (char*, std::streamsize); + +public: + ofhbuf (void*, size_t (*)(void*, const void*, size_t)); + ~ofhbuf (); // WARNING: calls sync() and ignores exceptions +}; + +class ofhstream : public std::ostream { + mutable ofhbuf buf; +public: + ofhstream (void* handle, size_t (*write_fun)(void*, const void*, size_t)) + : std::ostream(0), buf(handle, write_fun) + { + std::ostream::rdbuf(&buf); + } + + ofhbuf* rdbuf () const { return &buf; } +}; + + +/* + * ifhstream + */ +class ifhbuf : public std::streambuf { + enum { + default_buffer_size = 8192, + putback_size = 4 + }; + + void* handle; + size_t (*read_fun)(void*, void*, size_t); + char* buffer; + size_t buffer_size; + + inline void reset_buffer (size_t nputback, size_t nread) + { + std::streambuf::setg(buffer + (putback_size - nputback), buffer + putback_size, buffer + putback_size + nread); + } + // Disallow copy +#if __cplusplus >= 201103L /* C++11 */ + ifhbuf (const ifhbuf&) = delete; + ifhbuf& operator= (const ifhbuf&) = delete; +#else + ifhbuf (const ifhbuf&); + ifhbuf& operator= (const ifhbuf&); +#endif + +protected: + virtual int_type underflow (); + virtual std::streamsize xsgetn (char*, std::streamsize); + virtual std::streambuf* setbuf (char*, std::streamsize); + +public: + ifhbuf (void*, size_t (*)(void*, void*, size_t)); + ~ifhbuf (); // Can't fail +}; + +class ifhstream : public std::istream { + mutable ifhbuf buf; +public: + explicit ifhstream (void* handle, size_t (*read_fun)(void*, void*, size_t)) + : std::istream(0), buf(handle, read_fun) + { + std::istream::rdbuf(&buf); + } + + ifhbuf* rdbuf () const { return &buf; } +}; + +#endif diff --git a/util-unix.cpp b/util-unix.cpp index 4eed56d..7c7c05b 100644 --- a/util-unix.cpp +++ b/util-unix.cpp @@ -162,119 +162,6 @@ std::string our_exe_path () } } -static int execvp (const std::string& file, const std::vector& args) -{ - std::vector args_c_str; - args_c_str.reserve(args.size()); - for (std::vector::const_iterator arg(args.begin()); arg != args.end(); ++arg) { - args_c_str.push_back(arg->c_str()); - } - args_c_str.push_back(NULL); - return execvp(file.c_str(), const_cast(&args_c_str[0])); -} - -int exec_command (const std::vector& command) -{ - pid_t child = fork(); - if (child == -1) { - throw System_error("fork", "", errno); - } - if (child == 0) { - execvp(command[0], command); - perror(command[0].c_str()); - _exit(-1); - } - int status = 0; - if (waitpid(child, &status, 0) == -1) { - throw System_error("waitpid", "", errno); - } - return status; -} - -int exec_command (const std::vector& command, std::ostream& output) -{ - int pipefd[2]; - if (pipe(pipefd) == -1) { - throw System_error("pipe", "", errno); - } - pid_t child = fork(); - if (child == -1) { - int fork_errno = errno; - close(pipefd[0]); - close(pipefd[1]); - throw System_error("fork", "", fork_errno); - } - if (child == 0) { - close(pipefd[0]); - if (pipefd[1] != 1) { - dup2(pipefd[1], 1); - close(pipefd[1]); - } - execvp(command[0], command); - perror(command[0].c_str()); - _exit(-1); - } - close(pipefd[1]); - char buffer[1024]; - ssize_t bytes_read; - while ((bytes_read = read(pipefd[0], buffer, sizeof(buffer))) > 0) { - output.write(buffer, bytes_read); - } - if (bytes_read == -1) { - int read_errno = errno; - close(pipefd[0]); - throw System_error("read", "", read_errno); - } - close(pipefd[0]); - int status = 0; - if (waitpid(child, &status, 0) == -1) { - throw System_error("waitpid", "", errno); - } - return status; -} - -int exec_command_with_input (const std::vector& command, const char* p, size_t len) -{ - int pipefd[2]; - if (pipe(pipefd) == -1) { - throw System_error("pipe", "", errno); - } - pid_t child = fork(); - if (child == -1) { - int fork_errno = errno; - close(pipefd[0]); - close(pipefd[1]); - throw System_error("fork", "", fork_errno); - } - if (child == 0) { - close(pipefd[1]); - if (pipefd[0] != 0) { - dup2(pipefd[0], 0); - close(pipefd[0]); - } - execvp(command[0], command); - perror(command[0].c_str()); - _exit(-1); - } - close(pipefd[0]); - while (len > 0) { - ssize_t bytes_written = write(pipefd[1], p, len); - if (bytes_written == -1) { - int write_errno = errno; - close(pipefd[1]); - throw System_error("write", "", write_errno); - } - p += bytes_written; - len -= bytes_written; - } - close(pipefd[1]); - int status = 0; - if (waitpid(child, &status, 0) == -1) { - throw System_error("waitpid", "", errno); - } - return status; -} - int exit_status (int wait_status) { return wait_status != -1 && WIFEXITED(wait_status) ? WEXITSTATUS(wait_status) : -1; diff --git a/util-win32.cpp b/util-win32.cpp index ceda202..445d185 100644 --- a/util-win32.cpp +++ b/util-win32.cpp @@ -125,197 +125,6 @@ std::string our_exe_path () return std::string(buffer.begin(), buffer.begin() + len); } -static void escape_cmdline_argument (std::string& cmdline, const std::string& arg) -{ - // For an explanation of Win32's arcane argument quoting rules, see: - // http://msdn.microsoft.com/en-us/library/17w5ykft%28v=vs.85%29.aspx - // http://msdn.microsoft.com/en-us/library/bb776391%28v=vs.85%29.aspx - // http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx - // http://blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx - cmdline.push_back('"'); - - std::string::const_iterator p(arg.begin()); - while (p != arg.end()) { - if (*p == '"') { - cmdline.push_back('\\'); - cmdline.push_back('"'); - ++p; - } else if (*p == '\\') { - unsigned int num_backslashes = 0; - while (p != arg.end() && *p == '\\') { - ++num_backslashes; - ++p; - } - if (p == arg.end() || *p == '"') { - // Backslashes need to be escaped - num_backslashes *= 2; - } - while (num_backslashes--) { - cmdline.push_back('\\'); - } - } else { - cmdline.push_back(*p++); - } - } - - cmdline.push_back('"'); -} - -static std::string format_cmdline (const std::vector& command) -{ - std::string cmdline; - for (std::vector::const_iterator arg(command.begin()); arg != command.end(); ++arg) { - if (arg != command.begin()) { - cmdline.push_back(' '); - } - escape_cmdline_argument(cmdline, *arg); - } - return cmdline; -} - -static int wait_for_child (HANDLE child_handle) -{ - if (WaitForSingleObject(child_handle, INFINITE) == WAIT_FAILED) { - throw System_error("WaitForSingleObject", "", GetLastError()); - } - - DWORD exit_code; - if (!GetExitCodeProcess(child_handle, &exit_code)) { - throw System_error("GetExitCodeProcess", "", GetLastError()); - } - - return exit_code; -} - -static HANDLE spawn_command (const std::vector& command, HANDLE stdin_handle, HANDLE stdout_handle, HANDLE stderr_handle) -{ - PROCESS_INFORMATION proc_info; - ZeroMemory(&proc_info, sizeof(proc_info)); - - STARTUPINFO start_info; - ZeroMemory(&start_info, sizeof(start_info)); - - start_info.cb = sizeof(STARTUPINFO); - start_info.hStdInput = stdin_handle ? stdin_handle : GetStdHandle(STD_INPUT_HANDLE); - start_info.hStdOutput = stdout_handle ? stdout_handle : GetStdHandle(STD_OUTPUT_HANDLE); - start_info.hStdError = stderr_handle ? stderr_handle : GetStdHandle(STD_ERROR_HANDLE); - start_info.dwFlags |= STARTF_USESTDHANDLES; - - std::string cmdline(format_cmdline(command)); - - if (!CreateProcessA(NULL, // application name (NULL to use command line) - const_cast(cmdline.c_str()), - NULL, // process security attributes - NULL, // primary thread security attributes - TRUE, // handles are inherited - 0, // creation flags - NULL, // use parent's environment - NULL, // use parent's current directory - &start_info, - &proc_info)) { - throw System_error("CreateProcess", cmdline, GetLastError()); - } - - CloseHandle(proc_info.hThread); - - return proc_info.hProcess; -} - -int exec_command (const std::vector& command) -{ - HANDLE child_handle = spawn_command(command, NULL, NULL, NULL); - int exit_code = wait_for_child(child_handle); - CloseHandle(child_handle); - return exit_code; -} - -int exec_command (const std::vector& command, std::ostream& output) -{ - HANDLE stdout_pipe_reader = NULL; - HANDLE stdout_pipe_writer = NULL; - SECURITY_ATTRIBUTES sec_attr; - - // Set the bInheritHandle flag so pipe handles are inherited. - sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); - sec_attr.bInheritHandle = TRUE; - sec_attr.lpSecurityDescriptor = NULL; - - // Create a pipe for the child process's STDOUT. - if (!CreatePipe(&stdout_pipe_reader, &stdout_pipe_writer, &sec_attr, 0)) { - throw System_error("CreatePipe", "", GetLastError()); - } - - // Ensure the read handle to the pipe for STDOUT is not inherited. - if (!SetHandleInformation(stdout_pipe_reader, HANDLE_FLAG_INHERIT, 0)) { - throw System_error("SetHandleInformation", "", GetLastError()); - } - - HANDLE child_handle = spawn_command(command, NULL, stdout_pipe_writer, NULL); - CloseHandle(stdout_pipe_writer); - - // Read from stdout_pipe_reader. - // Note that ReadFile on a pipe may return with bytes_read==0 if the other - // end of the pipe writes zero bytes, so don't break out of the read loop - // when this happens. When the other end of the pipe closes, ReadFile - // fails with ERROR_BROKEN_PIPE. - char buffer[1024]; - DWORD bytes_read; - while (ReadFile(stdout_pipe_reader, buffer, sizeof(buffer), &bytes_read, NULL)) { - output.write(buffer, bytes_read); - } - const DWORD read_error = GetLastError(); - if (read_error != ERROR_BROKEN_PIPE) { - throw System_error("ReadFile", "", read_error); - } - - CloseHandle(stdout_pipe_reader); - - int exit_code = wait_for_child(child_handle); - CloseHandle(child_handle); - return exit_code; -} - -int exec_command_with_input (const std::vector& command, const char* p, size_t len) -{ - HANDLE stdin_pipe_reader = NULL; - HANDLE stdin_pipe_writer = NULL; - SECURITY_ATTRIBUTES sec_attr; - - // Set the bInheritHandle flag so pipe handles are inherited. - sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); - sec_attr.bInheritHandle = TRUE; - sec_attr.lpSecurityDescriptor = NULL; - - // Create a pipe for the child process's STDIN. - if (!CreatePipe(&stdin_pipe_reader, &stdin_pipe_writer, &sec_attr, 0)) { - throw System_error("CreatePipe", "", GetLastError()); - } - - // Ensure the write handle to the pipe for STDIN is not inherited. - if (!SetHandleInformation(stdin_pipe_writer, HANDLE_FLAG_INHERIT, 0)) { - throw System_error("SetHandleInformation", "", GetLastError()); - } - - HANDLE child_handle = spawn_command(command, stdin_pipe_reader, NULL, NULL); - CloseHandle(stdin_pipe_reader); - - // Write to stdin_pipe_writer. - while (len > 0) { - DWORD bytes_written; - if (!WriteFile(stdin_pipe_writer, p, len, &bytes_written, NULL)) { - throw System_error("WriteFile", "", GetLastError()); - } - p += bytes_written; - len -= bytes_written; - } - - CloseHandle(stdin_pipe_writer); - - int exit_code = wait_for_child(child_handle); - CloseHandle(child_handle); - return exit_code; -} - int exit_status (int status) { return status; diff --git a/util.cpp b/util.cpp index 2da0622..4e1aa78 100644 --- a/util.cpp +++ b/util.cpp @@ -30,9 +30,36 @@ #include "git-crypt.hpp" #include "util.hpp" +#include "coprocess.hpp" #include #include +int exec_command (const std::vector& args) +{ + Coprocess proc; + proc.spawn(args); + return proc.wait(); +} + +int exec_command (const std::vector& args, std::ostream& output) +{ + Coprocess proc; + std::istream* proc_stdout = proc.stdout_pipe(); + proc.spawn(args); + output << proc_stdout->rdbuf(); + return proc.wait(); +} + +int exec_command_with_input (const std::vector& args, const char* p, size_t len) +{ + Coprocess proc; + std::ostream* proc_stdin = proc.stdin_pipe(); + proc.spawn(args); + proc_stdin->write(p, len); + proc.close_stdin(); + return proc.wait(); +} + std::string escape_shell_arg (const std::string& str) { std::string new_str;