diff --git a/Makefile b/Makefile index a336360..2de93f9 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,8 @@ all: git-crypt git-crypt: $(OBJFILES) $(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) +util.o: util.cpp util-unix.cpp util-win32.cpp + clean: rm -f *.o git-crypt diff --git a/commands.cpp b/commands.cpp index 7c859a7..1cc47ef 100644 --- a/commands.cpp +++ b/commands.cpp @@ -33,8 +33,6 @@ #include "util.hpp" #include "key.hpp" #include "gpg.hpp" -#include -#include #include #include #include @@ -202,7 +200,7 @@ int clean (int argc, char** argv) Hmac_sha1_state hmac(key->hmac_key, HMAC_KEY_LEN); // Calculate the file's SHA1 HMAC as we go uint64_t file_size = 0; // Keep track of the length, make sure it doesn't get too big std::string file_contents; // First 8MB or so of the file go here - std::fstream temp_file; // The rest of the file spills into a temporary file on disk + temp_fstream temp_file; // The rest of the file spills into a temporary file on disk temp_file.exceptions(std::fstream::badbit); char buffer[1024]; @@ -219,7 +217,7 @@ int clean (int argc, char** argv) file_contents.append(buffer, bytes_read); } else { if (!temp_file.is_open()) { - open_tempfile(temp_file, std::fstream::in | std::fstream::out | std::fstream::binary | std::fstream::app); + temp_file.open(std::fstream::in | std::fstream::out | std::fstream::binary | std::fstream::app); } temp_file.write(buffer, bytes_read); } diff --git a/git-crypt.cpp b/git-crypt.cpp index 8bed1a0..aaf27fb 100644 --- a/git-crypt.cpp +++ b/git-crypt.cpp @@ -82,13 +82,7 @@ try { * General initialization */ - // The following two lines are essential for achieving good performance: - std::ios_base::sync_with_stdio(false); - std::cin.tie(0); - - std::cin.exceptions(std::ios_base::badbit); - std::cout.exceptions(std::ios_base::badbit); - + init_std_streams(); ERR_load_crypto_strings(); /* @@ -186,11 +180,7 @@ try { std::cerr << "git-crypt: GPG error: " << e.message << std::endl; return 1; } catch (const System_error& e) { - std::cerr << "git-crypt: " << e.action << ": "; - if (!e.target.empty()) { - std::cerr << e.target << ": "; - } - std::cerr << strerror(e.error) << std::endl; + std::cerr << "git-crypt: System error: " << e.message() << std::endl; return 1; } catch (const Crypto_error& e) { std::cerr << "git-crypt: Crypto error: " << e.where << ": " << e.message << std::endl; diff --git a/key.cpp b/key.cpp index 2fea653..508ff8d 100644 --- a/key.cpp +++ b/key.cpp @@ -33,6 +33,7 @@ #include "crypto.hpp" #include #include +#include #include #include #include @@ -133,7 +134,7 @@ bool Key_file::load_from_file (const char* key_file_name) bool Key_file::store_to_file (const char* key_file_name) const { - mode_t old_umask = umask(0077); // make sure key file is protected + mode_t old_umask = umask(0077); // make sure key file is protected (TODO: Windows compat) std::ofstream key_file_out(key_file_name, std::fstream::binary); umask(old_umask); if (!key_file_out) { diff --git a/util-unix.cpp b/util-unix.cpp new file mode 100644 index 0000000..214f501 --- /dev/null +++ b/util-unix.cpp @@ -0,0 +1,250 @@ +/* + * Copyright 2012, 2014 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +std::string System_error::message () const +{ + std::string mesg(action); + if (!target.empty()) { + mesg += ": "; + mesg += target; + } + if (error) { + mesg += ": "; + mesg += strerror(error); + } + return mesg; +} + +void temp_fstream::open (std::ios_base::openmode mode) +{ + close(); + + const char* tmpdir = getenv("TMPDIR"); + size_t tmpdir_len = tmpdir ? std::strlen(tmpdir) : 0; + if (tmpdir_len == 0 || tmpdir_len > 4096) { + // no $TMPDIR or it's excessively long => fall back to /tmp + tmpdir = "/tmp"; + tmpdir_len = 4; + } + std::vector path_buffer(tmpdir_len + 18); + char* path = &path_buffer[0]; + std::strcpy(path, tmpdir); + std::strcpy(path + tmpdir_len, "/git-crypt.XXXXXX"); + mode_t old_umask = umask(0077); + int fd = mkstemp(path); + if (fd == -1) { + int mkstemp_errno = errno; + umask(old_umask); + throw System_error("mkstemp", "", mkstemp_errno); + } + umask(old_umask); + std::fstream::open(path, mode); + if (!std::fstream::is_open()) { + unlink(path); + ::close(fd); + throw System_error("std::fstream::open", path, 0); + } + unlink(path); + ::close(fd); +} + +void temp_fstream::close () +{ + if (std::fstream::is_open()) { + std::fstream::close(); + } +} + +void mkdir_parent (const std::string& path) +{ + std::string::size_type slash(path.find('/', 1)); + while (slash != std::string::npos) { + std::string prefix(path.substr(0, slash)); + struct stat status; + if (stat(prefix.c_str(), &status) == 0) { + // already exists - make sure it's a directory + if (!S_ISDIR(status.st_mode)) { + throw System_error("mkdir_parent", prefix, ENOTDIR); + } + } else { + if (errno != ENOENT) { + throw System_error("mkdir_parent", prefix, errno); + } + // doesn't exist - mkdir it + if (mkdir(prefix.c_str(), 0777) == -1) { + throw System_error("mkdir", prefix, errno); + } + } + + slash = path.find('/', slash + 1); + } +} + +static std::string readlink (const char* pathname) +{ + std::vector buffer(64); + ssize_t len; + + while ((len = ::readlink(pathname, &buffer[0], buffer.size())) == static_cast(buffer.size())) { + // buffer may have been truncated - grow and try again + buffer.resize(buffer.size() * 2); + } + if (len == -1) { + throw System_error("readlink", pathname, errno); + } + + return std::string(buffer.begin(), buffer.begin() + len); +} + +std::string our_exe_path () +{ + try { + return readlink("/proc/self/exe"); + } catch (const System_error&) { + if (argv0[0] == '/') { + // argv[0] starts with / => it's an absolute path + return argv0; + } else if (std::strchr(argv0, '/')) { + // argv[0] contains / => it a relative path that should be resolved + char* resolved_path_p = realpath(argv0, NULL); + std::string resolved_path(resolved_path_p); + free(resolved_path_p); + return resolved_path; + } else { + // argv[0] is just a bare filename => not much we can do + return argv0; + } + } +} + +int exec_command (const char* 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]); + } + execl("/bin/sh", "sh", "-c", command, NULL); + perror("/bin/sh"); + _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 char* 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]); + } + execl("/bin/sh", "sh", "-c", command, NULL); + perror("/bin/sh"); + _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; +} + +bool successful_exit (int status) +{ + return status != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0; +} + +static void init_std_streams_platform () +{ +} diff --git a/util-win32.cpp b/util-win32.cpp new file mode 100644 index 0000000..d34e635 --- /dev/null +++ b/util-win32.cpp @@ -0,0 +1,124 @@ +/* + * Copyright 2014 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 +#include +#include +#include + +std::string System_error::message () const +{ + std::string mesg(action); + if (!target.empty()) { + mesg += ": "; + mesg += target; + } + if (error) { + // TODO: use FormatMessage() + } + return mesg; +} + +void temp_fstream::open (std::ios_base::openmode mode) +{ + close(); + + char tmpdir[MAX_PATH + 1]; + + DWORD ret = GetTempPath(sizeof(tmpdir), tmpdir); + if (ret == 0) { + throw System_error("GetTempPath", "", GetLastError()); + } else if (ret > sizeof(tmpdir) - 1) { + throw System_error("GetTempPath", "", ERROR_BUFFER_OVERFLOW); + } + + char tmpfilename[MAX_PATH + 1]; + if (GetTempFileName(tmpdir, TEXT("git-crypt"), 0, tmpfilename) == 0) { + throw System_error("GetTempFileName", "", GetLastError()); + } + + filename = tmpfilename; + + std::fstream::open(filename.c_str(), mode); + if (!std::fstream::is_open()) { + DeleteFile(filename.c_str()); + throw System_error("std::fstream::open", filename, 0); + } +} + +void temp_fstream::close () +{ + if (std::fstream::is_open()) { + std::fstream::close(); + DeleteFile(filename.c_str()); + } +} + +void mkdir_parent (const std::string& path) +{ + std::string::size_type slash(path.find('/', 1)); + while (slash != std::string::npos) { + std::string prefix(path.substr(0, slash)); + if (GetFileAttributes(prefix.c_str()) == INVALID_FILE_ATTRIBUTES) { + // prefix does not exist, so try to create it + if (!CreateDirectory(prefix.c_str(), NULL)) { + throw System_error("CreateDirectory", prefix, GetLastError()); + } + } + + slash = path.find('/', slash + 1); + } +} + +std::string our_exe_path () // TODO +{ + return argv0; +} + +int exec_command (const char* command, std::ostream& output) // TODO +{ + return -1; +} + +int exec_command_with_input (const char* command, const char* p, size_t len) // TODO +{ + return -1; +} + +bool successful_exit (int status) // TODO +{ + return status == 0; +} + +static void init_std_streams_platform () +{ + _setmode(_fileno(stdin), _O_BINARY); + _setmode(_fileno(stdout), _O_BINARY); +} diff --git a/util.cpp b/util.cpp index cd1c514..84e8253 100644 --- a/util.cpp +++ b/util.cpp @@ -31,198 +31,7 @@ #include "git-crypt.hpp" #include "util.hpp" #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -void mkdir_parent (const std::string& path) -{ - std::string::size_type slash(path.find('/', 1)); - while (slash != std::string::npos) { - std::string prefix(path.substr(0, slash)); - struct stat status; - if (stat(prefix.c_str(), &status) == 0) { - // already exists - make sure it's a directory - if (!S_ISDIR(status.st_mode)) { - throw System_error("mkdir_parent", prefix, ENOTDIR); - } - } else { - if (errno != ENOENT) { - throw System_error("mkdir_parent", prefix, errno); - } - // doesn't exist - mkdir it - if (mkdir(prefix.c_str(), 0777) == -1) { - throw System_error("mkdir", prefix, errno); - } - } - - slash = path.find('/', slash + 1); - } -} - -std::string readlink (const char* pathname) -{ - std::vector buffer(64); - ssize_t len; - - while ((len = ::readlink(pathname, &buffer[0], buffer.size())) == static_cast(buffer.size())) { - // buffer may have been truncated - grow and try again - buffer.resize(buffer.size() * 2); - } - if (len == -1) { - throw System_error("readlink", pathname, errno); - } - - return std::string(buffer.begin(), buffer.begin() + len); -} - -std::string our_exe_path () -{ - try { - return readlink("/proc/self/exe"); - } catch (const System_error&) { - if (argv0[0] == '/') { - // argv[0] starts with / => it's an absolute path - return argv0; - } else if (std::strchr(argv0, '/')) { - // argv[0] contains / => it a relative path that should be resolved - char* resolved_path_p = realpath(argv0, NULL); - std::string resolved_path(resolved_path_p); - free(resolved_path_p); - return resolved_path; - } else { - // argv[0] is just a bare filename => not much we can do - return argv0; - } - } -} - -int exec_command (const char* 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]); - } - execl("/bin/sh", "sh", "-c", command, NULL); - perror("/bin/sh"); - _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 char* 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]); - } - execl("/bin/sh", "sh", "-c", command, NULL); - perror("/bin/sh"); - _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; -} - -bool successful_exit (int status) -{ - return status != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0; -} - -void open_tempfile (std::fstream& file, std::ios_base::openmode mode) -{ - const char* tmpdir = getenv("TMPDIR"); - size_t tmpdir_len = tmpdir ? std::strlen(tmpdir) : 0; - if (tmpdir_len == 0 || tmpdir_len > 4096) { - // no $TMPDIR or it's excessively long => fall back to /tmp - tmpdir = "/tmp"; - tmpdir_len = 4; - } - std::vector path_buffer(tmpdir_len + 18); - char* path = &path_buffer[0]; - std::strcpy(path, tmpdir); - std::strcpy(path + tmpdir_len, "/git-crypt.XXXXXX"); - mode_t old_umask = umask(0077); - int fd = mkstemp(path); - if (fd == -1) { - int mkstemp_errno = errno; - umask(old_umask); - throw System_error("mkstemp", "", mkstemp_errno); - } - umask(old_umask); - file.open(path, mode); - if (!file.is_open()) { - unlink(path); - close(fd); - throw System_error("std::fstream::open", path, 0); - } - unlink(path); - close(fd); -} +#include std::string escape_shell_arg (const std::string& str) { @@ -272,3 +81,22 @@ void write_be32 (std::ostream& out, uint32_t i) out.write(reinterpret_cast(buffer), 4); } +static void init_std_streams_platform (); // platform-specific initialization + +void init_std_streams () +{ + // The following two lines are essential for achieving good performance: + std::ios_base::sync_with_stdio(false); + std::cin.tie(0); + + std::cin.exceptions(std::ios_base::badbit); + std::cout.exceptions(std::ios_base::badbit); + + init_std_streams_platform(); +} + +#ifdef _WIN32 +#include "util-win32.cpp" +#else +#include "util-unix.cpp" +#endif diff --git a/util.hpp b/util.hpp index c86e53d..2637098 100644 --- a/util.hpp +++ b/util.hpp @@ -35,6 +35,7 @@ #include #include #include +#include struct System_error { std::string action; @@ -42,20 +43,30 @@ struct System_error { int error; System_error (const std::string& a, const std::string& t, int e) : action(a), target(t), error(e) { } + + std::string message () const; +}; + +class temp_fstream : public std::fstream { + std::string filename; +public: + ~temp_fstream () { close(); } + + void open (std::ios_base::openmode); + void close (); }; void mkdir_parent (const std::string& path); // Create parent directories of path, __but not path itself__ -std::string readlink (const char* pathname); std::string our_exe_path (); int exec_command (const char* command, std::ostream& output); int exec_command_with_input (const char* command, const char* p, size_t len); bool successful_exit (int status); -void open_tempfile (std::fstream&, std::ios_base::openmode); std::string escape_shell_arg (const std::string&); uint32_t load_be32 (const unsigned char*); void store_be32 (unsigned char*, uint32_t); bool read_be32 (std::istream& in, uint32_t&); void write_be32 (std::ostream& out, uint32_t); +void init_std_streams (); #endif