3 # https://sourceware.org/bugzilla/show_bug.cgi?id=26034
4 export "BUP_ARGV_0"="$0"
7 export "BUP_ARGV_${arg_i}"="$arg"
11 # Here to end of preamble replaced during install
12 bup_python="$(dirname "$0")/../../../config/bin/python" || exit $?
13 exec "$bup_python" "$0"
17 from __future__ import absolute_import, print_function
19 # Intentionally replace the dirname "$0" that python prepends
21 sys.path[0] = os.path.dirname(os.path.realpath(__file__)) + '/../..'
23 from binascii import hexlify
24 import errno, re, stat, time
26 from bup import compat, metadata, options, git, index, drecurse, hlinkdb
27 from bup.compat import argv_bytes
28 from bup.drecurse import recursive_dirlist
29 from bup.hashsplit import GIT_MODE_TREE, GIT_MODE_FILE
30 from bup.helpers import (add_error, handle_ctrl_c, log, parse_excludes, parse_rx_excludes,
31 progress, qprogress, saved_errors)
32 from bup.io import byte_stream, path_msg
36 def __init__(self, l):
42 self.cur = next(self.i, None)
47 def check_index(reader):
49 log('check: checking forward iteration...\n')
52 for e in reader.forward_iter():
55 log('%08x+%-4d %r\n' % (e.children_ofs, e.children_n,
57 assert(e.children_ofs)
58 assert e.name.endswith(b'/')
59 assert(not d.get(e.children_ofs))
61 if e.flags & index.IX_HASHVALID:
62 assert(e.sha != index.EMPTY_SHA)
64 assert not e or bytes(e.name) == b'/' # last entry is *always* /
65 log('check: checking normal iteration...\n')
72 log('index error! at %r\n' % e)
74 log('check: passed.\n')
77 def clear_index(indexfile):
78 indexfiles = [indexfile, indexfile + b'.meta', indexfile + b'.hlink']
79 for indexfile in indexfiles:
83 log('clear: removed %s\n' % path_msg(indexfile))
85 if e.errno != errno.ENOENT:
89 def update_index(top, excluded_paths, exclude_rxs, xdev_exceptions, out=None):
90 # tmax must be epoch nanoseconds.
91 tmax = (time.time() - 1) * 10**9
92 ri = index.Reader(indexfile)
93 msw = index.MetaStoreWriter(indexfile + b'.meta')
94 wi = index.Writer(indexfile, msw, tmax)
95 rig = IterHelper(ri.iter(name=top))
97 hlinks = hlinkdb.HLinkDB(indexfile + b'.hlink')
102 return (GIT_MODE_FILE, index.FAKE_SHA)
105 bup_dir = os.path.abspath(git.repo())
106 index_start = time.time()
107 for path, pst in recursive_dirlist([top],
110 excluded_paths=excluded_paths,
111 exclude_rxs=exclude_rxs,
112 xdev_exceptions=xdev_exceptions):
113 if opt.verbose>=2 or (opt.verbose==1 and stat.S_ISDIR(pst.st_mode)):
114 out.write(b'%s\n' % path)
116 elapsed = time.time() - index_start
117 paths_per_sec = total / elapsed if elapsed else 0
118 qprogress('Indexing: %d (%d paths/s)\r' % (total, paths_per_sec))
119 elif not (total % 128):
120 elapsed = time.time() - index_start
121 paths_per_sec = total / elapsed if elapsed else 0
122 qprogress('Indexing: %d (%d paths/s)\r' % (total, paths_per_sec))
125 while rig.cur and rig.cur.name > path: # deleted paths
127 rig.cur.set_deleted()
129 if rig.cur.nlink > 1 and not stat.S_ISDIR(rig.cur.mode):
130 hlinks.del_path(rig.cur.name)
133 if rig.cur and rig.cur.name == path: # paths that already existed
135 if(rig.cur.stale(pst, check_device=opt.check_device)):
137 meta = metadata.from_path(path, statinfo=pst)
138 except (OSError, IOError) as e:
142 if not stat.S_ISDIR(rig.cur.mode) and rig.cur.nlink > 1:
143 hlinks.del_path(rig.cur.name)
144 if not stat.S_ISDIR(pst.st_mode) and pst.st_nlink > 1:
145 hlinks.add_path(path, pst.st_dev, pst.st_ino)
146 # Clear these so they don't bloat the store -- they're
147 # already in the index (since they vary a lot and they're
148 # fixed length). If you've noticed "tmax", you might
149 # wonder why it's OK to do this, since that code may
150 # adjust (mangle) the index mtime and ctime -- producing
151 # fake values which must not end up in a .bupm. However,
152 # it looks like that shouldn't be possible: (1) When
153 # "save" validates the index entry, it always reads the
154 # metadata from the filesytem. (2) Metadata is only
155 # read/used from the index if hashvalid is true. (3)
156 # "faked" entries will be stale(), and so we'll invalidate
158 meta.ctime = meta.mtime = meta.atime = 0
159 meta_ofs = msw.store(meta)
160 rig.cur.update_from_stat(pst, meta_ofs)
163 if not (rig.cur.flags & index.IX_HASHVALID):
165 if rig.cur.sha == index.EMPTY_SHA:
166 rig.cur.gitmode, rig.cur.sha = fake_hash(path)
167 rig.cur.flags |= index.IX_HASHVALID
177 meta = metadata.from_path(path, statinfo=pst)
178 except (OSError, IOError) as e:
181 # See same assignment to 0, above, for rationale.
182 meta.atime = meta.mtime = meta.ctime = 0
183 meta_ofs = msw.store(meta)
184 wi.add(path, pst, meta_ofs, hashgen=fake_hash)
185 if not stat.S_ISDIR(pst.st_mode) and pst.st_nlink > 1:
186 hlinks.add_path(path, pst.st_dev, pst.st_ino)
188 elapsed = time.time() - index_start
189 paths_per_sec = total / elapsed if elapsed else 0
190 progress('Indexing: %d, done (%d paths/s).\n' % (total, paths_per_sec))
192 hlinks.prepare_save()
200 log('check: before merging: oldfile\n')
202 log('check: before merging: newfile\n')
204 mi = index.Writer(indexfile, msw, tmax)
206 for e in index.merge(ri, wr):
207 # FIXME: shouldn't we remove deleted entries eventually? When?
222 bup index <-p|-m|-s|-u|--clear|--check> [options...] <filenames...>
225 p,print print the index entries for the given names (also works with -u)
226 m,modified print only added/deleted/modified files (implies -p)
227 s,status print each filename with a status char (A/M/D) (implies -p)
228 u,update recursively update the index entries for the given file/dir names (default if no mode is specified)
229 check carefully check index file integrity
230 clear clear the default index
232 H,hash print the hash for each object next to its name
233 l,long print more information about each file
234 no-check-device don't invalidate an entry if the containing device changes
235 fake-valid mark all index entries as up-to-date even if they aren't
236 fake-invalid mark all index entries as invalid
237 f,indexfile= the name of the index file (normally BUP_DIR/bupindex)
238 exclude= a path to exclude from the backup (may be repeated)
239 exclude-from= skip --exclude paths in file (may be repeated)
240 exclude-rx= skip paths matching the unanchored regex (may be repeated)
241 exclude-rx-from= skip --exclude-rx patterns in file (may be repeated)
242 v,verbose increase log output (can be used more than once)
243 x,xdev,one-file-system don't cross filesystem boundaries
245 o = options.Options(optspec)
246 opt, flags, extra = o.parse(compat.argv[1:])
248 if not (opt.modified or \
255 if (opt.fake_valid or opt.fake_invalid) and not opt.update:
256 o.fatal('--fake-{in,}valid are meaningless without -u')
257 if opt.fake_valid and opt.fake_invalid:
258 o.fatal('--fake-valid is incompatible with --fake-invalid')
259 if opt.clear and opt.indexfile:
260 o.fatal('cannot clear an external index (via -f)')
262 # FIXME: remove this once we account for timestamp races, i.e. index;
263 # touch new-file; index. It's possible for this to happen quickly
264 # enough that new-file ends up with the same timestamp as the first
265 # index, and then bup will ignore it.
266 tick_start = time.time()
267 time.sleep(1 - (tick_start - int(tick_start)))
269 git.check_repo_or_die()
273 if opt.verbose is None:
277 indexfile = argv_bytes(opt.indexfile)
279 indexfile = git.repo(b'bupindex')
282 log('check: starting initial check.\n')
283 check_index(index.Reader(indexfile))
286 log('clear: clearing index.\n')
287 clear_index(indexfile)
290 out = byte_stream(sys.stdout)
294 o.fatal('update mode (-u) requested but no paths given')
295 extra = [argv_bytes(x) for x in extra]
296 excluded_paths = parse_excludes(flags, o.fatal)
297 exclude_rxs = parse_rx_excludes(flags, o.fatal)
298 xexcept = index.unique_resolved_paths(extra)
299 for rp, path in index.reduce_paths(extra):
300 update_index(rp, excluded_paths, exclude_rxs, xdev_exceptions=xexcept,
303 if opt['print'] or opt.status or opt.modified:
304 extra = [argv_bytes(x) for x in extra]
305 for name, ent in index.Reader(indexfile).filter(extra or [b'']):
307 and (ent.is_valid() or ent.is_deleted() or not ent.mode)):
313 elif not ent.is_valid():
314 if ent.sha == index.EMPTY_SHA:
321 line += hexlify(ent.sha) + b' '
323 line += b'%7s %7s ' % (oct(ent.mode).encode('ascii'),
324 oct(ent.gitmode).encode('ascii'))
325 out.write(line + (name or b'./') + b'\n')
327 if opt.check and (opt['print'] or opt.status or opt.modified or opt.update):
328 log('check: starting final check.\n')
329 check_index(index.Reader(indexfile))
332 log('WARNING: %d errors encountered.\n' % len(saved_errors))