1package main
2
3import (
4	"flag"
5	"fmt"
6	"io"
7	"log"
8	"os"
9
10	"github.com/docker/distribution/version"
11	"github.com/opencontainers/go-digest"
12
13	_ "crypto/sha256"
14	_ "crypto/sha512"
15)
16
17var (
18	algorithm   = digest.Canonical
19	showVersion bool
20)
21
22type job struct {
23	name   string
24	reader io.Reader
25}
26
27func init() {
28	flag.Var(&algorithm, "a", "select the digest algorithm (shorthand)")
29	flag.Var(&algorithm, "algorithm", "select the digest algorithm")
30	flag.BoolVar(&showVersion, "version", false, "show the version and exit")
31
32	log.SetFlags(0)
33	log.SetPrefix(os.Args[0] + ": ")
34}
35
36func usage() {
37	fmt.Fprintf(os.Stderr, "usage: %s [files...]\n", os.Args[0])
38	fmt.Fprint(os.Stderr, `
39Calculate the digest of one or more input files, emitting the result
40to standard out. If no files are provided, the digest of stdin will
41be calculated.
42
43`)
44	flag.PrintDefaults()
45}
46
47func unsupported() {
48	log.Fatalf("unsupported digest algorithm: %v", algorithm)
49}
50
51func main() {
52	var jobs []job
53
54	flag.Usage = usage
55	flag.Parse()
56	if showVersion {
57		version.PrintVersion()
58		return
59	}
60
61	var fail bool // if we fail on one item, foul the exit code
62	if flag.NArg() > 0 {
63		for _, path := range flag.Args() {
64			fp, err := os.Open(path)
65
66			if err != nil {
67				log.Printf("%s: %v", path, err)
68				fail = true
69				continue
70			}
71			defer fp.Close()
72
73			jobs = append(jobs, job{name: path, reader: fp})
74		}
75	} else {
76		// just read stdin
77		jobs = append(jobs, job{name: "-", reader: os.Stdin})
78	}
79
80	digestFn := algorithm.FromReader
81
82	if !algorithm.Available() {
83		unsupported()
84	}
85
86	for _, job := range jobs {
87		dgst, err := digestFn(job.reader)
88		if err != nil {
89			log.Printf("%s: %v", job.name, err)
90			fail = true
91			continue
92		}
93
94		fmt.Printf("%v\t%s\n", dgst, job.name)
95	}
96
97	if fail {
98		os.Exit(1)
99	}
100}
101