Commit c60dfb6d authored by jan.koester's avatar jan.koester
Browse files

test

parent 035883c0
Loading
Loading
Loading
Loading
+3 −1
Changes for commands/text/CMakeLists.txt: 3 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@ target_sources(fresh PRIVATE
  ${CMAKE_CURRENT_SOURCE_DIR}/flex.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/xargs.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/textutils.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/checksums.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/textutils2.cpp
)

set(FRESH_APPLETS ${FRESH_APPLETS} head tail wc sort uniq tr expr grep md5sum sha1sum sha256sum cut m4 bison flex xargs od cmp tee seq tac comm paste tsort nl fold base64 yes PARENT_SCOPE)
set(FRESH_APPLETS ${FRESH_APPLETS} head tail wc sort uniq tr expr grep md5sum sha1sum sha256sum cut m4 bison flex xargs od cmp tee seq tac comm paste tsort nl fold base64 yes sha224sum sha384sum sha512sum cksum sum expand unexpand join split PARENT_SCOPE)
+355 −0
Changes for commands/text/checksums.cpp: 355 added lines, 0 removed lines.
Original line number Diff line number Diff line
/*******************************************************************************
Copyright (c) 2026, Jan Koester jan.koester@gmx.net
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the <organization> nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/


// sha224sum, sha384sum, sha512sum (FIPS 180-4) with the GNU *sum CLI
// (-c/--check, --tag, --quiet, --status, -w), plus cksum (POSIX CRC-32)
// and sum (BSD 16-bit / SysV -s).

#include <iostream>
#include <string>
#include <vector>
#include <functional>
#include <cstdint>
#include <cstring>
#include <cstdio>
#include <cerrno>

#include "shell.h"
#include "textutil.h"

namespace fresh { namespace cmd {

    namespace {

        inline uint32_t ror32(uint32_t x,int n){ return (x>>n)|(x<<(32-n)); }
        inline uint64_t ror64(uint64_t x,int n){ return (x>>n)|(x<<(64-n)); }

        // SHA-224 (the SHA-256 core with other IVs, truncated)
        std::string sha224(const std::string &d){
            static const uint32_t K[64]={
                0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
                0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
                0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
                0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
                0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
                0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
                0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
                0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2};
            uint32_t h[8]={0xc1059ed8,0x367cd507,0x3070dd17,0xf70e5939,0xffc00b31,0x68581511,0x64f98fa7,0xbefa4fa4};
            std::string m=d;
            uint64_t bits=(uint64_t)d.size()*8;
            m+=(char)0x80;
            while(m.size()%64!=56) m+=(char)0;
            for(int i=7;i>=0;--i) m+=(char)((bits>>(i*8))&0xff);
            for(size_t off=0;off<m.size();off+=64){
                uint32_t w[64];
                for(int i=0;i<16;++i){
                    const unsigned char *p=(const unsigned char*)m.data()+off+i*4;
                    w[i]=((uint32_t)p[0]<<24)|((uint32_t)p[1]<<16)|((uint32_t)p[2]<<8)|p[3];
                }
                for(int i=16;i<64;++i){
                    uint32_t s0=ror32(w[i-15],7)^ror32(w[i-15],18)^(w[i-15]>>3);
                    uint32_t s1=ror32(w[i-2],17)^ror32(w[i-2],19)^(w[i-2]>>10);
                    w[i]=w[i-16]+s0+w[i-7]+s1;
                }
                uint32_t a=h[0],b=h[1],c=h[2],dd=h[3],e=h[4],f=h[5],g=h[6],hh=h[7];
                for(int i=0;i<64;++i){
                    uint32_t t1=hh+(ror32(e,6)^ror32(e,11)^ror32(e,25))+((e&f)^(~e&g))+K[i]+w[i];
                    uint32_t t2=(ror32(a,2)^ror32(a,13)^ror32(a,22))+((a&b)^(a&c)^(b&c));
                    hh=g; g=f; f=e; e=dd+t1; dd=c; c=b; b=a; a=t1+t2;
                }
                h[0]+=a; h[1]+=b; h[2]+=c; h[3]+=dd; h[4]+=e; h[5]+=f; h[6]+=g; h[7]+=hh;
            }
            char out[57];
            for(int i=0;i<7;++i) snprintf(out+i*8,9,"%08x",h[i]);
            return std::string(out,56);
        }

        // SHA-512 core; outWords 8 (512) or 6 (384)
        std::string sha512family(const std::string &d,const uint64_t iv[8],int outWords){
            static const uint64_t K[80]={
                0x428a2f98d728ae22ULL,0x7137449123ef65cdULL,0xb5c0fbcfec4d3b2fULL,0xe9b5dba58189dbbcULL,0x3956c25bf348b538ULL,
                0x59f111f1b605d019ULL,0x923f82a4af194f9bULL,0xab1c5ed5da6d8118ULL,0xd807aa98a3030242ULL,0x12835b0145706fbeULL,
                0x243185be4ee4b28cULL,0x550c7dc3d5ffb4e2ULL,0x72be5d74f27b896fULL,0x80deb1fe3b1696b1ULL,0x9bdc06a725c71235ULL,
                0xc19bf174cf692694ULL,0xe49b69c19ef14ad2ULL,0xefbe4786384f25e3ULL,0x0fc19dc68b8cd5b5ULL,0x240ca1cc77ac9c65ULL,
                0x2de92c6f592b0275ULL,0x4a7484aa6ea6e483ULL,0x5cb0a9dcbd41fbd4ULL,0x76f988da831153b5ULL,0x983e5152ee66dfabULL,
                0xa831c66d2db43210ULL,0xb00327c898fb213fULL,0xbf597fc7beef0ee4ULL,0xc6e00bf33da88fc2ULL,0xd5a79147930aa725ULL,
                0x06ca6351e003826fULL,0x142929670a0e6e70ULL,0x27b70a8546d22ffcULL,0x2e1b21385c26c926ULL,0x4d2c6dfc5ac42aedULL,
                0x53380d139d95b3dfULL,0x650a73548baf63deULL,0x766a0abb3c77b2a8ULL,0x81c2c92e47edaee6ULL,0x92722c851482353bULL,
                0xa2bfe8a14cf10364ULL,0xa81a664bbc423001ULL,0xc24b8b70d0f89791ULL,0xc76c51a30654be30ULL,0xd192e819d6ef5218ULL,
                0xd69906245565a910ULL,0xf40e35855771202aULL,0x106aa07032bbd1b8ULL,0x19a4c116b8d2d0c8ULL,0x1e376c085141ab53ULL,
                0x2748774cdf8eeb99ULL,0x34b0bcb5e19b48a8ULL,0x391c0cb3c5c95a63ULL,0x4ed8aa4ae3418acbULL,0x5b9cca4f7763e373ULL,
                0x682e6ff3d6b2b8a3ULL,0x748f82ee5defb2fcULL,0x78a5636f43172f60ULL,0x84c87814a1f0ab72ULL,0x8cc702081a6439ecULL,
                0x90befffa23631e28ULL,0xa4506cebde82bde9ULL,0xbef9a3f7b2c67915ULL,0xc67178f2e372532bULL,0xca273eceea26619cULL,
                0xd186b8c721c0c207ULL,0xeada7dd6cde0eb1eULL,0xf57d4f7fee6ed178ULL,0x06f067aa72176fbaULL,0x0a637dc5a2c898a6ULL,
                0x113f9804bef90daeULL,0x1b710b35131c471bULL,0x28db77f523047d84ULL,0x32caab7b40c72493ULL,0x3c9ebe0a15c9bebcULL,
                0x431d67c49c100d4cULL,0x4cc5d4becb3e42b6ULL,0x597f299cfc657e2aULL,0x5fcb6fab3ad6faecULL,0x6c44198c4a475817ULL};
            uint64_t h[8];
            memcpy(h,iv,sizeof h);
            std::string m=d;
            uint64_t bits=(uint64_t)d.size()*8;
            m+=(char)0x80;
            while(m.size()%128!=112) m+=(char)0;
            for(int i=0;i<8;++i) m+=(char)0;          // high 64 bits of the length
            for(int i=7;i>=0;--i) m+=(char)((bits>>(i*8))&0xff);
            for(size_t off=0;off<m.size();off+=128){
                uint64_t w[80];
                for(int i=0;i<16;++i){
                    const unsigned char *p=(const unsigned char*)m.data()+off+i*8;
                    uint64_t v=0;
                    for(int k=0;k<8;++k) v=(v<<8)|p[k];
                    w[i]=v;
                }
                for(int i=16;i<80;++i){
                    uint64_t s0=ror64(w[i-15],1)^ror64(w[i-15],8)^(w[i-15]>>7);
                    uint64_t s1=ror64(w[i-2],19)^ror64(w[i-2],61)^(w[i-2]>>6);
                    w[i]=w[i-16]+s0+w[i-7]+s1;
                }
                uint64_t a=h[0],b=h[1],c=h[2],dd=h[3],e=h[4],f=h[5],g=h[6],hh=h[7];
                for(int i=0;i<80;++i){
                    uint64_t t1=hh+(ror64(e,14)^ror64(e,18)^ror64(e,41))+((e&f)^(~e&g))+K[i]+w[i];
                    uint64_t t2=(ror64(a,28)^ror64(a,34)^ror64(a,39))+((a&b)^(a&c)^(b&c));
                    hh=g; g=f; f=e; e=dd+t1; dd=c; c=b; b=a; a=t1+t2;
                }
                h[0]+=a; h[1]+=b; h[2]+=c; h[3]+=dd; h[4]+=e; h[5]+=f; h[6]+=g; h[7]+=hh;
            }
            std::string out;
            char b[17];
            for(int i=0;i<outWords;++i){ snprintf(b,sizeof b,"%016llx",(unsigned long long)h[i]); out+=b; }
            return out;
        }
        std::string sha512(const std::string &d){
            static const uint64_t iv[8]={0x6a09e667f3bcc908ULL,0xbb67ae8584caa73bULL,0x3c6ef372fe94f82bULL,0xa54ff53a5f1d36f1ULL,
                                         0x510e527fade682d1ULL,0x9b05688c2b3e6c1fULL,0x1f83d9abfb41bd6bULL,0x5be0cd19137e2179ULL};
            return sha512family(d,iv,8);
        }
        std::string sha384(const std::string &d){
            static const uint64_t iv[8]={0xcbbb9d5dc1059ed8ULL,0x629a292a367cd507ULL,0x9159015a3070dd17ULL,0x152fecd8f70e5939ULL,
                                         0x67332667ffc00b31ULL,0x8eb44a8768581511ULL,0xdb0c2e0d64f98fa7ULL,0x47b5481dbefa4fa4ULL};
            return sha512family(d,iv,6);
        }

        // GNU *sum front end shared by the digests above
        int digestMain(const char *prog,const char *tagName,std::function<std::string(const std::string&)> fn,int argc,const char **argv){
            bool check=false,tag=false,quiet=false,status=false,warn=false,strict=false,zero=false;
            text::Opts o=text::parseOpts(argc,argv,"",[&](char c,const std::string &)->bool{
                switch(c){
                    case 'c': check=true; return true;
                    case 'b': case 't': return true;
                    case 'w': warn=true; return true;
                    case 'z': zero=true; return true;
                    default: return text::badOpt(prog,c);
                }
            },[&](const std::string &n,const std::string &,bool)->int{
                if(n=="check"){ check=true; return 0; }
                if(n=="tag"){ tag=true; return 0; }
                if(n=="quiet"){ quiet=true; return 0; }
                if(n=="status"){ status=true; return 0; }
                if(n=="warn"){ warn=true; return 0; }
                if(n=="strict"){ strict=true; return 0; }
                if(n=="zero"){ zero=true; return 0; }
                if(n=="binary" || n=="text" || n=="ignore-missing") return 0;
                return text::badLong(prog,n);
            });
            if(!o.ok) return 1;
            if(o.operands.empty()) o.operands.push_back("-");
            text::Out out;
            int rc=0;
            size_t hexLen=fn("").size();
            if(!check){
                for(auto &f: o.operands){
                    std::string d;
                    if(!text::readInput(f,d)){ out.flush(); std::cerr << prog << ": " << f << ": " << strerror(errno) << std::endl; rc=1; continue; }
                    if(tag) out << tagName << " (" << f << ") = " << fn(d) << (zero ? '\0' : '\n');
                    else out << fn(d) << "  " << f << (zero ? '\0' : '\n');
                }
                return rc;
            }
            for(auto &list: o.operands){
                std::string d;
                if(!text::readInput(list,d)){ out.flush(); std::cerr << prog << ": " << list << ": " << strerror(errno) << std::endl; rc=1; continue; }
                long good=0,bad=0,unreadable=0,failed=0,lineNo=0;
                for(auto l: text::splitLines(d)){
                    ++lineNo;
                    if(!l.empty() && l.back()=='\n') l.pop_back();
                    if(!l.empty() && l.back()=='\r') l.pop_back();
                    if(l.empty()) continue;
                    std::string want,name;
                    std::string tagPre=std::string(tagName)+" (";
                    if(l.compare(0,tagPre.size(),tagPre)==0){
                        size_t eq=l.rfind(") = ");
                        if(eq!=std::string::npos){ name=l.substr(tagPre.size(),eq-tagPre.size()); want=l.substr(eq+4); }
                    }else if(l.size()>hexLen+2 && l[hexLen]==' ' && (l[hexLen+1]==' ' || l[hexLen+1]=='*')){
                        want=l.substr(0,hexLen); name=l.substr(hexLen+2);
                    }
                    bool hex=want.size()==hexLen && want.find_first_not_of("0123456789abcdefABCDEF")==std::string::npos;
                    if(!hex || name.empty()){
                        ++bad;
                        if(warn){ out.flush(); std::cerr << prog << ": " << list << ": " << lineNo << ": improperly formatted " << tagName << " checksum line" << std::endl; }
                        continue;
                    }
                    for(auto &c: want) c=(char)tolower((unsigned char)c);
                    ++good;
                    std::string content;
                    if(!text::readInput(name,content)){
                        out.flush();
                        std::cerr << prog << ": " << name << ": " << strerror(errno) << std::endl;
                        if(!status) out << name << ": FAILED open or read\n";
                        ++unreadable;
                        continue;
                    }
                    if(fn(content)==want){ if(!quiet && !status) out << name << ": OK\n"; }
                    else{ if(!status) out << name << ": FAILED\n"; ++failed; }
                }
                out.flush();
                if(good==0){
                    std::cerr << prog << ": " << list << ": no properly formatted checksum lines found" << std::endl;
                    rc=1; continue;
                }
                if(!status){
                    if(bad) std::cerr << prog << ": WARNING: " << bad << " line" << (bad==1 ? " is" : "s are") << " improperly formatted" << std::endl;
                    if(unreadable) std::cerr << prog << ": WARNING: " << unreadable << " listed file" << (unreadable==1 ? "" : "s") << " could not be read" << std::endl;
                    if(failed) std::cerr << prog << ": WARNING: " << failed << " computed checksum" << (failed==1 ? " did" : "s did") << " NOT match" << std::endl;
                }
                if(unreadable || failed || (strict && bad)) rc=1;
            }
            return rc;
        }

        uint32_t crcTable[256];
        bool crcInit=false;
        uint32_t posixCksum(const std::string &d){
            if(!crcInit){
                for(uint32_t i=0;i<256;++i){
                    uint32_t c=i<<24;
                    for(int k=0;k<8;++k) c=(c&0x80000000U) ? (c<<1)^0x04C11DB7U : c<<1;
                    crcTable[i]=c;
                }
                crcInit=true;
            }
            uint32_t crc=0;
            for(unsigned char c: d) crc=(crc<<8)^crcTable[((crc>>24)^c)&0xff];
            for(uint64_t n=d.size();n;n>>=8) crc=(crc<<8)^crcTable[((crc>>24)^(n&0xff))&0xff];
            return ~crc;
        }
    }

    class Sha224sumCommand : public ShellCommand {
    public:
        Sha224sumCommand():ShellCommand("sha224sum"){}
        int runCommand(int argc,const char **argv){ return digestMain("sha224sum","SHA224",sha224,argc,argv); }
    };
    class Sha384sumCommand : public ShellCommand {
    public:
        Sha384sumCommand():ShellCommand("sha384sum"){}
        int runCommand(int argc,const char **argv){ return digestMain("sha384sum","SHA384",sha384,argc,argv); }
    };
    class Sha512sumCommand : public ShellCommand {
    public:
        Sha512sumCommand():ShellCommand("sha512sum"){}
        int runCommand(int argc,const char **argv){ return digestMain("sha512sum","SHA512",sha512,argc,argv); }
    };

    // cksum [FILE...]: POSIX CRC, byte count, name
    class CksumCommand : public ShellCommand {
    public:
        CksumCommand():ShellCommand("cksum"){}
        int runCommand(int argc,const char **argv){
            std::vector<std::string> files;
            for(int i=1;i<argc;++i){
                std::string a=argv[i];
                if(a=="-a" || a.compare(0,12,"--algorithm=")==0){
                    std::string alg=a=="-a" ? (i+1<argc ? argv[++i] : "") : a.substr(12);
                    if(alg=="sha512") return digestMain("cksum","SHA512",sha512,argc-i,argv+i);
                    if(alg!="crc"){ std::cerr << "cksum: unsupported algorithm '" << alg << "'" << std::endl; return 1; }
                    continue;
                }
                files.push_back(a);
            }
            bool noName=files.empty();
            if(noName) files.push_back("-");
            text::Out out;
            int rc=0;
            for(auto &f: files){
                std::string d;
                if(!text::readInput(f,d)){ out.flush(); std::cerr << "cksum: " << f << ": " << strerror(errno) << std::endl; rc=1; continue; }
                out << std::to_string(posixCksum(d)) << " " << std::to_string(d.size());
                if(!noName) out << " " << f;
                out << "\n";
            }
            return rc;
        }
    };

    // sum [-r|-s] [FILE...]: BSD (default) or System V checksum
    class SumCommand : public ShellCommand {
    public:
        SumCommand():ShellCommand("sum"){}
        int runCommand(int argc,const char **argv){
            bool sysv=false;
            std::vector<std::string> files;
            for(int i=1;i<argc;++i){
                std::string a=argv[i];
                if(a=="-s" || a=="--sysv") sysv=true;
                else if(a=="-r") sysv=false;
                else files.push_back(a);
            }
            bool showName=!files.empty();
            if(files.empty()) files.push_back("-");
            text::Out out;
            int rc=0;
            for(auto &f: files){
                std::string d;
                if(!text::readInput(f,d)){ out.flush(); std::cerr << "sum: " << f << ": " << strerror(errno) << std::endl; rc=1; continue; }
                char b[64];
                if(sysv){
                    uint32_t s=0;
                    for(unsigned char c: d) s+=c;
                    uint32_t r=(s&0xffff)+(s>>16);
                    r=(r&0xffff)+(r>>16);
                    snprintf(b,sizeof b,"%u %llu",r,(unsigned long long)((d.size()+511)/512));
                }else{
                    uint32_t ck=0;
                    for(unsigned char c: d){ ck=(ck>>1)+((ck&1)<<15); ck+=c; ck&=0xffff; }
                    snprintf(b,sizeof b,"%05u %5llu",ck,(unsigned long long)((d.size()+1023)/1024));
                }
                out << b;
                if(showName) out << " " << f;
                out << "\n";
            }
            return rc;
        }
    };

    FRESH_REGISTER_COMMAND(Sha224sumCommand);
    FRESH_REGISTER_COMMAND(Sha384sumCommand);
    FRESH_REGISTER_COMMAND(Sha512sumCommand);
    FRESH_REGISTER_COMMAND(CksumCommand);
    FRESH_REGISTER_COMMAND(SumCommand);

}}
+460 −0

File added.

Preview size limit exceeded, changes collapsed.