1 #!/usr/bin/env python2.5
2 import sys, re, errno, stat, tempfile, struct, mmap
6 INDEX_SIG = '!IIIIIQ20sH'
7 ENTLEN = struct.calcsize(INDEX_SIG)
14 def __init__(self, path):
16 self.fd = os.open(path, os.O_RDONLY|os.O_LARGEFILE|os.O_NOFOLLOW)
17 #self.st = os.fstat(self.fd)
30 def __init__(self, name, m, ofs):
34 (self.dev, self.ctime, self.mtime, self.uid, self.gid,
36 self.flags) = struct.unpack(INDEX_SIG, buffer(m, ofs, ENTLEN))
39 return ("(%s,0x%04x,%d,%d,%d,%d,%d,0x%04x)"
40 % (self.name, self.dev,
41 self.ctime, self.mtime, self.uid, self.gid,
42 self.size, self.flags))
45 return struct.pack(INDEX_SIG, self.dev, self.ctime, self.mtime,
46 self.uid, self.gid, self.size, self.sha,
50 self._m[self._ofs:self._ofs+ENTLEN] = self.pack()
52 def from_stat(self, st):
53 old = (self.dev, self.ctime, self.mtime,
54 self.uid, self.gid, self.size)
55 new = (st.st_dev, int(st.st_ctime), int(st.st_mtime),
56 st.st_uid, st.st_gid, st.st_size)
58 self.ctime = int(st.st_ctime)
59 self.mtime = int(st.st_mtime)
62 self.size = st.st_size
63 self.flags |= IX_EXISTS
65 self.flags &= ~IX_HASHVALID
72 def __init__(self, filename):
73 self.filename = filename
78 f = open(filename, 'r+')
80 if e.errno == errno.ENOENT:
85 st = os.fstat(f.fileno())
87 self.m = mmap.mmap(f.fileno(), 0,
88 mmap.MAP_SHARED, mmap.PROT_READ|mmap.PROT_WRITE)
89 f.close() # map will persist beyond file close
94 while ofs < len(self.m):
95 eon = self.m.find('\0', ofs)
97 yield IxEntry(buffer(self.m, ofs, eon-ofs),
99 ofs = eon + 1 + ENTLEN
106 def ix_encode(st, sha, flags):
107 return struct.pack(INDEX_SIG, st.st_dev, int(st.st_ctime),
108 int(st.st_mtime), st.st_uid, st.st_gid,
109 st.st_size, sha, flags)
113 def __init__(self, filename):
117 self.filename = filename = os.path.realpath(filename)
118 (dir,name) = os.path.split(filename)
119 (ffd,self.tmpname) = tempfile.mkstemp('.tmp', filename, dir)
120 self.f = os.fdopen(ffd, 'wb', 65536)
130 os.unlink(self.tmpname)
137 os.rename(self.tmpname, self.filename)
139 def add(self, name, st):
140 #log('ADDING %r\n' % name)
142 assert(cmp(self.lastfile, name) > 0) # reverse order only
144 data = name + '\0' + ix_encode(st, '\0'*20, IX_EXISTS|IX_HASHVALID)
147 def add_ixentry(self, e):
149 e.flags |= IX_HASHVALID
151 assert(cmp(self.lastfile, e.name) > 0) # reverse order only
152 self.lastfile = e.name
153 data = e.name + '\0' + e.pack()
156 def new_reader(self):
158 return IndexReader(self.tmpname)
163 saved_errors.append(e)
167 # the use of fchdir() and lstat() are for two reasons:
168 # - help out the kernel by not making it repeatedly look up the absolute path
169 # - avoid race conditions caused by doing listdir() on a changing symlink
170 def handle_path(ri, wi, dir, name, pst, xdev):
173 #log('handle_path(%r,%r)\n' % (dir, name))
174 if stat.S_ISDIR(pst.st_mode):
175 if opt.verbose == 1: # log dirs only
176 sys.stdout.write('%s\n' % path)
179 OsFile(name).fchdir()
181 add_error(Exception('in %s: %s' % (dir, str(e))))
186 #log('* %r: %r\n' % (name, ld))
188 add_error(Exception('in %s: %s' % (path, str(e))))
195 add_error(Exception('in %s: %s' % (path, str(e))))
197 if xdev != None and st.st_dev != xdev:
198 log('Skipping %r: different filesystem.\n'
199 % os.path.realpath(p))
201 if stat.S_ISDIR(st.st_mode):
204 for p,st in reversed(sorted(lds)):
205 dirty += handle_path(ri, wi, path, p, st, xdev)
208 #log('endloop: ri.cur:%r path:%r\n' % (ri.cur.name, path))
209 while ri.cur and ri.cur.name > path:
210 #log('ricur:%r path:%r\n' % (ri.cur, path))
211 if dir and ri.cur.name.startswith(dir):
212 #log(' --- deleting\n')
213 ri.cur.flags &= ~(IX_EXISTS | IX_HASHVALID)
217 if ri.cur and ri.cur.name == path:
218 dirty += ri.cur.from_stat(pst)
220 #log(' --- updating %r\n' % path)
226 if opt.verbose > 1: # all files, not just dirs
227 sys.stdout.write('%s\n' % path)
235 except StopIteration:
239 def merge_indexes(out, r1, r2):
240 log('Merging indexes.\n')
247 if e1 and (not e2 or e2.name < e1.name):
248 if e1.flags & IX_EXISTS:
251 elif e2 and (not e1 or e1.name < e2.name):
252 if e2.flags & IX_EXISTS:
255 elif e1.name == e2.name:
256 assert(0) # duplicate name? should never happen anymore.
257 if e2.flags & IX_EXISTS:
264 def __init__(self, l):
271 self.cur = self.i.next()
272 except StopIteration:
277 def update_index(path):
278 ri = IndexReader(indexfile)
279 wi = IndexWriter(indexfile)
280 rpath = os.path.realpath(path)
289 (dir, name) = os.path.split(rpath)
290 if dir and dir[-1] != '/':
292 if stat.S_ISDIR(st.st_mode) and (not rpath or rpath[-1] != '/'):
294 rig = MergeGetter(ri)
295 OsFile(dir or '/').fchdir()
296 dirty = handle_path(rig, wi, dir, name, st, xdev)
298 # make sure all the parents of the updated path exist and are invalidated
301 (rpath, junk) = os.path.split(rpath)
308 while rig.cur and rig.cur.name > p:
309 #log('FINISHING: %r path=%r d=%r\n' % (rig.cur.name, p, dirty))
311 if rig.cur and rig.cur.name == p:
313 rig.cur.flags &= ~IX_HASHVALID
316 wi.add(p, os.lstat(p))
322 mi = IndexWriter(indexfile)
323 merge_indexes(mi, ri, wi.new_reader())
329 bup index [options...] <filenames...>
331 p,print print index after updating
332 m,modified print only modified files (implies -p)
333 x,xdev,one-file-system don't cross filesystem boundaries
334 fake-valid mark all index entries as up-to-date even if they aren't
335 f,indexfile= the name of the index file (default 'index')
336 s,status print each filename with a status char (A/M/D) (implies -p)
337 v,verbose increase log output (can be used more than once)
339 o = options.Options('bup index', optspec)
340 (opt, flags, extra) = o.parse(sys.argv[1:])
342 indexfile = opt.indexfile or 'index'
347 if opt.fake_valid and not extra:
348 mi = IndexWriter(indexfile)
349 merge_indexes(mi, IndexReader(indexfile),
350 IndexWriter(indexfile).new_reader())
353 if opt['print'] or opt.status or opt.modified:
354 for ent in IndexReader(indexfile):
355 if opt.modified and ent.flags & IX_HASHVALID:
358 if not ent.flags & IX_EXISTS:
359 print 'D ' + ent.name
360 elif not ent.flags & IX_HASHVALID:
361 print 'M ' + ent.name
369 log('WARNING: %d errors encountered.\n' % len(saved_errors))