From 67a0e8d70f0caf8b85e0cf2031333236b2a3dcdf Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Sun, 24 Jan 2016 14:46:19 +0200 Subject: Add checksum verification --- bpkg/checksum.cxx | 385 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 385 insertions(+) create mode 100644 bpkg/checksum.cxx (limited to 'bpkg/checksum.cxx') diff --git a/bpkg/checksum.cxx b/bpkg/checksum.cxx new file mode 100644 index 0000000..95f2d80 --- /dev/null +++ b/bpkg/checksum.cxx @@ -0,0 +1,385 @@ +// file : bpkg/checksum.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include + +#include +#include +#include + +#include + +using namespace std; +using namespace butl; + +namespace bpkg +{ + // sha256 + // + static bool + check_sha256 (const path& prog) + { + // This one doesn't have --version or --help. Running it without any + // arguments causes it to calculate the sum of STDIN. But we can ask + // it to calculate a sum of an empty string. + // + const char* args[] = {prog.string ().c_str (), "-q", "-s", "", nullptr}; + + if (verb >= 3) + print_process (args); + + try + { + process pr (args, 0, -1, 1); // Redirect STDOUT and STDERR to a pipe. + + ifdstream is (pr.in_ofd); + string l; + getline (is, l); + + return + l == "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + && pr.wait (); + } + catch (const process_error& e) + { + if (e.child ()) + exit (1); + + return false; + } + } + + static process + start_sha256 (const path& prog, const strings& ops) + { + cstrings args {prog.string ().c_str (), "-q"}; + + for (const string& o: ops) + args.push_back (o.c_str ()); + + args.push_back (nullptr); + + if (verb >= 2) + print_process (args); + + // Pipe both STDIN and STDOUT. Process exceptions must be handled by + // the caller. + // + return process (args.data (), -1, -1); + } + + // sha256sum + // + static bool + check_sha256sum (const path& prog) + { + // sha256sum --version prints the version to STDOUT and exits with 0 + // status. The first line starts with "sha256sum (GNU coreutils) 8.21". + // + const char* args[] = {prog.string ().c_str (), "--version", nullptr}; + + if (verb >= 3) + print_process (args); + + try + { + process pr (args, 0, -1); // Redirect STDOUT to a pipe. + + ifdstream is (pr.in_ofd); + string l; + getline (is, l); + + return l.compare (0, 9, "sha256sum") == 0 && pr.wait (); + } + catch (const process_error& e) + { + if (e.child ()) + exit (1); + + return false; + } + } + + static process + start_sha256sum (const path& prog, const strings& ops) + { + cstrings args {prog.string ().c_str (), "-b"}; + + for (const string& o: ops) + args.push_back (o.c_str ()); + + args.push_back (nullptr); + + if (verb >= 2) + print_process (args); + + // Pipe both STDIN and STDOUT. Process exceptions must be handled by + // the caller. + // + return process (args.data (), -1, -1); + } + + // shasum + // + static bool + check_shasum (const path& prog) + { + // shasum --version prints just the version to STDOUT and exits with 0 + // status. The output looks like "5.84". + // + const char* args[] = {prog.string ().c_str (), "--version", nullptr}; + + if (verb >= 3) + print_process (args); + + try + { + process pr (args, 0, -1); // Redirect STDOUT to a pipe. + + ifdstream is (pr.in_ofd); + string l; + getline (is, l); + + return l.size () != 0 && l[0] >= '0' && l[0] <= '9' && pr.wait (); + } + catch (const process_error& e) + { + if (e.child ()) + exit (1); + + return false; + } + } + + static process + start_shasum (const path& prog, const strings& ops) + { + cstrings args {prog.string ().c_str (), "-a", "256", "-b"}; + + for (const string& o: ops) + args.push_back (o.c_str ()); + + args.push_back (nullptr); + + if (verb >= 2) + print_process (args); + + // Pipe both STDIN and STDOUT. Process exceptions must be handled by + // the caller. + // + return process (args.data (), -1, -1); + } + + // The dispatcher. + // + // Cache the result of finding/testing the sha256 program. Sometimes + // a simple global variable is really the right solution... + // + enum class kind {sha256, sha256sum, shasum}; + + static path sha256_path; + static kind sha256_kind; + + static kind + check (const common_options& o) + { + if (!sha256_path.empty ()) + return sha256_kind; // Cached. + + if (o.sha256_specified ()) + { + const path& p (sha256_path = o.sha256 ()); + + // Figure out which one it is. + // + const path& n (p.leaf ()); + const string& s (n.string ()); + + if (s.find ("sha256sum") != string::npos) + { + if (!check_sha256sum (p)) + fail << p << " does not appear to be the 'sha256sum' program"; + + sha256_kind = kind::sha256sum; + } + else if (s.find ("shasum") != string::npos) + { + if (!check_shasum (p)) + fail << p << " does not appear to be the 'shasum' program"; + + sha256_kind = kind::shasum; + } + else if (s.find ("sha256") != string::npos) + { + if (!check_sha256 (p)) + fail << p << " does not appear to be the 'sha256' program"; + + sha256_kind = kind::sha256; + } + else + fail << "unknown sha256 program " << p; + } + else + { + // See if any is available. The preference order is: + // + // sha256 (FreeBSD) + // sha256sum (Linux coreutils) + // shasum (Perl tool, Mac OS) + // + if (check_sha256 (sha256_path = path ("sha256"))) + { + sha256_kind = kind::sha256; + } + else if (check_sha256sum (sha256_path = path ("sha256sum"))) + { + sha256_kind = kind::sha256sum; + } + else if (check_shasum (sha256_path = path ("shasum"))) + { + sha256_kind = kind::shasum; + } + else + fail << "unable to find 'sha256', 'sha256sum', or 'shasum'" << + info << "use --sha256 to specify the sha256 program location"; + + if (verb > 1) + info << "using '" << sha256_path << "' as the sha256 program, " + << "use --sha256 to override"; + } + + return sha256_kind; + } + + static process + start (const common_options& o) + { + process (*f) (const path&, const strings&) = nullptr; + + switch (check (o)) + { + case kind::sha256: f = &start_sha256; break; + case kind::sha256sum: f = &start_sha256sum; break; + case kind::shasum: f = &start_shasum; break; + } + + try + { + return f (sha256_path, o.sha256_option ()); + } + catch (const process_error& e) + { + error << "unable to execute " << sha256_path << ": " << e.what (); + + if (e.child ()) + exit (1); + + throw failed (); + } + } + + static string + sha256 (const common_options& o, streambuf& sb) + { + process pr (start (o)); + + try + { + ofdstream os (pr.out_fd); + os.exceptions (ofdstream::badbit | ofdstream::failbit); + + ifdstream is (pr.in_ofd); + is.exceptions (ifdstream::badbit | ifdstream::failbit); + + os << &sb; + os.close (); + + // All three tools output the sum as the first word. + // + string s; + is >> s; + is.close (); + + if (pr.wait ()) + { + if (s.size () != 64) + fail << "'" << s << "' doesn't appear to be a SHA256 sum" << + info << "produced by '" << sha256_path << "'; " + << "use --sha256 to override"; + + return s; + } + + // Child existed with an error, fall through. + } + // Ignore these exceptions if the child process exited with an error status + // since that's the source of the failure. + // + catch (const iostream::failure&) + { + if (pr.wait ()) + fail << "unable to read/write '" << sha256_path << "' output/input"; + } + + // We should only get here if the child exited with an error status. + // + assert (!pr.wait ()); + + // While it is reasonable to assuming the child process issued diagnostics, + // issue something just in case. + // + error << "unable to calculate SHA256 sum using '" << sha256_path << "'" << + info << "re-run with -v for more information"; + + throw failed (); + } + + struct memstreambuf: streambuf + { + memstreambuf (const char* buf, size_t n) + { + char* b (const_cast (buf)); + setg (b, b, b + n); + } + }; + + string + sha256 (const common_options& o, const char* buf, size_t n) + { + memstreambuf msb (buf, n); + return sha256 (o, msb); + } + + string + sha256 (const common_options& o, istream& is) + { + return sha256 (o, *is.rdbuf ()); + } + + string + sha256 (const common_options& o, const path& f) + { + if (!exists (f)) + fail << "file " << f << " does not exist"; + + try + { + ifstream ifs (f.string (), ios::binary); + if (!ifs.is_open ()) + fail << "unable to open " << f << " in read mode"; + + ifs.exceptions (ofstream::badbit | ofstream::failbit); + + return sha256 (o, ifs); + } + catch (const iostream::failure&) + { + error << "unable read " << f; + throw failed (); + } + } +} -- cgit v1.1