github.com/rsc/tmp@v0.0.0-20240517235954-6deaab19748b/dirhash/main.go (about)

     1  // Copyright 2017 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Dirhash computes a hash of a file system directory tree.
     6  //
     7  // Usage:
     8  //
     9  //	dirhash [-d] [dir ...]
    10  //
    11  // For each directory named on the command line, dirhash prints
    12  // the hash of the file system tree rooted at that directory.
    13  //
    14  // The hash is computed by considering all files in the tree,
    15  // in the lexical order used by Go's filepath.Walk, computing
    16  // the sha256 hash of each, and then computing a sha256 of
    17  // the list of hashes and file names. If the -d flag is given,
    18  // dirhash prints to standard error a shell script computing
    19  // the overall sha256.
    20  //
    21  // Except for occasional differences in sort order, "dirhash mydir"
    22  // is equivalent to
    23  //
    24  //	(cd mydir; sha256sum $(find . -type f | sort) | sha256sum)
    25  //
    26  package main
    27  
    28  import (
    29  	"crypto/sha256"
    30  	"flag"
    31  	"fmt"
    32  	"io"
    33  	"log"
    34  	"os"
    35  	"path/filepath"
    36  )
    37  
    38  func usage() {
    39  	fmt.Fprintf(os.Stderr, "usage: dirhash [-d] [dir...]\n")
    40  	os.Exit(2)
    41  }
    42  
    43  var debug = flag.Bool("d", false, "print input for overall sha256sum")
    44  
    45  func main() {
    46  	log.SetFlags(0)
    47  	log.SetPrefix("dirhash: ")
    48  	flag.Usage = usage
    49  	flag.Parse()
    50  
    51  	args := flag.Args()
    52  	if len(args) == 0 {
    53  		args = []string{"."}
    54  	}
    55  
    56  	for _, dir := range args {
    57  		dirhash(dir)
    58  	}
    59  }
    60  
    61  func dirhash(dir string) {
    62  	dir = filepath.Clean(dir)
    63  	h := sha256.New()
    64  	info, err := os.Lstat(dir)
    65  	if err == nil && info.Mode()&os.ModeSymlink != 0 {
    66  		log.Printf("%s is a symlink\n", dir)
    67  		return
    68  	}
    69  	if *debug {
    70  		fmt.Fprintf(os.Stderr, "sha256sum << 'EOF'\n")
    71  	}
    72  	filepath.Walk(dir, func(file string, info os.FileInfo, err error) error {
    73  		if info.Mode()&os.ModeSymlink != 0 {
    74  			i, err := os.Stat(file)
    75  			if err != nil {
    76  				return err
    77  			}
    78  			info = i
    79  		}
    80  		if info.IsDir() {
    81  			return nil
    82  		}
    83  		rel := file
    84  		if dir != "." {
    85  			rel = file[len(dir)+1:]
    86  		}
    87  		rel = filepath.ToSlash(rel)
    88  		fh := filehash(file)
    89  		if *debug {
    90  			fmt.Fprintf(os.Stderr, "%s  ./%s\n", fh, rel)
    91  		}
    92  		fmt.Fprintf(h, "%s  ./%s\n", fh, rel)
    93  		return nil
    94  	})
    95  	if *debug {
    96  		fmt.Fprintf(os.Stderr, "EOF\n")
    97  	}
    98  	fmt.Printf("%x %s\n", h.Sum(nil), dir)
    99  }
   100  
   101  func filehash(file string) string {
   102  	h := sha256.New()
   103  	f, err := os.Open(file)
   104  	if err != nil {
   105  		log.Print(err)
   106  	}
   107  	_, err = io.Copy(h, f)
   108  	if err != nil {
   109  		log.Print(err)
   110  	}
   111  	f.Close()
   112  	return fmt.Sprintf("%x", h.Sum(nil))
   113  }