Browse Source

Implement md5sum

dtluna 5 months ago
parent
commit
7d6c06b251
7 changed files with 273 additions and 135 deletions
  1. 1
    1
      README.md
  2. 1
    1
      error.go
  3. 1
    0
      go.sum
  4. 199
    0
      hashsum.go
  5. 69
    0
      hashsum_test.go
  6. 1
    133
      md5sum/md5sum.go
  7. 1
    0
      test_data/some_file.txt

+ 1
- 1
README.md View File

@@ -45,7 +45,7 @@ Utilities:
45 45
  * [ ] `logger`
46 46
  * [ ] `logname`
47 47
  * [ ] `ls`
48
- * [ ] `md5sum`
48
+ * [x] `md5sum`
49 49
  * [ ] `mkdir`
50 50
  * [ ] `mkfifo`
51 51
  * [ ] `mktemp`

+ 1
- 1
error.go View File

@@ -1,4 +1,4 @@
1
-package common
1
+package coreutils
2 2
 
3 3
 import (
4 4
 	"fmt"

+ 1
- 0
go.sum View File

@@ -4,6 +4,7 @@ github.com/alexflint/go-scalar v1.0.0 h1:NGupf1XV/Xb04wXskDFzS0KWOLH632W/EO4fAFi
4 4
 github.com/alexflint/go-scalar v1.0.0/go.mod h1:GpHzbCOZXEKMEcygYQ5n/aa4Aq84zbxjy3MxYW0gjYw=
5 5
 github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
6 6
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
7 8
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8 9
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
9 10
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=

+ 199
- 0
hashsum.go View File

@@ -0,0 +1,199 @@
1
+package coreutils
2
+
3
+import (
4
+	"bufio"
5
+	"crypto/md5"
6
+	"errors"
7
+	"fmt"
8
+	"hash"
9
+	"io"
10
+	"os"
11
+	"regexp"
12
+
13
+	"github.com/alexflint/go-arg"
14
+)
15
+
16
+var MD5Regex = regexp.MustCompile("^(?P<hash>[0-9a-f]{32})  (?P<filename>.*)$")
17
+
18
+func MD5Sum(r io.Reader) (hash.Hash, error) {
19
+	h := md5.New()
20
+	if _, err := io.Copy(h, r); err != nil {
21
+		return nil, err
22
+	}
23
+	return h, nil
24
+}
25
+
26
+type CheckingResults struct {
27
+	ImproperlyFormattedCount uint
28
+	InvalidChecksumCount     uint
29
+	FilesNotRead             uint
30
+}
31
+
32
+// SumFunc is a type of function that computes a hash.Hash for data in io.Reader
33
+type SumFunc func(io.Reader) (hash.Hash, error)
34
+
35
+// ImproperlyFormattedErr is an error return when a line for checking
36
+// has an incorrect number or set of characters
37
+var ImproperlyFormattedErr = errors.New("improperly formatted line")
38
+
39
+func printHash(h hash.Hash, filename string) {
40
+	fmt.Printf("%x  %v\n", h.Sum(nil), filename)
41
+}
42
+
43
+// PrintSumForFile prints checksum computed by f for file with filename
44
+func PrintSumForFile(filename string, f SumFunc) error {
45
+	file, err := os.Open(filename)
46
+	if err != nil {
47
+		return err
48
+	}
49
+	defer file.Close()
50
+
51
+	h, err := f(file)
52
+	if err != nil {
53
+		return err
54
+	}
55
+
56
+	printHash(h, filename)
57
+	return nil
58
+}
59
+
60
+// PrintSumForStdin prints checksum computed by f for data from stdin
61
+func PrintSumForStdin(f SumFunc) {
62
+	h, err := f(os.Stdin)
63
+	if err != nil {
64
+		PrintToStderr(err)
65
+		os.Exit(1)
66
+	}
67
+	printHash(h, "<stdin>")
68
+	os.Exit(0)
69
+}
70
+
71
+func checkLine(line string, re *regexp.Regexp, f SumFunc) (valid bool, err error) {
72
+	matches := re.MatchString(line)
73
+	if !matches {
74
+		err = ImproperlyFormattedErr
75
+		valid = false
76
+		return
77
+	}
78
+	submatches := re.FindStringSubmatch(line)
79
+	hash := submatches[1]
80
+	filename := submatches[2]
81
+
82
+	file, err := os.Open(filename)
83
+	if err != nil {
84
+		valid = false
85
+		return
86
+	}
87
+	defer file.Close()
88
+	h, err := f(file)
89
+	if err != nil {
90
+		valid = false
91
+		return
92
+	}
93
+
94
+	result := "OK"
95
+	valid = true
96
+	if fmt.Sprintf("%x", h.Sum(nil)) != hash {
97
+		result = "FAILED"
98
+		valid = false
99
+	}
100
+	fmt.Printf("%s: %s\n", filename, result)
101
+
102
+	return
103
+}
104
+
105
+// CheckSumsInReader checks checksums in r using f
106
+func CheckSumsInReader(r io.Reader, re *regexp.Regexp, f SumFunc) (*CheckingResults, error) {
107
+	scanner := bufio.NewScanner(r)
108
+
109
+	results := CheckingResults{}
110
+	for scanner.Scan() {
111
+		valid, err := checkLine(scanner.Text(), re, f)
112
+		if err == ImproperlyFormattedErr {
113
+			results.ImproperlyFormattedCount++
114
+		} else if err != nil {
115
+			PrintToStderr(err)
116
+			results.FilesNotRead++
117
+		} else if !valid {
118
+			results.InvalidChecksumCount++
119
+		}
120
+	}
121
+	return &results, scanner.Err()
122
+}
123
+
124
+// CheckSumsInFile checks checksums in file with filename using f
125
+func CheckSumsInFile(filename string, re *regexp.Regexp, f SumFunc) (*CheckingResults, error) {
126
+	file, err := os.Open(filename)
127
+	if err != nil {
128
+		return nil, err
129
+	}
130
+	defer file.Close()
131
+
132
+	return CheckSumsInReader(file, re, f)
133
+}
134
+
135
+// PrintCheckingResults prints the number of mismatched checksums and improperly formatted lines to os.Stderr
136
+func PrintCheckingResults(results CheckingResults) {
137
+	if results.InvalidChecksumCount > 0 {
138
+		PrintToStderr(
139
+			fmt.Sprintf("%v computed checksums did NOT match", results.InvalidChecksumCount),
140
+		)
141
+	}
142
+	if results.ImproperlyFormattedCount > 0 {
143
+		PrintToStderr(
144
+			fmt.Sprintf("%v lines are improperly formatted", results.ImproperlyFormattedCount),
145
+		)
146
+	}
147
+	if results.FilesNotRead > 0 {
148
+		PrintToStderr(
149
+			fmt.Sprintf("%v listed files could not be read", results.FilesNotRead),
150
+		)
151
+	}
152
+}
153
+
154
+// SumMain is a function you're supposed to run in main of a hash sum utility
155
+func SumMain(re *regexp.Regexp, f SumFunc) {
156
+	var args struct {
157
+		Check bool     `arg:"-c"`
158
+		Files []string `arg:"positional"`
159
+	}
160
+
161
+	arg.MustParse(&args)
162
+
163
+	exitCode := 0
164
+	if len(args.Files) == 0 {
165
+		if !args.Check {
166
+			PrintSumForStdin(f)
167
+		} else {
168
+			results, err := CheckSumsInReader(os.Stdin, re, f)
169
+			if err != nil {
170
+				PrintToStderr(err)
171
+				exitCode = 1
172
+			}
173
+			PrintCheckingResults(*results)
174
+		}
175
+	}
176
+
177
+	totalResults := CheckingResults{}
178
+	for _, filename := range args.Files {
179
+		if !args.Check {
180
+			err := PrintSumForFile(filename, f)
181
+			if err != nil {
182
+				PrintToStderr(err)
183
+				exitCode = 1
184
+			}
185
+		} else {
186
+			results, err := CheckSumsInFile(filename, re, f)
187
+			if err != nil {
188
+				PrintToStderr(err)
189
+				exitCode = 1
190
+			}
191
+			if results != nil {
192
+				totalResults.ImproperlyFormattedCount += results.ImproperlyFormattedCount
193
+				totalResults.InvalidChecksumCount += results.InvalidChecksumCount
194
+			}
195
+		}
196
+	}
197
+	PrintCheckingResults(totalResults)
198
+	os.Exit(exitCode)
199
+}

+ 69
- 0
hashsum_test.go View File

@@ -0,0 +1,69 @@
1
+package coreutils
2
+
3
+import (
4
+	"bytes"
5
+	"github.com/stretchr/testify/assert"
6
+	"github.com/stretchr/testify/suite"
7
+	"path"
8
+	"strings"
9
+	"testing"
10
+)
11
+
12
+type CheckLineTestSuite struct {
13
+	suite.Suite
14
+}
15
+
16
+func (suite *CheckLineTestSuite) TestImproperlyFormattedLine() {
17
+	t := suite.T()
18
+	valid, err := checkLine("name chef", MD5Regex, MD5Sum)
19
+	assert.Equal(t, false, valid)
20
+	assert.Error(t, err)
21
+	assert.Equal(t, ImproperlyFormattedErr, err)
22
+}
23
+
24
+func (suite *CheckLineTestSuite) TestMismatchedChecksum() {
25
+	t := suite.T()
26
+	line := "4a5fb9ebd6c8ea7efb53d071053ef778  " + path.Join("test_data", "some_file.txt")
27
+	valid, err := checkLine(line, MD5Regex, MD5Sum)
28
+	assert.Equal(t, false, valid)
29
+	assert.NoError(t, err)
30
+}
31
+
32
+func (suite *CheckLineTestSuite) TestValid() {
33
+	t := suite.T()
34
+	line := "6a5fb9ebd6c8ea7efb53d071053ef778  " + path.Join("test_data", "some_file.txt")
35
+	valid, err := checkLine(line, MD5Regex, MD5Sum)
36
+	assert.Equal(t, true, valid)
37
+	assert.NoError(t, err)
38
+}
39
+
40
+func TestCheckLineTestSuite(t *testing.T) {
41
+	suite.Run(t, new(CheckLineTestSuite))
42
+}
43
+
44
+type CheckSumsInReaderTestSuite struct {
45
+	suite.Suite
46
+}
47
+
48
+func (suite *CheckSumsInReaderTestSuite) Test() {
49
+	t := suite.T()
50
+	buf := bytes.NewBufferString(strings.Join(
51
+		[]string{
52
+			"name chef",
53
+			"4a5fb9ebd6c8ea7efb53d071053ef778  " + path.Join("test_data", "some_file.txt"),
54
+			"6a5fb9ebd6c8ea7efb53d071053ef778  " + path.Join("test_data", "some_file.txt"),
55
+			"4a5fb9ebd6c8ea7efb53d071053ef778  nonexistant_file",
56
+		},
57
+		"\n",
58
+	))
59
+	results, err := CheckSumsInReader(buf, MD5Regex, MD5Sum)
60
+	assert.NotNil(t, results)
61
+	assert.Equal(t, uint(1), results.ImproperlyFormattedCount)
62
+	assert.Equal(t, uint(1), results.FilesNotRead)
63
+	assert.Equal(t, uint(1), results.InvalidChecksumCount)
64
+	assert.NoError(t, err)
65
+}
66
+
67
+func TestCheckSumsInReaderTestSuite(t *testing.T) {
68
+	suite.Run(t, new(CheckSumsInReaderTestSuite))
69
+}

+ 1
- 133
md5sum/md5sum.go View File

@@ -1,141 +1,9 @@
1 1
 package main
2 2
 
3 3
 import (
4
-	"bufio"
5
-	"crypto/md5"
6
-	"errors"
7
-	"fmt"
8
-	"hash"
9
-	"io"
10
-	"os"
11
-	"strings"
12
-
13
-	"github.com/alexflint/go-arg"
14 4
 	common "source.heropunch.io/tomo/go-coreutils"
15 5
 )
16 6
 
17
-var ImproperlyFormattedErr = errors.New("improperly formatted line")
18
-
19
-func printSumForFile(filename string, sumFunc func(io.Reader) (hash.Hash, error)) error {
20
-	file, err := os.Open(filename)
21
-	if err != nil {
22
-		return err
23
-	}
24
-	defer file.Close()
25
-
26
-	h, err := sumFunc(file)
27
-	if err != nil {
28
-		return err
29
-	}
30
-
31
-	printHash(h, filename)
32
-	return nil
33
-}
34
-
35
-func printSumForStdin(sumFunc func(io.Reader) (hash.Hash, error)) {
36
-	h, err := sumFunc(os.Stdin)
37
-	if err != nil {
38
-		common.PrintToStderr(err)
39
-		os.Exit(1)
40
-	}
41
-	printHash(h, "<stdin>")
42
-	os.Exit(0)
43
-}
44
-
45
-func md5sum(r io.Reader) (hash.Hash, error) {
46
-	h := md5.New()
47
-	if _, err := io.Copy(h, r); err != nil {
48
-		return nil, err
49
-	}
50
-	return h, nil
51
-}
52
-
53
-func printHash(h hash.Hash, filename string) {
54
-	fmt.Printf("%x  %v\n", h.Sum(nil), filename)
55
-}
56
-
57
-func checkSumForFile(filename string) error {
58
-	file, err := os.Open(filename)
59
-	if err != nil {
60
-		return err
61
-	}
62
-	defer file.Close()
63
-
64
-	return checkSums(file)
65
-}
66
-
67
-func checkSums(r io.Reader) error {
68
-	scanner := bufio.NewScanner(r)
69
-
70
-	for scanner.Scan() {
71
-		err := checkMD5Sum(scanner.Text())
72
-		if err != nil {
73
-			return err
74
-		}
75
-	}
76
-	return scanner.Err()
77
-}
78
-
79
-func checkMD5Sum(line string) error {
80
-	s := strings.SplitN(line, "  ", 2)
81
-	hash := s[0]
82
-	filename := s[1]
83
-
84
-	if filename == "" {
85
-		return ImproperlyFormattedErr
86
-	}
87
-
88
-	file, err := os.Open(filename)
89
-	if err != nil {
90
-		return err
91
-	}
92
-	defer file.Close()
93
-
94
-	h, err := md5sum(file)
95
-	if err != nil {
96
-		return err
97
-	}
98
-	result := "OK"
99
-	if fmt.Sprintf("%x", h.Sum(nil)) != hash {
100
-		result = "FAILED"
101
-	}
102
-	fmt.Printf("%s: %s\n", filename, result)
103
-
104
-	return nil
105
-}
106
-
107 7
 func main() {
108
-	var args struct {
109
-		Check bool     `arg:"-c"`
110
-		Files []string `arg:"positional"`
111
-	}
112
-
113
-	arg.MustParse(&args)
114
-
115
-	exitCode := 0
116
-	if len(args.Files) == 0 {
117
-		if !args.Check {
118
-			printSumForStdin(md5sum)
119
-		} else {
120
-			fmt.Println("Checking from stdin")
121
-		}
122
-	}
123
-
124
-	for _, filename := range args.Files {
125
-		if !args.Check {
126
-			err := printSumForFile(filename, md5sum)
127
-			if err != nil {
128
-				common.PrintToStderr(err)
129
-				exitCode = 1
130
-			}
131
-		} else {
132
-			err := checkSumForFile(filename)
133
-			if err != nil {
134
-				common.PrintToStderr(err)
135
-				exitCode = 1
136
-			}
137
-		}
138
-	}
139
-
140
-	os.Exit(exitCode)
8
+	common.SumMain(common.MD5Regex, common.MD5Sum)
141 9
 }

+ 1
- 0
test_data/some_file.txt View File

@@ -0,0 +1 @@
1
+blah blah

Loading…
Cancel
Save