3 # Directory integrity scanner.
8 from os
.path
import join
10 from cPickle
import dump
, load
14 """Root of directory generator"""
15 topstat
= os
.lstat(top
)
16 for x
in walker(top
, '.', topstat
):
19 def walker(path
, name
, topstat
):
20 """Directory tree generator.
22 At one point, this started as a copy of os.walk from Python's
23 library. Even the arguments are different now.
27 names
= os
.listdir(path
)
29 sys
.stderr
.write("Warning, can't read dir: %s\n" % path
)
32 # The verification algorithm requires the names to be sorted.
35 # Stat each name found, and put the result in one of two lists.
36 dirs
, nondirs
= [], []
38 if path
== '.' and (onename
== "0sure.dat.gz" or
39 onename
== "0sure.bak.gz" or
40 onename
== "0sure.0.gz"):
42 st
= os
.lstat(join(path
, onename
))
43 if S_ISDIR(st
.st_mode
):
44 dirs
.append((onename
, st
))
46 nondirs
.append((onename
, st
))
48 # Indicate "entering" the directory.
51 # Then recursively walk into all of the subdirectories.
52 for (onename
, st
) in dirs
:
53 subpath
= join(path
, onename
)
54 if st
.st_dev
== topstat
.st_dev
:
55 for x
in walker(subpath
, onename
, topstat
):
58 # Then yield each entry that is not a subdirectory.
59 for (onename
, st
) in nondirs
:
62 # Last, yield the leaving.
65 def empty_generator():
70 """Class for comparing two directory iterations. Keeps track of
71 state, and allows child classes to define handlers for the various
72 types of differences found."""
74 def __init__(self
, left
, right
):
78 # Default handlers for the 6 possible changes (or not changes)
79 # that can happen in a directory. The adds and deletes take an
80 # additional argument that will be set to true if this added or
81 # remoted entity is contained in an entirely new directory. Some
82 # handlers may want to avoid printing verbose messages for the
83 # contents of added or deleted directories, and can use this
85 def handle_same_dir(self
, path
, a
, b
):
86 #print "same_dir(%s, %s, %s)" % (path, a, b)
87 return empty_generator()
88 def handle_delete_dir(self
, path
, a
, recursing
):
89 #print "delete_dir(%s, %s, %s)" % (path, a, recursing)
90 return empty_generator()
91 def handle_add_dir(self
, path
, a
, recursing
):
92 #print "add_dir(%s, %s, %s)" % (path, a, recursing)
93 return empty_generator()
94 def handle_same_nondir(self
, path
, a
, b
):
95 #print "same_nondir(%s, %s, %s)" % (path, a, b)
96 return empty_generator()
97 def handle_delete_nondir(self
, path
, a
, recursing
):
98 #print "delete_nondir(%s, %s, %s)" % (path, a, recursing)
99 return empty_generator()
100 def handle_add_nondir(self
, path
, a
, recursing
):
101 #print "add_nondir(%s, %s, %s)" % (path, a, recursing)
102 return empty_generator()
105 a
= self
.__left
.next()
107 raise "Scan doesn't start with a directory"
108 b
= self
.__right
.next()
110 raise "Tree walk doesn't start with a directory"
111 return self
.__run
(b
[1], 1)
113 def __run(self
, path
, depth
):
114 """Iterate both pairs of directories equally
116 Processes the contents of a single directory, recursively
117 calling itself to handle child directories. Returns with both
118 iterators advanced past the 'u' node that ends the dir."""
119 # print "run(%d): '%s'" % (depth, path)
120 a
= self
.__left
.next()
121 b
= self
.__right
.next()
124 # print "Comparing (%d) %s and %s" % (depth, a, b)
125 if a
[0] == 'u' and b
[0] == 'u':
126 # Both are leaving the directory.
127 # print "leave(%d): '%s'" % (depth, path)
130 elif a
[0] == 'd' and b
[0] == 'd':
131 # Both looking at a directory entry.
134 # if the name is the same, walk the tree.
135 for x
in self
.handle_same_dir(path
, a
, b
):
137 for x
in self
.__run
(os
.path
.join(path
, a
[1]), depth
+ 1):
139 a
= self
.__left
.next()
140 b
= self
.__right
.next()
144 # A directory has been deleted.
145 for x
in self
.handle_delete_dir(path
, a
, False):
147 for x
in self
.delete_whole_dir(self
.__left
):
149 a
= self
.__left
.next()
153 # A directory has been added.
154 for x
in self
.handle_add_dir(path
, b
, False):
157 for x
in self
.add_whole_dir(self
.__right
, path
):
159 b
= self
.__right
.next()
162 elif a
[0] == '-' and b
[0] == '-':
163 # Both are looking at a non-dir.
167 for x
in self
.handle_same_nondir(path
, a
, b
):
169 a
= self
.__left
.next()
170 b
= self
.__right
.next()
175 for x
in self
.handle_delete_nondir(path
, a
, False):
177 a
= self
.__left
.next()
182 for x
in self
.handle_add_nondir(path
, b
, False):
184 b
= self
.__right
.next()
187 elif a
[0] == '-' and b
[0] == 'u':
188 for x
in self
.handle_delete_nondir(path
, a
, False):
190 a
= self
.__left
.next()
193 elif a
[0] == 'u' and b
[0] == '-':
194 for x
in self
.handle_add_nondir(path
, b
, False):
196 b
= self
.__right
.next()
199 elif a
[0] == 'd' and b
[0] == '-':
200 for x
in self
.handle_delete_dir(path
, a
, False):
202 for x
in self
.delete_whole_dir(self
.__left
, path
):
204 a
= self
.__left
.next()
207 elif (a
[0] == '-' or a
[0] == 'u') and b
[0] == 'd':
208 for x
in self
.handle_add_dir(path
, b
, False):
210 for x
in self
.add_whole_dir(self
.__right
, path
):
212 b
= self
.__right
.next()
216 print "Unhandled case!!!"
219 def add_whole_dir(self
, iter, path
):
220 "Consume entries until this directory has been added"
221 # print "add_whole_dir: %s" % path
227 for x
in self
.handle_add_dir(path
, a
, True):
229 for x
in self
.add_whole_dir(iter, os
.path
.join(path
, a
[1])):
232 for x
in self
.handle_add_nondir(path
, a
, True):
235 def delete_whole_dir(self
, iter, path
):
236 "Consume entries until this directory has been deleted"
237 # print "delete_whole_dir: %s" % path
243 for x
in self
.handle_delete_dir(path
, a
, True):
245 for x
in self
.delete_whole_dir(iter, os
.path
.join(path
, a
[1])):
248 for x
in self
.handle_delete_nondir(path
, a
, True):
251 class check_comparer(comparer
):
252 """Comparer for comparing either two trees, or a tree and a
253 filesystem. 'right' should be the newer tree.
254 Yields strings giving the tree differences.
256 def handle_same_dir(self
, path
, a
, b
):
257 #print "same_dir(%s, %s, %s)" % (path, a, b)
258 return empty_generator()
259 def handle_delete_dir(self
, path
, a
, recursing
):
263 yield "- dir %s" % (os
.path
.join(path
, a
[1]))
264 def handle_add_dir(self
, path
, a
, recursing
):
268 yield "+ dir %s" % (os
.path
.join(path
, a
[1]))
269 def handle_same_nondir(self
, path
, a
, b
):
270 #print "same_nondir(%s, %s, %s)" % (path, a, b)
271 return empty_generator()
272 def handle_delete_nondir(self
, path
, a
, recursing
):
276 yield "- %s" % (os
.path
.join(path
, a
[1]))
277 def handle_add_nondir(self
, path
, a
, recursing
):
281 yield "+ %s" % (os
.path
.join(path
, a
[1]))
283 version
= 'Asure scan version 1.0'
286 """Iterate over a previously written dump"""
287 fd
= gzip
.open(path
, 'rb')
290 raise "incompatible version of asure file"
297 def writer(path
, tmppath
, iter):
298 """Write the given item (probably assembled iterator)"""
299 fd
= gzip
.open(tmppath
, 'wb')
300 dump(version
, fd
, -1)
304 os
.rename(tmppath
, path
)
307 """Perform a fresh scan of the filesystem"""
308 writer('0sure.dat.gz', '0sure.0.gz', walk('.'))
311 """Perform a scan of the filesystem, and compare it with the scan
312 file. reports differences."""
313 prior
= reader('0sure.dat.gz')
315 # compare_trees(prior, cur)
316 for x
in check_comparer(prior
, cur
).run():
322 if argv
[0] == 'scan':
324 elif argv
[0] == 'update':
326 elif argv
[0] == 'check':
328 elif argv
[0] == 'show':
329 for i
in reader('0sure.dat.gz'):
333 print "Usage: asure {scan|update|check}"
336 if __name__
== '__main__':