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")/bup-python" || exit $?
13 exec "$bup_python" "$0"
17 from __future__ import absolute_import, print_function
18 from binascii import hexlify
19 import sys, stat, time, os, errno, re
21 from bup import compat, metadata, options, git, index, drecurse, hlinkdb
22 from bup.compat import argv_bytes
23 from bup.drecurse import recursive_dirlist
24 from bup.hashsplit import GIT_MODE_TREE, GIT_MODE_FILE
25 from bup.helpers import (add_error, handle_ctrl_c, log, parse_excludes, parse_rx_excludes,
26 progress, qprogress, saved_errors)
27 from bup.io import byte_stream, path_msg
31 def __init__(self, l):
37 self.cur = next(self.i, None)
42 def check_index(reader):
44 log('check: checking forward iteration...\n')
47 for e in reader.forward_iter():
50 log('%08x+%-4d %r\n' % (e.children_ofs, e.children_n,
52 assert(e.children_ofs)
53 assert e.name.endswith(b'/')
54 assert(not d.get(e.children_ofs))
56 if e.flags & index.IX_HASHVALID:
57 assert(e.sha != index.EMPTY_SHA)
59 assert not e or bytes(e.name) == b'/' # last entry is *always* /
60 log('check: checking normal iteration...\n')
67 log('index error! at %r\n' % e)
69 log('check: passed.\n')
72 def clear_index(indexfile):
73 indexfiles = [indexfile, indexfile + b'.meta', indexfile + b'.hlink']
74 for indexfile in indexfiles:
75 path = git.repo(indexfile)
79 log('clear: removed %s\n' % path_msg(path))
81 if e.errno != errno.ENOENT:
85 def update_index(top, excluded_paths, exclude_rxs, xdev_exceptions, out=None):
86 # tmax and start must be epoch nanoseconds.
87 tmax = (time.time() - 1) * 10**9
88 ri = index.Reader(indexfile)
89 msw = index.MetaStoreWriter(indexfile + b'.meta')
90 wi = index.Writer(indexfile, msw, tmax)
91 rig = IterHelper(ri.iter(name=top))
92 tstart = int(time.time()) * 10**9
94 hlinks = hlinkdb.HLinkDB(indexfile + b'.hlink')
99 return (GIT_MODE_FILE, index.FAKE_SHA)
102 bup_dir = os.path.abspath(git.repo())
103 index_start = time.time()
104 for path, pst in recursive_dirlist([top],
107 excluded_paths=excluded_paths,
108 exclude_rxs=exclude_rxs,
109 xdev_exceptions=xdev_exceptions):
110 if opt.verbose>=2 or (opt.verbose==1 and stat.S_ISDIR(pst.st_mode)):
111 out.write(b'%s\n' % path)
113 elapsed = time.time() - index_start
114 paths_per_sec = total / elapsed if elapsed else 0
115 qprogress('Indexing: %d (%d paths/s)\r' % (total, paths_per_sec))
116 elif not (total % 128):
117 elapsed = time.time() - index_start
118 paths_per_sec = total / elapsed if elapsed else 0
119 qprogress('Indexing: %d (%d paths/s)\r' % (total, paths_per_sec))
122 while rig.cur and rig.cur.name > path: # deleted paths
124 rig.cur.set_deleted()
126 if rig.cur.nlink > 1 and not stat.S_ISDIR(rig.cur.mode):
127 hlinks.del_path(rig.cur.name)
130 if rig.cur and rig.cur.name == path: # paths that already existed
132 if(rig.cur.stale(pst, tstart, check_device=opt.check_device)):
134 meta = metadata.from_path(path, statinfo=pst)
135 except (OSError, IOError) as e:
139 if not stat.S_ISDIR(rig.cur.mode) and rig.cur.nlink > 1:
140 hlinks.del_path(rig.cur.name)
141 if not stat.S_ISDIR(pst.st_mode) and pst.st_nlink > 1:
142 hlinks.add_path(path, pst.st_dev, pst.st_ino)
143 # Clear these so they don't bloat the store -- they're
144 # already in the index (since they vary a lot and they're
145 # fixed length). If you've noticed "tmax", you might
146 # wonder why it's OK to do this, since that code may
147 # adjust (mangle) the index mtime and ctime -- producing
148 # fake values which must not end up in a .bupm. However,
149 # it looks like that shouldn't be possible: (1) When
150 # "save" validates the index entry, it always reads the
151 # metadata from the filesytem. (2) Metadata is only
152 # read/used from the index if hashvalid is true. (3)
153 # "faked" entries will be stale(), and so we'll invalidate
155 meta.ctime = meta.mtime = meta.atime = 0
156 meta_ofs = msw.store(meta)
157 rig.cur.update_from_stat(pst, meta_ofs)
160 if not (rig.cur.flags & index.IX_HASHVALID):
162 if rig.cur.sha == index.EMPTY_SHA:
163 rig.cur.gitmode, rig.cur.sha = fake_hash(path)
164 rig.cur.flags |= index.IX_HASHVALID
174 meta = metadata.from_path(path, statinfo=pst)
175 except (OSError, IOError) as e:
178 # See same assignment to 0, above, for rationale.
179 meta.atime = meta.mtime = meta.ctime = 0
180 meta_ofs = msw.store(meta)
181 wi.add(path, pst, meta_ofs, hashgen=fake_hash)
182 if not stat.S_ISDIR(pst.st_mode) and pst.st_nlink > 1:
183 hlinks.add_path(path, pst.st_dev, pst.st_ino)
185 elapsed = time.time() - index_start
186 paths_per_sec = total / elapsed if elapsed else 0
187 progress('Indexing: %d, done (%d paths/s).\n' % (total, paths_per_sec))
189 hlinks.prepare_save()
197 log('check: before merging: oldfile\n')
199 log('check: before merging: newfile\n')
201 mi = index.Writer(indexfile, msw, tmax)
203 for e in index.merge(ri, wr):
204 # FIXME: shouldn't we remove deleted entries eventually? When?
219 bup index <-p|-m|-s|-u|--clear|--check> [options...] <filenames...>
222 p,print print the index entries for the given names (also works with -u)
223 m,modified print only added/deleted/modified files (implies -p)
224 s,status print each filename with a status char (A/M/D) (implies -p)
225 u,update recursively update the index entries for the given file/dir names (default if no mode is specified)
226 check carefully check index file integrity
227 clear clear the default index
229 H,hash print the hash for each object next to its name
230 l,long print more information about each file
231 no-check-device don't invalidate an entry if the containing device changes
232 fake-valid mark all index entries as up-to-date even if they aren't
233 fake-invalid mark all index entries as invalid
234 f,indexfile= the name of the index file (normally BUP_DIR/bupindex)
235 exclude= a path to exclude from the backup (may be repeated)
236 exclude-from= skip --exclude paths in file (may be repeated)
237 exclude-rx= skip paths matching the unanchored regex (may be repeated)
238 exclude-rx-from= skip --exclude-rx patterns in file (may be repeated)
239 v,verbose increase log output (can be used more than once)
240 x,xdev,one-file-system don't cross filesystem boundaries
242 o = options.Options(optspec)
243 opt, flags, extra = o.parse(compat.argv[1:])
245 if not (opt.modified or \
252 if (opt.fake_valid or opt.fake_invalid) and not opt.update:
253 o.fatal('--fake-{in,}valid are meaningless without -u')
254 if opt.fake_valid and opt.fake_invalid:
255 o.fatal('--fake-valid is incompatible with --fake-invalid')
256 if opt.clear and opt.indexfile:
257 o.fatal('cannot clear an external index (via -f)')
259 # FIXME: remove this once we account for timestamp races, i.e. index;
260 # touch new-file; index. It's possible for this to happen quickly
261 # enough that new-file ends up with the same timestamp as the first
262 # index, and then bup will ignore it.
263 tick_start = time.time()
264 time.sleep(1 - (tick_start - int(tick_start)))
266 git.check_repo_or_die()
270 if opt.verbose is None:
274 indexfile = argv_bytes(opt.indexfile)
276 indexfile = git.repo(b'bupindex')
279 log('check: starting initial check.\n')
280 check_index(index.Reader(indexfile))
283 log('clear: clearing index.\n')
284 clear_index(indexfile)
287 out = byte_stream(sys.stdout)
291 o.fatal('update mode (-u) requested but no paths given')
292 extra = [argv_bytes(x) for x in extra]
293 excluded_paths = parse_excludes(flags, o.fatal)
294 exclude_rxs = parse_rx_excludes(flags, o.fatal)
295 xexcept = index.unique_resolved_paths(extra)
296 for rp, path in index.reduce_paths(extra):
297 update_index(rp, excluded_paths, exclude_rxs, xdev_exceptions=xexcept,
300 if opt['print'] or opt.status or opt.modified:
301 extra = [argv_bytes(x) for x in extra]
302 for name, ent in index.Reader(indexfile).filter(extra or [b'']):
304 and (ent.is_valid() or ent.is_deleted() or not ent.mode)):
310 elif not ent.is_valid():
311 if ent.sha == index.EMPTY_SHA:
318 line += hexlify(ent.sha) + b' '
320 line += b'%7s %7s ' % (oct(ent.mode), oct(ent.gitmode))
321 out.write(line + (name or b'./') + b'\n')
323 if opt.check and (opt['print'] or opt.status or opt.modified or opt.update):
324 log('check: starting final check.\n')
325 check_index(index.Reader(indexfile))
328 log('WARNING: %d errors encountered.\n' % len(saved_errors))