// 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() { pipe := flag.Bool("pipe", false, "Verify file data from stdin, copy to stdout") 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() if *showVersion { fmt.Println(meta4ra.Version()) return } if *showWarranty { fmt.Println(meta4ra.Warranty) return } 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 *pipe && len(toCheck) != 1 { log.Fatalln("exactly single FILE must be specified when using -pipe") } 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 { log.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 !*pipe { s, err := os.Stat(fullPath) if err != nil { log.Println(err) bad = true continue } if uint64(s.Size()) != f.Size { log.Println("size mismatch", f.Name, "our:", s.Size(), "their:", f.Size) bad = true continue } } hasher, err := meta4ra.NewHasher(*hashes) if err != nil { log.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 } } } log.Println("no common hashes found for:", f.Name) bad = true continue HashFound: fd := os.Stdin if !*pipe { fd, err = os.Open(fullPath) if err != nil { log.Println("Error:", f.Name, err) bad = true continue } } err = hasher.Start() if err != nil { if !*pipe { fd.Close() } hasher.Stop() log.Println("Error:", f.Name, err) bad = true continue } var w io.Writer if *pipe { w = io.MultiWriter(os.Stdout, hasher) } else { w = hasher } _, err = io.Copy(w, bufio.NewReaderSize(fd, meta4ra.BufLen)) if !*pipe { fd.Close() } if err != nil { hasher.Stop() log.Println("Error:", f.Name, err) bad = true continue } sums, err := hasher.Sums() if err != nil { hasher.Stop() log.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 { if !*pipe { fmt.Fprintln(os.Stderr, f.Name, h.Type, "good") } } else { log.Println( "hash mismatch:", f.Name, h.Type, "our:", hashOur, "their:", h.Hash, ) bad = true } } } else { hashOur := sums[0].Hash if hashOur == hashTheir { if !*pipe { fmt.Fprintln(os.Stderr, f.Name, hashName, "good") } } else { log.Println( "hash mismatch:", f.Name, hashName, "our:", hashOur, "their:", hashTheir, ) bad = true continue } } } if len(toCheck) != 0 { if _, ok := toCheck["."]; !(len(toCheck) == 1 && ok) { log.Println("not all FILEs met") bad = true } } if bad { os.Exit(1) } }