github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/cmds/core/cmp/cmp.go (about) 1 // Copyright 2013-2017 the u-root 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 // cmp compares two files and prints a message if their contents differ. 6 // 7 // Synopsis: 8 // 9 // cmp [–lLs] FILE1 FILE2 [OFFSET1 [OFFSET2]] 10 // 11 // Description: 12 // 13 // If offsets are given, comparison starts at the designated byte position 14 // of the corresponding file. 15 // 16 // Offsets that begin with 0x are hexadecimal; with 0, octal; with anything 17 // else, decimal. 18 // 19 // Options: 20 // 21 // –l: Print the byte number (decimal) and the differing bytes (octal) for 22 // each difference. 23 // –L: Print the line number of the first differing byte. 24 // –s: Print nothing for differing files, but set the exit status. 25 package main 26 27 import ( 28 "bufio" 29 "flag" 30 "fmt" 31 "io" 32 "log" 33 "os" 34 35 "github.com/rck/unit" 36 ) 37 38 var ( 39 long = flag.Bool("l", false, "print the byte number (decimal) and the differing bytes (hexadecimal) for each difference") 40 line = flag.Bool("L", false, "print the line number of the first differing byte") 41 silent = flag.Bool("s", false, "print nothing for differing files, but set the exit status") 42 ) 43 44 func emit(rs io.ReadSeeker, c chan byte, offset int64) error { 45 if offset > 0 { 46 if _, err := rs.Seek(offset, 0); err != nil { 47 log.Fatalf("%v", err) 48 } 49 } 50 51 b := bufio.NewReader(rs) 52 for { 53 b, err := b.ReadByte() 54 if err != nil { 55 close(c) 56 return err 57 } 58 c <- b 59 } 60 } 61 62 func readFileOrStdin(stdin *os.File, name string) (*os.File, error) { 63 var f *os.File 64 var err error 65 66 if name == "-" { 67 f = stdin 68 } else { 69 f, err = os.Open(name) 70 } 71 72 return f, err 73 } 74 75 func cmp(w io.Writer, args ...string) error { 76 var offset [2]int64 77 var f *os.File 78 var err error 79 80 cmpUnits := unit.DefaultUnits 81 82 off, err := unit.NewUnit(cmpUnits) 83 if err != nil { 84 return fmt.Errorf("could not create unit based on mapping: %v", err) 85 } 86 87 var v *unit.Value 88 switch len(args) { 89 case 2: 90 case 3: 91 if v, err = off.ValueFromString(args[2]); err != nil { 92 return fmt.Errorf("bad offset1: %s: %v", args[2], err) 93 } 94 offset[0] = v.Value 95 case 4: 96 if v, err = off.ValueFromString(args[2]); err != nil { 97 return fmt.Errorf("bad offset1: %s: %v", args[2], err) 98 } 99 offset[0] = v.Value 100 101 if v, err = off.ValueFromString(args[3]); err != nil { 102 return fmt.Errorf("bad offset2: %s: %v", args[3], err) 103 } 104 offset[1] = v.Value 105 default: 106 return fmt.Errorf("expected two filenames (and one to two optional offsets), got %d", len(args)) 107 } 108 109 c := make([]chan byte, 2) 110 111 for i := 0; i < 2; i++ { 112 if f, err = readFileOrStdin(os.Stdin, args[i]); err != nil { 113 return fmt.Errorf("failed to open %s: %v", args[i], err) 114 } 115 c[i] = make(chan byte, 8192) 116 go emit(f, c[i], offset[i]) 117 } 118 119 lineno, charno := int64(1), int64(1) 120 var b1, b2 byte 121 for { 122 b1 = <-c[0] 123 b2 = <-c[1] 124 125 if b1 != b2 { 126 if *silent { 127 return nil 128 } 129 if *line { 130 return fmt.Errorf("%s %s differ: char %d line %d", args[0], args[1], charno, lineno) 131 } 132 if *long { 133 if b1 == '\u0000' { 134 return fmt.Errorf("EOF on %s", args[0]) 135 } 136 if b2 == '\u0000' { 137 return fmt.Errorf("EOF on %s", args[1]) 138 } 139 fmt.Fprintf(w, "%8d %#.2o %#.2o\n", charno, b1, b2) 140 goto skip 141 } 142 return fmt.Errorf("%s %s differ: char %d", args[0], args[1], charno) 143 } 144 skip: 145 charno++ 146 if b1 == '\n' { 147 lineno++ 148 } 149 if b1 == '\u0000' && b2 == '\u0000' { 150 return nil 151 } 152 } 153 } 154 155 // cmp is defined to fail with exit code 2 156 func main() { 157 flag.Parse() 158 if err := cmp(os.Stderr, flag.Args()...); err != nil { 159 os.Exit(2) 160 } 161 }