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/buildfile | 1 + bpkg/cfg-fetch.cxx | 57 ++++++-- bpkg/checksum | 32 +++++ bpkg/checksum.cxx | 385 ++++++++++++++++++++++++++++++++++++++++++++++++++++ bpkg/common.cli | 26 +++- bpkg/fetch | 9 ++ bpkg/fetch.cxx | 71 ++++++++-- bpkg/package | 7 +- bpkg/package.xml | 1 + bpkg/pkg-fetch.cxx | 16 ++- bpkg/rep-create.cxx | 45 +++--- bpkg/rep-info.cxx | 14 +- bpkg/types | 12 +- tests/test.sh | 3 + 14 files changed, 631 insertions(+), 48 deletions(-) create mode 100644 bpkg/checksum create mode 100644 bpkg/checksum.cxx diff --git a/bpkg/buildfile b/bpkg/buildfile index e4f5756..4effcaf 100644 --- a/bpkg/buildfile +++ b/bpkg/buildfile @@ -13,6 +13,7 @@ exe{bpkg}: \ {hxx cxx}{ cfg-add } {hxx ixx cxx}{ cfg-add-options } \ {hxx cxx}{ cfg-create } {hxx ixx cxx}{ cfg-create-options } \ {hxx cxx}{ cfg-fetch } {hxx ixx cxx}{ cfg-fetch-options } \ +{hxx cxx}{ checksum } \ {hxx ixx cxx}{ common-options } \ {hxx ixx cxx}{ configuration-options } \ {hxx cxx}{ database } \ diff --git a/bpkg/cfg-fetch.cxx b/bpkg/cfg-fetch.cxx index c261863..d90ab93 100644 --- a/bpkg/cfg-fetch.cxx +++ b/bpkg/cfg-fetch.cxx @@ -58,10 +58,26 @@ namespace bpkg r->fetched = true; // Mark as being fetched. - // Load the 'repositories' file and use it to populate the - // prerequisite and complement repository sets. + // Load the 'packages' file. We do this first so that we can get and + // verify the checksum of the 'repositories' file which below. // - repository_manifests rms (fetch_repositories (co, rl, true)); + package_manifests pms (fetch_packages (co, rl, true)); + + // Load the 'repositories' file and use it to populate the prerequisite and + // complement repository sets. + // + repository_manifests rms; + + try + { + rms = fetch_repositories (co, rl, pms.sha256sum, true); + } + catch (const checksum_mismatch&) + { + fail << "repository files checksum mismatch for " + << rl.canonical_name () << + info << "try again"; + } for (repository_manifest& rm: rms) { @@ -150,13 +166,6 @@ namespace bpkg } } - // Load the 'packages' file. - // - // @@ We need to check that that 'repositories' file hasn't - // changed since. - // - package_manifests pms (fetch_packages (co, rl, true)); - // "Suspend" session while persisting packages to reduce memory // consumption. // @@ -178,6 +187,27 @@ namespace bpkg p = make_shared (move (pm)); persist = true; } + else + { + // Make sure this is the same package. + // + assert (p->sha256sum && !p->locations.empty ()); // Can't be transient. + + if (*pm.sha256sum != *p->sha256sum) + { + // All the previous repositories that contain this package have the + // same checksum (since they passed this test), so we can pick any + // to show to the user. + // + const string& r1 (rl.canonical_name ()); + const string& r2 (p->locations[0].repository.object_id ()); + + fail << "checksum mismatch for " << pm.name << " " << pm.version << + info << r1 << " has " << *pm.sha256sum << + info << r2 << " has " << *p->sha256sum << + info << "consider reporting this to the repository maintainers"; + } + } // This repository shouldn't already be in the location set since // that would mean it has already been loaded and we shouldn't be @@ -251,7 +281,12 @@ namespace bpkg // their packages. // for (const lazy_shared_ptr& lp: ua) - cfg_fetch (o, t, lp.load (), root, ""); // No reason (user-added). + { + shared_ptr r (lp.load ()); + + if (!r->fetched) // Can already be loaded as a prerequisite/complement. + cfg_fetch (o, t, r, root, ""); // No reason (user-added). + } size_t rcount, pcount; if (verb) diff --git a/bpkg/checksum b/bpkg/checksum new file mode 100644 index 0000000..3ef35cc --- /dev/null +++ b/bpkg/checksum @@ -0,0 +1,32 @@ +// file : bpkg/checksum -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BPKG_CHECKSUM +#define BPKG_CHECKSUM + +#include +#include + +#include + +namespace bpkg +{ + // Calculate SHA256 sum of the specified memory buffer in binary mode. Issue + // diagnostics and throw failed if anything goes wrong. + // + string + sha256 (const common_options&, const char* buf, size_t n); + + // The same but for a stream (if ifstream, open in binary mode). + // + string + sha256 (const common_options&, istream&); + + // The same but for a file. + // + string + sha256 (const common_options&, const path& file); +} + +#endif // BPKG_CHECKSUM 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 (); + } + } +} diff --git a/bpkg/common.cli b/bpkg/common.cli index 60692c3..4776e73 100644 --- a/bpkg/common.cli +++ b/bpkg/common.cli @@ -101,7 +101,7 @@ namespace bpkg to the fetch program with \cb{--fetch-option}. If the fetch program is not specified, then \cb{bpkg} will try to - discover if one of the above program is available and use that. + discover if one of the above programs is available and use that. Currently, \cb{bpkg} has the following preference order: \cb{wget} 1.16 or higher (supports \cb{--show-progress}), \cb{curl}, \cb{wget}, and \cb{fetch}." @@ -115,6 +115,30 @@ namespace bpkg specify multiple fetch options." } + path --sha256 + { + "", + "The sha256 program to be used to calculate SHA256 sums. Currently, + \cb{bpkg} recognizes \cb{sha256}, \cb{sha256sum}, and \cb{shasum}. + Note that the last component of must contain one of these names + as a substring in order for \cb{bpkg} to recognize which program is + being used. You can also specify additional options that should be + passed to the sha256 program with \cb{--sha256-option}. + + If the sha256 program is not specified, then \cb{bpkg} will try to + discover if one of the above programs is available and use that. + Currently, \cb{bpkg} has the following preference order: \cb{sha256}, + \cb{sha256sum}, and \cb{shasum}." + } + + strings --sha256-option + { + "", + "Additional option to be passed to the sha256 program. See \cb{--sha256} + for more information on the sha256 program. Repeat this option to + specify multiple sha256 options." + } + path --tar = "tar" { "", diff --git a/bpkg/fetch b/bpkg/fetch index 18dd665..06c2d15 100644 --- a/bpkg/fetch +++ b/bpkg/fetch @@ -5,18 +5,27 @@ #ifndef BPKG_FETCH #define BPKG_FETCH +#include + #include #include #include + #include namespace bpkg { + class checksum_mismatch: public std::exception {}; + repository_manifests fetch_repositories (const dir_path&, bool ignore_unknown); + + // Verify the checksum and throw checksum_mismatch if it doesn't match. + // repository_manifests fetch_repositories (const common_options&, const repository_location&, + const string& sha256sum, bool ignore_unknown); package_manifests fetch_packages (const dir_path&, diff --git a/bpkg/fetch.cxx b/bpkg/fetch.cxx index c47fb55..257d903 100644 --- a/bpkg/fetch.cxx +++ b/bpkg/fetch.cxx @@ -5,7 +5,7 @@ #include #include -#include // uint16_t +#include #include #include @@ -13,6 +13,7 @@ #include +#include #include #include @@ -354,7 +355,7 @@ namespace bpkg static path fetch_path; static kind fetch_kind; - kind + static kind check (const common_options& o) { if (!fetch_path.empty ()) @@ -523,6 +524,8 @@ namespace bpkg return r; } + // If sha256sum is empty, then don't verify. + // template static M fetch_manifest (const common_options& o, @@ -530,6 +533,7 @@ namespace bpkg const string& host, uint16_t port, const path& f, + const string& sha256sum, bool ignore_unknown) { string url (to_url (proto, host, port, f)); @@ -540,9 +544,32 @@ namespace bpkg ifdstream is (pr.in_ofd); is.exceptions (ifdstream::badbit | ifdstream::failbit); - manifest_parser mp (is, url); - M m (mp, ignore_unknown); - is.close (); + M m; + if (sha256sum.empty ()) + { + manifest_parser mp (is, url); + m = M (mp, ignore_unknown); + is.close (); + } + else + { + // Unfortunately we cannot rewind STDOUT as we do below for files. + // There doesn't seem to be anything better than reading the entire + // file into memory and then streaming it twice, once to calculate + // the checksum and the second time to actually parse. + // + stringstream ss; + ss << is.rdbuf (); + is.close (); + + if (sha256sum != sha256 (o, ss)) + throw checksum_mismatch (); + + ss.clear (); ss.seekg (0); // Rewind. + + manifest_parser mp (ss, url); + m = M (mp, ignore_unknown); + } if (pr.wait ()) return m; @@ -618,9 +645,14 @@ namespace bpkg return r; } + // If sha256sum is empty, then don't verify. + // template static M - fetch_manifest (const path& f, bool ignore_unknown) + fetch_manifest (const common_options* o, + const path& f, + const string& sha256sum, + bool ignore_unknown) { if (!exists (f)) fail << "file " << f << " does not exist"; @@ -631,6 +663,16 @@ namespace bpkg ifs.exceptions (ofstream::badbit | ofstream::failbit); ifs.open (f.string ()); + if (!sha256sum.empty ()) + { + assert (o != nullptr); + + if (sha256sum != sha256 (*o, ifs)) + throw checksum_mismatch (); + + ifs.seekg (0); // Rewind the file stream. + } + manifest_parser mp (ifs, f.string ()); return M (mp, ignore_unknown); } @@ -651,12 +693,17 @@ namespace bpkg repository_manifests fetch_repositories (const dir_path& d, bool iu) { - return fetch_manifest (d / repositories, iu); + return fetch_manifest ( + nullptr, + d / repositories, + "", // No checksum verification. + iu); } repository_manifests fetch_repositories (const common_options& o, const repository_location& rl, + const string& sha256sum, bool iu) { assert (rl.remote () || rl.absolute ()); @@ -665,8 +712,8 @@ namespace bpkg return rl.remote () ? fetch_manifest ( - o, rl.proto (), rl.host (), rl.port (), f, iu) - : fetch_manifest (f, iu); + o, rl.proto (), rl.host (), rl.port (), f, sha256sum, iu) + : fetch_manifest (&o, f, sha256sum, iu); } static const path packages ("packages"); @@ -674,7 +721,7 @@ namespace bpkg package_manifests fetch_packages (const dir_path& d, bool iu) { - return fetch_manifest (d / packages, iu); + return fetch_manifest (nullptr, d / packages, "", iu); } package_manifests @@ -688,8 +735,8 @@ namespace bpkg return rl.remote () ? fetch_manifest ( - o, rl.proto (), rl.host (), rl.port (), f, iu) - : fetch_manifest (f, iu); + o, rl.proto (), rl.host (), rl.port (), f, "", iu) + : fetch_manifest (&o, f, "", iu); } path diff --git a/bpkg/package b/bpkg/package index 3e8c989..da19def 100644 --- a/bpkg/package +++ b/bpkg/package @@ -302,11 +302,16 @@ namespace bpkg dependencies_type dependencies; + // Present for non-transient objects only. + // + optional sha256sum; + public: available_package (package_manifest&& m) : id (move (m.name), m.version), version (move (m.version)), - dependencies (move (m.dependencies)) {} + dependencies (move (m.dependencies)), + sha256sum (move (m.sha256sum)) {} // Database mapping. // diff --git a/bpkg/package.xml b/bpkg/package.xml index 80a4aa7..0e0872d 100644 --- a/bpkg/package.xml +++ b/bpkg/package.xml @@ -53,6 +53,7 @@ + diff --git a/bpkg/pkg-fetch.cxx b/bpkg/pkg-fetch.cxx index 3032eda..a57059d 100644 --- a/bpkg/pkg-fetch.cxx +++ b/bpkg/pkg-fetch.cxx @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -23,7 +24,6 @@ using namespace butl; namespace bpkg { - static shared_ptr pkg_fetch (dir_path c, transaction& t, @@ -218,6 +218,20 @@ namespace bpkg path a (fetch_archive (co, pl->repository->location, pl->location, c)); auto_rm arm (a); + // We can't be fetching an archive for a transient object. + // + assert (ap->sha256sum); + + const string& sha256sum (sha256 (co, a)); + if (sha256sum != *ap->sha256sum) + { + fail << "checksum mismatch for " << n << " " << v << + info << pl->repository->name << " has " << *ap->sha256sum << + info << "fetched archive has " << sha256sum << + info << "try again, if problem persists, consider reporting this to " + << "the repository maintainer"; + } + shared_ptr p ( pkg_fetch (c, t, diff --git a/bpkg/rep-create.cxx b/bpkg/rep-create.cxx index c6d07ec..cc2ade1 100644 --- a/bpkg/rep-create.cxx +++ b/bpkg/rep-create.cxx @@ -14,9 +14,11 @@ #include #include -#include #include #include + +#include +#include #include #include @@ -51,6 +53,9 @@ namespace bpkg using package_map = map; + static const path repositories ("repositories"); + static const path packages ("packages"); + static void collect (const rep_create_options& o, package_map& map, @@ -89,8 +94,7 @@ namespace bpkg // if (d == root) { - if (p == path ("repositories") || - p == path ("packages")) + if (p == repositories || p == packages) continue; } @@ -99,7 +103,12 @@ namespace bpkg path a (d / p); package_manifest m (pkg_verify (o, a, o.ignore_unknown ())); - level4 ([&]{trace << m.name << " " << m.version << " in " << a;}); + // Calculate its checksum. + // + m.sha256sum = sha256 (o, a); + + level4 ([&]{trace << m.name << " " << m.version << " in " << a + << " sha256sum " << *m.sha256sum;}); // Add package archive location relative to the repository root. // @@ -156,9 +165,22 @@ namespace bpkg package_map pm; collect (o, pm, d, d); + package_manifests manifests; + manifests.sha256sum = sha256 (o, path (d / repositories)); + + for (auto& p: pm) + { + package_manifest& m (p.second.manifest); + + if (verb) + text << "adding " << m.name << " " << m.version; + + manifests.emplace_back (move (m)); + } + // Serialize. // - path p (d / path ("packages")); + path p (d / packages); try { @@ -167,18 +189,7 @@ namespace bpkg ofs.open (p.string ()); manifest_serializer s (ofs, p.string ()); - - for (const auto& p: pm) - { - const package_manifest& m (p.second.manifest); - - if (verb) - text << "adding " << m.name << " " << m.version; - - m.serialize (s); - } - - s.next ("", ""); // The end. + manifests.serialize (s); } catch (const manifest_serialization& e) { diff --git a/bpkg/rep-info.cxx b/bpkg/rep-info.cxx index 2205a5b..241f04b 100644 --- a/bpkg/rep-info.cxx +++ b/bpkg/rep-info.cxx @@ -35,9 +35,21 @@ namespace bpkg // Fetch everything we will need before printing anything. Ignore // unknown manifest entries unless we are dumping them. // - repository_manifests rms (fetch_repositories (o, rl, !o.manifest ())); package_manifests pms (fetch_packages (o, rl, !o.manifest ())); + repository_manifests rms; + + try + { + rms = fetch_repositories (o, rl, pms.sha256sum, !o.manifest ()); + } + catch (const checksum_mismatch&) + { + fail << "repository files checksum mismatch for " + << rl.canonical_name () << + info << "try again"; + } + // Now print. // bool all (!o.name () && !o.repositories () && !o.packages ()); diff --git a/bpkg/types b/bpkg/types index e667fc0..b0db478 100644 --- a/bpkg/types +++ b/bpkg/types @@ -10,6 +10,7 @@ #include // shared_ptr, unique_ptr #include // size_t #include // uint{8,16,32,64}_t +#include #include #include @@ -33,6 +34,9 @@ namespace bpkg using strings = vector; using cstrings = vector; + using std::istream; + using std::ostream; + using butl::optional; using butl::nullopt; @@ -56,11 +60,11 @@ namespace bpkg using paths = std::vector; using dir_paths = std::vector; - inline std::ostream& - operator<< (std::ostream& os, const path& p) {return os << p.string ();} + inline ostream& + operator<< (ostream& os, const path& p) {return os << p.string ();} - inline std::ostream& - operator<< (std::ostream& os, const dir_path& p) + inline ostream& + operator<< (ostream& os, const dir_path& p) { const string& s (p.string ()); os << s; diff --git a/tests/test.sh b/tests/test.sh index e2ff4d0..e68b7eb 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -214,6 +214,8 @@ EOF test rep-info -m -p $rep/common/bar/unstable <= 1.1.0 location: libbar-1.1.1.tar.gz +sha256sum: abb073e9729bbc914f26ed0dedad9f5ead2c73a36cbc1e8f2702d6fde7634ff3 EOF -- cgit v1.1