aboutsummaryrefslogtreecommitdiff
#include "config.h"

#include <iostream>
#include <cstring>

#include "hash.hh"
#include "archive.hh"
#include "util.hh"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


namespace nix {


Hash::Hash()
{
    type = htUnknown;
    hashSize = 0;
    memset(hash, 0, maxHashSize);
}


Hash::Hash(HashType type)
{
    this->type = type;
    hashSize = gcry_md_get_algo_dlen(type);

    if (hashSize == 0) throw Error("unknown hash type");
    assert(hashSize <= maxHashSize);
    memset(hash, 0, maxHashSize);
}


bool Hash::operator == (const Hash & h2) const
{
    if (hashSize != h2.hashSize) return false;
    for (unsigned int i = 0; i < hashSize; i++)
        if (hash[i] != h2.hash[i]) return false;
    return true;
}


bool Hash::operator != (const Hash & h2) const
{
    return !(*this == h2);
}


bool Hash::operator < (const Hash & h) const
{
    for (unsigned int i = 0; i < hashSize; i++) {
        if (hash[i] < h.hash[i]) return true;
        if (hash[i] > h.hash[i]) return false;
    }
    return false;
}


const string base16Chars = "0123456789abcdef";


string printHash(const Hash & hash)
{
    char buf[hash.hashSize * 2];
    for (unsigned int i = 0; i < hash.hashSize; i++) {
        buf[i * 2] = base16Chars[hash.hash[i] >> 4];
        buf[i * 2 + 1] = base16Chars[hash.hash[i] & 0x0f];
    }
    return string(buf, hash.hashSize * 2);
}


Hash parseHash(HashType ht, const string & s)
{
    Hash hash(ht);
    if (s.length() != hash.hashSize * 2) {
	string algo = gcry_md_algo_name(ht);
        throw Error(format("invalid %1% hash '%2%' (%3% bytes but expected %4%)")
		    % algo % s % (s.length() / 2) % hash.hashSize);
    }
    for (unsigned int i = 0; i < hash.hashSize; i++) {
        string s2(s, i * 2, 2);
        if (!isxdigit(s2[0]) || !isxdigit(s2[1]))
            throw Error(format("invalid hash `%1%'") % s);
        std::istringstream str(s2);
        int n;
        str >> std::hex >> n;
        hash.hash[i] = n;
    }
    return hash;
}


unsigned int hashLength32(const Hash & hash)
{
    return (hash.hashSize * 8 - 1) / 5 + 1;
}


// omitted: E O U T
const string base32Chars = "0123456789abcdfghijklmnpqrsvwxyz";


string printHash32(const Hash & hash)
{
    Hash hash2(hash);
    unsigned int len = hashLength32(hash);

    string s;
    s.reserve(len);

    for (int n = len - 1; n >= 0; n--) {
        unsigned int b = n * 5;
        unsigned int i = b / 8;
        unsigned int j = b % 8;
        unsigned char c =
            (hash.hash[i] >> j)
            | (i >= hash.hashSize - 1 ? 0 : hash.hash[i + 1] << (8 - j));
        s.push_back(base32Chars[c & 0x1f]);
    }

    return s;
}


string printHash16or32(const Hash & hash)
{
    return hash.type == htMD5 ? printHash(hash) : printHash32(hash);
}


Hash parseHash32(HashType ht, const string & s)
{
    Hash hash(ht);
    unsigned int len = hashLength32(ht);
    assert(s.size() == len);

    for (unsigned int n = 0; n < len; ++n) {
        char c = s[len - n - 1];
        unsigned char digit;
        for (digit = 0; digit < base32Chars.size(); ++digit) /* !!! slow */
            if (base32Chars[digit] == c) break;
        if (digit >= 32)
            throw Error(format("invalid base-32 hash '%1%'") % s);
        unsigned int b = n * 5;
        unsigned int i = b / 8;
        unsigned int j = b % 8;
        hash.hash[i] |= digit << j;
        if (i < hash.hashSize - 1) hash.hash[i + 1] |= digit >> (8 - j);
    }

    return hash;
}


Hash parseHash16or32(HashType ht, const string & s)
{
    Hash hash(ht);
    if (s.size() == hash.hashSize * 2)
        /* hexadecimal representation */
        hash = parseHash(ht, s);
    else if (s.size() == hashLength32(hash))
        /* base-32 representation */
        hash = parseHash32(ht, s);
    else
        throw Error(format("hash `%1%' has wrong length for hash type `%2%'")
            % s % printHashType(ht));
    return hash;
}


bool isHash(const string & s)
{
    if (s.length() != 32) return false;
    for (int i = 0; i < 32; i++) {
        char c = s[i];
        if (!((c >= '0' && c <= '9') ||
              (c >= 'a' && c <= 'f')))
            return false;
    }
    return true;
}

/* The "hash context".  */
struct Ctx
{
  /* This copy constructor is needed in 'HashSink::currentHash()' where we
     expect the copy of a 'Ctx' object to yield a truly different context.  */
  Ctx(Ctx &ref)
  {
    if (ref.md_handle == NULL)
      md_handle = NULL;
    else
      gcry_md_copy (&md_handle, ref.md_handle);
  }

