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  }