Example #1
0
def main(argv):
    o = options.Options(optspec)
    opt, flags, extra = o.parse_bytes(argv[1:])
    if opt.name: opt.name = argv_bytes(opt.name)
    if opt.verbose is None: opt.verbose = 0

    if not (opt.blobs or opt.tree or opt.commit or opt.name or opt.noop
            or opt.copy):
        o.fatal("use one or more of -b, -t, -c, -n, --noop, --copy")
    if opt.copy and (opt.blobs or opt.tree):
        o.fatal('--copy is incompatible with -b, -t')
    if (opt.noop or opt.copy) and (opt.commit or opt.name):
        o.fatal('--noop and --copy are incompatible with -c, -n')
    if opt.blobs and (opt.tree or opt.commit or opt.name):
        o.fatal('-b is incompatible with -t, -c, -n')
    if extra and opt.git_ids:
        o.fatal("don't provide filenames when using --git-ids")

    if opt.verbose >= 2:
        git.verbose = opt.verbose - 1
        opt.bench = 1

    fanout = None
    if opt.fanout:
        # This used to be in hashsplit, but that's just confusing;
        # hashsplit now defaults to the real default (16) if 0 (or
        # None) is passed, but keep the command-line compatible...
        fanout = parse_num(opt.fanout) or 128
    blobbits = None
    if opt.blobbits:
        blobbits = parse_num(opt.blobbits)
    if opt.bwlimit:
        client.bwlimit = parse_num(opt.bwlimit)
    if opt.date:
        date = parse_date_or_fatal(opt.date, o.fatal)
    else:
        date = time.time()

    # Hack around lack of nonlocal vars in python 2
    total_bytes = [0]

    def prog(filenum, nbytes):
        total_bytes[0] += nbytes
        if filenum > 0:
            qprogress('Splitting: file #%d, %d kbytes\r' %
                      (filenum + 1, total_bytes[0] // 1024))
        else:
            qprogress('Splitting: %d kbytes\r' % (total_bytes[0] // 1024))

    start_time = time.time()

    if opt.name and not valid_save_name(opt.name):
        o.fatal("'%r' is not a valid branch name." % opt.name)
    refname = opt.name and b'refs/heads/%s' % opt.name or None

    if opt.noop or opt.copy:
        repo = oldref = None
    else:
        repo = from_opts(opt)
        oldref = refname and repo.read_ref(refname) or None
        repobits = repo.config(b'bup.blobbits',
                               opttype='int') or hashsplit.BUP_BLOBBITS
        if not blobbits:
            blobbits = repobits
        else:
            print("overriding repo blobbits %d from cmdline with %d" %
                  (repobits, blobbits))

    input = byte_stream(sys.stdin)

    if opt.git_ids:
        # the input is actually a series of git object ids that we should retrieve
        # and split.
        #
        # This is a bit messy, but basically it converts from a series of
        # repo.cat() iterators into a series of file-type objects.
        # It would be less ugly if either repo.cat() returned a file-like object
        # (not very efficient), or split_to_shalist() expected an iterator instead
        # of a file.
        class IterToFile:
            def __init__(self, it):
                self.it = iter(it)

            def read(self, size):
                v = next(self.it, None)
                return v or b''

        def read_ids():
            while 1:
                line = input.readline()
                if not line:
                    break
                if line:
                    line = line.strip()
                try:
                    it = repo.cat(line.strip())
                    next(it, None)  # skip the file info
                except KeyError as e:
                    add_error('error: %s' % e)
                    continue
                yield IterToFile(it)

        files = read_ids()
    else:
        # the input either comes from a series of files or from stdin.
        files = extra and (open(argv_bytes(fn), 'rb')
                           for fn in extra) or [input]

    if repo:
        write_data = repo.write_data
        write_tree = repo.write_tree
    elif opt.blobs or opt.tree:
        # --noop mode
        write_data = lambda content: git.calc_hash(b'blob', content)
        write_tree = lambda shalist: git.calc_hash(b'tree',
                                                   git.tree_encode(shalist))

    sys.stdout.flush()
    out = byte_stream(sys.stdout)

    if opt.blobs:
        shalist = hashsplit.split_to_blobs(write_data,
                                           files,
                                           keep_boundaries=opt.keep_boundaries,
                                           progress=prog,
                                           blobbits=blobbits)
        for (sha, size, level) in shalist:
            out.write(hexlify(sha) + b'\n')
            reprogress()
    elif opt.tree or opt.commit or opt.name:
        if opt.name:  # insert dummy_name which may be used as a restore target
            mode, sha = \
                hashsplit.split_to_blob_or_tree(write_data, write_tree, files,
                                                keep_boundaries=opt.keep_boundaries,
                                                progress=prog, fanout=fanout,
                                                blobbits=blobbits)
            splitfile_name = git.mangle_name(b'data', hashsplit.GIT_MODE_FILE,
                                             mode)
            shalist = [(mode, splitfile_name, sha)]
        else:
            shalist = hashsplit.split_to_shalist(
                write_data,
                write_tree,
                files,
                keep_boundaries=opt.keep_boundaries,
                progress=prog,
                fanout=fanout,
                blobbits=blobbits)
        tree = write_tree(shalist)
    else:
        last = 0
        it = hashsplit.hashsplit_iter(files,
                                      keep_boundaries=opt.keep_boundaries,
                                      progress=prog,
                                      fanout=fanout,
                                      blobbits=blobbits)
        for (blob, level) in it:
            hashsplit.total_split += len(blob)
            if opt.copy:
                sys.stdout.write(str(blob))
            megs = hashsplit.total_split // 1024 // 1024
            if not opt.quiet and last != megs:
                last = megs

    if opt.verbose:
        log('\n')
    if opt.tree:
        out.write(hexlify(tree) + b'\n')
    if opt.commit or opt.name:
        msg = b'bup split\n\nGenerated by command:\n%r\n' % compat.get_argvb()
        ref = opt.name and (b'refs/heads/%s' % opt.name) or None
        userline = b'%s <%s@%s>' % (userfullname(), username(), hostname())
        commit = repo.write_commit(tree, oldref, userline, date, None,
                                   userline, date, None, msg)
        if opt.commit:
            out.write(hexlify(commit) + b'\n')

    if opt.name and repo:
        repo.update_ref(refname, commit, oldref)

    if repo:
        repo.close()

    secs = time.time() - start_time
    size = hashsplit.total_split
    if opt.bench:
        log('bup: %.2f kbytes in %.2f secs = %.2f kbytes/sec\n' %
            (size / 1024, secs, size / 1024 / secs))

    if saved_errors:
        log('WARNING: %d errors encountered while saving.\n' %
            len(saved_errors))
        sys.exit(1)
Example #2
0
def main():
    o = options.Options(optspec)
    opt, flags, extra = o.parse(sys.argv[1:])
    verbosity = opt.verbose if not opt.quiet else -1
    
    git.check_repo_or_die()

    if not extra:
        o.fatal('must specify at least one filename to restore')

    exclude_rxs = parse_rx_excludes(flags, o.fatal)

    owner_map = {}
    for map_type in ('user', 'group', 'uid', 'gid'):
        owner_map[map_type] = parse_owner_mappings(map_type, flags, o.fatal)

    if opt.outdir:
        mkdirp(opt.outdir)
        os.chdir(opt.outdir)

    repo = RemoteRepo(opt.remote) if opt.remote else LocalRepo()
    top = os.getcwd()
    hardlinks = {}
    for path in extra:
        if not valid_restore_path(path):
            add_error("path %r doesn't include a branch and revision" % path)
            continue
        try:
            resolved = vfs.resolve(repo, path, want_meta=True, follow=False)
        except vfs.IOError as e:
            add_error(e)
            continue
        if len(resolved) == 3 and resolved[2][0] == 'latest':
            # Follow latest symlink to the actual save
            try:
                resolved = vfs.resolve(repo, 'latest', parent=resolved[:-1],
                                       want_meta=True)
            except vfs.IOError as e:
                add_error(e)
                continue
            # Rename it back to 'latest'
            resolved = tuple(elt if i != 2 else ('latest',) + elt[1:]
                             for i, elt in enumerate(resolved))
        path_parent, path_name = os.path.split(path)
        leaf_name, leaf_item = resolved[-1]
        if not leaf_item:
            add_error('error: cannot access %r in %r'
                      % ('/'.join(name for name, item in resolved),
                         path))
            continue
        if not path_name or path_name == '.':
            # Source is /foo/what/ever/ or /foo/what/ever/. -- extract
            # what/ever/* to the current directory, and if name == '.'
            # (i.e. /foo/what/ever/.), then also restore what/ever's
            # metadata to the current directory.
            treeish = vfs.item_mode(leaf_item)
            if not treeish:
                add_error('%r cannot be restored as a directory' % path)
            else:
                items = vfs.contents(repo, leaf_item, want_meta=True)
                dot, leaf_item = next(items, None)
                assert(dot == '.')
                for sub_name, sub_item in items:
                    restore(repo, '', sub_name, sub_item, top,
                            opt.sparse, opt.numeric_ids, owner_map,
                            exclude_rxs, verbosity, hardlinks)
                if path_name == '.':
                    leaf_item = vfs.augment_item_meta(repo, leaf_item,
                                                      include_size=True)
                    apply_metadata(leaf_item.meta, '.',
                                   opt.numeric_ids, owner_map)
        else:
            restore(repo, '', leaf_name, leaf_item, top,
                    opt.sparse, opt.numeric_ids, owner_map,
                    exclude_rxs, verbosity, hardlinks)

    if verbosity >= 0:
        progress('Restoring: %d, done.\n' % total_restored)
    die_if_errors()
Example #3
0
def test_invalid_optspec():
    with no_lingering_errors():
        WVPASS(options.Options(invalid_optspec0).parse([]))
        WVPASS(options.Options(invalid_optspec1).parse([]))
        WVPASS(options.Options(invalid_optspec2).parse([]))
Example #4
0
File: ls.py Project: prodigeni/bup
def do_ls(args, pwd, default='.', onabort=None, spec_prefix=''):
    """Output a listing of a file or directory in the bup repository.

    When a long listing is not requested and stdout is attached to a
    tty, the output is formatted in columns. When not attached to tty
    (for example when the output is piped to another command), one
    file is listed per line.

    """
    if onabort:
        o = options.Options(optspec % spec_prefix, onabort=onabort)
    else:
        o = options.Options(optspec % spec_prefix)
    (opt, flags, extra) = o.parse(args)

    # Handle order-sensitive options.
    classification = None
    show_hidden = None
    for flag in flags:
        (option, parameter) = flag
        if option in ('-F', '--classify'):
            classification = 'all'
        elif option == '--file-type':
            classification = 'type'
        elif option in ('-a', '--all'):
            show_hidden = 'all'
        elif option in ('-A', '--almost-all'):
            show_hidden = 'almost'

    L = []
    def output_node_info(node, name):
        info = node_info(node, name,
                         show_hash = opt.hash,
                         long_fmt = opt.l,
                         classification = classification,
                         numeric_ids = opt.numeric_ids,
                         human_readable = opt.human_readable)
        if not opt.l and istty1:
            L.append(info)
        else:
            print info

    ret = 0
    for path in (extra or [default]):
        try:
            n = pwd.try_resolve(path)

            if stat.S_ISDIR(n.mode):
                if show_hidden == 'all':
                    output_node_info(n, '.')
                    # Match non-bup "ls -a ... /".
                    if n.parent:
                        output_node_info(n.parent, '..')
                    else:
                        output_node_info(n, '..')
                for sub in n:
                    name = sub.name
                    if show_hidden in ('almost', 'all') \
                       or not len(name)>1 or not name.startswith('.'):
                        output_node_info(sub, name)
            else:
                output_node_info(n, path)
        except vfs.NodeError, e:
            log('error: %s\n' % e)
            ret = 1
Example #5
0
File: index.py Project: fakegit/bup
def main(argv):
    o = options.Options(optspec)
    opt, flags, extra = o.parse_bytes(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()

    handle_ctrl_c()

    if opt.verbose is None:
        opt.verbose = 0

    if opt.indexfile:
        indexfile = argv_bytes(opt.indexfile)
    else:
        indexfile = git.repo(b'bupindex')

    if opt.check:
        log('check: starting initial check.\n')
        with index.Reader(indexfile) as reader:
            check_index(reader, opt.verbose)

    if opt.clear:
        log('clear: clearing index.\n')
        clear_index(indexfile, opt.verbose)

    sys.stdout.flush()
    out = byte_stream(sys.stdout)

    if opt.update:
        if not extra:
            o.fatal('update mode (-u) requested but no paths given')
        extra = [argv_bytes(x) for x in extra]
        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,
                         indexfile,
                         check=opt.check,
                         check_device=opt.check_device,
                         xdev=opt.xdev,
                         xdev_exceptions=xexcept,
                         fake_valid=opt.fake_valid,
                         fake_invalid=opt.fake_invalid,
                         out=out,
                         verbose=opt.verbose)

    if opt['print'] or opt.status or opt.modified:
        extra = [argv_bytes(x) for x in extra]
        with index.Reader(indexfile) as reader:
            for name, ent in reader.filter(extra or [b'']):
                if (opt.modified and
                    (ent.is_valid() or ent.is_deleted() or not ent.mode)):
                    continue
                line = b''
                if opt.status:
                    if ent.is_deleted():
                        line += b'D '
                    elif not ent.is_valid():
                        if ent.sha == index.EMPTY_SHA:
                            line += b'A '
                        else:
                            line += b'M '
                    else:
                        line += b'  '
                if opt.hash:
                    line += hexlify(ent.sha) + b' '
                if opt.long:
                    line += b'%7s %7s ' % (oct(ent.mode).encode('ascii'),
                                           oct(ent.gitmode).encode('ascii'))
                out.write(line + (name or b'./') + b'\n')

    if opt.check and (opt['print'] or opt.status or opt.modified
                      or opt.update):
        log('check: starting final check.\n')
        with index.Reader(indexfile) as reader:
            check_index(reader, opt.verbose)

    if saved_errors:
        log('WARNING: %d errors encountered.\n' % len(saved_errors))
        sys.exit(1)
Example #6
0
def main(argv):
    o = options.Options(optspec)
    opt, flags, roots = o.parse_bytes(argv[1:])
    roots = [argv_bytes(x) for x in roots]

    if not opt.unsafe:
        o.fatal(
            'refusing to run dangerous, experimental command without --unsafe')

    now = int(time()) if opt.wrt is None else opt.wrt
    if not isinstance(now, int):
        o.fatal('--wrt value ' + str(now) + ' is not an integer')

    period_start = {}
    for period, extent in (('all', opt.keep_all_for), ('dailies',
                                                       opt.keep_dailies_for),
                           ('monthlies', opt.keep_monthlies_for),
                           ('yearlies', opt.keep_yearlies_for)):
        if extent:
            secs = period_as_secs(extent.encode('ascii'))
            if not secs:
                o.fatal('%r is not a valid period' % extent)
            period_start[period] = now - secs

    if not period_start:
        o.fatal('at least one keep argument is required')

    period_start = defaultdict(lambda: float('inf'), period_start)

    if opt.verbose:
        epoch_ymd = strftime('%Y-%m-%d-%H%M%S', localtime(0))
        for kind in ['all', 'dailies', 'monthlies', 'yearlies']:
            period_utc = period_start[kind]
            if period_utc != float('inf'):
                if not (period_utc > float('-inf')):
                    log('keeping all ' + kind)
                else:
                    try:
                        when = strftime('%Y-%m-%d-%H%M%S',
                                        localtime(period_utc))
                        log('keeping ' + kind + ' since ' + when + '\n')
                    except ValueError as ex:
                        if period_utc < 0:
                            log('keeping %s since %d seconds before %s\n' %
                                (kind, abs(period_utc), epoch_ymd))
                        elif period_utc > 0:
                            log('keeping %s since %d seconds after %s\n' %
                                (kind, period_utc, epoch_ymd))
                        else:
                            log('keeping %s since %s\n' % (kind, epoch_ymd))

    git.check_repo_or_die()

    # This could be more efficient, but for now just build the whole list
    # in memory and let bup_rm() do some redundant work.

    def parse_info(f):
        author_secs = f.readline().strip()
        return int(author_secs)

    sys.stdout.flush()
    out = byte_stream(sys.stdout)

    removals = []
    for branch, branch_id in branches(roots):
        die_if_errors()
        saves = ((utc, unhexlify(oidx)) for (
            oidx,
            utc) in git.rev_list(branch_id, format=b'%at', parse=parse_info))
        for keep_save, (utc, id) in classify_saves(saves, period_start):
            assert (keep_save in (False, True))
            # FIXME: base removals on hashes
            if opt.pretend:
                out.write((b'+ ' if keep_save else b'- ') +
                          save_name(branch, utc) + b'\n')
            elif not keep_save:
                removals.append(save_name(branch, utc))

    if not opt.pretend:
        die_if_errors()
        with LocalRepo() as repo:
            bup_rm(repo,
                   removals,
                   compression=opt.compress,
                   verbosity=opt.verbose)
        if opt.gc:
            die_if_errors()
            bup_gc(threshold=opt.gc_threshold,
                   compression=opt.compress,
                   verbosity=opt.verbose)

    die_if_errors()
Example #7
0
File: xstat.py Project: fakegit/bup
def main(argv):

    target_filename = b''
    active_fields = metadata.all_fields

    o = options.Options(optspec)
    (opt, flags, remainder) = o.parse_bytes(argv[1:])

    atime_resolution = parse_timestamp_arg(o, 'atime', opt.atime_resolution)
    mtime_resolution = parse_timestamp_arg(o, 'mtime', opt.mtime_resolution)
    ctime_resolution = parse_timestamp_arg(o, 'ctime', opt.ctime_resolution)

    treat_include_fields_as_definitive = True
    for flag, value in flags:
        if flag == '--exclude-fields':
            exclude_fields = frozenset(value.split(','))
            for f in exclude_fields:
                if not f in metadata.all_fields:
                    o.fatal(f + ' is not a valid field name')
            active_fields = active_fields - exclude_fields
            treat_include_fields_as_definitive = False
        elif flag == '--include-fields':
            include_fields = frozenset(value.split(','))
            for f in include_fields:
                if not f in metadata.all_fields:
                    o.fatal(f + ' is not a valid field name')
            if treat_include_fields_as_definitive:
                active_fields = include_fields
                treat_include_fields_as_definitive = False
            else:
                active_fields = active_fields | include_fields

    opt.verbose = opt.verbose or 0
    opt.quiet = opt.quiet or 0
    metadata.verbose = opt.verbose - opt.quiet

    sys.stdout.flush()
    out = byte_stream(sys.stdout)

    first_path = True
    for path in remainder:
        path = argv_bytes(path)
        try:
            m = metadata.from_path(path, archive_path = path)
        except (OSError,IOError) as e:
            if e.errno == errno.ENOENT:
                add_error(e)
                continue
            else:
                raise
        if metadata.verbose >= 0:
            if not first_path:
                out.write(b'\n')
            if atime_resolution != 1:
                m.atime = (m.atime / atime_resolution) * atime_resolution
            if mtime_resolution != 1:
                m.mtime = (m.mtime / mtime_resolution) * mtime_resolution
            if ctime_resolution != 1:
                m.ctime = (m.ctime / ctime_resolution) * ctime_resolution
            out.write(metadata.detailed_bytes(m, active_fields))
            out.write(b'\n')
            first_path = False

    if saved_errors:
        log('WARNING: %d errors encountered.\n' % len(saved_errors))
        sys.exit(1)
    else:
        sys.exit(0)
Example #8
0
def main(argv):
    global opt
    signal.signal(signal.SIGTERM, handle_sigterm)

    UnixAddress = namedtuple('UnixAddress', ['path'])
    InetAddress = namedtuple('InetAddress', ['host', 'port'])

    o = options.Options(optspec)
    opt, flags, extra = o.parse_bytes(argv[1:])

    if len(extra) > 1:
        o.fatal("at most one argument expected")

    if len(extra) == 0:
        address = InetAddress(host='127.0.0.1', port=8080)
    else:
        bind_url = extra[0]
        if bind_url.startswith('unix://'):
            address = UnixAddress(path=bind_url[len('unix://'):])
        else:
            addr_parts = extra[0].split(':', 1)
            if len(addr_parts) == 1:
                host = '127.0.0.1'
                port = addr_parts[0]
            else:
                host, port = addr_parts
            try:
                port = int(port)
            except (TypeError, ValueError) as ex:
                o.fatal('port must be an integer, not %r' % port)
            address = InetAddress(host=host, port=port)

    settings = dict(
        debug=1,
        template_path=resource_path(b'web').decode('utf-8'),
        static_path=resource_path(b'web/static').decode('utf-8'),
    )

    # Disable buffering on stdout, for debug messages
    try:
        sys.stdout._line_buffering = True
    except AttributeError:
        sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

    repo = from_opts(opt, reverse=False)
    application = tornado.web.Application([
        (r"(?P<path>/.*)", BupRequestHandler, dict(repo=repo)),
    ], **settings)

    http_server = HTTPServer(application)
    io_loop_pending = IOLoop.instance()

    if isinstance(address, InetAddress):
        sockets = tornado.netutil.bind_sockets(address.port, address.host)
        http_server.add_sockets(sockets)
        print('Serving HTTP on %s:%d...' % sockets[0].getsockname()[0:2])
        if opt.browser:
            browser_addr = 'http://' + address[0] + ':' + str(address[1])
            io_loop_pending.add_callback(lambda: webbrowser.open(browser_addr))
    elif isinstance(address, UnixAddress):
        unix_socket = bind_unix_socket(address.path)
        http_server.add_socket(unix_socket)
        print('Serving HTTP on filesystem socket %r' % address.path)
    else:
        log('error: unexpected address %r', address)
        sys.exit(1)

    io_loop = io_loop_pending
    io_loop.start()

    if saved_errors:
        log('WARNING: %d errors encountered while saving.\n' %
            len(saved_errors))
        sys.exit(1)
Example #9
0
def main(argv):
    global opt, par2_ok

    o = options.Options(optspec)
    opt, flags, extra = o.parse_bytes(argv[1:])
    opt.verbose = opt.verbose or 0

    par2_setup()
    if opt.par2_ok:
        if par2_ok:
            sys.exit(0)  # 'true' in sh
        else:
            sys.exit(1)
    if opt.disable_par2:
        par2_ok = 0

    git.check_repo_or_die()

    if extra:
        extra = [argv_bytes(x) for x in extra]
    else:
        debug('fsck: No filenames given: checking all packs.\n')
        extra = glob.glob(git.repo(b'objects/pack/*.pack'))

    sys.stdout.flush()
    out = byte_stream(sys.stdout)
    code = 0
    count = 0
    outstanding = {}
    for name in extra:
        if name.endswith(b'.pack'):
            base = name[:-5]
        elif name.endswith(b'.idx'):
            base = name[:-4]
        elif name.endswith(b'.par2'):
            base = name[:-5]
        elif os.path.exists(name + b'.pack'):
            base = name
        else:
            raise Exception('%r is not a pack file!' % name)
        (dir, last) = os.path.split(base)
        par2_exists = os.path.exists(base + b'.par2')
        if par2_exists and os.stat(base + b'.par2').st_size == 0:
            par2_exists = 0
        sys.stdout.flush(
        )  # Not sure we still need this, but it'll flush out too
        debug('fsck: checking %r (%s)\n' %
              (last, par2_ok and par2_exists and 'par2' or 'git'))
        if not opt.verbose:
            progress('fsck (%d/%d)\r' % (count, len(extra)))

        if not opt.jobs:
            nc = do_pack(base, last, par2_exists, out)
            code = code or nc
            count += 1
        else:
            while len(outstanding) >= opt.jobs:
                (pid, nc) = os.wait()
                nc >>= 8
                if pid in outstanding:
                    del outstanding[pid]
                    code = code or nc
                    count += 1
            pid = os.fork()
            if pid:  # parent
                outstanding[pid] = 1
            else:  # child
                try:
                    sys.exit(do_pack(base, last, par2_exists, out))
                except Exception as e:
                    log('exception: %r\n' % e)
                    sys.exit(99)

    while len(outstanding):
        (pid, nc) = os.wait()
        nc >>= 8
        if pid in outstanding:
            del outstanding[pid]
            code = code or nc
            count += 1
        if not opt.verbose:
            progress('fsck (%d/%d)\r' % (count, len(extra)))

    if istty2:
        debug('fsck done.           \n')
    sys.exit(code)
Example #10
0
def main(argv):
    o = options.Options(optspec, optfunc=getopt.getopt)
    opt, flags, extra = o.parse_bytes(argv[1:])
    if len(extra) < 2:
        o.fatal('arguments expected')

    class SigException(Exception):
        def __init__(self, signum):
            self.signum = signum
            Exception.__init__(self, 'signal %d received' % signum)

    def handler(signum, frame):
        raise SigException(signum)

    signal.signal(signal.SIGTERM, handler)
    signal.signal(signal.SIGINT, handler)

    sys.stdout.flush()
    out = byte_stream(sys.stdout)

    try:
        sp = None
        p = None
        ret = 99

        hp = argv_bytes(extra[0]).split(b':')
        if len(hp) == 1:
            (hostname, port) = (hp[0], None)
        else:
            (hostname, port) = hp
        argv = [argv_bytes(x) for x in extra[1:]]
        p = ssh.connect(hostname, port, b'on--server', stderr=PIPE)

        try:
            argvs = b'\0'.join([b'bup'] + argv)
            p.stdin.write(struct.pack('!I', len(argvs)) + argvs)
            p.stdin.flush()
            sp = subprocess.Popen([path.exe(), b'server'],
                                  stdin=p.stdout,
                                  stdout=p.stdin)
            p.stdin.close()
            p.stdout.close()
            # Demultiplex remote client's stderr (back to stdout/stderr).
            dmc = DemuxConn(p.stderr.fileno(), open(os.devnull, "wb"))
            for line in iter(dmc.readline, b''):
                out.write(line)
        finally:
            while 1:
                # if we get a signal while waiting, we have to keep waiting, just
                # in case our child doesn't die.
                try:
                    ret = p.wait()
                    if sp:
                        sp.wait()
                    break
                except SigException as e:
                    log('\nbup on: %s\n' % e)
                    os.kill(p.pid, e.signum)
                    ret = 84
    except SigException as e:
        if ret == 0:
            ret = 99
        log('\nbup on: %s\n' % e)

    sys.exit(ret)
Example #11
0
File: save.py Project: fakegit/bup
def opts_from_cmdline(argv):
    o = options.Options(optspec)
    opt, flags, extra = o.parse_bytes(argv[1:])

    if opt.indexfile:
        opt.indexfile = argv_bytes(opt.indexfile)
    if opt.name:
        opt.name = argv_bytes(opt.name)
    if opt.remote:
        opt.remote = argv_bytes(opt.remote)
    if opt.strip_path:
        opt.strip_path = argv_bytes(opt.strip_path)
    if not (opt.tree or opt.commit or opt.name):
        o.fatal("use one or more of -t, -c, -n")
    if not extra:
        o.fatal("no filenames given")
    if opt.date:
        opt.date = parse_date_or_fatal(opt.date, o.fatal)
    else:
        opt.date = time.time()

    opt.progress = (istty2 and not opt.quiet)
    opt.smaller = parse_num(opt.smaller or 0)

    if opt.bwlimit:
        opt.bwlimit = parse_num(opt.bwlimit)

    if opt.strip and opt.strip_path:
        o.fatal("--strip is incompatible with --strip-path")

    opt.sources = [argv_bytes(x) for x in extra]

    grafts = []
    if opt.graft:
        if opt.strip:
            o.fatal("--strip is incompatible with --graft")

        if opt.strip_path:
            o.fatal("--strip-path is incompatible with --graft")

        for (option, parameter) in flags:
            if option == "--graft":
                parameter = argv_bytes(parameter)
                splitted_parameter = parameter.split(b'=')
                if len(splitted_parameter) != 2:
                    o.fatal("a graft point must be of the form old_path=new_path")
                old_path, new_path = splitted_parameter
                if not (old_path and new_path):
                    o.fatal("a graft point cannot be empty")
                grafts.append((resolve_parent(old_path),
                               resolve_parent(new_path)))
    opt.grafts = grafts

    opt.is_reverse = environ.get(b'BUP_SERVER_REVERSE')
    if opt.is_reverse and opt.remote:
        o.fatal("don't use -r in reverse mode; it's automatic")

    if opt.name and not valid_save_name(opt.name):
        o.fatal("'%s' is not a valid branch name" % path_msg(opt.name))

    return opt
Example #12
0
def main(argv):
    o = options.Options(optspec)
    opt, flags, extra = o.parse_bytes(argv[1:])

    git.check_repo_or_die()

    global repo

    sys.stdout.flush()
    out = byte_stream(sys.stdout)
    stdin = byte_stream(sys.stdin)
    repo = LocalRepo()
    pwd = vfs.resolve(repo, b'/')
    rv = 0

    def inputiter(f):
        if os.isatty(f.fileno()):
            while 1:
                prompt = b'bup %s> ' % (b'/'.join(name for name, item in pwd) or b'/', )
                if hasattr(_helpers, 'readline'):
                    try:
                        yield _helpers.readline(prompt)
                    except EOFError:
                        print()  # Clear the line for the terminal's next prompt
                        break
                else:
                    out.write(prompt)
                    out.flush()
                    read_line = f.readline()
                    if not read_line:
                        print('')
                        break
                    yield read_line
        else:
            for line in f:
                yield line


    if extra:
        lines = (argv_bytes(arg) for arg in extra)
    else:
        if hasattr(_helpers, 'readline'):
            _helpers.set_completer_word_break_characters(b' \t\n\r/')
            _helpers.set_attempted_completion_function(attempt_completion)
            _helpers.set_completion_entry_function(enter_completion)
            if sys.platform.startswith('darwin'):
                # MacOS uses a slightly incompatible clone of libreadline
                _helpers.parse_and_bind(b'bind ^I rl_complete')
            _helpers.parse_and_bind(b'tab: complete')
        lines = inputiter(stdin)

    for line in lines:
        if not line.strip():
            continue
        words = [word for (wordstart,word) in shquote.quotesplit(line)]
        cmd = words[0].lower()
        #log('execute: %r %r\n' % (cmd, parm))
        try:
            if cmd == b'ls':
                do_ls(repo, pwd, words[1:], out)
                out.flush()
            elif cmd == b'cd':
                np = pwd
                for parm in words[1:]:
                    res = vfs.resolve(repo, parm, parent=np)
                    _, leaf_item = res[-1]
                    if not leaf_item:
                        raise Exception('%s does not exist'
                                        % path_msg(b'/'.join(name for name, item
                                                             in res)))
                    if not stat.S_ISDIR(vfs.item_mode(leaf_item)):
                        raise Exception('%s is not a directory' % path_msg(parm))
                    np = res
                pwd = np
            elif cmd == b'pwd':
                if len(pwd) == 1:
                    out.write(b'/')
                out.write(b'/'.join(name for name, item in pwd) + b'\n')
                out.flush()
            elif cmd == b'cat':
                for parm in words[1:]:
                    res = vfs.resolve(repo, parm, parent=pwd)
                    _, leaf_item = res[-1]
                    if not leaf_item:
                        raise Exception('%s does not exist' %
                                        path_msg(b'/'.join(name for name, item
                                                           in res)))
                    with vfs.fopen(repo, leaf_item) as srcfile:
                        write_to_file(srcfile, out)
                out.flush()
            elif cmd == b'get':
                if len(words) not in [2,3]:
                    rv = 1
                    raise Exception('Usage: get <filename> [localname]')
                rname = words[1]
                (dir,base) = os.path.split(rname)
                lname = len(words) > 2 and words[2] or base
                res = vfs.resolve(repo, rname, parent=pwd)
                _, leaf_item = res[-1]
                if not leaf_item:
                    raise Exception('%s does not exist' %
                                    path_msg(b'/'.join(name for name, item in res)))
                with vfs.fopen(repo, leaf_item) as srcfile:
                    with open(lname, 'wb') as destfile:
                        log('Saving %s\n' % path_msg(lname))
                        write_to_file(srcfile, destfile)
            elif cmd == b'mget':
                for parm in words[1:]:
                    dir, base = os.path.split(parm)

                    res = vfs.resolve(repo, dir, parent=pwd)
                    _, dir_item = res[-1]
                    if not dir_item:
                        raise Exception('%s does not exist' % path_msg(dir))
                    for name, item in vfs.contents(repo, dir_item):
                        if name == b'.':
                            continue
                        if fnmatch.fnmatch(name, base):
                            if stat.S_ISLNK(vfs.item_mode(item)):
                                deref = vfs.resolve(repo, name, parent=res)
                                deref_name, deref_item = deref[-1]
                                if not deref_item:
                                    raise Exception('%s does not exist' %
                                                    path_msg('/'.join(name for name, item
                                                                      in deref)))
                                item = deref_item
                            with vfs.fopen(repo, item) as srcfile:
                                with open(name, 'wb') as destfile:
                                    log('Saving %s\n' % path_msg(name))
                                    write_to_file(srcfile, destfile)
            elif cmd == b'help' or cmd == b'?':
                out.write(b'Commands: ls cd pwd cat get mget help quit\n')
                out.flush()
            elif cmd in (b'quit', b'exit', b'bye'):
                break
            else:
                rv = 1
                raise Exception('no such command %r' % cmd)
        except Exception as e:
            rv = 1
            log('error: %s\n' % e)
            raise

    sys.exit(rv)
Example #13
0
File: tag.py Project: jmberg/bup
def main(argv):
    o = options.Options(optspec)
    opt, flags, extra = o.parse_bytes(argv[1:])

    r = from_opts(opt)

    refs = { r[0]: r[1] for r in r.refs() if r[0].startswith(b'refs/tags/') }

    if opt.delete:
        # git.delete_ref() doesn't complain if a ref doesn't exist.  We
        # could implement this verification but we'd need to read in the
        # contents of the tag file and pass the hash, and we already know
        # about the tag's existance via "tags".
        tag_name = argv_bytes(opt.delete)
        refname = b'refs/tags/' + tag_name
        if not opt.force and refname not in refs:
            log("error: tag '%s' doesn't exist\n" % path_msg(tag_name))
            sys.exit(1)
        r.delete_ref(b'refs/tags/%s' % tag_name)
        sys.exit(0)

    if not extra:
        for t in refs:
            sys.stdout.flush()
            out = byte_stream(sys.stdout)
            out.write(t[len(b'refs/tags/'):])
            out.write(b'\n')
        sys.exit(0)
    elif len(extra) != 2:
        o.fatal('expected commit ref and hash')

    tag_name, commit = map(argv_bytes, extra[:2])
    if not tag_name:
        o.fatal("tag name must not be empty.")
    debug1("args: tag name = %s; commit = %s\n"
           % (path_msg(tag_name), commit.decode('ascii')))
    refname = b'refs/tags/' + tag_name

    if refname in refs and not opt.force:
        log("bup: error: tag '%s' already exists\n" % path_msg(tag_name))
        sys.exit(1)

    if tag_name.startswith(b'.'):
        o.fatal("'%s' is not a valid tag name." % path_msg(tag_name))

    try:
        hash = r.rev_parse(commit)
    except git.GitError as e:
        log("bup: error: %s" % e)
        sys.exit(2)

    if not r.exists(hash):
        log("bup: error: commit %s not found.\n" % commit.decode('ascii'))
        sys.exit(2)

    try:
        oldval = refs.get(refname, None)
        r.update_ref(refname, hash, oldval)
    except git.GitError as e:
        log("bup: error: could not create tag '%s': %s" % (path_msg(tag_name), e))
        sys.exit(3)
Example #14
0
def main(argv):
    o = options.Options(optspec)
    opt, flags, extra = o.parse_bytes(argv[1:])

    git.check_repo_or_die()

    tags = [t for sublist in git.tags().values() for t in sublist]

    if opt.delete:
        # git.delete_ref() doesn't complain if a ref doesn't exist.  We
        # could implement this verification but we'd need to read in the
        # contents of the tag file and pass the hash, and we already know
        # about the tag's existance via "tags".
        tag_name = argv_bytes(opt.delete)
        if not opt.force and tag_name not in tags:
            log("error: tag '%s' doesn't exist\n" % path_msg(tag_name))
            sys.exit(1)
        tag_file = b'refs/tags/%s' % tag_name
        git.delete_ref(tag_file)
        sys.exit(0)

    if not extra:
        for t in tags:
            sys.stdout.flush()
            out = byte_stream(sys.stdout)
            out.write(t)
            out.write(b'\n')
        sys.exit(0)
    elif len(extra) != 2:
        o.fatal('expected commit ref and hash')

    tag_name, commit = map(argv_bytes, extra[:2])
    if not tag_name:
        o.fatal("tag name must not be empty.")
    debug1("args: tag name = %s; commit = %s\n"
           % (path_msg(tag_name), commit.decode('ascii')))

    if tag_name in tags and not opt.force:
        log("bup: error: tag '%s' already exists\n" % path_msg(tag_name))
        sys.exit(1)

    if tag_name.startswith(b'.'):
        o.fatal("'%s' is not a valid tag name." % path_msg(tag_name))

    try:
        hash = git.rev_parse(commit)
    except git.GitError as e:
        log("bup: error: %s" % e)
        sys.exit(2)

    if not hash:
        log("bup: error: commit %s not found.\n" % commit.decode('ascii'))
        sys.exit(2)

    pL = git.PackIdxList(git.repo(b'objects/pack'))
    if not pL.exists(hash):
        log("bup: error: commit %s not found.\n" % commit.decode('ascii'))
        sys.exit(2)

    tag_file = git.repo(b'refs/tags/' + tag_name)
    try:
        tag = open(tag_file, 'wb')
    except OSError as e:
        log("bup: error: could not create tag '%s': %s" % (path_msg(tag_name), e))
        sys.exit(3)
    with tag as tag:
        tag.write(hexlify(hash))
        tag.write(b'\n')
Example #15
0
"""": # -*-python-*-
bup_python="$(dirname "$0")/bup-python" || exit $?
exec "$bup_python" "$0" ${1+"$@"}
"""
# end of bup preamble
import sys, getopt, socket, subprocess, fcntl
from bup import options, path
from bup.helpers import *

optspec = """
bup daemon [options...] -- [bup-server options...]
--
l,listen  ip address to listen on, defaults to *
p,port    port to listen on, defaults to 1982
"""
o = options.Options(optspec, optfunc=getopt.getopt)
(opt, flags, extra) = o.parse(sys.argv[1:])

host = opt.listen
port = opt.port and int(opt.port) or 1982

import socket
import sys

socks = []
e = None
for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM,
                              0, socket.AI_PASSIVE):
    af, socktype, proto, canonname, sa = res
    try:
        s = socket.socket(af, socktype, proto)
Example #16
0
def test_invalid_optspec():
    WVPASS(options.Options(invalid_optspec0).parse([]))
    WVPASS(options.Options(invalid_optspec1).parse([]))
    WVPASS(options.Options(invalid_optspec2).parse([]))
Example #17
0
File: save.py Project: jmberg/bup
def main(argv):

    # Hack around lack of nonlocal vars in python 2
    _nonlocal = {}

    o = options.Options(optspec)
    opt, flags, extra = o.parse_bytes(argv[1:])

    if opt.indexfile:
        opt.indexfile = argv_bytes(opt.indexfile)
    if opt.name:
        opt.name = argv_bytes(opt.name)
    if opt.strip_path:
        opt.strip_path = argv_bytes(opt.strip_path)

    if not (opt.tree or opt.commit or opt.name):
        o.fatal("use one or more of -t, -c, -n")
    if not extra:
        o.fatal("no filenames given")

    extra = [argv_bytes(x) for x in extra]

    opt.progress = (istty2 and not opt.quiet)
    opt.smaller = parse_num(opt.smaller or 0)
    if opt.bwlimit:
        client.bwlimit = parse_num(opt.bwlimit)

    if opt.date:
        date = parse_date_or_fatal(opt.date, o.fatal)
    else:
        date = time.time()

    if opt.strip and opt.strip_path:
        o.fatal("--strip is incompatible with --strip-path")

    graft_points = []
    if opt.graft:
        if opt.strip:
            o.fatal("--strip is incompatible with --graft")

        if opt.strip_path:
            o.fatal("--strip-path is incompatible with --graft")

        for (option, parameter) in flags:
            if option == "--graft":
                parameter = argv_bytes(parameter)
                splitted_parameter = parameter.split(b'=')
                if len(splitted_parameter) != 2:
                    o.fatal(
                        "a graft point must be of the form old_path=new_path")
                old_path, new_path = splitted_parameter
                if not (old_path and new_path):
                    o.fatal("a graft point cannot be empty")
                graft_points.append(
                    (resolve_parent(old_path), resolve_parent(new_path)))

    name = opt.name
    if name and not valid_save_name(name):
        o.fatal("'%s' is not a valid branch name" % path_msg(name))
    refname = name and b'refs/heads/%s' % name or None

    repo = from_opts(opt)
    blobbits = repo.config(b'bup.blobbits', opttype='int')

    oldref = refname and repo.read_ref(refname) or None

    handle_ctrl_c()

    # Metadata is stored in a file named .bupm in each directory.  The
    # first metadata entry will be the metadata for the current directory.
    # The remaining entries will be for each of the other directory
    # elements, in the order they're listed in the index.
    #
    # Since the git tree elements are sorted according to
    # git.shalist_item_sort_key, the metalist items are accumulated as
    # (sort_key, metadata) tuples, and then sorted when the .bupm file is
    # created.  The sort_key should have been computed using the element's
    # mangled name and git mode (after hashsplitting), but the code isn't
    # actually doing that but rather uses the element's real name and mode.
    # This makes things a bit more difficult when reading it back, see
    # vfs.ordered_tree_entries().

    # Maintain a stack of information representing the current location in
    # the archive being constructed.

    stack = Stack(repo)

    _nonlocal['count'] = 0
    _nonlocal['subcount'] = 0
    _nonlocal['lastremain'] = None

    def progress_report(file, n):
        _nonlocal['subcount'] += n
        cc = _nonlocal['count'] + _nonlocal['subcount']
        pct = total and (cc * 100.0 / total) or 0
        now = time.time()
        elapsed = now - tstart
        kps = elapsed and int(cc / 1024. / elapsed)
        kps_frac = 10**int(math.log(kps + 1, 10) - 1)
        kps = int(kps / kps_frac) * kps_frac
        if cc:
            remain = elapsed * 1.0 / cc * (total - cc)
        else:
            remain = 0.0
        if (_nonlocal['lastremain'] and (remain > _nonlocal['lastremain']) and
            ((remain - _nonlocal['lastremain']) / _nonlocal['lastremain'] <
             0.05)):
            remain = _nonlocal['lastremain']
        else:
            _nonlocal['lastremain'] = remain
        hours = int(remain / 60 / 60)
        mins = int(remain / 60 - hours * 60)
        secs = int(remain - hours * 60 * 60 - mins * 60)
        if elapsed < 30:
            remainstr = ''
            kpsstr = ''
        else:
            kpsstr = '%dk/s' % kps
            if hours:
                remainstr = '%dh%dm' % (hours, mins)
            elif mins:
                remainstr = '%dm%d' % (mins, secs)
            else:
                remainstr = '%ds' % secs
        qprogress(
            'Saving: %.2f%% (%d/%dk, %d/%d files) %s %s\r' %
            (pct, cc / 1024, total / 1024, fcount, ftotal, remainstr, kpsstr))

    indexfile = opt.indexfile or path.index()
    r = index.Reader(indexfile)
    try:
        msr = index.MetaStoreReader(indexfile + b'.meta')
    except IOError as ex:
        if ex.errno != EACCES:
            raise
        log('error: cannot access %r; have you run bup index?' %
            path_msg(indexfile))
        sys.exit(1)
    hlink_db = hlinkdb.HLinkDB(indexfile + b'.hlink')

    def already_saved(ent):
        return ent.is_valid() and repo.exists(ent.sha) and ent.sha

    def wantrecurse_pre(ent):
        return not already_saved(ent)

    def wantrecurse_during(ent):
        return not already_saved(ent) or ent.sha_missing()

    def find_hardlink_target(hlink_db, ent):
        if hlink_db and not stat.S_ISDIR(ent.mode) and ent.nlink > 1:
            link_paths = hlink_db.node_paths(ent.dev, ent.ino)
            if link_paths:
                return link_paths[0]

    total = ftotal = 0
    if opt.progress:
        for (transname, ent) in r.filter(extra, wantrecurse=wantrecurse_pre):
            if not (ftotal % 10024):
                qprogress('Reading index: %d\r' % ftotal)
            exists = ent.exists()
            hashvalid = already_saved(ent)
            ent.set_sha_missing(not hashvalid)
            if not opt.smaller or ent.size < opt.smaller:
                if exists and not hashvalid:
                    total += ent.size
            ftotal += 1
        progress('Reading index: %d, done.\n' % ftotal)

    # Root collisions occur when strip or graft options map more than one
    # path to the same directory (paths which originally had separate
    # parents).  When that situation is detected, use empty metadata for
    # the parent.  Otherwise, use the metadata for the common parent.
    # Collision example: "bup save ... --strip /foo /foo/bar /bar".

    # FIXME: Add collision tests, or handle collisions some other way.

    # FIXME: Detect/handle strip/graft name collisions (other than root),
    # i.e. if '/foo/bar' and '/bar' both map to '/'.

    first_root = None
    root_collision = None
    tstart = time.time()
    fcount = 0
    lastskip_name = None
    lastdir = b''
    for (transname, ent) in r.filter(extra, wantrecurse=wantrecurse_during):
        (dir, file) = os.path.split(ent.name)
        exists = (ent.flags & index.IX_EXISTS)
        hashvalid = already_saved(ent)
        wasmissing = ent.sha_missing()
        oldsize = ent.size
        if opt.verbose:
            if not exists:
                status = 'D'
            elif not hashvalid:
                if ent.sha == index.EMPTY_SHA:
                    status = 'A'
                else:
                    status = 'M'
            else:
                status = ' '
            if opt.verbose >= 2:
                log('%s %-70s\n' % (status, path_msg(ent.name)))
            elif not stat.S_ISDIR(ent.mode) and lastdir != dir:
                if not lastdir.startswith(dir):
                    log('%s %-70s\n' %
                        (status, path_msg(os.path.join(dir, b''))))
                lastdir = dir

        if not opt.progress:
            progress_report = None
        fcount += 1

        if not exists:
            continue
        if opt.smaller and ent.size >= opt.smaller:
            if exists and not hashvalid:
                if opt.verbose:
                    log('skipping large file "%s"\n' % path_msg(ent.name))
                lastskip_name = ent.name
            continue

        assert (dir.startswith(b'/'))
        if opt.strip:
            dirp = stripped_path_components(dir, extra)
        elif opt.strip_path:
            dirp = stripped_path_components(dir, [opt.strip_path])
        elif graft_points:
            dirp = grafted_path_components(graft_points, dir)
        else:
            dirp = path_components(dir)

        # At this point, dirp contains a representation of the archive
        # path that looks like [(archive_dir_name, real_fs_path), ...].
        # So given "bup save ... --strip /foo/bar /foo/bar/baz", dirp
        # might look like this at some point:
        #   [('', '/foo/bar'), ('baz', '/foo/bar/baz'), ...].

        # This dual representation supports stripping/grafting, where the
        # archive path may not have a direct correspondence with the
        # filesystem.  The root directory is represented by an initial
        # component named '', and any component that doesn't have a
        # corresponding filesystem directory (due to grafting, for
        # example) will have a real_fs_path of None, i.e. [('', None),
        # ...].

        if first_root == None:
            first_root = dirp[0]
        elif first_root != dirp[0]:
            root_collision = True

        # If switching to a new sub-tree, finish the current sub-tree.
        while list(stack.namestack) > [x[0] for x in dirp]:
            stack, _ = stack.pop()

        # If switching to a new sub-tree, start a new sub-tree.
        for path_component in dirp[len(stack):]:
            dir_name, fs_path = path_component
            # Not indexed, so just grab the FS metadata or use empty metadata.
            try:
                meta = metadata.from_path(fs_path, normalized=True) \
                    if fs_path else metadata.Metadata()
            except (OSError, IOError) as e:
                add_error(e)
                lastskip_name = dir_name
                meta = metadata.Metadata()
            stack = stack.push(dir_name, meta)

        if not file:
            if len(stack) == 1:
                continue  # We're at the top level -- keep the current root dir
            # Since there's no filename, this is a subdir -- finish it.
            oldtree = hashvalid  # may be None
            stack, newtree = stack.pop(override_tree=oldtree)
            if not oldtree:
                if lastskip_name and lastskip_name.startswith(ent.name):
                    ent.invalidate()
                else:
                    ent.validate(GIT_MODE_TREE, newtree)
                ent.repack()
            if exists and wasmissing:
                _nonlocal['count'] += oldsize
            continue

        # it's not a directory
        if hashvalid:
            meta = msr.metadata_at(ent.meta_ofs)
            meta.hardlink_target = find_hardlink_target(hlink_db, ent)
            # Restore the times that were cleared to 0 in the metastore.
            (meta.atime, meta.mtime, meta.ctime) = (ent.atime, ent.mtime,
                                                    ent.ctime)
            stack.append(file, ent.mode, ent.gitmode, ent.sha, meta)
        else:
            id = None
            hlink = find_hardlink_target(hlink_db, ent)
            try:
                meta = metadata.from_path(
                    ent.name,
                    hardlink_target=hlink,
                    normalized=True,
                    after_stat=after_nondir_metadata_stat)
            except (OSError, IOError) as e:
                add_error(e)
                lastskip_name = ent.name
                continue
            if stat.S_IFMT(ent.mode) != stat.S_IFMT(meta.mode):
                # The mode changed since we indexed the file, this is bad.
                # This can cause two issues:
                # 1) We e.g. think the file is a regular file, but now it's
                #    something else (a device, socket, FIFO or symlink, etc.)
                #    and _read_ from it when we shouldn't.
                # 2) We then record it as valid, but don't update the index
                #    metadata, and on a subsequent save it has 'hashvalid'
                #    but is recorded as the file type from the index, when
                #    the content is something else ...
                # Avoid all of these consistency issues by just skipping such
                # things - it really ought to not happen anyway.
                add_error("%s: mode changed since indexing, skipping." %
                          path_msg(ent.name))
                lastskip_name = ent.name
                continue
            if stat.S_ISREG(ent.mode):
                try:
                    # If the file changes while we're reading it, then our reading
                    # may stop at some point, but the stat() above may have gotten
                    # a different size already. Recalculate the meta size so that
                    # the repository records the accurate size in the metadata, even
                    # if the other stat() data might be slightly older than the file
                    # content (which we can't fix, this is inherently racy, but we
                    # can prevent the size mismatch.)
                    meta.size = 0

                    def write_data(data):
                        meta.size += len(data)
                        return repo.write_data(data)

                    before_saving_regular_file(ent.name)
                    with hashsplit.open_noatime(ent.name) as f:
                        (mode, id) = hashsplit.split_to_blob_or_tree(
                            write_data,
                            repo.write_tree, [f],
                            keep_boundaries=False,
                            progress=progress_report,
                            blobbits=blobbits)
                except (IOError, OSError) as e:
                    add_error('%s: %s' % (ent.name, e))
                    lastskip_name = ent.name
            elif stat.S_ISDIR(ent.mode):
                assert (0)  # handled above
            elif stat.S_ISLNK(ent.mode):
                mode, id = (GIT_MODE_SYMLINK,
                            repo.write_symlink(meta.symlink_target))
            else:
                # Everything else should be fully described by its
                # metadata, so just record an empty blob, so the paths
                # in the tree and .bupm will match up.
                (mode, id) = (GIT_MODE_FILE, repo.write_data(b''))

            if id:
                ent.validate(mode, id)
                ent.repack()
                stack.append(file, ent.mode, ent.gitmode, id, meta)

        if exists and wasmissing:
            _nonlocal['count'] += oldsize
            _nonlocal['subcount'] = 0

    if opt.progress:
        pct = total and _nonlocal['count'] * 100.0 / total or 100
        progress(
            'Saving: %.2f%% (%d/%dk, %d/%d files), done.    \n' %
            (pct, _nonlocal['count'] / 1024, total / 1024, fcount, ftotal))

    # pop all parts above the root folder
    while not stack.parent.nothing:
        stack, _ = stack.pop()

    # Finish the root directory.
    # When there's a collision, use empty metadata for the root.
    root_meta = metadata.Metadata() if root_collision else None
    stack, tree = stack.pop(override_meta=root_meta)

    sys.stdout.flush()
    out = byte_stream(sys.stdout)

    if opt.tree:
        out.write(hexlify(tree))
        out.write(b'\n')
    if opt.commit or name:
        if compat.py_maj > 2:
            # Strip b prefix from python 3 bytes reprs to preserve previous format
            msgcmd = b'[%s]' % b', '.join(
                [repr(argv_bytes(x))[1:].encode('ascii') for x in argv])
        else:
            msgcmd = repr(argv)
        msg = b'bup save\n\nGenerated by command:\n%s\n' % msgcmd
        userline = (b'%s <%s@%s>' % (userfullname(), username(), hostname()))
        commit = repo.write_commit(tree, oldref, userline, date, None,
                                   userline, date, None, msg)
        if opt.commit:
            out.write(hexlify(commit))
            out.write(b'\n')

    msr.close()

    if opt.name:
        repo.update_ref(refname, commit, oldref)

    repo.close()

    if saved_errors:
        log('WARNING: %d errors encountered while saving.\n' %
            len(saved_errors))
        sys.exit(1)
Example #18
0
def main(argv):

    o = options.Options(optspec)
    opt, flags, remainder = o.parse_bytes([b'--paths', b'--symlinks', b'--recurse']
                                          + argv[1:])

    opt.verbose = opt.verbose or 0
    opt.quiet = opt.quiet or 0
    metadata.verbose = opt.verbose - opt.quiet
    opt.file = argv_bytes(opt.file) if opt.file else None

    action_count = sum([bool(x) for x in [opt.create, opt.list, opt.extract,
                                          opt.start_extract, opt.finish_extract,
                                          opt.edit]])
    if action_count > 1:
        o.fatal("bup: only one action permitted: --create --list --extract --edit")
    if action_count == 0:
        o.fatal("bup: no action specified")

    if opt.create:
        if len(remainder) < 1:
            o.fatal("no paths specified for create")
        output_file = open_output(opt.file)
        metadata.save_tree(output_file,
                           [argv_bytes(r) for r in remainder],
                           recurse=opt.recurse,
                           write_paths=opt.paths,
                           save_symlinks=opt.symlinks,
                           xdev=opt.xdev)
    elif opt.list:
        if len(remainder) > 0:
            o.fatal("cannot specify paths for --list")
        src = open_input(opt.file)
        metadata.display_archive(src, open_output(b'-'))
    elif opt.start_extract:
        if len(remainder) > 0:
            o.fatal("cannot specify paths for --start-extract")
        src = open_input(opt.file)
        metadata.start_extract(src, create_symlinks=opt.symlinks)
    elif opt.finish_extract:
        if len(remainder) > 0:
            o.fatal("cannot specify paths for --finish-extract")
        src = open_input(opt.file)
        metadata.finish_extract(src, restore_numeric_ids=opt.numeric_ids)
    elif opt.extract:
        if len(remainder) > 0:
            o.fatal("cannot specify paths for --extract")
        src = open_input(opt.file)
        metadata.extract(src,
                         restore_numeric_ids=opt.numeric_ids,
                         create_symlinks=opt.symlinks)
    elif opt.edit:
        if len(remainder) < 1:
            o.fatal("no paths specified for edit")
        output_file = open_output(opt.file)

        unset_user = False # True if --unset-user was the last relevant option.
        unset_group = False # True if --unset-group was the last relevant option.
        for flag in flags:
            if flag[0] == '--set-user':
                unset_user = False
            elif flag[0] == '--unset-user':
                unset_user = True
            elif flag[0] == '--set-group':
                unset_group = False
            elif flag[0] == '--unset-group':
                unset_group = True

        for path in remainder:
            f = open(argv_bytes(path), 'rb')
            try:
                for m in metadata._ArchiveIterator(f):
                    if opt.set_uid is not None:
                        try:
                            m.uid = int(opt.set_uid)
                        except ValueError:
                            o.fatal("uid must be an integer")

                    if opt.set_gid is not None:
                        try:
                            m.gid = int(opt.set_gid)
                        except ValueError:
                            o.fatal("gid must be an integer")

                    if unset_user:
                        m.user = b''
                    elif opt.set_user is not None:
                        m.user = argv_bytes(opt.set_user)

                    if unset_group:
                        m.group = b''
                    elif opt.set_group is not None:
                        m.group = argv_bytes(opt.set_group)

                    m.write(output_file)
            finally:
                f.close()


    if saved_errors:
        log('WARNING: %d errors encountered.\n' % len(saved_errors))
        sys.exit(1)
    else:
        sys.exit(0)
Example #19
0
check      carefully check index file integrity
clear      clear the 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 (can be used more than once)
exclude-from= a file that contains exclude paths (can be used more than once)
exclude-rx= skip paths that match the unanchored regular expression
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')

# FIXME: remove this once we account for timestamp races, i.e. index;
Example #20
0
def main(argv):
    o = options.Options(optspec)
    opt, flags, extra = o.parse_bytes(argv[1:])
    if opt.name: opt.name = argv_bytes(opt.name)
    if opt.remote: opt.remote = argv_bytes(opt.remote)
    if opt.verbose is None: opt.verbose = 0

    if not (opt.blobs or opt.tree or opt.commit or opt.name or opt.noop
            or opt.copy):
        o.fatal("use one or more of -b, -t, -c, -n, --noop, --copy")
    if opt.copy and (opt.blobs or opt.tree):
        o.fatal('--copy is incompatible with -b, -t')
    if (opt.noop or opt.copy) and (opt.commit or opt.name):
        o.fatal('--noop and --copy are incompatible with -c, -n')
    if opt.blobs and (opt.tree or opt.commit or opt.name):
        o.fatal('-b is incompatible with -t, -c, -n')
    if extra and opt.git_ids:
        o.fatal("don't provide filenames when using --git-ids")

    if opt.verbose >= 2:
        git.verbose = opt.verbose - 1
        opt.bench = 1

    max_pack_size = None
    if opt.max_pack_size:
        max_pack_size = parse_num(opt.max_pack_size)
    max_pack_objects = None
    if opt.max_pack_objects:
        max_pack_objects = parse_num(opt.max_pack_objects)

    if opt.fanout:
        hashsplit.fanout = parse_num(opt.fanout)
    if opt.blobs:
        hashsplit.fanout = 0
    if opt.bwlimit:
        client.bwlimit = parse_num(opt.bwlimit)
    if opt.date:
        date = parse_date_or_fatal(opt.date, o.fatal)
    else:
        date = time.time()

    # Hack around lack of nonlocal vars in python 2
    total_bytes = [0]

    def prog(filenum, nbytes):
        total_bytes[0] += nbytes
        if filenum > 0:
            qprogress('Splitting: file #%d, %d kbytes\r' %
                      (filenum + 1, total_bytes[0] // 1024))
        else:
            qprogress('Splitting: %d kbytes\r' % (total_bytes[0] // 1024))

    is_reverse = environ.get(b'BUP_SERVER_REVERSE')
    if is_reverse and opt.remote:
        o.fatal("don't use -r in reverse mode; it's automatic")
    start_time = time.time()

    if opt.name and not valid_save_name(opt.name):
        o.fatal("'%r' is not a valid branch name." % opt.name)
    refname = opt.name and b'refs/heads/%s' % opt.name or None

    if opt.noop or opt.copy:
        cli = pack_writer = oldref = None
    elif opt.remote or is_reverse:
        git.check_repo_or_die()
        cli = client.Client(opt.remote)
        oldref = refname and cli.read_ref(refname) or None
        pack_writer = cli.new_packwriter(compression_level=opt.compress,
                                         max_pack_size=max_pack_size,
                                         max_pack_objects=max_pack_objects)
    else:
        git.check_repo_or_die()
        cli = None
        oldref = refname and git.read_ref(refname) or None
        pack_writer = git.PackWriter(compression_level=opt.compress,
                                     max_pack_size=max_pack_size,
                                     max_pack_objects=max_pack_objects)

    input = byte_stream(sys.stdin)

    if opt.git_ids:
        # the input is actually a series of git object ids that we should retrieve
        # and split.
        #
        # This is a bit messy, but basically it converts from a series of
        # CatPipe.get() iterators into a series of file-type objects.
        # It would be less ugly if either CatPipe.get() returned a file-like object
        # (not very efficient), or split_to_shalist() expected an iterator instead
        # of a file.
        cp = git.CatPipe()

        class IterToFile:
            def __init__(self, it):
                self.it = iter(it)

            def read(self, size):
                v = next(self.it, None)
                return v or b''

        def read_ids():
            while 1:
                line = input.readline()
                if not line:
                    break
                if line:
                    line = line.strip()
                try:
                    it = cp.get(line.strip())
                    next(it, None)  # skip the file info
                except KeyError as e:
                    add_error('error: %s' % e)
                    continue
                yield IterToFile(it)

        files = read_ids()
    else:
        # the input either comes from a series of files or from stdin.
        files = extra and (open(argv_bytes(fn), 'rb')
                           for fn in extra) or [input]

    if pack_writer:
        new_blob = pack_writer.new_blob
        new_tree = pack_writer.new_tree
    elif opt.blobs or opt.tree:
        # --noop mode
        new_blob = lambda content: git.calc_hash(b'blob', content)
        new_tree = lambda shalist: git.calc_hash(b'tree',
                                                 git.tree_encode(shalist))

    sys.stdout.flush()
    out = byte_stream(sys.stdout)

    if opt.blobs:
        shalist = hashsplit.split_to_blobs(new_blob,
                                           files,
                                           keep_boundaries=opt.keep_boundaries,
                                           progress=prog)
        for (sha, size, level) in shalist:
            out.write(hexlify(sha) + b'\n')
            reprogress()
    elif opt.tree or opt.commit or opt.name:
        if opt.name:  # insert dummy_name which may be used as a restore target
            mode, sha = \
                hashsplit.split_to_blob_or_tree(new_blob, new_tree, files,
                                                keep_boundaries=opt.keep_boundaries,
                                                progress=prog)
            splitfile_name = git.mangle_name(b'data', hashsplit.GIT_MODE_FILE,
                                             mode)
            shalist = [(mode, splitfile_name, sha)]
        else:
            shalist = hashsplit.split_to_shalist(
                new_blob,
                new_tree,
                files,
                keep_boundaries=opt.keep_boundaries,
                progress=prog)
        tree = new_tree(shalist)
    else:
        last = 0
        it = hashsplit.hashsplit_iter(files,
                                      keep_boundaries=opt.keep_boundaries,
                                      progress=prog)
        for (blob, level) in it:
            hashsplit.total_split += len(blob)
            if opt.copy:
                sys.stdout.write(str(blob))
            megs = hashsplit.total_split // 1024 // 1024
            if not opt.quiet and last != megs:
                last = megs

    if opt.verbose:
        log('\n')
    if opt.tree:
        out.write(hexlify(tree) + b'\n')
    if opt.commit or opt.name:
        msg = b'bup split\n\nGenerated by command:\n%r\n' % compat.get_argvb()
        ref = opt.name and (b'refs/heads/%s' % opt.name) or None
        userline = b'%s <%s@%s>' % (userfullname(), username(), hostname())
        commit = pack_writer.new_commit(tree, oldref, userline, date, None,
                                        userline, date, None, msg)
        if opt.commit:
            out.write(hexlify(commit) + b'\n')

    if pack_writer:
        pack_writer.close()  # must close before we can update the ref

    if opt.name:
        if cli:
            cli.update_ref(refname, commit, oldref)
        else:
            git.update_ref(refname, commit, oldref)

    if cli:
        cli.close()

    secs = time.time() - start_time
    size = hashsplit.total_split
    if opt.bench:
        log('bup: %.2f kbytes in %.2f secs = %.2f kbytes/sec\n' %
            (size / 1024, secs, size / 1024 / secs))

    if saved_errors:
        log('WARNING: %d errors encountered while saving.\n' %
            len(saved_errors))
        sys.exit(1)