// meta4ra -- Metalink 4.0 utilities // Copyright (C) 2021-2024 Sergey Matveev // // This program 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, version 3 of the License. // // This program 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 this program. If not, see . package main import ( "bufio" "encoding/xml" "flag" "fmt" "io" "io/fs" "log" "os" "path" "strings" meta4ra "go.stargrave.org/meta4ra/internal" ) func runCheck() { stdin := flag.Bool("stdin", false, "Compare data of single file taken from stdin") allHashes := flag.Bool("all-hashes", false, "Check all hashes, not the first common one") hashes := flag.String("hashes", meta4ra.HashesDefault, "hash-name:commandline[,...]") extractSig := flag.Bool("extract-sig", false, "Extract signature files") metaPath := flag.String("meta4", "file.meta4", "Metalink file") flag.Usage = func() { fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] [FILE ...]\n", os.Args[0]) flag.PrintDefaults() fmt.Fprint(flag.CommandLine.Output(), ` If no FILEs are specified, then all s from metalink are searched and verified if they exist. Otherwise only specified FILEs are checked. If you want to skip any verification (for example only to validate the format and -extract-sig, then you can just specify an empty ("") FILE. `) } flag.Parse() data, err := os.ReadFile(*metaPath) if err != nil { log.Fatalln(err) } var meta meta4ra.Metalink err = xml.Unmarshal(data, &meta) if err != nil { log.Fatalln(err) } toCheck := make(map[string]string) for _, fn := range flag.Args() { toCheck[path.Base(fn)] = fn } if *stdin && len(toCheck) != 1 { log.Fatalln("exactly single FILE must be specified when using -stdin") } bad := false for _, f := range meta.Files { for _, sig := range f.Signature { if !*extractSig { continue } var fn string switch sig.MediaType { case meta4ra.SigMediaTypePGP: fn = f.Name + ".asc" case meta4ra.SigMediaTypeSSH: fn = f.Name + ".sig" } if fn == "" { continue } if err = os.WriteFile( fn, []byte(strings.TrimPrefix(sig.Signature, "\n")), fs.FileMode(0666), ); err != nil { fmt.Println("Error:", f.Name, "can not save signature:", err) bad = true } } fullPath := toCheck[f.Name] delete(toCheck, f.Name) if !(len(toCheck) == 0 || fullPath != "") { continue } if fullPath == "" { fullPath = f.Name } if !*stdin { s, err := os.Stat(fullPath) if err != nil { fmt.Println(err) bad = true continue } if uint64(s.Size()) != f.Size { fmt.Println("size mismatch", f.Name, "our:", s.Size(), "their:", f.Size) bad = true continue } } hasher, err := meta4ra.NewHasher(*hashes) if err != nil { fmt.Println(f.Name, err) bad = true continue } var hashTheir string var hashName string if *allHashes { goto HashFound } for i, name := range hasher.Names { for _, h := range f.Hashes { if h.Type == name { hasher.Names = []string{name} hasher.Cmds = append(hasher.Cmds[:0], hasher.Cmds[i]) hasher.Ins = append(hasher.Ins[:0], hasher.Ins[i]) hasher.Outs = append(hasher.Outs[:0], hasher.Outs[i]) hashName = name hashTheir = h.Hash goto HashFound } } } fmt.Println("no common hashes found for:", f.Name) bad = true continue HashFound: fd := os.Stdin if !*stdin { fd, err = os.Open(fullPath) if err != nil { fmt.Println("Error:", f.Name, err) bad = true continue } } err = hasher.Start() if err != nil { if !*stdin { fd.Close() } hasher.Stop() fmt.Println("Error:", f.Name, err) bad = true continue } _, err = io.Copy(hasher, bufio.NewReaderSize(fd, meta4ra.BufLen)) if !*stdin { fd.Close() } if err != nil { hasher.Stop() fmt.Println("Error:", f.Name, err) bad = true continue } sums, err := hasher.Sums() if err != nil { hasher.Stop() fmt.Println("Error:", f.Name, err) bad = true continue } if *allHashes { hashesOur := make(map[string]string, len(sums)) for _, h := range sums { hashesOur[h.Type] = h.Hash } for _, h := range f.Hashes { hashOur := hashesOur[h.Type] if h.Hash == hashOur { fmt.Println(f.Name, h.Type, "good") } else { fmt.Println( "hash mismatch:", f.Name, h.Type, "our:", hashOur, "their:", h.Hash, ) bad = true } } } else { hashOur := sums[0].Hash if hashOur == hashTheir { fmt.Println(f.Name, hashName, "good") } else { fmt.Println( "hash mismatch:", f.Name, hashName, "our:", hashOur, "their:", hashTheir, ) bad = true continue } } } if len(toCheck) != 0 { fmt.Println("not all FILEs met") bad = true } if bad { os.Exit(1) } }