forked from rfjakob/cshatag
-
Notifications
You must be signed in to change notification settings - Fork 0
/
check.go
185 lines (169 loc) · 4.24 KB
/
check.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
package main
import (
"bytes"
"crypto/sha256"
"fmt"
"io"
"os"
"strconv"
"strings"
"syscall"
"github.com/pkg/xattr"
)
const xattrSha256 = "user.shatag.sha256"
const xattrTs = "user.shatag.ts"
const zeroSha256 = "0000000000000000000000000000000000000000000000000000000000000000"
type fileTimestamp struct {
s uint64
ns uint32
}
func zeroFileTimeStamp() fileTimestamp {
return fileTimestamp{
s: uint64(0),
ns: uint32(0),
}
}
func (ts *fileTimestamp) prettyPrint() string {
return fmt.Sprintf("%010d.%09d", ts.s, ts.ns)
}
type fileAttr struct {
ts fileTimestamp
sha256 []byte
}
func (a *fileAttr) prettyPrint() string {
return fmt.Sprintf("%s %s", string(a.sha256), a.ts.prettyPrint())
}
// getStoredAttr reads the stored extendend attributes from a file. The file
// should look like this:
//
// $ getfattr -d foo.txt
// user.shatag.sha256="dc9fe2260fd6748b29532be0ca2750a50f9eca82046b15497f127eba6dda90e8"
// user.shatag.ts="1560177334.020775051"
func getStoredAttr(f *os.File) (attr fileAttr, err error) {
attr.sha256 = []byte(zeroSha256)
val, err := xattr.FGet(f, xattrSha256)
if err == nil {
copy(attr.sha256, val)
}
val, err = xattr.FGet(f, xattrTs)
if err == nil {
parts := strings.SplitN(string(val), ".", 2)
attr.ts.s, _ = strconv.ParseUint(parts[0], 10, 64)
if len(parts) > 1 {
ns64, _ := strconv.ParseUint(parts[1], 10, 32)
attr.ts.ns = uint32(ns64)
}
}
return attr, nil
}
// getMtime reads the actual modification time of file "f" from disk.
func getMtime(f *os.File) (ts fileTimestamp, err error) {
fi, err := f.Stat()
if err != nil {
return
}
ts.s = uint64(fi.ModTime().Unix())
ts.ns = uint32(fi.ModTime().Nanosecond())
return
}
// getActualAttr reads the actual modification time and hashes the file content.
func getActualAttr(f *os.File) (attr fileAttr, err error) {
attr.sha256 = []byte(zeroSha256)
attr.ts, err = getMtime(f)
if err != nil {
return attr, err
}
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return attr, err
}
// Check if the file was modified while we were computing the hash
ts2, err := getMtime(f)
if err != nil {
return attr, err
} else if attr.ts != ts2 {
return attr, syscall.EINPROGRESS
}
attr.sha256 = []byte(fmt.Sprintf("%x", h.Sum(nil)))
return attr, nil
}
// printComparison prints something like this:
//
// stored: faa28bfa6332264571f28b4131b0673f0d55a31a2ccf5c873c435c235647bf76 1560177189.769244818
// actual: dc9fe2260fd6748b29532be0ca2750a50f9eca82046b15497f127eba6dda90e8 1560177334.020775051
func printComparison(stored fileAttr, actual fileAttr) {
fmt.Printf(" stored: %s\n actual: %s\n", stored.prettyPrint(), actual.prettyPrint())
}
func checkFile(fn string) {
stats.total++
f, err := os.Open(fn)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
stats.errorsOpening++
return
}
defer f.Close()
if args.remove {
if err = removeAttr(f); err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
stats.errorsOther++
return
}
if !args.q {
fmt.Printf("<removed xattr> %s\n", fn)
}
stats.ok++
return
}
stored, _ := getStoredAttr(f)
actual, err := getActualAttr(f)
if err == syscall.EINPROGRESS {
if !args.qq {
fmt.Printf("<concurrent modification> %s\n", fn)
}
stats.inprogress++
return
} else if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
stats.errorsOther++
return
}
if stored.ts == actual.ts {
if bytes.Equal(stored.sha256, actual.sha256) {
if !args.q {
fmt.Printf("<ok> %s\n", fn)
}
stats.ok++
return
}
fmt.Fprintf(os.Stderr, "Error: corrupt file %q\n", fn)
fmt.Printf("<corrupt> %s\n", fn)
stats.corrupt++
} else if bytes.Equal(stored.sha256, actual.sha256) {
if !args.qq {
fmt.Printf("<timechange> %s\n", fn)
}
stats.timechange++
} else if bytes.Equal(stored.sha256, []byte(zeroSha256)) && (stored.ts == zeroFileTimeStamp()) {
// no metadata indicates a 'new' file
if !args.qq {
fmt.Printf("<new> %s\n", fn)
}
stats.newfile++
} else {
// timestamp is outdated
if !args.qq {
fmt.Printf("<outdated> %s\n", fn)
}
stats.outdated++
}
if !args.qq {
printComparison(stored, actual)
}
err = storeAttr(f, actual)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
stats.errorsWritingXattr++
return
}
}