  /* Make sure 'md_handle' is always initialized.  */
  Ctx(): md_handle (NULL) { };

  gcry_md_hd_t md_handle;
};


static void start(HashType ht, Ctx & ctx)
{
    gcry_error_t err;

    err = gcry_md_open (&ctx.md_handle, ht, 0);
    assert (err == GPG_ERR_NO_ERROR);
}


static void update(HashType ht, Ctx & ctx,
    const unsigned char * bytes, unsigned int len)
{
    gcry_md_write (ctx.md_handle, bytes, len);
}


static void finish(HashType ht, Ctx & ctx, unsigned char * hash)
{
    memcpy (hash, gcry_md_read (ctx.md_handle, ht),
	    gcry_md_get_algo_dlen (ht));
    gcry_md_close (ctx.md_handle);
    ctx.md_handle = NULL;
}


Hash hashString(HashType ht, const string & s)
{
    Ctx ctx;
    Hash hash(ht);
    start(ht, ctx);
    update(ht, ctx, (const unsigned char *) s.data(), s.length());
    finish(ht, ctx, hash.hash);
    return hash;
}


Hash hashFile(HashType ht, const Path & path)
{
    Ctx ctx;
    Hash hash(ht);
    start(ht, ctx);

    AutoCloseFD fd = open(path.c_str(), O_RDONLY);
    if (fd == -1) throw SysError(format("computing hash of file `%1%'") % path);

    unsigned char buf[8192];
    ssize_t n;
    while ((n = read(fd, buf, sizeof(buf)))) {
        checkInterrupt();
        if (n == -1) throw SysError(format("reading file `%1%'") % path);
        update(ht, ctx, buf, n);
    }

    finish(ht, ctx, hash.hash);
    return hash;
}


HashSink::HashSink(HashType ht) : ht(ht)
{
    ctx = new Ctx;
    bytes = 0;
    start(ht, *ctx);
}

HashSink::~HashSink()
{
    bufPos = 0;
    delete ctx;
}

void HashSink::write(const unsigned char * data, size_t len)
{
    bytes += len;
    update(ht, *ctx, data, len);
}

HashResult HashSink::finish()
{
    flush();
    Hash hash(ht);
    nix::finish(ht, *ctx, hash.hash);
    return HashResult(hash, bytes);
}

HashResult HashSink::currentHash()
{
    flush();
    Ctx ctx2 = *ctx;
    Hash hash(ht);
    nix::finish(ht, ctx2, hash.hash);
    return HashResult(hash, bytes);
}


HashResult hashPath(
    HashType ht, const Path & path, PathFilter & filter)
{
    HashSink sink(ht);
    dumpPath(path, sink, filter);
    return sink.finish();
}


Hash compressHash(const Hash & hash, unsigned int newSize)
{
    Hash h;
    h.hashSize = newSize;
    for (unsigned int i = 0; i < hash.hashSize; ++i)
        h.hash[i % newSize] ^= hash.hash[i];
    return h;
}


HashType parseHashType(const string & s)
{
    if (s == "md5") return htMD5;
    else if (s == "sha1") return htSHA1;
    else if (s == "sha256") return htSHA256;
    else if (s == "sha512") return htSHA512;
    else if (s == "sha3-256") return htSHA3_256;
    else if (s == "sha3-512") return htSHA3_512;
    else if (s == "blake2s-256") return htBLAKE2s_256;
    else return htUnknown;
}


string printHashType(HashType ht)
{
    if (ht == htMD5) return "md5";
    else if (ht == htSHA1) return "sha1";
    else if (ht == htSHA256) return "sha256";
    else if (ht == htSHA512) return "sha512";
    else if (ht == htSHA3_256) return "sha3-256";
    else if (ht == htSHA3_512) return "sha3-512";
    else if (ht == htBLAKE2s_256) return "blake2s-256";
    else throw Error("cannot print unknown hash type");
}


}
t be on $PATH. test -x `sed -r 's/^export PATH="(.*)"/\1/' "$tmpdir/a"`/guile cmp "$tmpdir/a" "$tmpdir/b" # Make sure the exit value is preserved. if guix environment --bootstrap --ad-hoc guile-bootstrap --pure \ -- guile -c '(exit 42)' then false else test $? = 42 fi # Make sure 'GUIX_ENVIRONMENT' points to the profile. guix environment --bootstrap --ad-hoc guile-bootstrap --pure \ -- "$SHELL" -c 'test -f "$GUIX_ENVIRONMENT/bin/guile"' # Make sure '-r' works as expected. rm -f "$gcroot" expected="`guix environment --bootstrap --ad-hoc guile-bootstrap \ -- "$SHELL" -c 'echo $GUIX_ENVIRONMENT'`" guix environment --bootstrap -r "$gcroot" --ad-hoc guile-bootstrap \ -- guile -c 1 test `readlink "$gcroot"` = "$expected" # Make sure '-r' is idempotent. guix environment --bootstrap -r "$gcroot" --ad-hoc guile-bootstrap \ -- guile -c 1 test `readlink "$gcroot"` = "$expected" rm "$gcroot" # Same with an absolute file name. guix environment --bootstrap -r "$PWD/$gcroot" --ad-hoc guile-bootstrap \ -- guile -c 1 test `readlink "$gcroot"` = "$expected" case "`uname -m`" in x86_64) # On x86_64, we should be able to create a 32-bit environment. guix environment --bootstrap --ad-hoc guile-bootstrap --pure \ -- guile -c '(exit (string-prefix? "x86_64" %host-type))' guix environment --bootstrap --ad-hoc guile-bootstrap --pure \ -s i686-linux \ -- guile -c '(exit (string-prefix? "i686" %host-type))' ;; *) echo "nothing to do" >&2 ;; esac # Same as above, but with deprecated -E flag. if guix environment --bootstrap --ad-hoc guile-bootstrap --pure \ -E "guile -c '(exit 42)'" then false else test $? = 42 fi if guile -c '(getaddrinfo "www.gnu.org" "80" AI_NUMERICSERV)' 2> /dev/null then # Compute the build environment for the initial GNU Make. guix environment --bootstrap --no-substitutes --search-paths --pure \ -e '(@@ (gnu packages commencement) gnu-make-boot0)' > "$tmpdir/a" # Make sure bootstrap binaries are in the profile. profile=`grep "^export PATH" "$tmpdir/a" | sed -r 's|^.*="(.*)/bin"|\1|'` # Make sure the bootstrap binaries are all listed where they belong. grep -E "^export PATH=\"$profile/bin\"" "$tmpdir/a" grep -E "^export CPATH=\"$profile/include\"" "$tmpdir/a" grep -E "^export LIBRARY_PATH=\"$profile/lib\"" "$tmpdir/a" for dep in bootstrap-binaries-0 gcc-bootstrap-0 glibc-bootstrap-0 do guix gc --references "$profile" | grep "$dep" done # 'make-boot0' itself must not be listed. if guix gc --references "$profile" | grep make-boot0 then false; else true; fi # Make sure that the shell spawned with '--exec' sees the same environment # as returned by '--search-paths'. guix environment --bootstrap --no-substitutes --pure \ -e '(@@ (gnu packages commencement) gnu-make-boot0)' \ -- /bin/sh -c 'echo $PATH $CPATH $LIBRARY_PATH' > "$tmpdir/b" ( . "$tmpdir/a" ; echo $PATH $CPATH $LIBRARY_PATH ) > "$tmpdir/c" cmp "$tmpdir/b" "$tmpdir/c" rm "$tmpdir"/* # Compute the build environment for the initial GNU Findutils. guix environment --bootstrap --no-substitutes --search-paths --pure \ -e '(@@ (gnu packages commencement) findutils-boot0)' > "$tmpdir/a" profile=`grep "^export PATH" "$tmpdir/a" | sed -r 's|^.*="(.*)/bin"|\1|'` # Make sure the bootstrap binaries are all listed where they belong. grep -E "^export PATH=\"$profile/bin\"" "$tmpdir/a" grep -E "^export CPATH=\"$profile/include\"" "$tmpdir/a" grep -E "^export LIBRARY_PATH=\"$profile/lib\"" "$tmpdir/a" for dep in bootstrap-binaries-0 gcc-bootstrap-0 glibc-bootstrap-0 \ make-boot0 do guix gc --references "$profile" | grep "$dep" done # The following test assumes 'make-boot0' has a "debug" output. make_boot0_debug="`guix build -e '(@@ (gnu packages commencement) gnu-make-boot0)' | grep -e -debug`" test "x$make_boot0_debug" != "x" # Make sure the "debug" output is not listed. if guix gc --references "$profile" | grep "$make_boot0_debug" then false; else true; fi # Compute the build environment for the initial GNU Make, but add in the # bootstrap Guile as an ad-hoc addition. guix environment --bootstrap --no-substitutes --search-paths --pure \ -e '(@@ (gnu packages commencement) gnu-make-boot0)' \ --ad-hoc guile-bootstrap > "$tmpdir/a" profile=`grep "^export PATH" "$tmpdir/a" | sed -r 's|^.*="(.*)/bin"|\1|'` # Make sure the bootstrap binaries are all listed where they belong. grep -E "^export PATH=\"$profile/bin\"" "$tmpdir/a" grep -E "^export CPATH=\"$profile/include\"" "$tmpdir/a" grep -E "^export LIBRARY_PATH=\"$profile/lib\"" "$tmpdir/a" for dep in bootstrap-binaries-0 gcc-bootstrap-0 glibc-bootstrap-0 \ guile-bootstrap do guix gc --references "$profile" | grep "$dep" done # Make sure a package list with plain package objects and package+output # tuples can be used with -e. expr_list_test_code=" (list (@@ (gnu packages commencement) gnu-make-boot0) (list (@ (gnu packages bootstrap) %bootstrap-guile) \"out\"))" guix environment --bootstrap --ad-hoc --no-substitutes --search-paths \ --pure -e "$expr_list_test_code" > "$tmpdir/a" profile=`grep "^export PATH" "$tmpdir/a" | sed -r 's|^.*="(.*)/bin"|\1|'` for dep in make-boot0 guile-bootstrap do guix gc --references "$profile" | grep "$dep" done fi