/usr/lib/bup/cmd/bup-index is in bup 0.29-3.
This file is owned by root:root, with mode 0o755.
The actual contents of the file can be viewed below.
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 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 | #!/usr/bin/python2.7
import sys, stat, time, os, errno, re
from bup import metadata, options, git, index, drecurse, hlinkdb
from bup.drecurse import recursive_dirlist
from bup.hashsplit import GIT_MODE_TREE, GIT_MODE_FILE
from bup.helpers import (add_error, handle_ctrl_c, log, parse_excludes, parse_rx_excludes,
progress, qprogress, saved_errors)
class IterHelper:
def __init__(self, l):
self.i = iter(l)
self.cur = None
self.next()
def next(self):
try:
self.cur = self.i.next()
except StopIteration:
self.cur = None
return self.cur
def check_index(reader):
try:
log('check: checking forward iteration...\n')
e = None
d = {}
for e in reader.forward_iter():
if e.children_n:
if opt.verbose:
log('%08x+%-4d %r\n' % (e.children_ofs, e.children_n,
e.name))
assert(e.children_ofs)
assert(e.name.endswith('/'))
assert(not d.get(e.children_ofs))
d[e.children_ofs] = 1
if e.flags & index.IX_HASHVALID:
assert(e.sha != index.EMPTY_SHA)
assert(e.gitmode)
assert(not e or e.name == '/') # last entry is *always* /
log('check: checking normal iteration...\n')
last = None
for e in reader:
if last:
assert(last > e.name)
last = e.name
except:
log('index error! at %r\n' % e)
raise
log('check: passed.\n')
def clear_index(indexfile):
indexfiles = [indexfile, indexfile + '.meta', indexfile + '.hlink']
for indexfile in indexfiles:
path = git.repo(indexfile)
try:
os.remove(path)
if opt.verbose:
log('clear: removed %s\n' % path)
except OSError as e:
if e.errno != errno.ENOENT:
raise
def update_index(top, excluded_paths, exclude_rxs, xdev_exceptions):
# tmax and start must be epoch nanoseconds.
tmax = (time.time() - 1) * 10**9
ri = index.Reader(indexfile)
msw = index.MetaStoreWriter(indexfile + '.meta')
wi = index.Writer(indexfile, msw, tmax)
rig = IterHelper(ri.iter(name=top))
tstart = int(time.time()) * 10**9
hlinks = hlinkdb.HLinkDB(indexfile + '.hlink')
fake_hash = None
if opt.fake_valid:
def fake_hash(name):
return (GIT_MODE_FILE, index.FAKE_SHA)
total = 0
bup_dir = os.path.abspath(git.repo())
index_start = time.time()
for path, pst in recursive_dirlist([top],
xdev=opt.xdev,
bup_dir=bup_dir,
excluded_paths=excluded_paths,
exclude_rxs=exclude_rxs,
xdev_exceptions=xdev_exceptions):
if opt.verbose>=2 or (opt.verbose==1 and stat.S_ISDIR(pst.st_mode)):
sys.stdout.write('%s\n' % path)
sys.stdout.flush()
elapsed = time.time() - index_start
paths_per_sec = total / elapsed if elapsed else 0
qprogress('Indexing: %d (%d paths/s)\r' % (total, paths_per_sec))
elif not (total % 128):
elapsed = time.time() - index_start
paths_per_sec = total / elapsed if elapsed else 0
qprogress('Indexing: %d (%d paths/s)\r' % (total, paths_per_sec))
total += 1
while rig.cur and rig.cur.name > path: # deleted paths
if rig.cur.exists():
rig.cur.set_deleted()
rig.cur.repack()
if rig.cur.nlink > 1 and not stat.S_ISDIR(rig.cur.mode):
hlinks.del_path(rig.cur.name)
rig.next()
if rig.cur and rig.cur.name == path: # paths that already existed
need_repack = False
if(rig.cur.stale(pst, tstart, check_device=opt.check_device)):
try:
meta = metadata.from_path(path, statinfo=pst)
except (OSError, IOError) as e:
add_error(e)
rig.next()
continue
if not stat.S_ISDIR(rig.cur.mode) and rig.cur.nlink > 1:
hlinks.del_path(rig.cur.name)
if not stat.S_ISDIR(pst.st_mode) and pst.st_nlink > 1:
hlinks.add_path(path, pst.st_dev, pst.st_ino)
# Clear these so they don't bloat the store -- they're
# already in the index (since they vary a lot and they're
# fixed length). If you've noticed "tmax", you might
# wonder why it's OK to do this, since that code may
# adjust (mangle) the index mtime and ctime -- producing
# fake values which must not end up in a .bupm. However,
# it looks like that shouldn't be possible: (1) When
# "save" validates the index entry, it always reads the
# metadata from the filesytem. (2) Metadata is only
# read/used from the index if hashvalid is true. (3)
# "faked" entries will be stale(), and so we'll invalidate
# them below.
meta.ctime = meta.mtime = meta.atime = 0
meta_ofs = msw.store(meta)
rig.cur.update_from_stat(pst, meta_ofs)
rig.cur.invalidate()
need_repack = True
if not (rig.cur.flags & index.IX_HASHVALID):
if fake_hash:
rig.cur.gitmode, rig.cur.sha = fake_hash(path)
rig.cur.flags |= index.IX_HASHVALID
need_repack = True
if opt.fake_invalid:
rig.cur.invalidate()
need_repack = True
if need_repack:
rig.cur.repack()
rig.next()
else: # new paths
try:
meta = metadata.from_path(path, statinfo=pst)
except (OSError, IOError) as e:
add_error(e)
continue
# See same assignment to 0, above, for rationale.
meta.atime = meta.mtime = meta.ctime = 0
meta_ofs = msw.store(meta)
wi.add(path, pst, meta_ofs, hashgen=fake_hash)
if not stat.S_ISDIR(pst.st_mode) and pst.st_nlink > 1:
hlinks.add_path(path, pst.st_dev, pst.st_ino)
elapsed = time.time() - index_start
paths_per_sec = total / elapsed if elapsed else 0
progress('Indexing: %d, done (%d paths/s).\n' % (total, paths_per_sec))
hlinks.prepare_save()
if ri.exists():
ri.save()
wi.flush()
if wi.count:
wr = wi.new_reader()
if opt.check:
log('check: before merging: oldfile\n')
check_index(ri)
log('check: before merging: newfile\n')
check_index(wr)
mi = index.Writer(indexfile, msw, tmax)
for e in index.merge(ri, wr):
# FIXME: shouldn't we remove deleted entries eventually? When?
mi.add_ixentry(e)
ri.close()
mi.close()
wr.close()
wi.abort()
else:
wi.close()
msw.close()
hlinks.commit_save()
optspec = """
bup index <-p|-m|-s|-u|--clear|--check> [options...] <filenames...>
--
Modes:
p,print print the index entries for the given names (also works with -u)
m,modified print only added/deleted/modified files (implies -p)
s,status print each filename with a status char (A/M/D) (implies -p)
u,update recursively update the index entries for the given file/dir names (default if no mode is specified)
check carefully check index file integrity
clear clear the default index
Options:
H,hash print the hash for each object next to its name
l,long print more information about each file
no-check-device don't invalidate an entry if the containing device changes
fake-valid mark all index entries as up-to-date even if they aren't
fake-invalid mark all index entries as invalid
f,indexfile= the name of the index file (normally BUP_DIR/bupindex)
exclude= a path to exclude from the backup (may be repeated)
exclude-from= skip --exclude paths in file (may be repeated)
exclude-rx= skip paths matching the unanchored regex (may be repeated)
exclude-rx-from= skip --exclude-rx patterns in file (may be repeated)
v,verbose increase log output (can be used more than once)
x,xdev,one-file-system don't cross filesystem boundaries
"""
o = options.Options(optspec)
(opt, flags, extra) = o.parse(sys.argv[1:])
if not (opt.modified or \
opt['print'] or \
opt.status or \
opt.update or \
opt.check or \
opt.clear):
opt.update = 1
if (opt.fake_valid or opt.fake_invalid) and not opt.update:
o.fatal('--fake-{in,}valid are meaningless without -u')
if opt.fake_valid and opt.fake_invalid:
o.fatal('--fake-valid is incompatible with --fake-invalid')
if opt.clear and opt.indexfile:
o.fatal('cannot clear an external index (via -f)')
# FIXME: remove this once we account for timestamp races, i.e. index;
# touch new-file; index. It's possible for this to happen quickly
# enough that new-file ends up with the same timestamp as the first
# index, and then bup will ignore it.
tick_start = time.time()
time.sleep(1 - (tick_start - int(tick_start)))
git.check_repo_or_die()
indexfile = opt.indexfile or git.repo('bupindex')
handle_ctrl_c()
if opt.check:
log('check: starting initial check.\n')
check_index(index.Reader(indexfile))
if opt.clear:
log('clear: clearing index.\n')
clear_index(indexfile)
if opt.update:
if not extra:
o.fatal('update mode (-u) requested but no paths given')
excluded_paths = parse_excludes(flags, o.fatal)
exclude_rxs = parse_rx_excludes(flags, o.fatal)
xexcept = index.unique_resolved_paths(extra)
for rp, path in index.reduce_paths(extra):
update_index(rp, excluded_paths, exclude_rxs, xdev_exceptions=xexcept)
if opt['print'] or opt.status or opt.modified:
for (name, ent) in index.Reader(indexfile).filter(extra or ['']):
if (opt.modified
and (ent.is_valid() or ent.is_deleted() or not ent.mode)):
continue
line = ''
if opt.status:
if ent.is_deleted():
line += 'D '
elif not ent.is_valid():
if ent.sha == index.EMPTY_SHA:
line += 'A '
else:
line += 'M '
else:
line += ' '
if opt.hash:
line += ent.sha.encode('hex') + ' '
if opt.long:
line += "%7s %7s " % (oct(ent.mode), oct(ent.gitmode))
print line + (name or './')
if opt.check and (opt['print'] or opt.status or opt.modified or opt.update):
log('check: starting final check.\n')
check_index(index.Reader(indexfile))
if saved_errors:
log('WARNING: %d errors encountered.\n' % len(saved_errors))
sys.exit(1)
|