This repository has been archived by the owner on Dec 20, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathrcheck.py
executable file
·258 lines (243 loc) · 8.48 KB
/
rcheck.py
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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
#!/usr/bin/python2 -u
import subprocess
import optparse
import sys
# Custom imports
sys.dont_write_bytecode = True
from libs import utils
from libs import config
# Defines
FAKESUM = "00000000000000000000000000000000"
CSUMLEN = len(FAKESUM)
FLAGLEN = 12
def parse_options():
parser = optparse.OptionParser()
parser.add_option("-r", "--remote-host", dest="dsthost",
help="Remote host", action="store", default=None)
parser.add_option("-e", "--exclude", dest="rsync_excludes",
help="Files to exclude", action="append",
default=config.rsync_excludes)
parser.add_option("-x", "--extra", dest="extra",
help="Extra rsync options", action="append",
default=config.rsync_extra)
parser.add_option("-d", "--debug", dest="debug", help="Debug",
action="store", default=config.debug)
parser.add_option("-l", "--lite", dest="lite", help="Relaxed checks",
action="store_true", default=False)
parser.add_option("-c", "--checksum", dest="checksum",
help="Compute checksum for changed files",
action="store_true", default=False)
parser.add_option("-m", "--modified_only", dest="modified_only",
help="Consider only modified files, ignoring new files",
action="store_true", default=False)
parser.add_option("-X", "--nolinks", dest="nolinks",
help="Exclude links from comparison",
action="store_true", default=False)
parser.add_option("-b", "--backup", dest="backup", help="Do backups",
action="store_true", default=False)
parser.add_option("--srcroot", dest="srcroot", action="store",
default=None)
parser.add_option("--dstroot", dest="dstroot", action="store",
default=None)
(options, args) = parser.parse_args()
# Checksum or modified_only automatically disables lite check
if options.checksum or options.modified_only:
options.lite = False
# srcroot and dstroot
options.srcroot = utils.normalize_dir(args[0])
options.dstroot = utils.normalize_dir(args[1])
return (options, args)
def execute(cmd, stdin=None):
# Execute
if options.debug:
print "cmd: "+str(cmd)
print "stdin: "+stdin
process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(output, error) = process.communicate(stdin)
# Output reporting
if options.debug and output:
print output
# Ignore specific rsync errors
if process.returncode in utils.RSYNC_SUCCESS:
process.returncode = 0
# Error reporting
if process.returncode:
print "ERROR executing command "+str(cmd)
if error:
sys.stderr.write(error)
return (process, output, error)
def check(src, dst, filelist="", checksum=False):
# If checksum is enabled, continue only with a filelist
if not filelist and checksum:
return 0, ""
# Check via rsync
excludelist = utils.gen_exclude(options.rsync_excludes)
try:
excludelist.remove("--exclude=*"+config.safesuffix)
except:
pass
rsync_args = ["-anui"]
# Set filesize limit
# For lite or modified_only checks, use the default from config.py
# For full check or when using cheksum, increse size limit
if options.modified_only or options.lite:
pass
else:
rsync_args.append("--max-size=1024G")
# Enable checksum
if checksum:
rsync_args.append("--checksum")
# Link disabling
if options.nolinks:
try:
options.extra.remove("-L")
except:
pass
rsync_args.append("--no-l")
# Pass filelist
if filelist:
rsync_args.append("--files-from=-")
# Construct command and execute
cmd = (["rsync"] + options.extra + rsync_args + ["-n"] +
excludelist + [src, dst])
(process, output, error) = execute(cmd, filelist)
# Return
return process.returncode, output
def parse_output(output, strip=False, checksum=False):
# Initial values
count = 0
changed = ""
alert = False
# Count changed files
for line in output.split("\n"):
# If empty, ignore
if len(line) <= 0:
continue
# If not transfered, ignore
if line[0] != "<" and line[0] != ">":
continue
# If local checksum, ignore any matching files:
if checksum and line[2] != "c":
continue
# If checksum or modified_only, ignore new files
elif (options.checksum or options.modified_only) and line[3] == "+":
continue
# Lite checks ignore existing files with same size
elif options.lite and line[3] != "s" and line[3] != "+":
continue
# If we arrived here, the line is interesting.
# Count changed lines
count = count+1
# If strip, grep the filename only
if strip:
line = line[FLAGLEN:]
# Append the changed line
changed = utils.concat(changed, line)
# Alerts
# For checksum, raise an alert for a non-matching file
if checksum and line[2] == "c":
alert = True
# For full checks, raise an alert if size OR time
# of an existing file changed. Otherwise, continue
elif not options.lite and (line[3] == "s" or line[4] == "t"):
alert = True
# Return
return (count, changed, alert)
def showrdiff(src, dst, changed):
if len(changed):
print "\nDifferences found while checking FROM "+src+" TO "+dst
print changed.rstrip("\n")
def dosidebackup(changed, side):
if not changed:
return (None, None, None)
rsync_args = ["-avAX"]
filelist = ""
for line in utils.deconcat(changed):
if line[2] == "+":
continue
line = line[FLAGLEN:]
filelist = utils.concat(filelist, line)
if not filelist:
return (None, None, None)
cmd = (["rsync"] + options.extra + rsync_args +
["--files-from=-", side, backupdir])
(process, output, error) = execute(cmd, filelist)
return (process, output, error)
def dobackup(lchanged, rchanged):
(lprocess, loutput, lerror) = dosidebackup(lchanged, dst)
(rprocess, routput, rerror) = dosidebackup(rchanged, src)
if not lprocess and not rprocess:
return
print
print "Doing backups..."
print loutput
print
print routput
print "...done"
# Initial values and options parsing
error = 0
(options, args) = parse_options()
(src, dst) = (options.srcroot, options.dsthost+":"+options.dstroot)
backupdir = utils.normalize_dir(src+config.backupdir)
# Find changed files with rsync
(lcheck, loutput) = check(src, dst)
(rcheck, routput) = check(dst, src)
if lcheck or rcheck:
error = 1
quit(error)
# Parse rsync output
(lcount, lchanged, lalert) = parse_output(loutput, strip=options.checksum)
(rcount, rchanged, ralert) = parse_output(routput, strip=options.checksum)
# If checksum, do the second pass
if options.checksum:
# Only check the specified files
(lcheck, loutput) = check(src, dst, filelist=lchanged, checksum=True)
(rcheck, routput) = check(dst, src, filelist=rchanged, checksum=True)
if lcheck or rcheck:
error = 1
quit(error)
# Parse rsync output
(lcount, lchanged, lalert) = parse_output(loutput, checksum=True)
(rcount, rchanged, ralert) = parse_output(routput, checksum=True)
# Print the differences
showrdiff(src, dst, lchanged)
showrdiff(dst, src, rchanged)
# Error reporting
# 0: no differences at all
# 1: process error
# 2: alert
# 3: notice
if options.checksum:
if lalert or ralert:
error = 2
else:
error = 0
else:
if lalert or ralert:
error = 2
elif (lcount+rcount) >= config.alert_threshold:
error = 2
elif (lcount+rcount) > 0:
error = 3
else:
error = 0
# Lite checks have relaxed error codes
# 1 (process error) become 0
# 3 (notice) become 0
if options.lite:
if error == 1:
error = 0
elif error == 3:
error = 0
# If needed, do backups
if options.backup:
# If backupdir is not defined, skip backups.
# Else, execute backup
if not config.backupdir or src == backupdir:
print "Skipping backup due to backupdir not defined"
else:
dobackup(lchanged, rchanged)
# Exit with error code
quit(error)