File: shame512.go
   1 /*
   2 The MIT License (MIT)
   3 
   4 Copyright © 2020-2025 pacman64
   5 
   6 Permission is hereby granted, free of charge, to any person obtaining a copy of
   7 this software and associated documentation files (the “Software”), to deal
   8 in the Software without restriction, including without limitation the rights to
   9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  10 of the Software, and to permit persons to whom the Software is furnished to do
  11 so, subject to the following conditions:
  12 
  13 The above copyright notice and this permission notice shall be included in all
  14 copies or substantial portions of the Software.
  15 
  16 THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22 SOFTWARE.
  23 */
  24 
  25 /*
  26 Single-file source-code for shame512.
  27 
  28 To compile a smaller-sized command-line app, you can use the `go` command as
  29 follows:
  30 
  31 go build -ldflags "-s -w" -trimpath shame512.go
  32 */
  33 
  34 package main
  35 
  36 import (
  37     "crypto/sha512"
  38     "encoding/hex"
  39     "io"
  40     "io/fs"
  41     "os"
  42     "path/filepath"
  43 )
  44 
  45 func main() {
  46     got := make(map[string]struct{})
  47     checksums := make(map[string][]string)
  48     for _, path := range os.Args[1:] {
  49         check(path, checksums, got)
  50     }
  51     if len(os.Args) < 2 {
  52         check(`.`, checksums, got)
  53     }
  54 
  55     shown := 0
  56 
  57     for chsum, files := range checksums {
  58         if len(files) < 2 {
  59             continue
  60         }
  61 
  62         if shown > 0 {
  63             os.Stdout.WriteString("\n")
  64         }
  65 
  66         for _, path := range files {
  67             os.Stdout.WriteString(chsum)
  68             os.Stdout.WriteString("\t")
  69             os.Stdout.WriteString(path)
  70             os.Stdout.WriteString("\n")
  71         }
  72 
  73         shown++
  74     }
  75 }
  76 
  77 func showError(path string, err error) {
  78     os.Stderr.WriteString("\x1b[31m")
  79     os.Stderr.WriteString(path)
  80     os.Stderr.WriteString(": ")
  81     os.Stderr.WriteString(err.Error())
  82     os.Stderr.WriteString("\x1b[0m\n")
  83 }
  84 
  85 func check(path string, sums map[string][]string, got map[string]struct{}) {
  86     if _, ok := got[path]; ok {
  87         return
  88     }
  89     got[path] = struct{}{}
  90 
  91     st, err := os.Stat(path)
  92     if err != nil {
  93         showError(path, err)
  94         return
  95     }
  96 
  97     if st.IsDir() {
  98         checkFolder(path, sums, got)
  99         return
 100     }
 101 
 102     chsum, err := sha(path)
 103     if err != nil {
 104         showError(path, err)
 105         return
 106     }
 107 
 108     sums[chsum] = append(sums[chsum], path)
 109 }
 110 
 111 func checkFolder(root string, sums map[string][]string, got map[string]struct{}) {
 112     filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
 113         if err != nil {
 114             return err
 115         }
 116 
 117         if d.IsDir() {
 118             return nil
 119         }
 120 
 121         if _, ok := got[path]; ok {
 122             return nil
 123         }
 124 
 125         check(path, sums, got)
 126         got[path] = struct{}{}
 127         return nil
 128     })
 129 }
 130 
 131 func sha(path string) (string, error) {
 132     f, err := os.Open(path)
 133     if err != nil {
 134         return ``, err
 135     }
 136     defer f.Close()
 137 
 138     sha := sha512.New()
 139     _, err = io.Copy(sha, f)
 140     if err != nil {
 141         return ``, err
 142     }
 143 
 144     return hex.EncodeToString(sha.Sum(nil)), err
 145 }