diff -Nru obnam-1.7.4/analyze-repository-files obnam-1.8/analyze-repository-files --- obnam-1.7.4/analyze-repository-files 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/analyze-repository-files 1970-01-01 00:00:00.000000000 +0000 @@ -1,121 +0,0 @@ -#!/usr/bin/env python -# Copyright 2011 Lars Wirzenius -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -'''Analyze the files in an Obnam backup repository. - -For performance reasons, it is best if Obnam does not write too many -files per directory, or too large or too small files. This program -analyzes all the files in an Obnam backup repository, or, indeed, any -local directory, and reports the following: - -* total number of files -* sum of lengths of files -* number of files per directory: fewest, most, average, median - (both number and name of directory) -* size of files: smallest, largest, average, median - (both size and name of file) - -''' - - -import os -import stat -import sys - - -class Stats(object): - - def __init__(self): - self.dirs = list() - self.files = list() - - def add_dir(self, dirname, count): - self.dirs.append((count, dirname)) - - def add_file(self, filename, size): - self.files.append((size, filename)) - - @property - def total_files(self): - return len(self.files) - - @property - def sum_of_sizes(self): - return sum(size for size, name in self.files) - - @property - def dirsizes(self): - self.dirs.sort() - num_dirs = len(self.dirs) - - fewest, fewest_name = self.dirs[0] - most, most_name = self.dirs[-1] - average = sum(count for count, name in self.dirs) / num_dirs - median = self.dirs[num_dirs/2][0] - - return fewest, fewest_name, most, most_name, average, median - - @property - def filesizes(self): - self.files.sort() - num_files = len(self.files) - - smallest, smallest_name = self.files[0] - largest, largest_name = self.files[-1] - average = sum(size for size, name in self.files) / num_files - median = self.files[num_files/2][0] - - return smallest, smallest_name, largest, largest_name, average, median - - -def main(): - stats = Stats() - for name in sys.argv[1:]: - stat_info = os.lstat(name) - if stat.S_ISDIR(stat_info.st_mode): - for dirname, subdirs, filenames in os.walk(name): - stats.add_dir(dirname, len(filenames) + len(subdirs)) - for filename in filenames: - pathname = os.path.join(dirname, filename) - stat_info = os.lstat(pathname) - if stat.S_ISREG(stat_info.st_mode): - stats.add_file(pathname, stat_info.st_size) - elif stat.S_ISREG(stat_info.st_mode): - stats.add_file(name, stat_info.st_size) - - print "total_files:", stats.total_files - print "sum of sizes:", stats.sum_of_sizes - - fewest, fewest_name, most, most_name, average, median = stats.dirsizes - print "files per dir:" - print " fewest:", fewest, fewest_name - print " most:", most, most_name - print " average:", average - print " median:", median - - smallest, smallest_name, largest, largest_name, average, median = \ - stats.filesizes - print "file sizes:" - print " smallest:", smallest, smallest_name - print " largest:", largest, largest_name - print " average:", average - print " median:", median - - -if __name__ == '__main__': - main() - diff -Nru obnam-1.7.4/check obnam-1.8/check --- obnam-1.7.4/check 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/check 2014-05-13 07:05:26.000000000 +0000 @@ -1,5 +1,5 @@ #!/bin/sh -# Copyright 2011 Lars Wirzenius +# Copyright 2011-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -22,4 +22,3 @@ python setup.py --quiet build_ext -i rm -rf build python setup.py --quiet check "$@" - diff -Nru obnam-1.7.4/check-lock-usage-from-log obnam-1.8/check-lock-usage-from-log --- obnam-1.7.4/check-lock-usage-from-log 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/check-lock-usage-from-log 1970-01-01 00:00:00.000000000 +0000 @@ -1,316 +0,0 @@ -#!/usr/bin/env python -# Copyright 2012 Lars Wirzenius -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -'''Check lock file usage from log files. - -This program reads a number of Obnam log files, produced with tracing -for obnamlib, and analyses them for bugs when using lock files. Each -log file is assumed to be produced by a separate Obnam instance. - -* Have any instances held the same lock during overlapping periods? - -''' - - -import cliapp -import logging -import os -import re -import time -import ttystatus - -timestamp_pat = \ - r'^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d (?P\d+\.\d+) .*' - -lock_pat = re.compile( - timestamp_pat + - r'vfs_local.py:[0-9]*:lock: got lockname=(?P.*)') -unlock_pat = re.compile( - timestamp_pat + - r'vfs_local.py:[0-9]*:unlock: lockname=(?P.*)') - -writefile_pat = re.compile( - timestamp_pat + - r'vfs_local.py:[0-9]*:write_file: write_file (?P.*)$') -overwritefile_pat = re.compile( - timestamp_pat + - r'vfs_local.py:[0-9]*:overwrite_file: overwrite_file (?P.*)$') - -node_open_pat = re.compile( - timestamp_pat + - r'nodestore_disk.py:\d+:get_node: reading node \d+ from file ' - r'(?P.*)$') -node_remove_pat = re.compile( - timestamp_pat + - r'vfs_local.py:\d+:remove: remove (?P.*/nodes/.*)$') - -rename_pat = re.compile( - timestamp_pat + - r'vfs_local.py:\d+:rename: rename (?P\S+) (?P\S+)$') - - -class LogEvent(object): - - def __init__(self, logfile, lineno, timestamp): - self.logfile = logfile - self.lineno = lineno - self.timestamp = timestamp - - def sortkey(self): - return self.timestamp - - -class LockEvent(LogEvent): - - def __init__(self, logfile, lineno, timestamp, lockname): - LogEvent.__init__(self, logfile, lineno, timestamp) - self.lockname = lockname - - def __str__(self): - return 'Lock(%s)' % self.lockname - - -class UnlockEvent(LockEvent): - - def __str__(self): - return 'Unlock(%s)' % self.lockname - - -class WriteFileEvent(LogEvent): - - def __init__(self, logfile, lineno, timestamp, filename): - LogEvent.__init__(self, logfile, lineno, timestamp) - self.filename = filename - - def __str__(self): - return 'WriteFile(%s)' % self.filename - - -class OverwriteFileEvent(WriteFileEvent): - - def __str__(self): - return 'OverwriteFile(%s)' % self.filename - - -class NodeCreateEvent(LogEvent): - - def __init__(self, logfile, lineno, timestamp, node_id): - LogEvent.__init__(self, logfile, lineno, timestamp) - self.node_id = node_id - - def __str__(self): - return 'NodeCreate(%s)' % self.node_id - - -class NodeDestroyEvent(NodeCreateEvent): - - def __str__(self): - return 'NodeDestroy(%s)' % self.node_id - - -class NodeReadEvent(NodeCreateEvent): - - def __str__(self): - return 'NodeOpen(%s)' % self.node_id - - -class RenameEvent(LogEvent): - - def __init__(self, logfile, lineno, timestamp, old, new): - LogEvent.__init__(self, logfile, lineno, timestamp) - self.old = old - self.new = new - - def __str__(self): - return 'Rename(%s -> %s)' % (self.old, self.new) - - -class CheckLocks(cliapp.Application): - - def setup(self): - self.events = [] - self.errors = 0 - self.latest_opened_node = None - - self.patterns = [ - (lock_pat, self.lock_event), - (unlock_pat, self.unlock_event), - (writefile_pat, self.writefile_event), - (overwritefile_pat, self.overwritefile_event), - (node_open_pat, self.read_node_event), - (node_remove_pat, self.node_remove_event), - (rename_pat, self.rename_event), - ] - - self.ts = ttystatus.TerminalStatus() - self.ts.format( - 'Reading %ElapsedTime() %Integer(lines): %Pathname(filename)') - self.ts['lines'] = 0 - - def cleanup(self): - self.ts.clear() - - self.analyse_phase_1() - - self.ts.finish() - if self.errors: - raise cliapp.AppException('There were %d errors' % self.errors) - - def error(self, msg): - logging.error(msg) - self.ts.error(msg) - self.errors += 1 - - def analyse_phase_1(self): - self.events.sort(key=lambda e: e.sortkey()) - self.events = self.create_node_events(self.events) - self.ts.format('Phase 1: %Index(event,events)') - self.ts['events'] = self.events - self.ts.flush() - - current_locks = set() - current_nodes = set() - - for e in self.events: - self.ts['event'] = e - logging.debug( - 'analysing: %s:%s: %s: %s' % - (e.logfile, e.lineno, repr(e.sortkey()), str(e))) - if type(e) is LockEvent: - if e.lockname in current_locks: - self.error( - 'Re-locking %s: %s:%s:%s' % - (e.lockname, e.logfile, e.lineno, - e.timestamp)) - else: - current_locks.add(e.lockname) - elif type(e) is UnlockEvent: - if e.lockname not in current_locks: - self.error( - 'Unlocking %s which was not locked: %s:%s:%s' % - (e.lockname, e.logfile, e.lineno, - e.timestamp)) - else: - current_locks.remove(e.lockname) - elif type(e) in (WriteFileEvent, OverwriteFileEvent): - lockname = self.determine_lockfile(e.filename) - if lockname and lockname not in current_locks: - self.error( - '%s:%s: ' - 'Write to file %s despite lock %s not existing' % - (e.logfile, e.lineno, e.filename, lockname)) - elif type(e) is NodeCreateEvent: - if e.node_id in current_nodes: - self.error( - '%s:%s: Node %s already exists' % - (e.logfile, e.lineno, e.node_id)) - else: - current_nodes.add(e.node_id) - elif type(e) is NodeDestroyEvent: - if e.node_id not in current_nodes: - self.error( - '%s:%s: Node %s does not exist' % - (e.logfile, e.lineno, e.node_id)) - else: - current_nodes.remove(e.node_id) - elif type(e) is NodeReadEvent: - if e.node_id not in current_nodes: - self.error( - '%s:%s: Node %s does not exist' % - (e.logfile, e.lineno, e.node_id)) - elif type(e) is RenameEvent: - if e.old in current_nodes: - current_nodes.remove(e.old) - current_nodes.add(e.new) - else: - raise NotImplementedError() - - def create_node_events(self, events): - new = [] - for e in events: - new.append(e) - if type(e) in (WriteFileEvent, OverwriteFileEvent): - if '/nodes/' in e.filename: - new_e = NodeCreateEvent( - e.logfile, e.lineno, e.timestamp, e.filename) - new_e.timestamp = e.timestamp - new.append(new_e) - return new - - def determine_lockfile(self, filename): - if filename.endswith('/lock'): - return None - toplevel = filename.split('/')[0] - if toplevel == 'chunks': - return None - if toplevel in ('metadata', 'clientlist'): - return './lock' - return toplevel + '/lock' - - def process_input(self, name): - self.ts['filename'] = name - return cliapp.Application.process_input(self, name) - - def process_input_line(self, filename, line): - self.ts['lines'] = self.global_lineno - for pat, func in self.patterns: - m = pat.search(line) - if m: - event = func(filename, line, m) - if event is not None: - self.events.append(event) - - def lock_event(self, filename, line, match): - return LockEvent( - filename, self.lineno, float(match.group('timestamp')), - match.group('lock')) - - def unlock_event(self, filename, line, match): - return UnlockEvent( - filename, self.lineno, float(match.group('timestamp')), - match.group('lock')) - - def writefile_event(self, filename, line, match): - return WriteFileEvent( - filename, self.lineno, float(match.group('timestamp')), - match.group('filename')) - - def overwritefile_event(self, filename, line, match): - return OverwriteFileEvent( - filename, self.lineno, float(match.group('timestamp')), - match.group('filename')) - - def read_node_event(self, filename, line, match): - node_id = match.group('nodeid') - if not os.path.basename(node_id).startswith('tmp'): - return NodeReadEvent( - filename, self.lineno, float(match.group('timestamp')), - node_id) - - def node_remove_event(self, filename, line, match): - return NodeDestroyEvent( - filename, self.lineno, float(match.group('timestamp')), - match.group('nodeid')) - - def rename_event(self, filename, line, match): - return RenameEvent( - filename, self.lineno, float(match.group('timestamp')), - match.group('old'), match.group('new')) - - -CheckLocks().run() - diff -Nru obnam-1.7.4/confs/common.conf obnam-1.8/confs/common.conf --- obnam-1.7.4/confs/common.conf 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/confs/common.conf 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -[config] -with-encryption = yes diff -Nru obnam-1.7.4/confs/historical-local.conf obnam-1.8/confs/historical-local.conf --- obnam-1.7.4/confs/historical-local.conf 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/confs/historical-local.conf 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ -[config] -profile-name = historical-local -size = 4k/4k -generations = 1000 diff -Nru obnam-1.7.4/confs/media-local.conf obnam-1.8/confs/media-local.conf --- obnam-1.7.4/confs/media-local.conf 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/confs/media-local.conf 1970-01-01 00:00:00.000000000 +0000 @@ -1,5 +0,0 @@ -[config] -profile-name = media-local -size = 1g/100m -file-size = 100m -generations = 2 diff -Nru obnam-1.7.4/confs/sourcecode-local.conf obnam-1.8/confs/sourcecode-local.conf --- obnam-1.7.4/confs/sourcecode-local.conf 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/confs/sourcecode-local.conf 1970-01-01 00:00:00.000000000 +0000 @@ -1,5 +0,0 @@ -[config] -profile-name = sourcecode-local -size = 1g/10m -file-size = 16k -generations = 2 diff -Nru obnam-1.7.4/crash-test obnam-1.8/crash-test --- obnam-1.7.4/crash-test 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/crash-test 2014-05-13 07:05:26.000000000 +0000 @@ -1,5 +1,5 @@ #!/bin/sh -# Copyright 2012 Lars Wirzenius +# Copyright 2012-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -41,7 +41,8 @@ trace = larch, obnamlib EOF -# Do a minimal backup to make sure the repository works at least once, without the crash-limit option +# Do a minimal backup to make sure the repository works at least once, +# without the crash-limit option mkdir "$tempdir/data" ./obnam backup --no-default-config --config "$tempdir/conf" @@ -56,7 +57,8 @@ # rm -f "$tempdir/obnam.log" echo "Trying backup with at most $N writes to repository" - ./obnam force-lock --no-default-config --config "$tempdir/conf" 2>/dev/null + ./obnam force-lock --no-default-config --config "$tempdir/conf" \ + 2>/dev/null if ./obnam backup --no-default-config --config "$tempdir/conf" 2>/dev/null then echo "Backup finished ok, done" diff -Nru obnam-1.7.4/create-reference-repo obnam-1.8/create-reference-repo --- obnam-1.7.4/create-reference-repo 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/create-reference-repo 2014-05-13 07:05:26.000000000 +0000 @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright 2013 Lars Wirzenius +# Copyright 2013-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -48,7 +48,7 @@ def add_settings(self): self.settings.string( ['obnam'], - 'which obnam to execute (default: %default)', + 'which obnam to execute', metavar='CMD', default='./obnam') diff -Nru obnam-1.7.4/create-vfat-disk-image obnam-1.8/create-vfat-disk-image --- obnam-1.7.4/create-vfat-disk-image 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/create-vfat-disk-image 1970-01-01 00:00:00.000000000 +0000 @@ -1,25 +0,0 @@ -#!/bin/sh -# Copyright 2012 Lars Wirzenius -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -set -eu - -filename="$1" -size="$2" - -qemu-img create -f raw "$filename" "$size" -#parted "$filename" mklabel msdos -#parted "$filename" mkpart primary fat32 0% 100% -/sbin/mkfs.vfat "$filename" diff -Nru obnam-1.7.4/debian/changelog obnam-1.8/debian/changelog --- obnam-1.7.4/debian/changelog 2014-05-13 23:13:25.000000000 +0000 +++ obnam-1.8/debian/changelog 2014-05-13 23:13:25.000000000 +0000 @@ -1,3 +1,13 @@ +obnam (1.8-1) unstable; urgency=low + + * New upstream version. + - "obnam excludes files with "syslog" in the name without me + specifying it" (Closes: #682667) + - "obnam: client not found, but cannot remove lock" (Closes: #675825) + - "data leak during restore" (Closes: #745112) + + -- Lars Wirzenius Tue, 13 May 2014 08:04:18 +0100 + obnam (1.7.4-1) unstable; urgency=low * New upstream release. diff -Nru obnam-1.7.4/dumpobjs obnam-1.8/dumpobjs --- obnam-1.7.4/dumpobjs 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/dumpobjs 1970-01-01 00:00:00.000000000 +0000 @@ -1,41 +0,0 @@ -#!/usr/bin/env python -# Copyright (C) 2009 Lars Wirzenius -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -import os -import sys -import time - -import obnamlib - - -def find_objids(fs): - basenames = fs.listdir('.') - return [x[:-len('.obj')] for x in basenames if x.endswith('.obj')] - - -fs = obnamlib.LocalFS(sys.argv[1]) -repo = obnamlib.Repository(fs, obnamlib.DEFAULT_NODE_SIZE, - obnamlib.DEFAULT_UPLOAD_QUEUE_SIZE, None, - obnamlib.IDPATH_DEPTH, - obnamlib.IDPATH_BITS, - obnamlib.IDPATH_SKIP, - time.time, 0) -for objid in find_objids(fs): - obj = repo.get_object(objid) - print 'id %s (%s):' % (obj.id, obj.__class__.__name__) - for name in obj.fieldnames(): - print ' %-10s %s' % (name, repr(getattr(obj, name))) diff -Nru obnam-1.7.4/find-all-obnam-errors obnam-1.8/find-all-obnam-errors --- obnam-1.7.4/find-all-obnam-errors 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/find-all-obnam-errors 2014-05-13 07:05:26.000000000 +0000 @@ -1,3 +1,21 @@ +# Copyright 2014 Lars Wirzenius +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# =*= License: GPL-3+ =*= + + import collections import inspect diff -Nru obnam-1.7.4/find-duplicate-chunks obnam-1.8/find-duplicate-chunks --- obnam-1.7.4/find-duplicate-chunks 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/find-duplicate-chunks 1970-01-01 00:00:00.000000000 +0000 @@ -1,108 +0,0 @@ -#!/usr/bin/env python -# -# Report duplicate chunks of data in a filesystem. - - -import hashlib -import os -import subprocess -import sys -import tempfile - - -def compute(f, chunk_size, offset): - chunk = f.read(chunk_size) - while len(chunk) == chunk_size: - yield hashlib.md5(chunk).hexdigest() - chunk = chunk[offset:] + f.read(offset) - - -def compute_checksums(f, chunk_size, offset, dirname): - for dirname, subdirs, filenames in os.walk(dirname): - for filename in filenames: - pathname = os.path.join(dirname, filename) - if os.path.isfile(pathname) and not os.path.islink(pathname): - ff = file(pathname) - for checksum in compute(ff, chunk_size, offset): - f.write('%s\n' % checksum) - ff.close() - - -def sort_checksums(f, checksums_name): - subprocess.check_call(['sort', - '-T', '.', - '--batch-size', '1000', - '-S', '1G', - ], - stdin=file(checksums_name), - stdout=f) - - -def count_duplicates(f, sorted_name): - subprocess.check_call(['uniq', '-c'], stdin=file(sorted_name), stdout=f) - - -def make_report(f, counts_name, chunk_size, offset): - num_diff_checksums = 0 - saved = 0 - total = 0 - - limits = [1] - counts = { 1: 0 } - - for line in file(counts_name): - count, checksum = line.split() - count = int(count) - num_diff_checksums += 1 - saved += (count-1) * chunk_size - total += count * chunk_size - while limits[-1] < count: - n = limits[-1] * 10 - limits.append(n) - counts[n] = 0 - for limit in limits: - if count <= limit: - counts[limit] += count - break - - f.write('chunk size: %d\n' % chunk_size) - f.write('offset: %d\n' % offset) - f.write('#different checksums: %d\n' % num_diff_checksums) - f.write('%8s %8s\n' % ('repeats', 'how many')) - for limit in limits: - f.write('%8d %8d\n' % (limit, counts[limit])) - f.write('bytes saved by de-duplication: %d\n' % saved) - f.write('%% saved: %f\n' % (100.0*saved/total)) - - -def main(): - chunk_size = int(sys.argv[1]) - offset = int(sys.argv[2]) - dirname = sys.argv[3] - - prefix = 'data-%04d-%04d' % (chunk_size, offset) - checksums_name = prefix + '.checksums' - sorted_name = prefix + '.sorted' - counts_name = prefix + '.counts' - report_name = prefix + '.report' - - steps = ( - (checksums_name, compute_checksums, (chunk_size, offset, dirname)), - (sorted_name, sort_checksums, (checksums_name,)), - (counts_name, count_duplicates, (sorted_name,)), - (report_name, make_report, (counts_name, chunk_size, offset)), - ) - - for filename, func, args in steps: - if not os.path.exists(filename): - print 'Step:', func.__name__ - fd, output_name = tempfile.mkstemp(dir='.') - os.close(fd) - f = file(output_name, 'w') - func(*((f,) + args)) - f.close() - os.rename(output_name, filename) - - -if __name__ == '__main__': - main() diff -Nru obnam-1.7.4/lock-and-increment obnam-1.8/lock-and-increment --- obnam-1.7.4/lock-and-increment 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/lock-and-increment 2014-05-13 07:05:26.000000000 +0000 @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright 2012 Lars Wirzenius +# Copyright 2012-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/manual/de/010-einleitung.mdwn obnam-1.8/manual/de/010-einleitung.mdwn --- obnam-1.7.4/manual/de/010-einleitung.mdwn 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/manual/de/010-einleitung.mdwn 2014-05-13 07:05:26.000000000 +0000 @@ -2,7 +2,7 @@ ============ > ... Backups? Hat hier jemand was von Backups gesagt? Ich bin sicher -das hier irgendwer über backups gesprochen hat. +> das hier irgendwer über Backups gesprochen hat. > Backups! BACKUPS! BACKUPS SIND ABGEFAHREN! Das ist ein Zitat direkt aus meiner IRC history. Ich finde Backups recht @@ -12,11 +12,30 @@ Ich bin ungewöhnlich: Leute finden Backups meistens nervtötend und allenfalls langweilig. Wenn ich mit Leuten über Backups rede, dann -ist die Reaktion normalwerweise "Ich weiß, ich sollte...". Es gibt viele -Gründe dafür. Einer ist, daß Backups so ähnlich wie Versicherungen sind: -Man muß zunächst Zeit, Arbeit und Geld 'reinstecken um hinterher von ihnen -Gebrauch zu machen. Ein anderer Grund ist, daß das gesamte Thema beängstigend ist: +ist die Reaktion normalerweise "Ich weiß, ich sollte...". Es gibt viele +Gründe dafür. Einer ist, dass Backups so ähnlich wie Versicherungen sind: +Man muss zunächst Zeit, Arbeit und Geld 'reinstecken um hinterher von ihnen +Gebrauch zu machen. Ein anderer Grund ist, dass das gesamte Thema beängstigend ist: Man muss darüber nachdenken was passiert wenn etwas richtig schief läuft, und das schreckt die Leute ab. Ein dritter Grund ist: Es gibt zwar eine Menge an Backup-Tools, aber es ist nicht immer einfach eines auszuwählen. Dieses Handbuch ist für das Obnam Programm, aber es versucht für jeden nützlich zu sein, der über Backups nachdenkt. + +Anmerkung des Übersetzers +------------------------- + +Ich bin kein professioneller Übersetzer, sondern ein +interessierter Obnam Anwender. Aus Erfahrung weiß ich, +dass man sich beim Übersetzen von fremdsprachigen Texten immer wieder +die Frage stellt, ob man Begriffe eindeutschen soll, oder nicht. +Viele englische Begriffe werden auch im deutschen Umfeld +verwendet, daher habe ich einige Begriffe nicht übersetzt von +denen ich denke, dass sie dem Leser bekannt sind. + +Sicherlich kann die eine oder andere Stelle anders, manchmal +auch besser übersetzt werden. Für Anregungen und konstruktive Kritik +bin ich dankbar und werde sie gegebenenfalls an Lars weiterleiten. + + +-- Jan Niggemann + diff -Nru obnam-1.7.4/manual/de/020-konzepte.mdwn obnam-1.8/manual/de/020-konzepte.mdwn --- obnam-1.7.4/manual/de/020-konzepte.mdwn 1970-01-01 00:00:00.000000000 +0000 +++ obnam-1.8/manual/de/020-konzepte.mdwn 2014-05-13 07:05:26.000000000 +0000 @@ -0,0 +1,295 @@ +Sie wissen, dass Sie sollten... +============================== + +Dieses Kapitel beschreibt Philosophie und Theorie von Backups. +Es erläutert, warum Sie sichern sollten, verschiedene Konzepte rund um Backups, +Dinge an die Sie bei der Einrichtung von Backups denken sollten +und was langfristig zu tun ist (z.B. Verifizierung usw.). Außerdem werden +einige Annahmen erläutert die Obnam macht, und welche Einschränkungen es gibt. + +Warum sichern? +-------------- + +FIXME: Add some horror stories here about why backups are important. +With references/links. + +Backup-Konzepte +--------------- + +Dieser Abschnitt behandelt Kernkonzepte von Backups und legt einige +Begriffe fest, die in diesem Buch verwendet werden. + +**Live-Daten** sind die Daten, mit denen Sie arbeiten oder die Sie aufbewahren, die Dateien auf +Ihrer Festplatte: Dokumente, die Sie schreiben, Fotos, die Sie speichern, der +unvollendete Roman, von dem Sie wünschen das Sie ihn fertig schrieben. + +Die meisten Live-Daten sind **kostbar**, Sie wären aufgeschmissen wenn +sie verloren gingen. Einige Live-Daten sind es allerdings nicht, zum Beispiel Ihr +Browser-Cache. Durch diese Unterscheidung können Sie die Datenmenge begrenzen, +welche Sie sichern müssen -- was die Kosten des Backups erheblich senken kann. + +Ein **Backup** ist eine Ersatz-Kopie Ihrer Live-Daten. Sollten Sie einige +oder alle Ihre Live-Daten verlieren, können Sie sie aus Ihrem +Backup wieder herstellen (**restore**). +Die Sicherungskopie ist notwendigerweise älter als Ihre Live-Daten, +aber wenn Sie die Sicherung erst kürzlich erstellt haben +werden Sie nicht viel verlieren. + +Manchmal ist es sinnvoll, mehr als eine Backup-Kopie Ihrer Live-Daten +zu haben. Eine Abfolge von Backups, die zu verschiedenen Zeiten erstellt wurden, +wird **Sicherungsverlauf** genannt. Jede Kopie Ihrer Live-Daten in +Ihrem Sicherungsverlauf ist eine **Generation**. Dadurch können Sie jetzt eine +Datei wieder herstellen, die Sie schon vor langer Zeit gelöscht hatten, +von der Sie aber nicht wussten, dass Sie sie heute benötigen. +Wenn Sie nur eine einzige Backup-Version vorhielten, könnten Sie die +Datei nicht zurückbekommen. Aber wenn Sie, sagen wir, einen Monat lang +ein tägliches Backup behalten, dann haben Sie einen Monat Zeit +zu erkennen, das Sie eine Datei noch brauchen, bevor sie für immer verloren ist. + +Der Ort an dem Ihre Backups gespeichert werden wird **Repository** genannt. +Sie können viele Arten von **Backup-Medien** verwenden um Ihr Backup +zu speichern: Festplatten, Bänder, optische Medien (DVD-R, DVD-RW, usw.), USB-Sticks, +Online-Speicher und so weiter. Jedes Medium hat dabei verschiedene Eigenschaften: +Größe, Geschwindigkeit, Komfort, Zuverlässigkeit, Preis. +Diese Eigenschaften sollten Sie bei der Auswahl Ihrer Backup-Lösung in Erwägung ziehen. + +Sie sollten mehrere Backup-Repositories benützen, eines +davon sollte sich **off-site** ("außerhalb") befinden, also wo anders, als +Ihre Computer in der Regel stehen. Andernfalls verlieren Sie alle Ihre Backups, +wenn z.B. Ihr Haus abbrennt. + + +Sie müssen **überprüfen**, dass Ihre Backups auch wirklich funktionieren. +Es wäre schade um den Aufwand für das Erstellen von Backups, wenn Sie im +Fall der Fälle dann nicht in der Lage wären, Ihre Daten wieder herzustellen. +Sie möchten vielleicht sogar Ihr **Desaster Recovery** testen. Tun Sie so +als wäre all Ihr Computer-Zeug weg, bis auf die Backup-Medien. Können Sie alles +wieder herstellen? Sie sollten dies von Zeit zu Zeit tun, um sicher zu stellen +das Ihr Backup-System einsatzfähig ist. + +Es gibt eine sehr große Auswahl an **Backup-Tools**. Sie können +sehr einfach und händisch sein: Sie können Ihre Dateien alle Jubeljahre +mit dem Dateimanager auf ein USB-Laufwerk kopieren. Sie können auch sehr komplex sein: +Enterprise Backup-Produkte, die riesige Mengen an Geld kosten und +mit mehrtägigen Schulungspaketen für Ihr Sysadmin-Team begleitet werden, und +nur dann vernünftig funktionieren, wenn das gesamte Team vernünftig funktioniert. + +Sie müssen eine **Backup-Strategie** definieren, die alles zusammenhält. +Welche Live-Daten sind zu sichern, auf welchem Medium, mit welchen Tools, +welche Art von Backup-History wollen Sie behalten, und wie überprüfen Sie, +dass sie auch funktionieren. + +Backup Strategien +----------------- + +Sie haben ein Backup-Repository eingerichtet und Sie erstellen seit +einem Monat Tag für Tag ein Backup. Ihre Backup-History +wird langsam lang genug, um nützlich zu sein. Können Sie Sich +jetzt zufrieden zurück lehnen? + +Willkommen in der Welt der Bedrohungsmodellierung. Backups sind Ihre +Versicherung, sie mildern kleine und große Katastrophen, aber Katastrophen +können auch Backups zustoßen. Wann sind Sie so sicher, dass keine Katastrophe +Ihnen mehr schaden können? + +Es gibt immer eine größeres Problem, das darauf wartet zu zuschlagen. Wenn +Sie Ihre Daten auf eine USB-Laufwerk auf Ihrem Schreibtisch sichern, und +jemand bricht ein und stiehlt Ihren PC und auch das USB-Laufwerk, +dann haben Sie nichts gewonnen. + +Eine Lösung wäre, zwei USB-Laufwerke zu verwenden. Eines bleibt bei Ihrem +Computer, das andere wandert in ein Bankschließfach. Das ist +recht sicher, außer wenn ein Erdbeben neben Ihrem Haus auch die +Bank zerstört. + +Eine Lösung wäre, dass Sie Online-Speicherplatz in einem anderen +Land mieten. Das ist recht sicher, außer es gibt einen Fehler im Betriebssystem +(das nicht nur Sie, sondern auch der Provider benutzen) und Hacker dringen +ein und löschen all Ihre Daten. + +Eine Lösung wäre, dass Sie mit einem 3D Drucker Betonblöcke drucken, +auf denen Ihre Daten mittels QR Code verewigt sind. Das ist recht +sicher, außer ein Meteorit trifft die Erde und zerstört unsere +gesamte Zivilisation. + +Eine Lösung wäre, dass Sie Satelliten mit Kopien Ihrer Daten in +stationäre Umlaufbahnen um alle 9 Planeten (Pluto ist auch ein Planet!) +des Sonnensystems schicken. Das ist - auch wenn Sie vom +Meteoriteneinschlag gestorben sind - recht sicher, außer die Sonne wird zur Supernova. + +Es gibt immer eine noch größere Katastrophe. Sie müssen entscheiden, +welche davon so wahrscheinlich sind, das Sie sie in Ihrem Szenario berücksichtigen +und welche Kosten Sie für den Schutz dagegen akzeptieren. + +Eine kurze Liste von Bedrohungsszenarien zum Bedenken: + +* Was wäre, wenn Sie Ihren Computer verlieren? +* Was wäre, wenn Sie Ihr Heim und alles was drin ist verlieren? +* Was wäre, wenn die Gegend in welcher Sie leben zerstört wird? +* Was wäre, wenn Sie Ihr Land verlassen müssten? + +Diese Fragen sind nicht abschließend, aber ein Anfang. Fragen Sie sich: + +* Können Sie mit dem Verlust Ihrer Daten leben? Wenn Ihre Daten + verloren sind, bedeutet es einen Verlust von Erinnerungen, + oder ein Paar Unbequemlichkeiten im täglichen Leben, oder wird es + Ihnen fast unmöglich, wieder normal zu leben und zu arbeiten? + Welche Daten sind Ihnen am wichtigsten? + +* Wie viel ist es Ihnen wert, Ihre Daten wieder zu bekommen, und wie schnell +  soll das passieren? Wie viel Geld und Mühe sind Sie bereit + für das erste Backup zu investieren, und wie viel für weitere Backups? + Und für die Wiederherstellung, wie viel sind Sie da bereit zu zahlen? + Ist es besser für Sie, weniger für Backups auszugeben, auch wenn das +  Restore dann langsamer und teurer ist und mehr Aufwand bedeutet? + Oder ist es umgekehrt? + +Dieses Bedrohungsmodell berücksichtigt Unfälle und +Naturkatastrophen. Modelle die Angriffe und Feinde berücksichtigen +sind ähnlich aber auch etwas anders, und werden in der Nächsten +Episode ein Thema sein, wenn es wieder heißt: Sie Abenteuer von Bac-Kup. + +Backups und Sicherheit +---------------------- + +Sie sind nicht der einzige, der sich um Ihre Daten sorgt. +Eine Vielzahl von Regierungen, Unternehmen, Kriminellen und allzu +neugierigen Schnüfflern sind wahrscheinlich ebenfalls interessiert +(... und es ist manchmal schwer, diese auseinander zu halten). Sie könnten +Beweise gegen Sie (er)finden wollen, Sie erpressen wollen, oder +einfach nur neugierig darauf sein, was Sie mit Freunden besprechen. + +Sie könnten Ihre Daten aus statistischen Gründen interessant finden +und überhaupt kein Interesse an Ihnen persönlich haben. +Oder sie könnten ausschließlich an Ihnen interessiert sein. + +Statt Ihre Dateien und eMails zu lesen oder Ihre Fotos +und Videos anzusehen könnten sie Interesse daran haben, Ihnen den Zugang +dazu zu versperren, oder einfach Ihre Daten ganz zerstören. Sie könnten sogar +Ihre Daten korrumpieren, indem sie Kinderpornographie in Ihr Foto-Archiv ablegen. + +Sie schützen Ihren Computer so gut Sie können damit diese und andere +schlimme Dinge nicht passieren. Ihre Sicherungen sollten Sie mit der gleichen Sorgfalt behandeln. + +Wenn Sie auf ein USB-Laufwerk sichern, sollten Sie das Laufwerk verschlüsseln, +genau wie auch Online-Speicher. Es gibt viele Arten von Verschlüsselung und +ich bin nicht qualifiziert Ihnen Rat zu geben, aber jegliche gängige, +moderne Verschlüsselung sollte ausreichen -- außer für besonders entschlossene Angreifer. + +Anstatt oder zusätzlich zur Verschlüsselung können Sie den physischen Zugang +zu Ihren Backup-Medien absichern. Lagern Sie Ihr USB-Laufwerk z.B. in einem Safe oder Schließfach. + +Die Vielzahl von Backups die Sie benötigen um sich gegen Erdbeben, +Flutkatastrophen und marodierende Gangs dreirad-fahrender Clowns zu schützen, +sind auch ein guter Schutz gegen Angreifer. Sie können Ihre Live-Daten +und die Backups bei Ihnen zu Hause korrumpieren, aber vermutlich könnten Sie +nicht an das USB-Laufwerk herankommen, das in Beton gegossen an einem geheimen +Ort vergraben ist, den nur Sie kennen. + +Auf der anderen Seite möchten oder müssen Sie vielleicht Anderen +Zugriff auf Ihre gesicherten Daten geben. Wenn Sie zum Beispiel von der Clown +Gang entführt wurden, sollte Ihr Partner in der Lage sein, Ihren +MI6-Verbindungsmann zu kontaktieren, damit der Geheimdienst Sie retten kann. +Den sicheren Zugang zu (Teilen) Ihres Backups herzustellen ist ein interessantes +Problem für sich, für das es verschiedene Lösungen gibt: +Geben Sie Ihrem Partner das Verschlüsselungspasswort, oder geben Sie es einem +Freund dem Sie vertrauen, oder einem Anwalt. Sie könnten auch so etwas wie +[libgfshare] verwenden, um die Schlüssel auf sichere Weise treuhänderisch zu hinterlegen. + +[libgfshare]: http://www.digital-scurf.org/software/libgfshare + +Betrachtung von Backup-Medien +----------------------------- + +Dieser Abschnitt behandelt Backup-Medien, ihre unterschiedlichen +Charakteristiken, und wie Sie für sich selbst etwas Passendes auswählen. + +Es gibt viele verschiedene Speicher-Medien. Die wohl bekanntesten sind: + +* verschiedene Arten von Magnetbändern +* Festplatte: interne oder externe, drehende Magnetscheiben oder SSDs oder USB-Sticks +* Optische Medien: CD, DVD, Blu-ray +* verschiedene Online-Speicher +* Papier + +Die exotischeren und / oder ungebräuchlichen Dinge wie Microfiche +überspringen wir mal. + +**Magnetbänder** sind traditionell die wohl gebräuchlichste Form von +Backup-Medien. Die Bänder an sich sind (je GB) preiswert, +aber für das Laufwerk ist eine hohe Anfangsinvestition fällig. +Vieles in der Backup-Terminologie ist Magnetbändern entlehnt, z.B. +volle Sicherung / inkrementelle Sicherung. Obnam unterstützt keine Bandlaufwerke. + +**Festplatten** sind eine gängige moderne Alternative zu Magnetbändern, +besonders für alle, die nichts für ein Bandlaufwerk ausgeben möchten. +Festplatten haben den Vorteil, das auf jedes Bit des Backups mit gleicher +Geschwindigkeit zugegriffen werden kann, was das Auffinden einer alten +Datei schneller und einfacher macht. So werden auch **Snapshots** möglich, +die von Obnam benutzt werden. + +Verschiedene Typen Festplatten haben verschiedene Eigenschaften z.B. +was Verlässlichkeit, Geschwindigkeit und Preis angeht, und diese +Eigenschaften variieren häufig von Woche zu Woche und Jahr zu Jahr. +Wir wollen nicht ins Details aller möglichen Eigenschaften gehen. +Aus Sicht von Obnam ist alles, das sich wie eine Festplatte verhält +(drehender Rost, SSD, USB Flash-Spiecher oder Online-Speicher) +brauchbar um Backups abzulegen, Hauptsache es ist wiederbeschreibbar. + +**Optische Medien**, besonders welche die nur einmal beschrieben werden können, +können für Backups benutzt werden. Sie eignen sich aber eher für +Komplettsicherungen, die lange Zeit aufbewahrt werden als für aktiv +genutzte Backup-Repositorien. Alternativ können Sie als eine Art +Bandsicherung verwendet werden, in der jedes Band nur einmal beschrieben wird. +Obnam unterstützt keine optischen Medien als Backup-Speicher. + +**Papier** funktioniert ebenso gut für die Archivierung, allerdings +nur für kleine Datenmengen. Trotzdem kann ein Papier-Backup, +das mit Archivier-Tinte auf gutem Papier gedruckt wurde, +Jahrzehnte oder sogar Jahrhunderte halten. Das macht es zu einer +guten Option für kleine, aber wichtige Datenmengen. Beispiele +wären persönliche Finanz-Unterlagen, geheime Chiffrierschlüssel und +Liebesbriefe Ihres Partners. Diese können ganz normal gedruckt werden +(am Besten in einer Schriftart die einfach zu OCRen ist), oder als +zweidimensionale Barcodes (z.B. QR). +Obnam unterstützt auch dies nicht. + +Obnam unterstützt ausschließlich Festplatten und alles das sich wie +eine (beschreibbare) Festplatte benimmt, z.B. Online-Speicher -- +erstaunlicherweise scheint das den Meisten zu reichen. + +Glossar +------- + +* **Backup**: Eine getrennte Sicherheitskopie Ihrer Live-Daten, + die intakt bleibt, auch wenn das Original zerstört, gelöscht oder ungewollt geändert wird. +* **corruption**: Unerwünschte Veränderung Ihrer (Backup-)Daten +* **disaster recovery**: Was Sie tun wenn etwas schief lief +* **full backup**: Eine komplette Sicherung Ihrer wertvollen Livedaten +* **generation**: Ein Backup in einer Serie von Backups der selben Livedaten, + eine historische Sicht auf selbige +* **history**: Alle generations +* **incremental backup**: Eine Sicherung jeglicher Änderungen (Neue Daten, geänderte Daten, + gelöschte Daten) im Vergleich zu einer früheren Generation (entweder die vorhergehende + Vollsicherung oder eine vorhergehende inkrementelle Sicherung) + Normalerweise kann eine Vollsicherung nur dann entfernt werden, wenn auch alle inkrementellen + Sicherungen die darauf aufbauen mitgelöscht werden) +* **live data**: Alle Daten die Sie haben +* **local backup**: Eine Sicherung, deren Repository physisch in der Nähe der Livedaten + gespeichert ist. +* **Medium**, **Backup-Medium**, **Speichermedium**: Das, wo ein Backup Repository + drauf gespeichert wird +* **off-site backup**: Eine Sicherung, deren Repository physisch weit weg von den Livedaten ist +* **precious data**: Alle Daten, die Ihnen etwas bedeuten, vgl. "live data" +* **Repository**: Der Ort an dem Backups gespeichert werden +* **restore**: Daten aus einem Repository wieder herstellen +* **root**, **backup root**: Ein Verzeichnis das gesichert werden soll, inkl. + aller Dateien und Verzeichnisse darunter +* **Snapshot-Sicherung**: Eine Alternative zu kompletter/inkrementeller Sicherung, + bei der jede Generation effektiv ein Vollbackup der Livedaten darstellt, das einzeln + wieder hergestellt oder gelöscht werden kann +* **strategy**, **backup strategy**: Ein Plan um Ihre Daten abzusichern, sogar + wenn die Dinosaurier in Raumschiffen zurückkommen um die Welt zu übernehmen, + jetzt wo die Eiszeit vorüber ist +* **verification**: Sicherstellen das ein Backup-System auch funktioniert und Daten + aus der Sicherung wieder hergestellt werden können und das die Sicherung nicht beschädigt ist diff -Nru obnam-1.7.4/manual/de/040-installation.mdwn obnam-1.8/manual/de/040-installation.mdwn --- obnam-1.7.4/manual/de/040-installation.mdwn 1970-01-01 00:00:00.000000000 +0000 +++ obnam-1.8/manual/de/040-installation.mdwn 2014-05-13 07:05:26.000000000 +0000 @@ -0,0 +1,40 @@ +Installation +============ + +Dieses Kapitel beschreibt die Installation von Obnam. Es ist bisher keine +sehr umfangreiche Anleitung. Insbesondere bietet es im Moment nur +Informationen für Debian Benutzer. Hinweise für andere Systeme +sind sehr willkommen. + +Debian +------ + +Obnam auf einem Debian System zu installieren ist am einfachsten. Wenn Sie +`Wheezy` oder ein neueres Release einsetzen ist Obnam bereits enthalten, Sie +können es mit einem einfachen Befehl installieren: + + apt-get install obnam + +Auf der Webseite des Autors könnte es eine neuere Version geben, +der Rest dieses Kapitels beschreibt wie das Repository des Autors +eingebunden und dann von dort installiert wird. + +Fügen Sie die folgende Zeile Ihrer `/etc/apt/sources.list` hinzu: + + deb http://code.liw.fi/debian wheezy main + +Führen Sie dann als root diese Befehle aus: + +* `apt-get update` +* `apt-get install obnam` + +Sie werden eine Fehlermeldung über fehlende PGP Schlüssel erhalten, mit welchen +das Archiv signiert ist. Sie können diese Meldung entweder ignorieren, oder +-- nach geeigneter Prüfung -- den Schlüssel von der Webseite + Ihren Schlüsseln hinzufügen. + +Andere Systeme +-------------- + +Auf anderen Systemen müssen Sie Obnam aus den Quellen installieren. +Hinweise dazu finden Sie in der Datei `README` in den Quellen. diff -Nru obnam-1.7.4/manual/de/050-uebersicht.mdwn obnam-1.8/manual/de/050-uebersicht.mdwn --- obnam-1.7.4/manual/de/050-uebersicht.mdwn 1970-01-01 00:00:00.000000000 +0000 +++ obnam-1.8/manual/de/050-uebersicht.mdwn 2014-05-13 07:05:26.000000000 +0000 @@ -0,0 +1,136 @@ +Eine Übersicht über Obnam +========================= + +Dieses Kapitel gibt eine kurze Einführung in die wichtigsten Teile von +Obnam. Der Rest des Buches ist im Grunde eine ausführliche Version dieses +Kapitels. Sie sollten dieses Kapitel zuerst lesen und dann einfach behaupten, +Sie hätten auch den Rest gelesen -- auf Cocktailparties wird Sie jeder +ehrfürchtig ansehen. Ich verspreche, dass auch niemand sonst den Rest +des Buches gelesen haben wird, es gibt also kein Risiko erwischt zu werden. + +Konfiguration +------------- + +Obnam benötigt keine Konfigurationsdatei, Sie können alles mittels +Optionen auf der Kommandozeile konfigurieren. +Aber natürlich können Sie auch eine Konfigurationsdatei verwenden, +speichern Sie sie unter `~/obnam.conf` und geben Sie ihr z.B. folgenden Inhalt: + + [config] + repository = sftp://your.server/home/youruser/backups/ + log = /home/liw/obnam.log + + +Die folgenden Beispiele gehen davon aus, dass Sie eine Konfigurationsdatei erstellt haben, +so dass Sie die Optionen nicht jedes Mal wiederholen müssen. + +Vermutlich wollen Sie jetzt die `log` Einstellung aktivieren, damit Sie im Falle eines +Problems alle verfügbaren Informationen zur Problembehebung in der Protokolldatei finden. + +Das erste Backup +---------------- + +Ihr erstes Backup wird recht groß sein und eine ganze Weile dauern. +Ein langes Backup kann abstürzen, aber das ist kein Problem: +Obnam erstellt alle Paar hundert Megabytes einen **Checkpoint**, +von dem aus es abgebrochene Vorgänge wieder aufnehmen kann. + + obnam backup $HOME + +Inkrementelle Backups +--------------------- + +Wenn Sie Ihr erstes Vollbackup gemacht haben (eventuell in mehreren Schritten), +sichern Sie sämtliche Änderungen einfach indem Sie Obnam nochmal aufrufen: + + obnam backup $HOME + +Dies wird alle neuen und geänderten Dateien sichern. Es wird ebenfalls +aufgezeichnet, welche Dateien seit dem letzten Backup gelöscht wurden. + +Sie können Obnam so oft ausführen, wie Sie mögen. Es werden immer nur +die Änderungen zum letzen Backup gesichert. + +Mehrere Clients in einem Repository +----------------------------------- + +Sie können mehrere Clients in ein einzelnes Repository sichern, +indem Sie die Option `--client-name=` beim Programmaufruf +mitgeben. Die Sicherungssätze werden getrennt gespeichert, aber die +Deduplizierung läuft über alle Sätze. + +Alte Generationen entfernen +--------------------------- + +Irgendwann wird Ihr Backup Repository so groß, das Sie einige alte +Generationen entfernen wollen. Diese Operation wird "forget" genannt: + + obnam forget --keep=30d + +Dieser Befehl behält ein Backup von jedem der letzten 30 Kalendertage, +beginnend mit dem neuesten Backup (nicht der aktuellen Uhrzeit). +Wenn Sie mehrmals am Tag gesichert haben, wird nur die letzte Generation +des Tages behalten. + +Alle Daten, die zu einer Generation gehören die behalten wird, +bleiben im Repository. Jegliche Daten die ausschließlich einer +Generation angehören die vergessen wird, wird entfernt. + +Daten wieder herstellen +----------------------- + +Hoffentlich werden Sie das nie benötigen, aber der einzige Grund warum +man Backups anlegt ist, dass man eines Tages die Daten wieder herstellen kann, +falls ein Unglück geschieht: + + obnam restore --to=/var/tmp/my-recovery $HOME + +Dieser Befehl wird Ihr gesamtes Home-Directory aus der letzten Generation +nach `/var/tmp/my-recovery` wiederherstellen. +Wenn Sie nur einzelne Verzeichnisse oder Dateien benötigen, können Sie diese +stattdessen angeben: + + obnam restore --to=/var/tmp/my-recover $HOME/Archive/receipts + +Sollten Sie Sich nicht mehr an den Dateinamen erinnern, benutzen Sie zuerst `obnam ls`: + + obnam ls > /var/tmp/my-recovery.list + +Dies wird den Inhalt der Generation in einem Format ähnlich `ls -lAR` ausgeben. +Speichern Sie den Inhalt in einer Datei und sehen Sie sie durch. +(Das ist ein eher langsames Kommando, daher ist es komfortabler +die Ausgabe in eine Datei zu speichern.) + +Verschlüsselung nutzen +---------------------- + +Obnam kann Backups mittels GnuPG verschlüsseln. Um dies einzuschalten +müssen Sie einen PGP-Schlüssel besitzen (oder erzeugen) und Obnam +dann erklären was es machen soll. + + [config] + encrypt-with = CAFEBABE + +In diesem Fall ist `CAFEBABE` die **Key-ID** Ihres Schlüssels, +so wie GnuPG sie ausgibt. Im Moment benötigen Sie entweder `gpg-agent` oder etwas +ähnliches, denn Obnam hat keine Möglichkeit nach dem Passwort zu fragen. + +Wenn das geschafft ist, wird Obnam von da an automatisch ver- und entschlüsseln. + +Wenn Sie Ihre Backups verschlüsseln, sollten Sie unbedingt auf anderem Weg eine +Sicherheitskopie Ihres GPG Schlüssels anfertigen. Ohne den Schlüssel +können Sie keine Dateien wieder herstellen, daher können Sie sich nicht +auf das gleiche Obnam Backup verlassen um den Schlüssel zu sichern. +Sichern Sie Ihren GPG-Schlüssel irgendwo anderes und stellen Sie sicher, +eine ausreichend starke Passphrase zu verwenden, um offline Brute-Force Attacken +stand zu halten. Denken Sie daran: Sollten Sie Ihren GPG Schlüssel +verlieren oder nicht mehr darauf zugreifen können, wird Ihre gesamte +Backup unbrauchbar. + +Wenn Sie die Verschlüsselung erst nachträglich aktivieren, +müssen Sie mit einem neuen Repository von vorne beginnen. +Sie können keine verschlüsselten und unverschlüsselten Backups im gleichen Repository mischen. + +(Es gibt eine Reihe von Befehlen für die Verwaltung von Obnams Verschlüsselung. +Normalerweise benötigen Sie sie nicht, es sei denn mehrere Ihrer Clients teilen sich +das gleiche Repository. In diesem Fall sollten Sie die manpage lesen.) diff -Nru obnam-1.7.4/manual/de/060-sichern.mdwn obnam-1.8/manual/de/060-sichern.mdwn --- obnam-1.7.4/manual/de/060-sichern.mdwn 1970-01-01 00:00:00.000000000 +0000 +++ obnam-1.8/manual/de/060-sichern.mdwn 2014-05-13 07:05:26.000000000 +0000 @@ -0,0 +1,387 @@ +Sichern +======= + +Dieses Kapitel beschreibt verschiedene Aspekte der Erstellung von Backups mit Obnam. + +Ihr erstes Backup +----------------- + +Ok, dann lasst uns mal ein Backup machen! Um den Beispielen zu folgen +benötigen Sie Live-Daten, die Sie sichern können. Diese Beispiele benutzen +Dateinamen, die Sie an Ihre eigenen Dateinamen anpassen müssen. +Die Beispiele gehen davon aus, daß Ihr Home-Verzeichnis `/home/tomjon` ist und +Sie Dokumente in einem weiteren Verzeichnis names `Documents` +in Ihrem Home-Verzeichnis haben. Weiterhin wird davon ausgegangen das Sie ein +USB-Laufwerk unter `/media/backups` gemounted haben und das Sie das Verzeichnis +`tomjon-repo` auf diesem USB-Laufwerk als Backup-Repository benutzen. + +Diesen Annahmen folgend sichern Sie Ihre Dokumente so: + + obnam backup -r /media/backups/tomjon-repo ~/Documents + +Das ist alles. Wenn Sie viele Dokumente haben dauert es eine Weile, aber +am Ende sieht es dann ungefähr so aus: + + Backed up 11 files (of 11 found), uploaded 97.7 KiB in 0s at 647.2 KiB/s average speed + +Das bedeutet, das Obnam insgesamt 11 Dateien gefunden hat, von denen +alle 11 gesichert wurden. Die Dateien waren zusammen ungefähr 100 +Kilobyte groß und die Upload Geschwindigkeit für diese Daten war +über 600 Kilobytes pro Sekunde. Für die Eindeutigkeit sind Einheiten +mit IEC Präfixen versehen (Basis 2), weitere Informationen finden +Sie unter [Wikipedia on kibibytes]. + +[Wikipedia zum Thema Kibibytes]: https://de.wikipedia.org/wiki/Kibibyte + +Ihr erster Sicherungslauf sollte eher wenige Daten enthalten, +so können Sie prüfen das alle Einstellungen korrekt sind ohne lange +zu warten. Anstatt Ihres gesamten Home-Verzeichnisses könnten Sie mit +einem kleineren Unterordner beginnen. + +Ihr zweites Backup +------------------ + +Nach Ihrem ersten Backup möchten Sie irgendwann einmal ein Weiteres erstellen. +Das tun Sie auf die gleiche Weise: + + obnam backup -r /media/backups/tomjon-repo ~/Documents + +Beachten Sie das Sie Obnam nicht mitteilen müssen ob Sie ein Vollbackup oder +ein inkrementelles Backup erstellen wollen. Obnam sorgt dafür das jede +Generation ein Snapshot der Daten zur Zeit des Backups ist. Somit wird nicht +zwischen Vollbackup und inkrementellem Backup unterschieden. +Jede Generation ist eine vollständige Sicherung, was aber nicht heisst, +daß jede einzelne Generation sämtliche Daten separat vorhält. +Obnam sorgt dafür das mit jeder neuen Generation nur die Daten gesichert +werden, die bisher nicht im Repository waren. Obnam sucht die Daten +in jeder Datei und jeder vorausgehenden Generation aller Clients, +die sich das Repository teilen. + +Wir kommen später auf das Thema zurück, wie Generationen gelöscht werden können, +Sie werden dabei sehen das Obnam jede beliebige Generation löschen kann, +auch wenn sie sich mit anderen Generationen Daten teilt. +Die anderen Generationen werden dabei natürlich keinerlei Daten verlieren. + +Nachdem Sie das zweite Backup erstellt haben, können Sie die Generationen +ansehen: + + $ obnam generations -r /media/backups/tomjon-repo + 2 2014-02-05 23:13:50 .. 2014-02-05 23:13:50 (14 files, 100000 bytes) + 5 2014-02-05 23:42:08 .. 2014-02-05 23:42:08 (14 files, 100000 bytes) + +Wir sehen zwei Generationen, die die Kennungen 2 und 5 haben. Die +Bezeichner der Generationen sind nicht unbedingt eine einfache Folge wie +1, 2, 3. Dies liegt daran wie einige der internen Datenstrukturen +in Obnam umgesetzt werden, und in keinster Weise daran das der Autor +Spaß daran hat, Menschen zu verwirren. + +Die beiden Zeitstempel zeigen wenn der Backup-Lauf begann und wann +er endete. Darüber hinaus wird für jede Generation die Anzahl der +Dateien in dieser Generation ausgegeben (insgesamt, nicht nur neue oder geänderte Dateien), +und die summierte Größe der Dateien. + +Auswählen was zu sichern ist -- und was nicht +--------------------------------------------- + +Obnam muss wissen, was gesichert werden soll. Dazu übergeben Sie eine Liste +von Verzeichnissen, die backup roots genannt werden. Bisher haben wir +in den Beispielen dieses Kapitels das Verzeichnis `~/Documents` als backup root benutzt +(das ist das Verzeichnis `Documents` in Ihrem Home Verzeichnis). +Es darf aber auch mehrere backup roots geben: + + obnam -r /media/backups/tomjon-repo ~/Documents ~/Photos + +Alles in den backup root Verzeichnissen wird gesichert -- außer es ist +explizit ausgeschlossen. Es gibt mehrere Möglichkeiten um etwas von Backups +auszuschließen: + +* Die `--exclude` Option verwendet reguläre Ausdrücke, die dem kompletten + Pfadnamen jeder Datei bzw. jeden Verzeichnisses entsprechen. + Wenn der Pfadname übereinstimmt, werden die Datei oder das Verzeichnis + nicht gesichert; Obnam tut so als wären die Dateien / das Verzeichnis + nicht vorhanden. Wird ein Verzeichnis ausgeschlossen, dann werden alle Dateien und + Unterverzeichnisse ebenfalls ausgeschlossen. Um beispielsweise + alle MP3-Dateien auszuschließen, verwenden Sie (`--exclude='\.mp3$'`). +* Die `--exclude-caches` Option schließt Verzeichnisse aus, + die eine spezielle Datei namens `CACHEDIR.TAG` enthalten, die + mit einer bestimmten Byte-Sequenz anfangen muss. Eine solche Datei könnte + z.B. im Cache-Verzeichnis Ihre Browsers abgelegt werden. Die Daten in + diesen Verzeichnissen sind meist nicht wichtig und brauchen nicht + gesichert zu werden und es ist einfacher, das gesamte Verzeichnis mittels + der Spezialdatei für den Ausschluß zu markieren, als einen regulären Ausdruck + für `--exclude` zu schreiben. +* Die `--one-file-system` Option schließt alle mount points und den Inhalt + der gemounteten Dateisysteme aus. Das ist praktisch um z.B. virtuelle + Dateisysteme wie `/proc`, remote Dateisysteme (z.B. NFS-mounts) und + mittels `obnam mount` eingebundene Obnam Repositories auszuschließen + (letzteres behandeln wir im nächsten Kapitel). + +Generell ist es besser zu viel zu sichern als zu wenig. +Sie sollten auch genau wissen, was gesichert wird und was nicht. +Die Option `--pretend` bewirkt das Obnam ein Backup anfertigt, das Repository +dabei aber nicht verändert, es ist also schnell durchgelaufen. So können Sie +sehen was gesichert würde und die excludes entsprechend anpassen. + +Konfigurationsdateien: Eine kurze Einführung +-------------------------------------------- + +Zu diesem Zeitpunkt werden Sie vielleicht bemerkt haben, dass Obnam +eine ganze Reihe von konfigurierbare Einstellungen hat, die Sie auf vielfältige +Art verändern können. Auf der Kommandozeile ist das immer möglich, +aber dann wird das Kommando doch recht lang. Stattdessen könnten +Sie auch eine Konfigurationsdatei verwenden. + +Jede Option die Obnam kennt, kann auch in einer Konfigurationsdatei verwendet werden. +Später in diesem Handbuch gibt es ein ganzes Kapitel, das alle Details +der Konfigurationsdateien und verschiedenen Einstellungen, die Sie +verwenden können, umfasst. Hier geben wir erstmal eine kurze Einführung. + +Eine Obnam Konfigurationsdatei sieht so aus: + + [config] + repository = /media/backup/tomjon-repo + root = /home/liw/Documents, /home/liw/Photos + exclude = \.mp3$ + exclude-caches = yes + one-file-system = no + +Diese Form der Konfigurationsdatei ist als "INI file" bekannt, +vielen z.B. von Microsoft Windows. Jede Obnam-Option wird in den +Abschnitt `[config]` geschrieben, und jede Einstellung hat den +gleichen Namen wie die Kommandozeilen-Option (ohne die Doppel-Minuszeichen). +Demnach wird `--exclude` auf der Kommandozeile und `exclude` im Config-File verwendet. + +Einige Optionen können mehrere Werte annehmen, z.B. `exclude` und `root`, +die Werte werde mittels Komma getrennt. Wenn die Anzahl der Werte zu groß wird +können Sie sie über mehrere Zeilen verteilen; die zweite +und weitere Zeilen müssen dann mit Leerzeichen oder TAB eingerückt werden. + +Jetzt sollten Sie genug für den Anfang haben, Details finden +Sie im Kapitel "Obnam Konfigurationsdateien und Einstellungen". + +Wenn Ihre wertvollen Daten sehr groß sind +----------------------------------------- + +Wenn Ihre wertvollen Daten sehr groß ist, kann die erste Sicherung sehr +lange dauern. Ditto, wenn Sie viele neue wertvolle Daten in eine späteres +Backup aufnehmen. In diesen Fällen müssen Sie sehr geduldig sein und +dem Backup Zeit lassen. Oder Sie können klein beginnen und dann nach +und nach Sicherungen hinzufügen. + +Die Option "Geduld" ist einfach: Sie lassen Obnam alles sichern, starten +die Sicherung und warten, bis es fertig ist, auch wenn das Stunden oder Tage +dauert. Sollte die Sicherung vorzeitig abbrechen, z. B. wegen einer +ausgefallenen Netzwerkverbindung, brauchen Sie Dank Obnams +Checkpoint-Unterstützung nicht von Grund auf neu beginnen. +Jedes Gigabyte oder so (standardmäßig) erzeugt Obnam +eine Checkpoint Generation. Wenn die Sicherung später +abstürzt, können Sie Obnam einfach erneut ausführen und es wird +beim letzten Checkpoint weitermachen. Das alles passiert vollautomatisch. +Wie oft die Checkpoints erstellt werden sollen können Sie in der +Option `--checkpoint` verändern. + +Das Problem mit der Option "Geduld" ist, dass Ihre wichtigsten +Daten nicht gesichert werden, während alle Ihre großen, aber weniger wertvollen +Daten gesichert werden. Zum Beispiel können Sie eine große Menge an +heruntergeladenen Videos von Konferenz-Präsentationen haben, was schön ist, aber nicht +enorm wichtig. Während diejenigen gesichert werden, bleiben Ihre eigenen Dokumente +ungesichert. + +Sie können dies umgehen, indem Sie zunächst alles außer Ihren +wertvollsten Daten vom Backup ausschließen. Wenn diese dann gesichert +sind, können Sie nach und nach die Ausschlüsse reduzieren, bis Sie alles +gesichert haben. Zum Beispiel könnte Ihr erstes Backup folgende +Konfiguration haben: + + obnam backup -r /media/backups/tomjon-repo ~ \ + --exclude ~/Downloads + +So werden alle Downloads ausgeschlossen. Im nächsten Lauf schließen +Sie nur noch Video-Dateien (mp4) aus: + + obnam backup -r /media/backups/tomjon-repo ~ \ + --exclude ~/Downloads/'.*\.mp4$' + +Dann reduzieren Sie den Ausschluss auf einzelne Videos, deren +Namen mit einem bestimmten Buchstaben beginnen: + + obnam backup -r /media/backups/tomjon-repo ~ \ + --exclude ~/Downloads/'[^b-zB-Z].*\.mp4$' + +Reduzieren Sie die ausschlüsse immer weiter bis alle Videos gesichert sind. + +Deduplizierung +-------------- + +Obnam de-dupliziert die Daten die es sichert über alle Dateien in allen +Generationen für alle Clients, die sich das Repository teilen. Dies +geschieht durch Aufteilen der Dateidaten in "chunks" genannte Teile. +Jedes Mal wenn Obnam eine Datei liest und ein chunk zusammenkommt, +schaut es im Repository nach, ob ein identischer chunk bereits vorhanden +ist. Wenn ja muss Obnam den chunk nicht hochladen, was Platz, Bandbreite +und Zeit spart. + +Deduplizierung in Obnam ist in verschiedenen Situationen hilfreich: + +* Wenn Sie zwei identische Dateien haben. Sie haben vielleicht + verschiedene Namen und liegen in verschiedenen Verzeichnissen, + enthalten aber die selben Daten. +* Wenn Dateien wachsen, aber neue Daten nur am Ende angehängt werden, + was z.B. für Logfiles typisch ist. Falls die ersten chunks unverändert + sind, müssen nur die neuen Daten gesichert werden. +* Wenn eine Datei oder ein Verzeichnis umbenannt oder verschoben wird. + Wenn Sie meinen das der englische Begriff `Photos` für das Verzeichnis + unpassen ist und Sie lieber das finnische `Valokuvat` möchteninstead, + können Sie das Verzeichnis einfach umbenennen. Ohne Deduplizierung + müssen Sie dann aber alle Fotos nochmal sichern. +* Wenn ein Team mit den gleichen Daten arbeitet und demnach jeder + eine Kopie der Daten vorhält, muss das Repository statt eine Kopie pro + Team-Mitglied nur eine einzelne Kopie der Daten vorhalten. + +Die Deduplizierung in Obnam ist nicht perfekt. Die Granularität des Findens +doppelter Daten ist recht grob (vgl. Option `--chunk-size`) , daher +kann Obnam oft keine Überschneidungen finden, wenn die Änderungen nur klein sind. + +Deduplizierung und Sicherheit gegen Prüfsummen-Kollisionen +---------------------------------------------------------- + +Dieses Thema ist ein bisschen beängstigend, aber es wäre unehrlich, +es überhaupt nicht zu behandeln. Sie dürfen gern später auf diesen Abschnitt +zurück kommen. + +Obnam verwendet den MD5-Algorithmus zur Erkennung von doppelten chunks. +MD5 hat den Ruf, unsicher zu sein: Menschen haben Dateien gebaut die zwar +unterschiedlich sind, aber zu der gleichen MD5-Prüfsumme führen. Es +stimmt -- MD5 ist nicht für sicherheitskritische Anwendungen geeignet. + +Jeder Prüfsummen-Algorithmus kann Kollisionen haben. Obnam auf, sagen +wir, SHA1, SHA2, oder den neuen SHA3 Algorithmus umzustellen, würde +nicht die Möglichkeit von Kollisionen ausschließen. Es reduzierte die +Chance von zufälligen Kollisionen, aber die Chance ist bereits so klein, +dass sie mit MD5 vernachlässigt werden kann. Oder, anders ausgedrückt: +Wenn Sie über zufällige MD5-Kollisionen besorgt sind, sollten Sie +ebenfalls über versehentliche SHA1, SHA2 oder SHA3 Kollisionen besorgt +sein. + +Abgesehen von zufälligen Kollisionen gibt es zwei Fälle, in denen Sorgen +um Prüfsummen-Kollisionen angebracht sind (unabhängig von Algorithmus). + +Erstens: Wenn Sie einen Gegner haben der Ihre gesicherten Daten +korrumpieren möchte, kann er einige der gesicherten Daten mit anderen +Daten mit der gleichen Prüfsumme austauschen. Auf diese Weise werden +Ihre Daten beschädigt ohne das Obnam es merkt und Sie können sie nicht +wieder herstellen. + +Zweitens: Wenn Sie Prüfsummen-Kollisionen erforschen, haben Sie +wahrscheinlich Dateien, deren Prüfsummen kollidieren. In diesem Fall +werden Sie nach einer Katastrophe die Daten in ihrem Ursprungszustand +wieder herstellen wollen, ohne das Obnam eine mit der anderen +verwechselt. + +Um mit diesen Situationen umzugehen besitzt Obnam drei +Deduplizierungs-Modi, die Sie mit der `--deduplicate` Einstellung +steuern: + +* Der Standard-Modus `fatalist` geht davon aus, das keine Kollisionen + auftreten. Das ist für die Meisten Anwender ein vernünftiger Kompromiss + zwischen Geschwindigkeit, Schutz und Sicherheit. +* Der Modus `verify` geht nimmt an das Kollisionen passieren und stellt sicher, + das bereits gesicherte chunks auch wirklich mit dem zu sichernden chunk + übereinstimmen, indem die eigentlichen Daten verglichen werden. + Um das zu tun muss der chunk aus dem Repository geladen werden, was + recht langsam sein könne, da die Prüfsummen oft passen. Dieser Modus + macht Sinn wenn Sie deduplizieren wollen und sehr schnellen Zugriff auf + das Repository haben, z.B. wenn es auf einer lokalen Platte liegt. +* Mit `never` wird die Deduplizierung komplett abgeschaltet. + Wenn Sie keine Deduplizierung benötigen oder Kollisionen fürchten, ist + dies der richtige Modus für Sie. + +Leider gibt es keine Deduplizierung die unverwundbar gegen Kollisionen +ist und dabei auch noch schnell arbeitet, wenn der Zugriff auf das +Repository langsam ist. Der einzige Weg um dagegen geschützt zu sein +ist, die Daten zu vergleichen. Wenn das Herunterladen der Daten aus dem +Repository langsam ist, dann wird der Vergleich natürlich erhebliche +Zeit in Anspruch nehmen. + +Locking +------- + +Mehrere Clients können sich ein Repository teilen. Um zu verhindern das +sie sich dabei gegenseitig auf die Füße treten, sperren sie Teile des +Repositories während des Vorgangs. Im Kapitel "Mehrere Clients in +einem Repository" finden Sie Details. + +Wenn Obnam abrupt abbricht kann die Sperre bestehen bleiben, auch wenn +es nur einen einzelnen Client im Repository gibt. Neue Backups sind so +nicht mehr möglich. Der Abbruch kann z.B. durch eine abgebrochene +Netzwerkverbindung oder aufgrund eines Fehlers in Obnam passieren, genau +so wenn Obnam vom Benutzer unterbrochen wird, bevor es fertig ist. + +Die Befehl `force-lock` kann diese Situation bereinigen, aber das ist +gefährlich. Wenn Sie eine Sperre, die von Obnam aktiv benutzt wird, +manuell aufheben, dann wird es wahrscheinlich ins Stolpern geraten. Im +Extremfall kann sogar das Repository beschädigt werden. Seien Sie also +vorsichtig. + +Wenn Sie entschieden haben das es kein Problem darstellt, wird die Sperre +wie folgt aufgehoben: + + obnam -r /media/backups/tomjon-repo force-lock + +Beachten Sie, dass manche Locks je Client gelten, um zu verhindern das +Obnam versehentlich zweimal auf dem gleichen Client läuft. Das wäre, wie +auf die eigenen Zehen zu steigen: Irgendwie beeindruckend, aber +unangenehm und nicht zu empfehlen. + +Wenn Sie eine Sperre für einen bestimmten Client manuell aufheben +müssen, können Sie den Client-Namen explizit angeben: + + obnam --client-name magrat \ + -r /media/backups/tomjon-repo force-lock + +(Lange Zeile aus Layout-Gründen umgebrochen.) + +Konsistenz der Live-Daten +------------------------- + +Das Erstellen einer Sicherungskopie kann eine ganze Weile dauern, und +während das Backup läuft, könnte sich das Dateisystem ändern. Dies führt +dazu, das der Snapshot, den Obnam als Generation sichert, in sich +inkonsistent ist. Ein Beispiel: Sie haben zwei Dateien, A und B, die +synchron gehalten werden müssen. Sie machen ein Backup, währenddessen +Sie zuerst A und dann B ändern. Unglücklicherweise sichert Obnam File A +bevor Sie Ihre Änderungen speichern und File B, nachdem Sie die +Änderungen gespeichert haben. Die Sicherungsgeneration hat nun Versionen +von A und B, die nicht synchron sind. Das ist schlecht. + +Es gibt mehrere Möglichkeiten, mit diesem Problem umzugehen: + +* Der Logical Volume Manager (LVM) ermöglichst Snapshots. Sie können + Ihre Backups so einrichten, das zuerst ein Snapshot von jedem logischen + Volume gemacht wird, das gesichert weden soll. Dann machen Sie das + Backup und löschen danach den Snapshot wieder. Niemand kann die Daten + im Snapshot verändern, während trotzdem normal weiter gearbeitet weden + kann, während das Backup läuft. +* Ähnlich können Sie auch mit btrfs und seinen subvolumes vorgehen. +* Sie können das System herunterfahren und im Einzelbenutzermodus + neu starten, das Backup machen, und dann wieder im Mehrbenutzermodus + starten. Das ist nicht der schönste Weg, aber der sicherste, um + ein konsistentes Backup zu erhalten. + +Beachten Sie, dass Snapshots auf Dateisystemebene nicht wirklich eine +konsistente Sicht auf die Live-Daten garantieren können. Eine Anwendung +kann mitten im Schreiben einer Datei oder Gruppe von Dateien sein, wenn +der Snapshot gemacht wird. Das ist gewissermaßen ein Bug in der +Anwendung, aber das zu wissen hilft Ihnen nicht besser zu schlafen. + +In der Regel haben die meisten Systeme aber ausreichend Leerlaufzeit, +um ein konsistentes Backup während dieser Zeit zu erstellen. Zum +Beispiel kann das Backup auf einem Laptop ausgeführt werden, während der +Benutzer anderswo ist und nicht aktiv mit der Maschine arbeitet. + +Wenn irgend möglich sollte Ihr Backup-Programm überprüfen, ob die Daten +einer Backup-Generation in sich konsistent sind. Ansonsten werden Sie +entweder darauf vertrauchen müssen das die Anwendungen, die Sie +verwenden, nicht zu buggy sind. + +Wenn Sie diesen Abschnitt nicht verstanden haben: Keine Sorge, seien Sie glücklich und schlafen Sie gut. diff -Nru obnam-1.7.4/manual/de/070-wiederherstellen.mdwn obnam-1.8/manual/de/070-wiederherstellen.mdwn --- obnam-1.7.4/manual/de/070-wiederherstellen.mdwn 1970-01-01 00:00:00.000000000 +0000 +++ obnam-1.8/manual/de/070-wiederherstellen.mdwn 2014-05-13 07:05:26.000000000 +0000 @@ -0,0 +1,110 @@ +Wiederherstellen von Backups +============================ + +Das Schlimmste ist passiert! Ihre Katze hat das Katzenklo und Ihre +Festplatte verwechselt! Ihre Ziege hat Ihr allerwichtigstes Dokument +gelöscht! Wehe Dir! + +Bleiben wir ruhig. Genau dafür haben wir ja Backups. Es gibt keinen +Grund für Ausrufezeichen. Atmen Sie tief ein, trinken Sie eine Tasse +Tee, und alles wird gut. + +Es gibt zwei verschiedene Ansätze für die Wiederherstellung von Daten +mit Obnam. Einer stützt sich auf das FUSE-Dateisystem. Dieses sehr +schöne Stück Technik macht es möglich, dass Obnam Ihre Backups einfach +als eine Verzeichnis darstellt. Es ist der bevorzugte Weg, aber nicht +immer möglich, daher hat Obnam auch eine primitivere, weniger leicht zu +bedienende Methode. + +Wiederherstellen mit FUSE^[Anm. d. Ü.: Sprachkomik zu übersetzen ist schwer, der +Witz bleibt dabei zu oft auf der Strecke. Daher versuche ich es garnicht erst.] +------------------------- + +Mittels `obnam mount` können Sie Ihre Backups ansehen, als wären sie ein +Verzeichnis wie jedes andere. Dazu müssen Sie allerdings FUSE installiert +haben (vgl. Kapitel "Installation" für Details). Die meisten modernen +Linux Desktops bringen FUSE schon mit. + + mkdir ~/backups + obnam mount --to ~/backups + +Führen Sie den Befehl oben aus und schauen Sie dann in das Verzeichnis +`~/backups`. Sie werden ungefähr das hier sehen: + + $ ls -l ~/backups + total 12 + drwxr-xr-x 24 root root 4096 Feb 11 21:41 2 + drwxr-xr-x 24 root root 4096 Feb 11 21:41 5 + lrwxr-xr-x 24 root root 4096 Feb 11 21:41 latest -> 5 + $ + +Jedes Verzeichnis unter `~/backups` ist eine Generation Ihres Backups, +bennant nach dem "generation identifier" den Obnam vergibt. +Der Symlink `latest` zeigt immer auf die neueste Generation. + +Jetzt können Sie kinderleicht eine Datei wieder herstellen: + + cp ~/backups/latest/home/tomjon/Documents/iloveyou.txt ~/restored.txt + +Sie können beliebige Dateien aus dem Verzeichnis `~/backups` kopieren, aus jeder +Generation, oder aus allen wenn Sie möchten. Sie können die Dateien auch erst +ansehen, bevor Sie sie herauskopieren: + + less ~/backups/2/home/tomjon/Documents/iloveyou.txt + +So finden Sie leicht die Version die Sie suchen, nicht nur die neueste. + +Etwas löschen können Sie in `~/backups` nicht. Das Verzeichnis ist read-only und +Sie können weder absichtlich noch unabsichtlich etwas darin löschen oder verändern. +Dieses Verhalten ist beabsichtigt: `obnam mount` soll eine sichere Möglichkeit bieten, +Ihre Backups zu betrachten, ohne das Sie besondere Sorgfalt walten lassen müssen. + +Wenn Sie Ihre Backups nicht mehr ansehen möchten, können Sie das Repository un-mounten: + + fusermount -u ~/backups + +Neben der Kommandozeile können Sie natürlich auch den Filemanager Ihrer Wahl benutzen. +Den mount und un-mount Vorgang müssen Sie (abhängig von der Konfiguration Ihres PCs) +eventuell trotzdem auf der Kommandozeile durchführen. + +Wiederherstellen ohne FUSE +-------------------------- + +Wenn `obnam mount` nicht verfügbar ist, können Sie direkt mit Obnam +wiederherstellen. Verwenden `obnam generations` und ` obnam ls`, um die +richtige Generation zur Wiederherstellung zu finden, und führen Sie dann +einen Befehl wie diesen aus: + + obnam restore --to /tmp/tomjon-restored /home/tomjon/Documents + +So wir das angegebene Verzeichnis wieder hergestellt. Ohne Angabe, was wieder +herzustellen ist wird die gesamte neueste Generation wieder hersgestellt. +Eine andere Generation währen Sie mit `--generation` aus: + + obnam restore --to /tmp/tomjon-restored --generation 2 + +Hinweis: Sie können kein Verzeichnis wiederherstellen, das bereits existiert. +So wird verhindert, das Sie ein bereits existierendes Verzeichnis mit den +wieder hergestellten Daten überschreiben. Wenn Sie Ihre Live-Daten +wirklich ersetzen möchten, sollten Sie zunächst in ein temporäres Verzeichnis +zurücksichern und dann die Daten verschieben. + +Übung macht den Meister +----------------------- + +Sie sollten das Zurücksichern üben. So bekommen Sie mehr Vertrauen und Ihre +Backups und können ruhiger bleiben, wenn das Schlimmste passiert. +Etwas hochgestocherner ausgedrückt: Sie sollten Ihren Disaster Recovery Plan +testen. + +Machen Sie testweise eine Wiederherstellung von ein paar Dateien oder +sogar allen, bis Sie sicher wissen wie das geht. Von Zeit zu Zeit +sollten Sie das wiederholen um sicher zu sein das Ihre Backups immer +noch funktionieren. Es ist viel weniger beängstigend, nach Datenverlust +eine echte Wiederherstellung zu machen, wenn man vorher geübt hat. + +In extremen Fällen, insbesondere wenn Sie ein Obnam Entwickler sind, +sollten Sie vielleicht mal Ihre Festplatte formatieren und dann die +Wiederherstellung durchführen, nur um zu wissen, das Sie es können. Wenn +Sie kein Obnam Entwickler sind, wäre das vielleicht ein bisschen extrem: +Benutzen Sie einfach eine separate Festplatte statt der eingebauten. diff -Nru obnam-1.7.4/manual/de/080-loeschen.mdwn obnam-1.8/manual/de/080-loeschen.mdwn --- obnam-1.7.4/manual/de/080-loeschen.mdwn 1970-01-01 00:00:00.000000000 +0000 +++ obnam-1.8/manual/de/080-loeschen.mdwn 2014-05-13 07:05:26.000000000 +0000 @@ -0,0 +1,95 @@ +Alte Backup-Generationen löschen +================================ + +Jedes Mal wenn Sie ein Backup erstellen, wächst Ihr Backup-Repository in +der Größe. Um ein Vollaufen des verfügbaren Speichers zu vermeiden +müssen Sie ab und zu einige alte Backups los werden. Das ist natürlich +ein bisschen ein Dilemma: Sie machen Backups um keine Daten zu +verlieren, und jetzt müssen Sie genau das tun. + +Obnam verwendet den Begriff "vergessen" für das Entfernen einer +Sicherungsgeneration. Sie können mittels des Generation Identifier +angeben, welche Generation manuell entfernt werden soll, oder Sie können +einen Zeitplan anlegen, nach dem dann automatisch "vergessen" wird. + +Eine bestimmte Generation löschen: + + obnam forget 2 + +(Dieses Beispiel setzt voraus, dass Sie eine Konfigurationsdatei haben +die Obnam automatisch findet, und dass Sie Dinge wie den Pfad zum +Repository oder die Verschlüsselung nicht auf der Kommandozeile +einzugeben brauchen). + +Sie können jede Generation unabhängig von einander löschen. Obnam +behandelt jede Generation als unabhängigen vollständigen Snapshot (in +Wirklichkeit wird natürlich nicht jedes Mal eine vollständige Sicherung +gemacht), Sie brauchen Sich also keine Sorgen um Unterschiede zwischen +einer vollständigen und inkrementellen Sicherung machen. + +Backups manuell zu löschen ist mühsam, wahrscheinlich werden Sie +einen Plan verwenden wollen, nach dem Obnam die Generationen automatisch +löscht. + +Eine oft angewandter Zeitplan ist zum Beispiel dieser: + +* Behalte ein Backup für jeden Tag der vorigen Woche +* Behalte ein Backup für jede Woche der vorigen 3 Monate +* Behalte ein Backup für jeden Monat der vergangenen 2 Jahre +* Behalte ein Backup für jedes Jahr der vergangenen 57 Jahre + +Obnam verwendet die `--keep` Option um einen Zeitplan festzulegen. +Die Einstellung für den oben Zeitplan sähe wie folgt aus: + + --keep 7d,15w,24m,57y + +Die Übereinstimmung ist etwas ungenau, weil ein Monat mehr oder weniger +Wochen haben kann, aber sie sollte ausreichen. Die Einstellung "7d" wird +als "die letzte Sicherung jedes Kalendertags der letzten sieben Tage, an +denen Sicherungen gemacht wurden" interpretiert. Für den Rest des +Zeitplans gilt das analog. Lesen Sie das Kapitel "Obnam +Konfigurationsdateien und Einstellungen" für genauere Details. + +Der Zeitplan wählt eine Reihe von Generationen aus, die behalten werden. +Alles andere wird gelöscht. + +Einen Zeitplan für das Löschen von Generationen auswählen +--------------------------------------------------------- + +Der Zeitplan für das löschen von Backup-Generationen ist ein bisschen +ein Ratespiel, genau wie Backups im Allgemeinen. Wenn Sie sicher die +Zukunft vorhersagen könnten, wüssten Sie alle Katastrophen, die Ihre +Daten gefährden können schon vorher und Sie könnten Ihr Backup auf die +Dinge beschränken, die sonst verloren gehen würden. + +In dieser Welt müssen Sie leider raten. Sie müssen darüber nachdenken +mit welchen Risiken Sie (oder Ihre Daten) konfrontiert sind und wie viel +Sie ausgeben wollen um sich (oder Ihre Daten) zu schützen. + +* Haben Sie Angst das Ihre Festplatte plötzlich auf sehr spektakuläre + Art und Weise, z.B. durch Brand oder Diebstahl abhanden kommt? Wenn ja, + brauchen Sie eigentlich nur eine recht aktuelle Sicherung, um das Risiko + abzusichern. +* Machen Sie sich Sorgen Ihre Festplatte, Ihr Dateisystem, Ihre + Anwendungen oder Sie selbst könnten langsam Ihre Daten zerstören? Wie + lange würde es dauern um das zu bemerken? Sie brauchen eine + Backup-History die weiter zurückreicht als es dauert, das Problem zu + erkennen. +* Ähnlich bei versehentlichem Löschen von Dateien. Wie lange wird es + dauern bis Sie das bemerken? Mindestens so lang sollte Ihre + Backup-History sein. + +Natürlich gibt es auch noch andere Kriterien, zum Beispiel: + +Möchten Sie in 50 Jahren sehen, wie Ihre Dateien werden heute abgelegt +sind? Wenn ja, benötigen Sie ein fünfzig Jahre altes Backup, sowie +vielleicht ein Backup von jedem Jahr, falls Sie vergleichen wollen, wie +sich die Dateien jedes Jahr entwickelt haben. Mit wachsenden +Speichermedien und guter Deduplizierung ist dies nicht ganz so teurer +als es auf den ersten Blick scheint. + +Es gibt keinen Zeitplan der jedermanns Bedürfnisse erfüllt. Sie müssen +für sich selbst entscheiden, deshalb ist die Standard-Einstellung in +Obnam alles für immer zu behalten. Es ist nicht Obnams Aufgabe zu +entscheiden, ob Sie diese oder jene Sicherungsgeneration nicht +vielleicht behalten sollten. diff -Nru obnam-1.7.4/manual/de/090-ueberpruefen.mdwn obnam-1.8/manual/de/090-ueberpruefen.mdwn --- obnam-1.7.4/manual/de/090-ueberpruefen.mdwn 1970-01-01 00:00:00.000000000 +0000 +++ obnam-1.8/manual/de/090-ueberpruefen.mdwn 2014-05-13 07:05:26.000000000 +0000 @@ -0,0 +1,64 @@ +Backups überprüfen +================== + +Es ist 9 Uhr abends. Wissen Sie, ob Ihre Backups funktionieren? Wissen Sie, +wann Sie das letzte mal eine erfolgreiche Sicherung aller Ihrer Daten gemacht +haben? Wissen Sie, ob Sie aus dieser Sicherung alles wieder herstellen +können? Wenn nicht, wie können Sie gut schlafen? + +Sie sollten Ihre Backups überprüfen, und zwar regelmäßig -- nicht nur das eine Mal, als +Sie das Backup-System aufsetzten. Überprüfen bedeutet, das Sie tun was auch immer +getan werden muss, um sicherzustellen, dass alle Ihre wertvollen Daten gesichert wurden +und fehlerfrei aus den Backups wieder hergestellt werden können. + +Der einfachste Weg dies zu tun ist alle Ihre Daten wieder herzustellen und +dann mit den Live-Daten zu vergleichen um Unterschiede zu finden. Das +erfordert entsprechend Speicherplatz um alles wieder herzustellen, aber es ist fast die +einzige Möglichkeit sicher zu sein. + +Gleichzeitig ist das auch ein guter Weg um sicherzustellen, das die Wiederherstellung +tatsächlich funktioniert. Wenn Sie dies nicht testen, dürfen Sie -- wenn es +darauf ankommt -- nicht erwarten, daß das Restore auch wirklich funktioniert. + +Wenn Sie den Speicherplatz haben um eine komplette Wiederherstellung zu +machhen, sollten Sie dies auch tun. Es ist eine großartige Möglichkeit, Ihre +Desaster-Recovery-Prozesse durchzuspielen. + +Ein Weg das mal zu tun wäre dieser: + +* Machen Sie ein Backup auf Ihrem Hauptcomputer +* Machen Sie eine komplette Wiederherstellung auf einem zweiten Computer + (eventuell leihen Sie einfach einen), ohne den Hauptcomputer überhaupt zu benutzen. +* Beginnen Sie, mit den wiederhergestellten Daten als Live-Daten zu arbeiten. + Machen Sie echte Arbeit und all die Dinge, die Sie normalerweise tun. Tun Sie + so, als wäre Ihr Hauptcomputer von Ihrem Haustier gefressen worden. +* Wenn Sie bemerken das etwas fehlt oder korrupt ist, oder zu alt, holen Sie + die Dateien von Ihrem Hauptcomputer und richten Sie Ihren Backup-Prozess + damit Sie das nächste Mal nicht dieses Problem haben werden. + +Wie oft man das tun sollte? Das wiederum hängt davon ab, wie viel Ihnen +Ihre Daten bedeuten und wie viel Sie Ihren Backup-Tools und Prozessen vertrauen. +Wenn es wirklich wichtig ist, dass Sie nach einer Katastrophe ein Restore erstellen +können, benötigen Sie häufigere Überprüfungen. Wenn der Datenverlust höchstens +Umstände macht und Ihr Leben nicht katastrophal verändert, +können Sie weniger häufig überprüfen. + +Neben der Wiederherstellung von Daten bietet Obnam noch zwei andere +Möglichkeiten, wie Sie Ihre Backups überprüfen können: + +* `obnam verify` ist fast wie `obnam restore`, außer das es die + gesicherten Daten mit Live-Daten vergleicht und meldet Unterschiede + meldet. Das bedeutet natürlich, dass Sie darauf vertrauen, dass Obnam + die Überprüfung richtig macht. +* Mit `obnam mount` können Sie auf Ihre + gesicherten Daten zugreifen, als ob sie in einem normalen Verzeichnis + lägen. Anschließend können Sie ein beliebiges Werkzeug Ihres Vertrauens + benutzen, um die gesicherten Daten mit den Live-Daten zu vergleichen. + Das ist fast genau so wie alles wiederherzustellen, da das + Vergleichs-Tool alle Daten und Metadaten aus dem Backup extrahieren + muss. Die Daten weden nur nicht weg geschrieben. + +Beide Ansätze haben das Problem, dass sie eine Sicherung mit Live-Daten +vergleichen und die Live-Daten sich nach der Sicherung geändert haben +könnten. Sie müssen alle Unterschiede manuell überprüfen, was eine +größere Aufgabe sein kann, wenn sich die Live-Daten häufig ändern. diff -Nru obnam-1.7.4/manual/de/100-mehrere-clients.mdwn obnam-1.8/manual/de/100-mehrere-clients.mdwn --- obnam-1.7.4/manual/de/100-mehrere-clients.mdwn 1970-01-01 00:00:00.000000000 +0000 +++ obnam-1.8/manual/de/100-mehrere-clients.mdwn 2014-05-13 07:05:26.000000000 +0000 @@ -0,0 +1,46 @@ +Ein Repository mit mehreren Clients benutzen +============================================ + +Mit Obnam können Sie mehrere Computer in das gleiche Repository sichern. +Jeder Client wird durch einen Namen identifiziert, der standardmäßig dem Hostnamen +entspricht, also dem Namen den Sie erhalten, wenn Sie den Befehl +`hostname` ausführen. Sie können den Name aber auch explizit mittels +`--client-name` selbst angeben. + +Alle Clients in einem gemeinsamen Repository benutzen sämtliche Daten +(die chunks), die Deduplizierung funktioniert also über Client-Grenzen +hinweg. Jeder Client hat seine eigenen Backup-Generationen, die völlig +unabhängig von anderen Clients sind. Sie können beispielsweise alle +Generationen eines Clients löschen (forget), ohne das dies einen +Einfluss auf die Generationen oder gar die gesicherten Daten der +anderen Clients hat. + +Obnam kümmert sich automatisch um das Locking, so dass Sie auf jedem +Client Obnam laufen lassen können, ohne sich darum kümmern zu müssen +das zu jeder Zeit nur ein Client aktiv ist. + +Einen Vorbehalt müssen Sie bei der gemeinsamen Nutzung von +Repositories beachten: Jeder Client hat Zugriff auf alle chunks und +kann jeden anderen Client aus dem Repository löschen. Das heißt, +Sie sollten nur ein Repository mit Clientes teilen, die in der +gleichen Sicherheitsdomäne sind: Allen Clients sollte gleichermaßen +vertraut werden. Wenn ein Client gehackt wird, dann erhält der +Eindringling Zugriff auf alle Daten im Repository und kann die Backups +aller Clients des Repository löschen. + +Um ein gemeinsames Repository zu erstellen müssen Sie folgendes tun: + +* Legen Sie einen eindeutigen Namen für jeden Client fest. Der + Name muss innerhalb des Repository eindeutig sein. +* Geben Sie jedem Client Zugriff auf das Repository. + +Das ist schon alles. + +Um zu sehen welche Clients ein Repository benutzen, führen Sie einfach dies aus: + + obnam clients + +Es gibt derzeit keine Möglichkeit, einen Client aus einem Repository +zu entfernen, es sei denn, Sie benutzen die GnuPG-Verschlüsselung. +Das ist als Bug in Obnam berücksichtigt und wird irgendwann gefixt. +Danach wird eine Zeitmaschine entwickelt, so dass dieser Absatz nie existiert haben wird. diff -Nru obnam-1.7.4/manual/de/110-verschluesselung.mdwn obnam-1.8/manual/de/110-verschluesselung.mdwn --- obnam-1.7.4/manual/de/110-verschluesselung.mdwn 1970-01-01 00:00:00.000000000 +0000 +++ obnam-1.8/manual/de/110-verschluesselung.mdwn 2014-05-13 07:05:26.000000000 +0000 @@ -0,0 +1,157 @@ +Verschlüsselung nutzen +====================== + +Mit Obnam können Sie Ihre Backups verschlüsseln. Dieses Kapitel beschreibt, warum +und wie Sie das tun. + +Sie sind kein Spion, ist Verschlüsselung da nicht überflüssig? +------------------------------------------------------------------------------- + +Sie sind nicht der einzige, der sich um Ihre Daten sorgt. +Eine Vielzahl von Regierungen, Unternehmen, Kriminellen und allzu +neugierigen Schnüfflern sind wahrscheinlich ebenfalls interessiert +(... und es ist manchmal schwer, diese auseinander zu halten). Sie könnten +Beweise gegen Sie (er)finden wollen, Sie erpressen wollen, oder +einfach nur neugierig darauf sein, was Sie mit Freunden besprechen. + +Sie könnten Ihre Daten aus statistischen Gründen interessant finden +und überhaupt kein Interesse an Ihnen persönlich haben. +Oder sie könnten ausschließlich an Ihnen interessiert sein. + +Statt Ihre Dateien und eMails zu lesen oder Ihre Fotos +und Videos anzusehen könnten sie Interesse daran haben, Ihnen den Zugang +dazu zu versperren, oder einfach Ihre Daten ganz zerstören. Sie könnten sogar +Ihre Daten korrumpieren, indem sie Kinderpornographie in Ihr Foto-Archiv ablegen. + +Sie schützen Ihren Computer so gut Sie können damit diese und andere +schlimme Dinge nicht passieren. Ihre Sicherungen sollten Sie mit der gleichen Sorgfalt behandeln. + +Wenn Sie auf ein USB-Laufwerk sichern, sollten Sie das Laufwerk verschlüsseln, +genau wie auch Online-Speicher. Es gibt viele Arten von Verschlüsselung und +ich bin nicht qualifiziert Ihnen Rat zu geben, aber jegliche gängige, +moderne Verschlüsselung sollte ausreichen -- außer für besonders entschlossene Angreifer. + +Anstatt oder zusätzlich zur Verschlüsselung können Sie den physischen Zugang +zu Ihren Backup-Medien absichern. Lagern Sie Ihr USB-Laufwerk z.B. in einem Safe oder Schließfach. + +Die Vielzahl von Backups die Sie benötigen um sich gegen Erdbeben, +Flutkatastrophen und marodierende Gangs dreirad-fahrender Clowns zu schützen, +sind auch ein guter Schutz gegen Angreifer. Sie können Ihre Live-Daten +und die Backups bei Ihnen zu Hause korrumpieren, aber vermutlich könnten Sie +nicht an das USB-Laufwerk herankommen, das in Beton gegossen an einem geheimen +Ort vergraben ist, den nur Sie kennen. + +Auf der anderen Seite möchten oder müssen Sie vielleicht Anderen +Zugriff auf Ihre gesicherten Daten geben. Wenn Sie zum Beispiel von der Clown +Gang entführt wurden, sollte Ihr Partner in der Lage sein, Ihren +MI6-Verbindungsmann zu kontaktieren, damit der Geheimdienst Sie retten kann. +Den sicheren Zugang zu (Teilen) Ihres Backups herzustellen ist ein interessantes +Problem für sich, für das es verschiedene Lösungen gibt: +Geben Sie Ihrem Partner das Verschlüsselungspasswort, oder geben Sie es einem +Freund dem Sie vertrauen, oder einem Anwalt. Sie könnten auch so etwas wie +[libgfshare] verwenden, um die Schlüssel auf sichere Weise treuhänderisch zu hinterlegen. + +[libgfshare]: http://www.digital-scurf.org/software/libgfshare + +Wie Obnams Verschlüsselung funktioniert +--------------------------------------- + +Ein Obnam Repository enthält mehrere Verzeichnisse für verschiedene +Arten von Daten. + +* Ein Verzeichnis je Client, für Daten, die nur für diesen Client + relevant sind, z.B. Generationen dieses Clients. +* Ein Verzeichnis für die Liste der Clients. +* Ein Verzeichnis für alle chunks, sowie zusätzliche Verzeichnisse, + die für die Deduplizierung von chunks verwendet werden. + +Das Verzeichnis je Client ist verschlüsselt, so dass nur der jeweilige +Client zugreifen kann. Das bedeutet, dass nur der Client selbst seine +Generationen, und die darin enthaltenen Dateien sehen kann. + +Die gemeinsam genutzten Verzeichnisse (Client-Liste, chunks) sind so +verschlüsselt, dass alle Clients sie benutzen können. Dies ermöglicht es +den Clients, chunks gemeinsam zu nutzen, so dass die Deduplizierung über +alle Clients laufen kann. + +Dieses Verschlüsselungsverfahren geht davon aus das alle Clients die +sich ein Repository teilen einander vertrauen und dass es in Ordnung +ist, sämtliche chunks zu lesen, die sie wollen. Zum Beispiel verhindert +die Verschlüsselung nicht, das Geschwister die eMails des anderen aus +dem Repository lesen, aber die Eltern können das nicht, weil ihnen der +geeignete Schlüssel fehlt. + +Zusätzlich zu den für die Client-Verschlüsselungen können Sie +zusätzliche Schlüssel hinzufügen. Diese Schlüssel haben dann ebenfalls +Zugang zum Backup-Repository. Beispielsweise könnte der Schlüssel der +Eltern dem Repository hinzugefügt werden, so dass, wenn es sein muss, +die Eltern Daten der Kinder wiederherstellen können, auch wenn das Kind +seinen eigenen Schlüssel verloren hat. + +In einer Unternehmensumgebung könnte der Schlüssel des Backup- +Administrators hinzugefügt werden. So kann dieser zum Beispiel +die Integrität des Repository prüfen, oder auf Daten eines Mitarbeiters +zugreifen, der im Lotto gewonnen hat und wegen der schlechten +Internet-Verbindung zum Mond nicht verfügbar ist. + +Solche zusätzlichen Schlüssel können entweder für jeden einzelnen Client +oder alle gleichzeitig hinzugefügt werden. + +Verschlüsselung in Obnam einrichten +----------------------------------- + +Obnam benutzt PGP-Schlüssel, genauer gesagt deren GNU Privacy +Guard (GnuPG, gpg) Implementierung. Um verschlüsselte Backups zu erstellen +müssen Sie erst ein PGP-Schlüsselpaar erzeugen. Wie das geht +steht in der [GnuPG Dokumentation] (englisch). + +[GnuPG Dokumentation]: http://www.gnupg.org/documentation/ + +Sobald Sie eine funktionierendes GnuPG-Setup und ein Schlüsselpaar +(bestehend aus einem öffentlichen Schlüssel und einem geheimen +Schlüssel) haben, müssen Sie die Schlüssel-ID finden. Führen Sie +den folgenden Befehl aus und wählen Sie Ihren Schlüssel aus der Liste. + + gpg --list-keys + +Für die restlichen Beispielen dieses Kapitels gehen wir davon aus das +Ihre Key-ID CAFEFACE ist. + +Um von der Verschlüsselung zu profitieren benutzten Sie den `--encrypt-with` +Schalter: + + [config] + encrypt-with = CAFEFACE + +Das ist alles. + +Beachten Sie, dass ein Repository vollständig oder gar nicht +verschlüsselt sein sollte, und dass man nicht hin und her wechseln kann. +Wenn Sie Ihre Meinung ob sie Verschlüsselung benutzen möchten ändern, +müssen Sie mit einem neuen Repository von vorn beginnen. Alle Clients, +die ein Repository teilen müssen verschlüsseln, oder aber keiner von +ihnen. Wenn Sie beides vermischen können verwirrende Fehlermeldungen +erscheinen. + +Obnam verschlüsselt automatisch alle Daten, die es ins Repository +schreibt und entschlüsselt sie wenn nötig. Solange Sie nur ein Schlüssel +für jeden Clientbenutzen, kümmert sich Obnam darum die richtigen +Schlüssel an den richtigen Stellen hinzu zu fügen. + +Prüfen ob ein Repository Verschlüsselung nutzt +---------------------------------------------- + +Es gibt keinen direkten Weg um mit Obnam zu überprüfen, ob ein +Repository Verschlüsselung benutzt. Sie können das jedoch auch manuell +überprüfen: Wenn Ihr Repository die Datei `clientlist/key` enthält, wird +das Repository verschlüsselt. + +FIXME: Verwalten der Schlüssel in einem Repository +---------------------------------------- + +In diesem Abschnitt wird erläutert, wie Sie die Schlüssel in einem +Repository verwalten: Wie Sie zusätzliche Schlüssel für jedes Toplevel +hinzufügen und wie Sie die Schlüssel eines Client ändern. Es zeigt auch +wie Sie prüfen können, welche Schlüssel verwendet werden, und welchen +Zugriff jeder Schlüssel hat. + diff -Nru obnam-1.7.4/manual/de/120-verschiedenes.mdwn obnam-1.8/manual/de/120-verschiedenes.mdwn --- obnam-1.7.4/manual/de/120-verschiedenes.mdwn 1970-01-01 00:00:00.000000000 +0000 +++ obnam-1.8/manual/de/120-verschiedenes.mdwn 2014-05-13 07:05:26.000000000 +0000 @@ -0,0 +1,6 @@ +Verschiedenes +============= + +Dieses Kapitel behandelt Themen die kein eigenes Kapitel füllen, +z.B. das Komprimieren von Backups und wie Obnam von cron ausgeführt wird. + diff -Nru obnam-1.7.4/manual/de/130-fallbeispiele.mdwn obnam-1.8/manual/de/130-fallbeispiele.mdwn --- obnam-1.7.4/manual/de/130-fallbeispiele.mdwn 1970-01-01 00:00:00.000000000 +0000 +++ obnam-1.8/manual/de/130-fallbeispiele.mdwn 2014-05-13 07:05:26.000000000 +0000 @@ -0,0 +1,19 @@ +Fallbeispiele +============= + +In diesem Kapitel gehen wir auf einige typische Anwendungsfälle für +Backups ein. Wir besprechen welche Daten gesichert werden und erklären +wie Sie eine passende Backup-Strategie, ein passendes Medium usw. auswählen. + +Einige Fallbeispiele: + +* Einzelner Laptop-Benutzer, typische Daten wie Dokumente, Fotos, Musik, + Backup auf eine USB-Festplatte. +* Ein VPS oder ähnlicher Server mit Web-Seiten, eMails und vielleicht anderen + Daten, Backup auf einen anderen Server. +* Ein kleines Unternehmen mit einer Reihe von Laptops und Desktop-PCs, einem + lokalen Fileserver, ein gemieteter Server bei einem Provider, + Backup auf einem gemieteten Server beim Provider und auf mehrere große + USB-Laufwerke die durchgetauscht werden. +* Wiederherstellung eines Offsite-Backup nachdem alle lokalen Computern und + Speichermedien zerstört wurden. diff -Nru obnam-1.7.4/manual/de/140-fehlersuche.mdwn obnam-1.8/manual/de/140-fehlersuche.mdwn --- obnam-1.7.4/manual/de/140-fehlersuche.mdwn 1970-01-01 00:00:00.000000000 +0000 +++ obnam-1.8/manual/de/140-fehlersuche.mdwn 2014-05-13 07:05:26.000000000 +0000 @@ -0,0 +1,7 @@ +Fehlersuche +=========== + +Dieses Kapitel beschreibt, wie Sie Probleme mit Obnam debuggen. Es umfasst +Dinge wie Log-Dateien, verschiedene Ebenen der Protokollierung und Ablaufverfolgung und +häufige Probleme bei der Benutzung von Obnam. Es erklärt auch, welche +Dinge wo ein einem Obnam Backup-Repository landen. diff -Nru obnam-1.7.4/manual/de/150-konfiguration.mdwn obnam-1.8/manual/de/150-konfiguration.mdwn --- obnam-1.7.4/manual/de/150-konfiguration.mdwn 1970-01-01 00:00:00.000000000 +0000 +++ obnam-1.8/manual/de/150-konfiguration.mdwn 2014-05-13 07:05:26.000000000 +0000 @@ -0,0 +1,134 @@ +Obnam Konfigurationsdateien und Einstellungen +============================================= + +In diesem Kapitel geht es um Obnams Konfigurationsdateien: Wo sie sind, was +sie enthalten, und wie sie verwendet werden. + +Wo ist meine Konfiguration? +--------------------------- + +Obnam sucht seine Konfigurationsdatei an folgenden Orten: + +* `/etc/obnam.conf` +* `/etc/obnam/*.conf` +* `~/.obnam.conf` +* `~/.config/obnam/*.conf` + +In `/etc/obnam` und `~/.config/obnam` werden alle Dateien mit dem +Suffix `.conf` in "asciibetischer" Reihenfolge geladen. Das ist ähnlich +wie alphabetisch, basiert aber auf dem Zeichencode und nicht auf dem, +was die Leute denken. Im Gegensatz zu alphabetisch ist das sprachunabhängig. + +Alle Dateien der obigen Liste können existieren (oder auch nicht). Wenn +eine Datei vorhanden ist wird sie gelesen, dann die nächste Datei, und +so weiter. Eine Einstellung in einer Datei wird durch eine spätere Datei +überschrieben, wenn auch dort die Option eingestellt ist. Zum +Beispiel könnte `/etc /obnam.conf` den `log-level` auf `INFO` setzen, +aber `~ /.obnam.conf` setzt ihn dann auf `DEBUG`, weil der Benutzer +detailiertere Log-Dateien wünscht. + +Die Obnam Konfigurationsdateien in `/ etc` gelten für alle, die Obnam +auf dieser Maschine benutzen. Das ist wichtig: Sie gelten nicht nur für +`root`. + +Wenn Sie mehrere Konfigurationen für Obnam haben möchten, um zum +Beispiel verschiedene Backup-Repositories zu nutzen, müssen Sie die +Dateien so ablegen oder benennen, das sie nicht zur Liste oben passen. +Zum Beispiel: + +* `/etc/obnam/system-backup.profile` +* `~/.config/obnam/online.profile` +* `~/.config/obnam/usbdrive.profile` + +Bei der Ausführung von Obnam müssen Sie dann nur noch +das File angeben, mit dessen Konfiguration gearbeitet werden soll: + + obnam --config ~/.config/obnam/usbdrive.profile` + +Sollten Sie außerdem wünschen das Obnam sämtliche Standard-Konfigurationsdateien +ignoriert, müssen Sie die Option `--no-default-config` mitgeben: + + obnam --no-default-config --config ~/.obnam-is-fun.conf + +Optionen die auf der Kommandozeile geben werden, überschreiben Werte die aus +Konfigurationsdateien geladen wurden. + +Syntax der Konfigurationsdateien +-------------------------------- + +Obnam Konfigurationsdateien verwenden die [INI-Datei] Syntax, +genauer gesagt die Variante, die von der Python [ConfigParser] Bibliothek implementiert wird. + +Sie sehen so aus: + + [config] + log-level = debug + log = /var/log/obnam.log + encrypt-with = CAFEBEEF + root = / + one-file-system = yes + +[INI-Datei]: https://de.wikipedia.org/wiki/Initialisierungsdatei +[ConfigParser]: http://docs.python.org/2/library/configparser.html + +Die Namen der Konfigurationsvariablen sind die gleichen wie die +entsprechenden Befehlszeilenoptionen. Wenn `--foo` die +Befehlszeilenoption ist, dann ist die Variable in der Datei `foo`. Jede +Kommandozeilen-Option `- foo = bar` kann in einer Konfigurationsdatei +als `foo = bar` verwendet werden. Es gibt einige Ausnahmen (`-- +no-default-config`, `--config`, `--help` und ein paar andere), aber die +würden Sie sowieso nicht in einer Konfigurationsdatei setzen. + +Jede Option oder Einstellung hat einen Typ. Meist ist dies nicht +relevant, es sei denn, Sie geben ihr einen Wert der ungeeignet ist. Die +beiden wichtigsten Ausnahmen sind: + +* Boolean bzw. ja/nein oder an/aus + Zum Beispiel ist `--exclude-caches` eine Option, die entweder an ist + (wenn die Option benutzt wird) oder aus ist (wenn sie nicht benutzt wird). + Für jede Option `--foo` gibt es auch eine Option `--no-foo`. In + Konfigurationsdateien wird `foo` durch setzen auf `yes` oder `true` + eingeschaltet, und durch `no` oder `false` abgeschaltet. + +* Einige Optionen können eine Werteliste aufnehmen, zum Beispiel + `--exclude`. Sie können `--exclude` verwenden so oft Sie wollen, + jedes Mal wird ein neuer Ausschluss hinzugefügt, statt den vorherigen + zu ersetzen. In einer Konfigurationsdatei trennen Sie die Werte mit + Komma und schreiben Sie hintereinander, z.B.: `exclude = foo, bar, + baz`. Durch später geladenen Konfigurationsdateien wird die gesamte + Werteliste ersetzt, anstatt hinzugefügt. + +Eine genauere Erklärung der Syntax finden Sie in der **cliapp**(5) +manpage Ihres Systems oder im WWW [cliapp man page]. + +[cliapp man page]: http://liw.fi/cliapp/cliapp.5.txt + +Meine Konfiguration prüfen +-------------------------- + +Weil Obnam seine Konfigurationsdaten von mehreren Stellen bezieht, +kann es schwierig sein herauszufinden welche Optionen nun wirklich +Anwendung finden. Die Option `--dump-config` hilft dabei. + + obnam --config ~/.obnam.fun --exclude-caches --dump-config + +Diese Option liest alle Konfigurationsdateien und gibt eine Zusammenfassung +auf stdout aus, die jede Einstellung enthält, als wäre `--dump-config` +nicht benutzt worden. + +So können Sie schnell die Einstellungen überprüfen. Außerdem ist das ein +guter Anfang wenn Sie mal eine Konfigurationsdatei von Hand neu erstellen +möchten. + +Alle Konfigurationseinstellungen herausfinden +--------------------------------------------- + +Diese Anleitung beinhaltet und erklärt noch nicht alle Einstellungen. +Obnam bietet aber eine integrierte Hilfe (`obnam - help`) und eine +manpage, die automatisch aus der integrierten Hilfe erzeugt wird (`man +obnam` oder siehe [obnam man page]). Eines Tages wird dieses Kapitel +einen automatisch generierten Abschnitt enthalten der jede Einstellung +erklärt. Bis dahin dürfen Sie gern mit dem Finger auf Obnams Autor +zeigen und über seine Faulheit kichern. + +[obnam man page]: http://liw.fi/obnam/obnam.1.txt diff -Nru obnam-1.7.4/manual/de/900-siehe-auch.mdwn obnam-1.8/manual/de/900-siehe-auch.mdwn --- obnam-1.7.4/manual/de/900-siehe-auch.mdwn 1970-01-01 00:00:00.000000000 +0000 +++ obnam-1.8/manual/de/900-siehe-auch.mdwn 2014-05-13 07:05:26.000000000 +0000 @@ -0,0 +1,14 @@ +Siehe auch... +============= + +Dieses Kapitel gibt Hinweise auf weitere Informationen über Obnam, Backups, +und verwandte Dinge. Zur Zeit ist dies eine sehr kurze Liste, aber +Vorschläge zu Dinden, die hinzugefügt werden könnnen akzeptieren wir gern. + +* Obnam Homepage (englisch): . + - Kurze Tutorials, Download-Links, eine FAQ, Kontaktinformationen usw. +* Lars Wirzenius, interessante Blog-Tags. + - + - +* Cache directory tag Standard: + - Ein Programm, um die Tag-Dateien zu verwalten diff -Nru obnam-1.7.4/manual/de/980-juristenzeug.mdwn obnam-1.8/manual/de/980-juristenzeug.mdwn --- obnam-1.7.4/manual/de/980-juristenzeug.mdwn 1970-01-01 00:00:00.000000000 +0000 +++ obnam-1.8/manual/de/980-juristenzeug.mdwn 2014-05-13 07:05:26.000000000 +0000 @@ -0,0 +1,34 @@ +Rechtliches +=========== + +Dieses Werk steht unter der GNU General Public License Version 3 +oder,je nach Vorliebe, einer beliebigen neueren Version. + +> Copyright 2010-2013 Lars Wirzenius +> +> This program is free software: you can redistribute it and/or modify +> it under the terms of the GNU General Public License as published by +> the Free Software Foundation, either version 3 of the License, or +> (at your option) any later version. +> +> This program is distributed in the hope that it will be useful, +> but WITHOUT ANY WARRANTY; without even the implied warranty of +> MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +> GNU General Public License for more details. +> +> You should have received a copy of the GNU General Public License +> along with this program. If not, see . + +Eine Kopie der GPL liegt in Form der Datei `COPYING` dem Quellcode +bei und kann zusätzlich unter der oben angegebenen URL +abgerufen werden. + +Dieses Handbuch (alle Inhalte des Verzeichnisses `manual` in den Quellen) +steht zusätzlich unter der Creative Commons Attribution 4.0 International +Lizenz. Sie können sich aussuchen ob Sie die GPL or die CC-Licenz +für das Handbuch anwenden. + +Eine Kopie der Creative Commons Lizenz liegt in Form der Datei +`CC-BY-SA-4.0.txt` den Quellen bei und kann zusätzlich online +unter folgender URL eingesehen werden (Englisch): + diff -Nru obnam-1.7.4/manual/de/990-klappentext.mdwn obnam-1.8/manual/de/990-klappentext.mdwn --- obnam-1.7.4/manual/de/990-klappentext.mdwn 1970-01-01 00:00:00.000000000 +0000 +++ obnam-1.8/manual/de/990-klappentext.mdwn 2014-05-13 07:05:26.000000000 +0000 @@ -0,0 +1,29 @@ +Die Entwicklung von Obnam unterstützen +====================================== + +Obnam ist freie Software: Sie erhalten vollen Zugriff auf den +Quellcode, können die Software so modifizieren wie Sie wollen, +und Sie können Kopien der Software in ihrer ursprünglichen +oder geänderten Form verteilen. Obnam ist auch komplett kostenlos. + +Eines der Ziele von Obnam ist es sicherzustellen, dass jeder +Zugang zu einer vernünftigen Backup-Software hat, ohne +jemand anderem dafür verpflichtet zu sein. Sie können Obnam +verwenden und Ihre Backups überall dort speichern wo es +Ihnen passt, die Obnam Entwickler haben dazu nichts zu sagen. + +Allerdings erfordert die Entwicklung von Obnam einige Ressourcen. +Obnam wird in erster Linie von Lars Wirzenius, seinem +ursprünglichen Autor, in seiner Freizeit entwickelt (Hi!). +Wenn Sie helfen möchten die Entwicklung zu unterstützen, +finden Sie hier eine Liste der Dinge, die Sie tun können: + +* Senden Sie Korrekturen und Verbesserungen, entweder + am Code oder der Dokumentation. +* Spenden Sie etwas an den Autor. Anregungen finden Sie + unter +* Beauftragen Sie den Autor mit der Obnam-Weiterentwicklung. + Treten Sie dazu mit ihm privat in Kontakt (liw@liw.fi). + +Beachten Sie, dass all dies optional ist. Wenn Sie Obnam +einfach benutzen und glücklich dabei sind, ist das völlig OK. diff -Nru obnam-1.7.4/manual/en/020-concepts.mdwn obnam-1.8/manual/en/020-concepts.mdwn --- obnam-1.7.4/manual/en/020-concepts.mdwn 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/manual/en/020-concepts.mdwn 2014-05-13 07:05:26.000000000 +0000 @@ -289,5 +289,5 @@ data is safe even if the dinosaurs return in space ships to re-take world now that the ice age is over * **verification**: making sure a backup system works and that data - actually an be restored from backups and that the backups have not + actually can be restored from backups and that the backups have not become corrupted diff -Nru obnam-1.7.4/manual/en/040-installing.mdwn obnam-1.8/manual/en/040-installing.mdwn --- obnam-1.7.4/manual/en/040-installing.mdwn 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/manual/en/040-installing.mdwn 2014-05-13 07:05:26.000000000 +0000 @@ -2,7 +2,7 @@ ================ This chapter explains how to install Obnam. It is not a very extensive -set of instructions, yet. In particular, itreally only caters to +set of instructions, yet. In particular, it really only caters to Debian users. Instructions for other systems would be very much welcome. Debian diff -Nru obnam-1.7.4/manual/format-de-html obnam-1.8/manual/format-de-html --- obnam-1.7.4/manual/format-de-html 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/manual/format-de-html 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,21 @@ #!/bin/sh +# Copyright 2014 Lars Wirzenius +# Copyright 2014 Jan Niggemann +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# =*= License: GPL-3+ =*= set -eu diff -Nru obnam-1.7.4/manual/format-de-pdf obnam-1.8/manual/format-de-pdf --- obnam-1.7.4/manual/format-de-pdf 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/manual/format-de-pdf 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,21 @@ #!/bin/sh +# Copyright 2014 Lars Wirzenius +# Copyright 2014 Jan Niggemann +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# =*= License: GPL-3+ =*= set -eu @@ -16,5 +33,6 @@ % $VERSION EOF -pandoc --smart --toc --chapters --number-sections -V lang=german -V geometry:a4paper -o "$OUTPUT" 000.mdwn "$@" +pandoc --smart --toc --chapters --number-sections \ + -V lang=german -V geometry:a4paper -o "$OUTPUT" 000.mdwn "$@" rm -f 000.mdwn diff -Nru obnam-1.7.4/manual/format-html obnam-1.8/manual/format-html --- obnam-1.7.4/manual/format-html 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/manual/format-html 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,20 @@ #!/bin/sh +# Copyright 2014 Lars Wirzenius +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# =*= License: GPL-3+ =*= set -eu diff -Nru obnam-1.7.4/manual/format-pdf obnam-1.8/manual/format-pdf --- obnam-1.7.4/manual/format-pdf 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/manual/format-pdf 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,20 @@ #!/bin/sh +# Copyright 2014 Lars Wirzenius +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# =*= License: GPL-3+ =*= set -eu diff -Nru obnam-1.7.4/manual/Makefile obnam-1.8/manual/Makefile --- obnam-1.7.4/manual/Makefile 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/manual/Makefile 2014-05-13 07:05:26.000000000 +0000 @@ -1,3 +1,21 @@ +# Copyright 2014 Lars Wirzenius +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# =*= License: GPL-3+ =*= + + TITLE_EN = Backups with Obnam TITLE_DE = Backups mit Obnam TRANS_DE = Übersetzung: Jan Niggemann (jn@hz6.de) @@ -6,7 +24,11 @@ en_sources = $(shell ls en/*.mdwn) de_sources = $(shell ls de/*.mdwn) -outputs = obnam-manual.en.pdf obnam-manual.en.html obnam-manual.de.pdf obnam-manual.de.html +outputs = \ + obnam-manual.en.pdf \ + obnam-manual.en.html \ + obnam-manual.de.pdf \ + obnam-manual.de.html all: $(outputs) @@ -17,10 +39,14 @@ ./format-html $@ "$(TITLE_EN)" "$(AUTHOR)" "$(VERSION)" $(en_sources) obnam-manual.de.pdf: Makefile $(de_sources) - ./format-de-pdf $@ "$(TITLE_DE)" "$(AUTHOR)" "$(VERSION)" "$(TRANS_DE)" $(de_sources) + ./format-de-pdf $@ \ + "$(TITLE_DE)" "$(AUTHOR)" "$(VERSION)" "$(TRANS_DE)" \ + $(de_sources) obnam-manual.de.html: Makefile $(de_sources) ../obnam.css - ./format-de-html $@ "$(TITLE_DE)" "$(AUTHOR)" "$(VERSION)" "$(TRANS_DE)" $(de_sources) + ./format-de-html $@ \ + "$(TITLE_DE)" "$(AUTHOR)" "$(VERSION)" "$(TRANS_DE)" \ + $(de_sources) clean: rm -f $(outputs) en/000.mdwn diff -Nru obnam-1.7.4/meliae-show obnam-1.8/meliae-show --- obnam-1.7.4/meliae-show 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/meliae-show 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,20 @@ #!/usr/bin/env python +# Copyright 2014 Lars Wirzenius +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# =*= License: GPL-3+ =*= from meliae import loader diff -Nru obnam-1.7.4/metadata-speed obnam-1.8/metadata-speed --- obnam-1.7.4/metadata-speed 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/metadata-speed 2014-05-13 07:05:26.000000000 +0000 @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright 2010 Lars Wirzenius +# Copyright 2010-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/mkfunnyfarm obnam-1.8/mkfunnyfarm --- obnam-1.7.4/mkfunnyfarm 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/mkfunnyfarm 2014-05-13 07:05:26.000000000 +0000 @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright 2013 Lars Wirzenius +# Copyright 2013-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/mksparse obnam-1.8/mksparse --- obnam-1.7.4/mksparse 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/mksparse 2014-05-13 07:05:26.000000000 +0000 @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright 2013 Lars Wirzenius +# Copyright 2013-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/NEWS obnam-1.8/NEWS --- obnam-1.7.4/NEWS 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/NEWS 2014-05-13 07:05:26.000000000 +0000 @@ -3,6 +3,56 @@ This file summarizes changes between releases of Obnam. +Version 1.8, released 2014-05-13 +-------------------------------- + +* The error message has been improved for when setting metadata + (owner, permission, and similar) of a restored file fails. + +* `obnam force-lock` now works even when the client running it is not + in the client list. + +Security issues: + +* Joey Hess found a problem in `obnam restore`: restored files would + be created with quite liberal default permissions, which would be + set to the backed-up permissions later. This could allow a snooper + to read files they shouldn't be. This has been fixed now by using + restrictive default permissions. A workaround for older versions is + to create a directory, set its permissions to 0700, and restore to a + subdirectory of that directory. + +Bug fixes: + +* `--help` output no longer shows the default value of any options. It + was shown only for a few options anyway. The proper way to see the + current settings is with the `--dump-config` option. The bug that + was fixed that the generated manual page no longer contains values + that are specific to the machine doing the generation, such as the + hostname as the default value for `--client-name`. Reported by + SanskritFritz. + +* When a file was backed up, and later excluded with `--exclude`, + Obnam wouldn't remove it from the new backups. Now it does. Bug + fixed by Anssi Hannula, though his patch got changed because it no + longer applied. + +* When restoring extended attributes _not_ in the user namespace + (named like `user.foo`) Obnam now ignores them, instead of trying to + set them and crashing. + +* When restoring from a directory that is not a repository, the + error message is now clearer. + +* Obnam would previously allow the backup root to be a symbolic link + pointing at a directory. However, this only worked for backups. No + other operations would work and would only see the symbolic link, + not the directory it pointed at. Obnam now gives an error message + even for the backup. + +* Obnam no longer excludes files named `syslog` or `none`, if the + setting `--log=none` or `--log=syslog` is used. + Version 1.7.4, released 2014-03-31 -------------------------------- diff -Nru obnam-1.7.4/nitpicker obnam-1.8/nitpicker --- obnam-1.7.4/nitpicker 1970-01-01 00:00:00.000000000 +0000 +++ obnam-1.8/nitpicker 2014-05-13 07:05:26.000000000 +0000 @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# Copyright 2014 Lars Wirzenius +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# =*= License: GPL-3+ =*= + + +import sys + +import cliapp + +import obnamlib + + +class TabCharacter(obnamlib.ObnamError): + + msg = 'TAB character found: {line}' + + +class LongLine(obnamlib.ObnamError): + + msg = 'long line ({length} chars after TAB expansion): {line}' + + +class SourceCodeNitpicker(cliapp.Application): + + def setup(self): + self.errors = False + + def cleanup(self): + if self.errors: + sys.exit(1) + + def process_input_line(self, filename, line): + line = line.rstrip('\n') + + self.check_for_tabs(filename, line) + self.check_for_long_line (filename, line) + + def check_for_tabs(self, filename, line): + if not filename.endswith('Makefile'): + if '\t' in line: + self.error(filename, self.lineno, TabCharacter(line=line)) + + def check_for_long_line(self, filename, line): + expanded = line.expandtabs() + if len(expanded) > 79: + self.error( + filename, self.lineno, + LongLine(line=line, length=len(expanded))) + + def error(self, filename, lineno, error): + self.output.write( + '%s:%s: %s\n' % (filename, lineno, str(error))) + self.errors = True + + +SourceCodeNitpicker().run() diff -Nru obnam-1.7.4/obnam obnam-1.8/obnam --- obnam-1.7.4/obnam 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnam 2014-05-13 07:05:26.000000000 +0000 @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (C) 2009 Lars Wirzenius +# Copyright (C) 2009-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnam.1.in obnam-1.8/obnam.1.in --- obnam-1.7.4/obnam.1.in 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnam.1.in 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -.\" Copyright 2010-2013 Lars Wirzenius +.\" Copyright 2010-2014 Lars Wirzenius .\" .\" This program is free software: you can redistribute it and/or modify .\" it under the terms of the GNU General Public License as published by @@ -181,7 +181,8 @@ to deal with files that have been changed or renamed since the previous backup run. It also allows several backup clients to avoid uploading the same data. -If, for example, everyone in the office has a copy of the same sales brochures, +If, +for example, everyone in the office has a copy of the same sales brochures, only one copy needs to be stored in the backup repository. .PP Every backup run is a @@ -431,8 +432,9 @@ .PP The long names of options are used as keys for configuration variables. -Any setting that can be set from the command line can be set in a configuration -file, in the +Any setting that can be set from the command line +can be set in a configuration file, +in the .I [config] section. .PP diff -Nru obnam-1.7.4/obnamlib/app.py obnam-1.8/obnamlib/app.py --- obnam-1.7.4/obnamlib/app.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/app.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2011 Lars Wirzenius +# Copyright (C) 2009-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -43,62 +43,23 @@ '''Main program for backup program.''' def add_settings(self): - devel_group = obnamlib.option_group['devel'] - perf_group = obnamlib.option_group['perf'] - self.settings.string(['repository', 'r'], 'name of backup repository') + # General settings. - self.settings.string(['client-name'], 'name of client (%default)', - default=self.deduce_client_name()) - - self.settings.bytesize(['node-size'], - 'size of B-tree nodes on disk; only affects new ' - 'B-trees so you may need to delete a client ' - 'or repository to change this for existing ' - 'repositories ' - '(default: %default)', - default=obnamlib.DEFAULT_NODE_SIZE, - group=perf_group) - - self.settings.bytesize(['chunk-size'], - 'size of chunks of file data backed up ' - '(default: %default)', - default=obnamlib.DEFAULT_CHUNK_SIZE, - group=perf_group) - - self.settings.bytesize(['upload-queue-size'], - 'length of upload queue for B-tree nodes ' - '(default: %default)', - default=obnamlib.DEFAULT_UPLOAD_QUEUE_SIZE, - group=perf_group) - - self.settings.bytesize(['lru-size'], - 'size of LRU cache for B-tree nodes ' - '(default: %default)', - default=obnamlib.DEFAULT_LRU_SIZE, - group=perf_group) - - self.settings.string_list(['trace'], - 'add to filename patters for which trace ' - 'debugging logging happens') - - - self.settings.integer(['idpath-depth'], - 'depth of chunk id mapping', - default=obnamlib.IDPATH_DEPTH, - group=perf_group) - self.settings.integer(['idpath-bits'], - 'chunk id level size', - default=obnamlib.IDPATH_BITS, - group=perf_group) - self.settings.integer(['idpath-skip'], - 'chunk id mapping lowest bits skip', - default=obnamlib.IDPATH_SKIP, - group=perf_group) + self.settings.string( + ['repository', 'r'], + 'name of backup repository', + metavar='URL') + + self.settings.string( + ['client-name'], + 'name of client (defaults to hostname)', + default=self.deduce_client_name()) self.settings.boolean( ['quiet'], 'be silent: show only error messages, no progress updates') + self.settings.boolean( ['verbose'], 'be verbose: tell the user more of what is going on and ' @@ -110,32 +71,95 @@ 'getting distracted by so many updates that they will move ' 'into the Gobi desert to live under a rock') - self.settings.boolean(['pretend', 'dry-run', 'no-act'], - 'do not actually change anything (works with ' - 'backup, forget and restore only, and may only ' - 'simulate approximately real behavior)') - - self.settings.string(['pretend-time'], - 'pretend it is TIMESTAMP (YYYY-MM-DD HH:MM:SS); ' - 'this is only useful for testing purposes', - metavar='TIMESTAMP', - group=devel_group) - - self.settings.integer(['lock-timeout'], - 'when locking in the backup repository, ' - 'wait TIMEOUT seconds for an existing lock ' - 'to go away before giving up', - metavar='TIMEOUT', - default=60) - - self.settings.integer(['crash-limit'], - 'artificially crash the program after COUNTER ' - 'files written to the repository; this is ' - 'useful for crash testing the application, ' - 'and should not be enabled for real use; ' - 'set to 0 to disable (disabled by default)', - metavar='COUNTER', - group=devel_group) + self.settings.boolean( + ['pretend', 'dry-run', 'no-act'], + 'do not actually change anything (works with ' + 'backup, forget and restore only, and may only ' + 'simulate approximately real behavior)') + + self.settings.integer( + ['lock-timeout'], + 'when locking in the backup repository, ' + 'wait TIMEOUT seconds for an existing lock ' + 'to go away before giving up', + metavar='TIMEOUT', + default=60) + + # Performance related settings. + + perf_group = obnamlib.option_group['perf'] + + self.settings.bytesize( + ['node-size'], + 'size of B-tree nodes on disk; only affects new ' + 'B-trees so you may need to delete a client ' + 'or repository to change this for existing ' + 'repositories', + default=obnamlib.DEFAULT_NODE_SIZE, + group=perf_group) + + self.settings.bytesize( + ['chunk-size'], + 'size of chunks of file data backed up', + default=obnamlib.DEFAULT_CHUNK_SIZE, + group=perf_group) + + self.settings.bytesize( + ['upload-queue-size'], + 'length of upload queue for B-tree nodes', + default=obnamlib.DEFAULT_UPLOAD_QUEUE_SIZE, + group=perf_group) + + self.settings.bytesize( + ['lru-size'], + 'size of LRU cache for B-tree nodes', + default=obnamlib.DEFAULT_LRU_SIZE, + group=perf_group) + + self.settings.integer( + ['idpath-depth'], + 'depth of chunk id mapping', + default=obnamlib.IDPATH_DEPTH, + group=perf_group) + + self.settings.integer( + ['idpath-bits'], + 'chunk id level size', + default=obnamlib.IDPATH_BITS, + group=perf_group) + + self.settings.integer( + ['idpath-skip'], + 'chunk id mapping lowest bits skip', + default=obnamlib.IDPATH_SKIP, + group=perf_group) + + # Settings to help developers and development of Obnam. + + devel_group = obnamlib.option_group['devel'] + + self.settings.string_list( + ['trace'], + 'add to filename patters for which trace ' + 'debugging logging happens', + group=devel_group) + + self.settings.string( + ['pretend-time'], + 'pretend it is TIMESTAMP (YYYY-MM-DD HH:MM:SS); ' + 'this is only useful for testing purposes', + metavar='TIMESTAMP', + group=devel_group) + + self.settings.integer( + ['crash-limit'], + 'artificially crash the program after COUNTER ' + 'files written to the repository; this is ' + 'useful for crash testing the application, ' + 'and should not be enabled for real use; ' + 'set to 0 to disable (disabled by default)', + metavar='COUNTER', + group=devel_group) # The following needs to be done here, because it needs # to be done before option processing. This is a bit ugly, @@ -145,19 +169,11 @@ self.setup_ttystatus() - self.pm = obnamlib.PluginManager() - self.pm.locations = [self.plugins_dir()] - self.pm.plugin_arguments = (self,) - self.fsf = obnamlib.VfsFactory() self.repo_factory = obnamlib.RepositoryFactory() self.setup_hooks() - self.pm.load_plugins() - self.pm.enable_plugins() - self.hooks.call('plugins-loaded') - self.settings['log-level'] = 'info' def deduce_client_name(self): @@ -165,15 +181,14 @@ def setup_hooks(self): self.hooks = obnamlib.HookManager() - self.hooks.new('plugins-loaded') self.hooks.new('config-loaded') self.hooks.new('shutdown') # The repository factory creates all repository related hooks. self.repo_factory.setup_hooks(self.hooks) - def plugins_dir(self): - return os.path.join(os.path.dirname(obnamlib.__file__), 'plugins') + def setup(self): + self.pluginmgr.plugin_arguments = (self,) def process_args(self, args): try: diff -Nru obnam-1.7.4/obnamlib/encryption.py obnam-1.8/obnamlib/encryption.py --- obnam-1.7.4/obnamlib/encryption.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/encryption.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright 2011 Lars Wirzenius +# Copyright 2011-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnamlib/encryption_tests.py obnam-1.8/obnamlib/encryption_tests.py --- obnam-1.7.4/obnamlib/encryption_tests.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/encryption_tests.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright 2011 Lars Wirzenius +# Copyright 2011-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -174,15 +174,27 @@ class PublicKeyEncryptionTests(unittest.TestCase): + def setUp(self): + self.dirname = tempfile.mkdtemp() + self.gpghome = os.path.join(self.dirname, 'gpghome') + shutil.copytree('test-gpghome', self.gpghome) + self.keyid = '1B321347' + + def tearDown(self): + shutil.rmtree(self.dirname) + def test_roundtrip_works(self): cleartext = 'hello, world' passphrase = 'password1' - keyring = obnamlib.Keyring(cat('test-gpghome/pubring.gpg')) - seckeys = obnamlib.SecretKeyring(cat('test-gpghome/secring.gpg')) + pubring = os.path.join(self.gpghome, 'pubring.gpg') + secring = os.path.join(self.gpghome, 'secring.gpg') + + keyring = obnamlib.Keyring(cat(pubring)) + seckeys = obnamlib.SecretKeyring(cat(secring)) encrypted = obnamlib.encrypt_with_keyring(cleartext, keyring) - decrypted = obnamlib.decrypt_with_secret_keys(encrypted, - gpghome='test-gpghome') + decrypted = obnamlib.decrypt_with_secret_keys( + encrypted, gpghome=self.gpghome) self.assertEqual(decrypted, cleartext) diff -Nru obnam-1.7.4/obnamlib/fmt_6/checksumtree.py obnam-1.8/obnamlib/fmt_6/checksumtree.py --- obnam-1.7.4/obnamlib/fmt_6/checksumtree.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/fmt_6/checksumtree.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright 2010 Lars Wirzenius +# Copyright 2010-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnamlib/fmt_6/checksumtree_tests.py obnam-1.8/obnamlib/fmt_6/checksumtree_tests.py --- obnam-1.7.4/obnamlib/fmt_6/checksumtree_tests.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/fmt_6/checksumtree_tests.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright 2010 Lars Wirzenius +# Copyright 2010-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnamlib/fmt_6/chunklist.py obnam-1.8/obnamlib/fmt_6/chunklist.py --- obnam-1.7.4/obnamlib/fmt_6/chunklist.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/fmt_6/chunklist.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright 2010 Lars Wirzenius +# Copyright 2010-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -37,9 +37,9 @@ tracing.trace('new ChunkList') self.fmt = '!Q' self.key_bytes = struct.calcsize(self.fmt) - obnamlib.RepositoryTree.__init__(self, fs, 'chunklist', self.key_bytes, - node_size, upload_queue_size, - lru_size, hooks) + obnamlib.RepositoryTree.__init__( + self, fs, 'chunklist', self.key_bytes, node_size, + upload_queue_size, lru_size, hooks) self.keep_just_one_tree = True def key(self, chunk_id): diff -Nru obnam-1.7.4/obnamlib/fmt_6/chunklist_tests.py obnam-1.8/obnamlib/fmt_6/chunklist_tests.py --- obnam-1.7.4/obnamlib/fmt_6/chunklist_tests.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/fmt_6/chunklist_tests.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright 2010 Lars Wirzenius +# Copyright 2010-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnamlib/fmt_6/clientlist.py obnam-1.8/obnamlib/fmt_6/clientlist.py --- obnam-1.7.4/obnamlib/fmt_6/clientlist.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/fmt_6/clientlist.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright 2010 Lars Wirzenius +# Copyright 2010-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnamlib/fmt_6/clientlist_tests.py obnam-1.8/obnamlib/fmt_6/clientlist_tests.py --- obnam-1.7.4/obnamlib/fmt_6/clientlist_tests.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/fmt_6/clientlist_tests.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright 2010 Lars Wirzenius +# Copyright 2010-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnamlib/fmt_6/clientmetadatatree.py obnam-1.8/obnamlib/fmt_6/clientmetadatatree.py --- obnam-1.7.4/obnamlib/fmt_6/clientmetadatatree.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/fmt_6/clientmetadatatree.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright 2010 Lars Wirzenius +# Copyright 2010-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -59,7 +59,7 @@ GEN_STARTED = 1 # subkey type for when generation was started GEN_ENDED = 2 # subkey type for when generation was ended GEN_IS_CHECKPOINT = 3 # subkey type for whether generation is checkpoint - GEN_FILE_COUNT = 4 # subkey type for count of files+dirs in generation + GEN_FILE_COUNT = 4 # subkey type for count of files+dirs in a gen GEN_TOTAL_DATA = 5 # subkey type for sum of all file sizes in gen GEN_TEST_DATA = 6 # subkey type for REPO_GENERATION_TEST_KEY diff -Nru obnam-1.7.4/obnamlib/fmt_6/clientmetadatatree_tests.py obnam-1.8/obnamlib/fmt_6/clientmetadatatree_tests.py --- obnam-1.7.4/obnamlib/fmt_6/clientmetadatatree_tests.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/fmt_6/clientmetadatatree_tests.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright 2010 Lars Wirzenius +# Copyright 2010-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -146,11 +146,10 @@ fs = obnamlib.LocalFS(self.tempdir) self.hooks = obnamlib.HookManager() self.hooks.new('repository-toplevel-init') - self.client = obnamlib.ClientMetadataTree(fs, 'clientid', - obnamlib.DEFAULT_NODE_SIZE, - obnamlib.DEFAULT_UPLOAD_QUEUE_SIZE, - obnamlib.DEFAULT_LRU_SIZE, - self) + self.client = obnamlib.ClientMetadataTree( + fs, 'clientid', obnamlib.DEFAULT_NODE_SIZE, + obnamlib.DEFAULT_UPLOAD_QUEUE_SIZE, obnamlib.DEFAULT_LRU_SIZE, + self) # Force use of filename hash collisions. self.client.default_file_id = self.client._bad_default_file_id self.client.start_generation() @@ -209,19 +208,24 @@ self.client.create('/foo/bar', self.dir_encoded) self.client.create('/foo/bar/baz', self.file_encoded) self.assertEqual(self.client.listdir(self.clientid, '/'), ['foo']) - self.assertEqual(sorted(self.client.listdir(self.clientid, '/foo')), - ['bar', 'foobar']) - self.assertEqual(self.client.listdir(self.clientid, '/foo/bar'), - ['baz']) - self.assertEqual(self.client.get_metadata(self.clientid, '/foo'), - self.dir_encoded) - self.assertEqual(self.client.get_metadata(self.clientid, '/foo/bar'), - self.dir_encoded) - self.assertEqual(self.client.get_metadata(self.clientid, '/foo/foobar'), - self.file_encoded) - self.assertEqual(self.client.get_metadata(self.clientid, - '/foo/bar/baz'), - self.file_encoded) + self.assertEqual( + sorted(self.client.listdir(self.clientid, '/foo')), + ['bar', 'foobar']) + self.assertEqual( + self.client.listdir(self.clientid, '/foo/bar'), + ['baz']) + self.assertEqual( + self.client.get_metadata(self.clientid, '/foo'), + self.dir_encoded) + self.assertEqual( + self.client.get_metadata(self.clientid, '/foo/bar'), + self.dir_encoded) + self.assertEqual( + self.client.get_metadata(self.clientid, '/foo/foobar'), + self.file_encoded) + self.assertEqual( + self.client.get_metadata(self.clientid, '/foo/bar/baz'), + self.file_encoded) def test_removes_directory_and_files_and_subdirs(self): self.client.create('/foo', self.dir_encoded) @@ -240,7 +244,9 @@ self.clientid, '/foo/bar/baz') def test_has_no_file_chunks_initially(self): - self.assertEqual(self.client.get_file_chunks(self.clientid, '/foo'), []) + self.assertEqual( + self.client.get_file_chunks(self.clientid, '/foo'), + []) def test_sets_file_chunks(self): self.client.set_file_chunks('/foo', [1, 2, 3]) diff -Nru obnam-1.7.4/obnamlib/fmt_6/__init__.py obnam-1.8/obnamlib/fmt_6/__init__.py --- obnam-1.7.4/obnamlib/fmt_6/__init__.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/fmt_6/__init__.py 2014-05-13 07:05:26.000000000 +0000 @@ -1 +1,19 @@ +# Copyright 2014 Lars Wirzenius +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# =*= License: GPL-3+ =*= + + import metadata_codec diff -Nru obnam-1.7.4/obnamlib/fmt_6/metadata_codec.py obnam-1.8/obnamlib/fmt_6/metadata_codec.py --- obnam-1.7.4/obnamlib/fmt_6/metadata_codec.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/fmt_6/metadata_codec.py 2014-05-13 07:05:26.000000000 +0000 @@ -66,9 +66,11 @@ logging.error('ERROR: Packing error due to %s' % str(e)) logging.error('ERROR: st_mode=%s' % repr(metadata.st_mode)) logging.error('ERROR: st_mtime_sec=%s' % repr(metadata.st_mtime_sec)) - logging.error('ERROR: st_mtime_nsec=%s' % repr(metadata.st_mtime_nsec)) + logging.error( + 'ERROR: st_mtime_nsec=%s' % repr(metadata.st_mtime_nsec)) logging.error('ERROR: st_atime_sec=%s' % repr(metadata.st_atime_sec)) - logging.error('ERROR: st_atime_nsec=%s' % repr(metadata.st_atime_nsec)) + logging.error( + 'ERROR: st_atime_nsec=%s' % repr(metadata.st_atime_nsec)) logging.error('ERROR: st_nlink=%s' % repr(metadata.st_nlink)) logging.error('ERROR: st_size=%s' % repr(metadata.st_size)) logging.error('ERROR: st_uid=%s' % repr(metadata.st_uid)) diff -Nru obnam-1.7.4/obnamlib/fmt_6/repo_fmt_6.py obnam-1.8/obnamlib/fmt_6/repo_fmt_6.py --- obnam-1.7.4/obnamlib/fmt_6/repo_fmt_6.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/fmt_6/repo_fmt_6.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2009-2013 Lars Wirzenius +# Copyright (C) 2009-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -88,6 +88,7 @@ self.client = client self.current_generation_number = None self.generations_removed = False + self.cached_generation_ids = None class RepositoryFormat6(obnamlib.RepositoryInterface): @@ -267,7 +268,7 @@ # ClientMetadataTree and is_locked. self._open_client_infos = {} - def _open_client(self, client_name): + def _get_open_client_info(self, client_name): if client_name not in self._open_client_infos: tracing.trace('client_name=%s', client_name) client_id = self._get_client_id(client_name) @@ -283,7 +284,11 @@ self._open_client_infos[client_name] = _OpenClientInfo(client) - return self._open_client_infos[client_name].client + return self._open_client_infos[client_name] + + def _open_client(self, client_name): + open_client_info = self._get_open_client_info(client_name) + return open_client_info.client def _get_client_dir(self, client_id): '''Return name of sub-directory for a given client.''' @@ -436,10 +441,33 @@ key_name=obnamlib.repo_key_name(key)) def get_client_generation_ids(self, client_name): - client = self._open_client(client_name) - return [ - self._construct_gen_id(client_name, gen_number) - for gen_number in client.list_generations()] + client_info = self._get_open_client_info(client_name) + self._refresh_open_client_info_cached_generation_ids( + client_name, client_info) + return client_info.cached_generation_ids + + def _refresh_open_client_info_cached_generation_ids(self, + client_name, + client_info): + if client_info.cached_generation_ids is None: + client_info.cached_generation_ids = [ + self._construct_gen_id(client_name, gen_number) + for gen_number in client_info.client.list_generations()] + + def _add_to_open_client_info_cached_generation_ids(self, + client_info, + gen_id): + ids = client_info.cached_generation_ids + assert ids is not None + if gen_id not in ids: # pragma: no cover + ids.append(gen_id) + + def _forget_open_client_info_cached_generation(self, + client_info, gen_id): + ids = client_info.cached_generation_ids + if ids is not None: + if gen_id in ids: + ids.remove(gen_id) def create_generation(self, client_name): tracing.trace('client_name=%s', client_name) @@ -453,9 +481,16 @@ open_client_info.client.start_generation() open_client_info.client.set_generation_started(self._current_time()) - open_client_info.current_generation_number = \ - open_client_info.client.get_generation_id( + + new_gen_number = open_client_info.client.get_generation_id( open_client_info.client.tree) + open_client_info.current_generation_number = new_gen_number + + self._refresh_open_client_info_cached_generation_ids( + client_name, open_client_info) + new_gen_id = self._construct_gen_id(client_name, new_gen_number) + self._add_to_open_client_info_cached_generation_ids( + open_client_info, new_gen_id) return self._construct_gen_id( client_name, open_client_info.current_generation_number) @@ -515,7 +550,8 @@ client_name=client_name, key_name=obnamlib.repo_key_name(key)) - def set_generation_key(self, generation_id, key, value): # pragma: no cover + def set_generation_key( + self, generation_id, key, value): # pragma: no cover # FIXME: This no worky for generations other than the currently # started one. There should at least be an assert about it. @@ -568,6 +604,8 @@ if gen_number == open_client_info.current_generation_number: open_client_info.current_generation_number = None open_client_info.generations_removed = True + self._forget_open_client_info_cached_generation( + open_client_info, gen_id) self._remove_chunks_from_removed_generations( client_name, open_client_info.client, [gen_number]) @@ -601,7 +639,8 @@ 'chunks', self._idpath_depth, self._idpath_bits, self._idpath_skip) - def _construct_in_tree_chunk_id(self, gen_id, filename): # pragma: no cover + def _construct_in_tree_chunk_id( + self, gen_id, filename): # pragma: no cover # This constructs a synthetic chunk id for in-tree data for a # file. The file is expected to have in-tree data. @@ -916,7 +955,8 @@ def _flush_file_key_cache(self): for cache_key, value in self._file_key_cache.items(): generation_id, filename = cache_key - client_name, generation_number = self._unpack_gen_id(generation_id) + client_name, generation_number = self._unpack_gen_id( + generation_id) dirty, metadata = value if dirty: encoded_metadata = \ diff -Nru obnam-1.7.4/obnamlib/fmt_6/repo_fmt_6_tests.py obnam-1.8/obnamlib/fmt_6/repo_fmt_6_tests.py --- obnam-1.7.4/obnamlib/fmt_6/repo_fmt_6_tests.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/fmt_6/repo_fmt_6_tests.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2013 Lars Wirzenius +# Copyright (C) 2013-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnamlib/fmt_6/repo_tree.py obnam-1.8/obnamlib/fmt_6/repo_tree.py --- obnam-1.7.4/obnamlib/fmt_6/repo_tree.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/fmt_6/repo_tree.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright 2010 Lars Wirzenius +# Copyright 2010-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnamlib/forget_policy.py obnam-1.8/obnamlib/forget_policy.py --- obnam-1.7.4/obnamlib/forget_policy.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/forget_policy.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010 Lars Wirzenius +# Copyright (C) 2010-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnamlib/forget_policy_tests.py obnam-1.8/obnamlib/forget_policy_tests.py --- obnam-1.7.4/obnamlib/forget_policy_tests.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/forget_policy_tests.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010 Lars Wirzenius +# Copyright (C) 2010-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnamlib/hooks.py obnam-1.8/obnamlib/hooks.py --- obnam-1.7.4/obnamlib/hooks.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/hooks.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2009 Lars Wirzenius +# Copyright (C) 2009-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnamlib/hooks_tests.py obnam-1.8/obnamlib/hooks_tests.py --- obnam-1.7.4/obnamlib/hooks_tests.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/hooks_tests.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2009 Lars Wirzenius +# Copyright (C) 2009-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnamlib/humanise.py obnam-1.8/obnamlib/humanise.py --- obnam-1.7.4/obnamlib/humanise.py 1970-01-01 00:00:00.000000000 +0000 +++ obnam-1.8/obnamlib/humanise.py 2014-05-13 07:05:26.000000000 +0000 @@ -0,0 +1,68 @@ +# Copyright 2014 Lars Wirzenius +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# =*= License: GPL-3+ =*= + + +def humanise_duration(seconds): + duration_string = '' + if seconds >= 3600: + duration_string += '%dh' % int(seconds/3600) + seconds %= 3600 + if seconds >= 60: + duration_string += '%dm' % int(seconds/60) + seconds %= 60 + if seconds > 0: + duration_string += '%ds' % round(seconds) + return duration_string + + +def humanise_size(size): + size_table = [ + (1024**4, 'TiB'), + (1024**3, 'GiB'), + (1024**2, 'MiB'), + (1024**1, 'KiB'), + (0, 'B') + ] + + for size_base, size_unit in size_table: + if size >= size_base: + if size_base > 0: + size_amount = int(float(size) / float(size_base)) + else: + size_amount = float(size) + return size_amount, size_unit + raise Exception("This can't happen: size=%r" % size) + + +def humanise_speed(size, duration): + speed_table = [ + (1024**3, 'GiB/s'), + (1024**2, 'MiB/s'), + (1024**1, 'KiB/s'), + (0, 'B/s') + ] + + speed = float(size) / duration + for speed_base, speed_unit in speed_table: + if speed >= speed_base: + if speed_base > 0: + speed_amount = speed / speed_base + else: + speed_amount = speed + return speed_amount, speed_unit + raise Exception( + "This can't happen: size=%r duration=%r" % (size, duration)) diff -Nru obnam-1.7.4/obnamlib/__init__.py obnam-1.8/obnamlib/__init__.py --- obnam-1.7.4/obnamlib/__init__.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/__init__.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2009-2011 Lars Wirzenius +# Copyright (C) 2009-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ import cliapp -__version__ = '1.7.4' +__version__ = '1.8' @@ -33,8 +33,6 @@ except ImportError: _obnam = DummyExtension() -from pluginmgr import PluginManager - # Exceptions defined by Obnam itself. They should all be a subclass # of obnamlib.ObnamError. @@ -90,12 +88,19 @@ from hooks import ( Hook, MissingFilterError, NoFilterTagError, FilterHook, HookManager) from pluginbase import ObnamPlugin -from vfs import VirtualFileSystem, VfsFactory, VfsTests, LockFail +from vfs import ( + VirtualFileSystem, + VfsFactory, + VfsTests, + LockFail, + NEW_DIR_MODE, + NEW_FILE_MODE) from vfs_local import LocalFS from fsck_work_item import WorkItem from lockmgr import LockManager from forget_policy import ForgetPolicy from app import App, ObnamIOError, ObnamSystemError +from humanise import humanise_duration, humanise_size, humanise_speed from repo_factory import ( RepositoryFactory, @@ -163,6 +168,7 @@ Metadata, read_metadata, set_metadata, + SetMetadataError, metadata_fields) from fmt_6.repo_fmt_6 import RepositoryFormat6 from fmt_6.repo_tree import RepositoryTree diff -Nru obnam-1.7.4/obnamlib/lockmgr.py obnam-1.8/obnamlib/lockmgr.py --- obnam-1.7.4/obnamlib/lockmgr.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/lockmgr.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright 2012 Lars Wirzenius +# Copyright 2012-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnamlib/lockmgr_tests.py obnam-1.8/obnamlib/lockmgr_tests.py --- obnam-1.7.4/obnamlib/lockmgr_tests.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/lockmgr_tests.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright 2012 Lars Wirzenius +# Copyright 2012-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -66,7 +66,8 @@ def test_notices_when_preexisting_lock_goes_away(self): self.lm.lock([self.dirnames[0]]) - self.lm._sleep = lambda: os.remove(self.lm._lockname(self.dirnames[0])) + self.lm._sleep = lambda: os.remove( + self.lm._lockname(self.dirnames[0])) self.lm.lock([self.dirnames[0]]) self.assertTrue(True) diff -Nru obnam-1.7.4/obnamlib/metadata.py obnam-1.8/obnamlib/metadata.py --- obnam-1.7.4/obnamlib/metadata.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/metadata.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2009 Lars Wirzenius +# Copyright (C) 2009-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -178,7 +178,7 @@ value_blob)) -def set_xattrs_from_blob(fs, filename, blob): # pragma: no cover +def set_xattrs_from_blob(fs, filename, blob, user_only): # pragma: no cover sizesize = struct.calcsize('!Q') name_blob_size = struct.unpack('!Q', blob[:sizesize])[0] name_blob = blob[sizesize : sizesize + name_blob_size] @@ -193,7 +193,12 @@ for i, name in enumerate(names): value = value_blob[pos:pos + lengths[i]] pos += lengths[i] - fs.lsetxattr(filename, name, value) + if not user_only or name.startswith('user.'): + fs.lsetxattr(filename, name, value) + else: + logging.warning( + '%s: Not setting extended attribute %s due to not being root', + filename, name) def read_metadata(fs, filename, st=None, getpwuid=None, getgrgid=None): @@ -226,6 +231,23 @@ return metadata +class SetMetadataError(obnamlib.ObnamError): + + msg = "{filename}: Couldn't set metadata {metadata}: {errno}: {strerror}" + + +def _set_something(filename, what, func): # pragma: no cover + try: + func() + except OSError as e: + logging.error(str(e), exc_info=True) + raise SetMetadataError( + filename=filename, + metadata=what, + errno=e.errno, + strerror=e.strerror) + + def set_metadata(fs, filename, metadata, getuid=None, always_set_id_bits=False): '''Set metadata for a filesystem entry. @@ -237,16 +259,22 @@ if they want to mess with things. This makes the user take care of error situations and looking up user preferences. + Raise SetMetadataError if setting any metadata fails. + ''' symlink = stat.S_ISLNK(metadata.st_mode) if symlink: - fs.symlink(metadata.target, filename) + _set_something( + filename, 'symlink target', + lambda: fs.symlink(metadata.target, filename)) # Set owner before mode, so that a setuid bit does not get reset. getuid = getuid or os.getuid if getuid() == 0: - fs.lchown(filename, metadata.st_uid, metadata.st_gid) + _set_something( + filename, 'uid and gid', + lambda: fs.lchown(filename, metadata.st_uid, metadata.st_gid)) # If we are not the owner, and not root, do not restore setuid/setgid, # unless explicitly told to do so. @@ -256,12 +284,24 @@ mode = mode & (~stat.S_ISUID) mode = mode & (~stat.S_ISGID) if symlink: - fs.chmod_symlink(filename, mode) + _set_something( + filename, 'symlink chmod', + lambda: fs.chmod_symlink(filename, mode)) else: - fs.chmod_not_symlink(filename, mode) + _set_something( + filename, 'chmod', + lambda: fs.chmod_not_symlink(filename, mode)) if metadata.xattr: # pragma: no cover - set_xattrs_from_blob(fs, filename, metadata.xattr) - - fs.lutimes(filename, metadata.st_atime_sec, metadata.st_atime_nsec, - metadata.st_mtime_sec, metadata.st_mtime_nsec) + user_only = getuid() != 0 + _set_something( + filename, 'xattrs', + lambda: + set_xattrs_from_blob(fs, filename, metadata.xattr, user_only)) + + _set_something( + filename, 'timestamps', + lambda: + fs.lutimes( + filename, metadata.st_atime_sec, metadata.st_atime_nsec, + metadata.st_mtime_sec, metadata.st_mtime_nsec)) diff -Nru obnam-1.7.4/obnamlib/metadata_tests.py obnam-1.8/obnamlib/metadata_tests.py --- obnam-1.7.4/obnamlib/metadata_tests.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/metadata_tests.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2009 Lars Wirzenius +# Copyright (C) 2009-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnamlib/pluginbase.py obnam-1.8/obnamlib/pluginbase.py --- obnam-1.7.4/obnamlib/pluginbase.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/pluginbase.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2009 Lars Wirzenius +# Copyright (C) 2009-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -14,13 +14,17 @@ # along with this program. If not, see . +import cliapp + import obnamlib -class ObnamPlugin(obnamlib.pluginmgr.Plugin): +class ObnamPlugin(cliapp.Plugin): '''Base class for plugins in Obnam.''' def __init__(self, app): self.app = app + def disable(self): + pass diff -Nru obnam-1.7.4/obnamlib/pluginbase_tests.py obnam-1.8/obnamlib/pluginbase_tests.py --- obnam-1.7.4/obnamlib/pluginbase_tests.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/pluginbase_tests.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2009 Lars Wirzenius +# Copyright (C) 2009-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -33,3 +33,5 @@ def test_has_an_app(self): self.assertEqual(self.plugin.app, self.fakeapp) + def test_disable_is_implemented(self): + self.plugin.disable() diff -Nru obnam-1.7.4/obnamlib/pluginmgr.py obnam-1.8/obnamlib/pluginmgr.py --- obnam-1.7.4/obnamlib/pluginmgr.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/pluginmgr.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,254 +0,0 @@ -# Copyright (C) 2009 Lars Wirzenius -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -'''A generic plugin manager. - -The plugin manager finds files with plugins and loads them. It looks -for plugins in a number of locations specified by the caller. To add -a plugin to be loaded, it is enough to put it in one of the locations, -and name it *_plugin.py. (The naming convention is to allow having -other modules as well, such as unit tests, in the same locations.) - -''' - - -import imp -import inspect -import os - - -class Plugin(object): - - '''Base class for plugins. - - A plugin MUST NOT have any side effects when it is instantiated. - This is necessary so that it can be safely loaded by unit tests, - and so that a user interface can allow the user to disable it, - even if it is installed, with no ill effects. Any side effects - that would normally happen should occur in the enable() method, - and be undone by the disable() method. These methods must be - callable any number of times. - - The subclass MAY define the following attributes: - - * name - * description - * version - * required_application_version - - name is the user-visible identifier for the plugin. It defaults - to the plugin's classname. - - description is the user-visible description of the plugin. It may - be arbitrarily long, and can use pango markup language. Defaults - to the empty string. - - version is the plugin version. Defaults to '0.0.0'. It MUST be a - sequence of integers separated by periods. If several plugins with - the same name are found, the newest version is used. Versions are - compared integer by integer, starting with the first one, and a - missing integer treated as a zero. If two plugins have the same - version, either might be used. - - required_application_version gives the version of the minimal - application version the plugin is written for. The first integer - must match exactly: if the application is version 2.3.4, the - plugin's required_application_version must be at least 2 and - at most 2.3.4 to be loaded. Defaults to 0. - - ''' - - @property - def name(self): - return self.__class__.__name__ - - @property - def description(self): - return '' - - @property - def version(self): - return '0.0.0' - - @property - def required_application_version(self): - return '0.0.0' - - def enable_wrapper(self): - '''Enable plugin. - - The plugin manager will call this method, which then calls the - enable method. Plugins should implement the enable method. - The wrapper method is there to allow an application to provide - an extended base class that does some application specific - magic when plugins are enabled or disabled. - - ''' - - self.enable() - - def disable_wrapper(self): - '''Corresponds to enable_wrapper, but for disabling a plugin.''' - self.disable() - - def enable(self): - '''Enable the plugin.''' - raise NotImplemented() - - def disable(self): - '''Disable the plugin.''' - raise NotImplemented() - - -class PluginManager(object): - - '''Manage plugins. - - This class finds and loads plugins, and keeps a list of them that - can be accessed in various ways. - - The locations are set via the locations attribute, which is a list. - - When a plugin is loaded, an instance of its class is created. This - instance is initialized using normal and keyword arguments specified - in the plugin manager attributes plugin_arguments and - plugin_keyword_arguments. - - The version of the application using the plugin manager is set via - the application_version attribute. This defaults to '0.0.0'. - - ''' - - suffix = '_plugin.py' - - def __init__(self): - self.locations = [] - self._plugins = None - self._plugin_files = None - self.plugin_arguments = [] - self.plugin_keyword_arguments = {} - self.application_version = '0.0.0' - - @property - def plugin_files(self): - if self._plugin_files is None: - self._plugin_files = self.find_plugin_files() - return self._plugin_files - - @property - def plugins(self): - if self._plugins is None: - self._plugins = self.load_plugins() - return self._plugins - - def __getitem__(self, name): - for plugin in self.plugins: - if plugin.name == name: - return plugin - raise KeyError('Plugin %s is not known' % name) - - def find_plugin_files(self): - '''Find files that may contain plugins. - - This finds all files named *_plugin.py in all locations. - The returned list is sorted. - - ''' - - pathnames = [] - - for location in self.locations: - try: - basenames = os.listdir(location) - except os.error: - continue - for basename in basenames: - s = os.path.join(location, basename) - if s.endswith(self.suffix) and os.path.exists(s): - pathnames.append(s) - - return sorted(pathnames) - - def load_plugins(self): - '''Load plugins from all plugin files.''' - - plugins = dict() - - for pathname in self.plugin_files: - for plugin in self.load_plugin_file(pathname): - if plugin.name in plugins: - p = plugins[plugin.name] - if self.is_older(p.version, plugin.version): - plugins[plugin.name] = plugin - else: - plugins[plugin.name] = plugin - - return plugins.values() - - def is_older(self, version1, version2): - '''Is version1 older than version2?''' - return self.parse_version(version1) < self.parse_version(version2) - - def load_plugin_file(self, pathname): - '''Return plugin classes in a plugin file.''' - - name, ext = os.path.splitext(os.path.basename(pathname)) - f = file(pathname, 'r') - module = imp.load_module(name, f, pathname, - ('.py', 'r', imp.PY_SOURCE)) - f.close() - - plugins = [] - for dummy, member in inspect.getmembers(module, inspect.isclass): - if issubclass(member, Plugin): - p = member(*self.plugin_arguments, - **self.plugin_keyword_arguments) - if self.compatible_version(p.required_application_version): - plugins.append(p) - - return plugins - - def compatible_version(self, required_application_version): - '''Check that the plugin is version-compatible with the application. - - This checks the plugin's required_application_version against - the declared application version and returns True if they are - compatible, and False if not. - - ''' - - req = self.parse_version(required_application_version) - app = self.parse_version(self.application_version) - - return app[0] == req[0] and app >= req - - def parse_version(self, version): - '''Parse a string represenation of a version into list of ints.''' - - return [int(s) for s in version.split('.')] - - def enable_plugins(self, plugins=None): - '''Enable all or selected plugins.''' - - for plugin in plugins or self.plugins: - plugin.enable_wrapper() - - def disable_plugins(self, plugins=None): - '''Disable all or selected plugins.''' - - for plugin in plugins or self.plugins: - plugin.disable_wrapper() - diff -Nru obnam-1.7.4/obnamlib/pluginmgr_tests.py obnam-1.8/obnamlib/pluginmgr_tests.py --- obnam-1.7.4/obnamlib/pluginmgr_tests.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/pluginmgr_tests.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,151 +0,0 @@ -# Copyright (C) 2009 Lars Wirzenius -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -import unittest - -from pluginmgr import Plugin, PluginManager - - -class PluginTests(unittest.TestCase): - - def setUp(self): - self.plugin = Plugin() - - def test_name_is_class_name(self): - self.assertEqual(self.plugin.name, 'Plugin') - - def test_description_is_empty_string(self): - self.assertEqual(self.plugin.description, '') - - def test_version_is_zeroes(self): - self.assertEqual(self.plugin.version, '0.0.0') - - def test_required_application_version_is_zeroes(self): - self.assertEqual(self.plugin.required_application_version, '0.0.0') - - def test_enable_raises_exception(self): - self.assertRaises(Exception, self.plugin.enable) - - def test_disable_raises_exception(self): - self.assertRaises(Exception, self.plugin.disable) - - def test_enable_wrapper_calls_enable(self): - self.plugin.enable = lambda: setattr(self, 'enabled', True) - self.plugin.enable_wrapper() - self.assert_(self.enabled, True) - - def test_disable_wrapper_calls_disable(self): - self.plugin.disable = lambda: setattr(self, 'disabled', True) - self.plugin.disable_wrapper() - self.assert_(self.disabled, True) - - -class PluginManagerInitialStateTests(unittest.TestCase): - - def setUp(self): - self.pm = PluginManager() - - def test_locations_is_empty_list(self): - self.assertEqual(self.pm.locations, []) - - def test_plugins_is_empty_list(self): - self.assertEqual(self.pm.plugins, []) - - def test_application_version_is_zeroes(self): - self.assertEqual(self.pm.application_version, '0.0.0') - - def test_plugin_files_is_empty(self): - self.assertEqual(self.pm.plugin_files, []) - - def test_plugin_arguments_is_empty(self): - self.assertEqual(self.pm.plugin_arguments, []) - - def test_plugin_keyword_arguments_is_empty(self): - self.assertEqual(self.pm.plugin_keyword_arguments, {}) - - -class PluginManagerTests(unittest.TestCase): - - def setUp(self): - self.pm = PluginManager() - self.pm.locations = ['test-plugins', 'not-exist'] - self.pm.plugin_arguments = ('fooarg',) - self.pm.plugin_keyword_arguments = { 'bar': 'bararg' } - - self.files = sorted(['test-plugins/hello_plugin.py', - 'test-plugins/aaa_hello_plugin.py', - 'test-plugins/oldhello_plugin.py', - 'test-plugins/wrongversion_plugin.py']) - - def test_finds_the_right_plugin_files(self): - self.assertEqual(self.pm.find_plugin_files(), self.files) - - def test_plugin_files_attribute_implicitly_searches(self): - self.assertEqual(self.pm.plugin_files, self.files) - - def test_loads_hello_plugin(self): - plugins = self.pm.load_plugins() - self.assertEqual(len(plugins), 1) - self.assertEqual(plugins[0].name, 'Hello') - - def test_plugins_attribute_implicitly_searches(self): - self.assertEqual(len(self.pm.plugins), 1) - self.assertEqual(self.pm.plugins[0].name, 'Hello') - - def test_initializes_hello_with_correct_args(self): - plugin = self.pm['Hello'] - self.assertEqual(plugin.foo, 'fooarg') - self.assertEqual(plugin.bar, 'bararg') - - def test_raises_keyerror_for_unknown_plugin(self): - self.assertRaises(KeyError, self.pm.__getitem__, 'Hithere') - - def test_enable_plugins_enables_all_plugins(self): - enabled = set() - for plugin in self.pm.plugins: - plugin.enable = lambda: enabled.add(plugin) - self.pm.enable_plugins() - self.assertEqual(enabled, set(self.pm.plugins)) - - def test_disable_plugins_disables_all_plugins(self): - disabled = set() - for plugin in self.pm.plugins: - plugin.disable = lambda: disabled.add(plugin) - self.pm.disable_plugins() - self.assertEqual(disabled, set(self.pm.plugins)) - - -class PluginManagerCompatibleApplicationVersionTests(unittest.TestCase): - - def setUp(self): - self.pm = PluginManager() - self.pm.application_version = '1.2.3' - - def test_rejects_zero(self): - self.assertFalse(self.pm.compatible_version('0')) - - def test_rejects_two(self): - self.assertFalse(self.pm.compatible_version('2')) - - def test_rejects_one_two_four(self): - self.assertFalse(self.pm.compatible_version('1.2.4')) - - def test_accepts_one(self): - self.assert_(self.pm.compatible_version('1')) - - def test_accepts_one_two_three(self): - self.assert_(self.pm.compatible_version('1.2.3')) - diff -Nru obnam-1.7.4/obnamlib/plugins/backup_plugin.py obnam-1.8/obnamlib/plugins/backup_plugin.py --- obnam-1.7.4/obnamlib/plugins/backup_plugin.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/plugins/backup_plugin.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010, 2011, 2012 Lars Wirzenius +# Copyright (C) 2009-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -43,6 +43,11 @@ msg = 'No backup roots specified' +class BackupRootDoesNotExist(obnamlib.ObnamError): + + msg = 'Backup root does not exist or is not a directory: {root}' + + class BackupErrors(obnamlib.ObnamError): msg = 'There were errors during the backup' @@ -133,21 +138,22 @@ def report_stats(self, fs): duration = time.time() - self.started - duration_string = self.humanise_duration(duration) + duration_string = obnamlib.humanise_duration(duration) - chunk_amount, chunk_unit = self.humanise_size(self.uploaded_bytes) + chunk_amount, chunk_unit = obnamlib.humanise_size( + self.uploaded_bytes) - fs_amount, fs_unit = self.humanise_size(fs.bytes_written) + fs_amount, fs_unit = obnamlib.humanise_size(fs.bytes_written) - dl_amount, dl_unit = self.humanise_size(fs.bytes_read) + dl_amount, dl_unit = obnamlib.humanise_size(fs.bytes_read) overhead_bytes = ( fs.bytes_read + (fs.bytes_written - self.uploaded_bytes)) overhead_bytes = max(0, overhead_bytes) - overhead_amount, overhead_unit = self.humanise_size( + overhead_amount, overhead_unit = obnamlib.humanise_size( overhead_bytes) - speed_amount, speed_unit = self.humanise_speed( + speed_amount, speed_unit = obnamlib.humanise_speed( self.uploaded_bytes, duration) logging.info( @@ -184,74 +190,40 @@ chunk_amount, chunk_unit, duration_string, speed_amount, speed_unit)) - def humanise_duration(self, seconds): - duration_string = '' - if seconds >= 3600: - duration_string += '%dh' % int(seconds/3600) - seconds %= 3600 - if seconds >= 60: - duration_string += '%dm' % int(seconds/60) - seconds %= 60 - if seconds > 0: - duration_string += '%ds' % round(seconds) - return duration_string - - def humanise_size(self, size): - size_table = [ - (1024**4, 'TiB'), - (1024**3, 'GiB'), - (1024**2, 'MiB'), - (1024**1, 'KiB'), - (0, 'B') - ] - - for size_base, size_unit in size_table: - if size >= size_base: - if size_base > 0: - size_amount = int(float(size) / float(size_base)) - else: - size_amount = float(size) - return size_amount, size_unit - raise Exception("This can't happen: size=%r" % size) - - def humanise_speed(self, size, duration): - speed_table = [ - (1024**3, 'GiB/s'), - (1024**2, 'MiB/s'), - (1024**1, 'KiB/s'), - (0, 'B/s') - ] - - speed = float(size) / duration - for speed_base, speed_unit in speed_table: - if speed >= speed_base: - if speed_base > 0: - speed_amount = speed / speed_base - else: - speed_amount = speed - return speed_amount, speed_unit - raise Exception( - "This can't happen: size=%r duration=%r" % (size, duration)) - class BackupPlugin(obnamlib.ObnamPlugin): def enable(self): + self.app.add_subcommand( + 'backup', self.backup, arg_synopsis='[DIRECTORY]...') + self.add_backup_settings() + self.app.hooks.new('backup-finished') + + def add_backup_settings(self): + + # Backup related settings. + backup_group = obnamlib.option_group['backup'] = 'Backing up' - perf_group = obnamlib.option_group['perf'] - self.app.add_subcommand('backup', self.backup, - arg_synopsis='[DIRECTORY]...') - self.app.settings.string_list(['root'], 'what to backup') - self.app.settings.string_list(['exclude'], - 'regular expression for pathnames to ' - 'exclude from backup (can be used multiple ' - 'times)', - group=backup_group) - self.app.settings.string_list(['exclude-from'], - 'read exclude patterns from FILE', - metavar='FILE', - group=backup_group) + self.app.settings.string_list( + ['root'], + 'what to backup', + metavar='URL', + group=backup_group) + + self.app.settings.string_list( + ['exclude'], + 'regular expression for pathnames to ' + 'exclude from backup (can be used multiple ' + 'times)', + group=backup_group) + + self.app.settings.string_list( + ['exclude-from'], + 'read exclude patterns from FILE', + metavar='FILE', + group=backup_group) + self.app.settings.boolean( ['exclude-caches'], 'exclude directories (and their subdirs) ' @@ -260,49 +232,67 @@ 'it needs to contain, and http://liw.fi/cachedir/ for a ' 'helper tool)', group=backup_group) - self.app.settings.boolean(['one-file-system'], - 'exclude directories (and their subdirs) ' - 'that are in a different filesystem', - group=backup_group) - self.app.settings.bytesize(['checkpoint'], - 'make a checkpoint after a given SIZE ' - '(%default)', - metavar='SIZE', - default=1024**3, - group=backup_group) - self.app.settings.integer(['chunkids-per-group'], - 'encode NUM chunk ids per group (%default)', - metavar='NUM', - default=obnamlib.DEFAULT_CHUNKIDS_PER_GROUP, - group=perf_group) - self.app.settings.choice(['deduplicate'], - ['fatalist', 'never', 'verify'], - 'find duplicate data in backed up data ' - 'and store it only once; three modes ' - 'are available: never de-duplicate, ' - 'verify that no hash collisions happen, ' - 'or (the default) fatalistically accept ' - 'the risk of collisions', - metavar='MODE', - group=backup_group) - self.app.settings.boolean(['leave-checkpoints'], - 'leave checkpoint generations at the end ' - 'of a successful backup run', - group=backup_group) - self.app.settings.boolean(['small-files-in-btree'], - 'put contents of small files directly into ' - 'the per-client B-tree, instead of ' - 'separate chunk files; do not use this ' - 'as it is quite bad for performance', - group=backup_group) + + self.app.settings.boolean( + ['one-file-system'], + 'exclude directories (and their subdirs) ' + 'that are in a different filesystem', + group=backup_group) + + self.app.settings.bytesize( + ['checkpoint'], + 'make a checkpoint after a given SIZE', + metavar='SIZE', + default=1024**3, + group=backup_group) + + self.app.settings.choice( + ['deduplicate'], + ['fatalist', 'never', 'verify'], + 'find duplicate data in backed up data ' + 'and store it only once; three modes ' + 'are available: never de-duplicate, ' + 'verify that no hash collisions happen, ' + 'or (the default) fatalistically accept ' + 'the risk of collisions', + metavar='MODE', + group=backup_group) + + self.app.settings.boolean( + ['leave-checkpoints'], + 'leave checkpoint generations at the end ' + 'of a successful backup run', + group=backup_group) + + self.app.settings.boolean( + ['small-files-in-btree'], + 'put contents of small files directly into ' + 'the per-client B-tree, instead of ' + 'separate chunk files; do not use this ' + 'as it is quite bad for performance', + group=backup_group) + + # Performance related settings. + + perf_group = obnamlib.option_group['perf'] + + self.app.settings.integer( + ['chunkids-per-group'], + 'encode NUM chunk ids per group', + metavar='NUM', + default=obnamlib.DEFAULT_CHUNKIDS_PER_GROUP, + group=perf_group) + + # Development related settings. + + devel_group = obnamlib.option_group['devel'] self.app.settings.string_list( ['testing-fail-matching'], 'development testing helper: simulate failures during backup ' - 'for files that match the given regular expressions', - metavar='REGEXP') - - self.app.hooks.new('backup-finished') + 'for files that match the given regular expressions', + metavar='REGEXP', + group=devel_group) def configure_ttystatus_for_backup(self): self.progress = BackupProgress(self.app.ts) @@ -339,7 +329,17 @@ self.got_client_lock = False self.got_chunk_indexes_lock = False if self.pretend: - self.repo = self.app.get_repository_object() + try: + self.repo = self.app.get_repository_object() + except Exception as e: + self.progress.error( + 'Are you using --pretend without an existing ' + 'repository? That does not\n' + 'work, sorry. You can create a small repository, ' + 'backing up just one\n' + 'small directory, and then use --pretend with ' + 'the real data.') + raise else: self.repo = self.app.get_repository_object(create=True) self.progress.what('adding client') @@ -371,7 +371,8 @@ self.progress.what('committing changes to repository') if not self.pretend: self.progress.what( - 'committing changes to repository: locking shared B-trees') + 'committing changes to repository: ' + 'locking shared B-trees') self.repo.lock_chunk_indexes() self.progress.what( @@ -461,8 +462,10 @@ # add patterns passed via --exclude exclude_patterns.extend(self.app.settings['exclude']) + # Ignore log file, except don't exclude the words cliapp uses + # for not logging or for logging to syslog. log = self.app.settings['log'] - if log: + if log and log not in ('none', 'syslog'): log = self.app.settings['log'] exclude_patterns.append(log) for pattern in exclude_patterns: @@ -475,7 +478,8 @@ self.exclude_pats.append(re.compile(x)) except re.error, e: msg = ( - 'error compiling regular expression "%s": %s' % (x, e)) + 'error compiling regular expression "%s": %s' % + (x, e)) logging.error(msg) self.progress.error(msg) @@ -491,8 +495,13 @@ def backup_roots(self, roots): self.progress.what('connecting to to repository') - self.fs = self.app.fsf.new(roots[0]) - self.fs.connect() + try: + self.fs = self.app.fsf.new(roots[0]) + self.fs.connect() + except OSError as e: + if e.errno == errno.ENOENT: + raise BackupRootDoesNotExist(root=roots[0]) + raise absroots = [] for root in roots: @@ -510,7 +519,13 @@ for root in roots: logging.info('Backing up root %s' % root) self.progress.what('connecting to live data %s' % root) - self.fs.reinit(root) + + try: + self.fs.reinit(root) + except OSError as e: + if e.errno == errno.ENOENT: + raise BackupRootDoesNotExist(root=root) + raise self.progress.what('scanning for files in %s' % root) absroot = self.fs.abspath('.') @@ -622,11 +637,14 @@ self.backup_parents('.') self.progress.what('making checkpoint: locking shared B-trees') self.repo.lock_chunk_indexes() - self.progress.what('making checkpoint: adding chunks to shared B-trees') + self.progress.what( + 'making checkpoint: adding chunks to shared B-trees') self.add_chunks_to_shared() - self.progress.what('making checkpoint: committing per-client B-tree') + self.progress.what( + 'making checkpoint: committing per-client B-tree') self.repo.set_generation_key( - self.new_generation, obnamlib.REPO_GENERATION_IS_CHECKPOINT, 1) + self.new_generation, + obnamlib.REPO_GENERATION_IS_CHECKPOINT, 1) self.repo.commit_client(self.client_name) self.progress.what('making checkpoint: committing shared B-trees') self.repo.commit_chunk_indexes() @@ -637,7 +655,8 @@ self.progress.what('making checkpoint: locking client') self.repo.lock_client(self.client_name) self.progress.what('making checkpoint: starting a new generation') - self.new_generation = self.repo.create_generation(self.client_name) + self.new_generation = self.repo.create_generation( + self.client_name) self.app.dump_memory_profile('at end of checkpoint') self.progress.what('making checkpoint: continuing backup') @@ -736,9 +755,6 @@ except obnamlib.ObnamError as e: # File does not exist in the previous generation, so it # does need to be backed up. - tracing.trace('%s not in previous gen so has changed' % pathname) - tracing.trace('error: %s' % str(e)) - tracing.trace(traceback.format_exc()) return True must_be_equal = ( @@ -754,11 +770,7 @@ for field in must_be_equal: current_value = getattr(current, field) old_value = getattr(old, field) - tracing.trace('current.%s=%r', field, current_value) - tracing.trace('old.%s=%r', field, old_value) if current_value != old_value: - tracing.trace( - 'DIFFERENT metadata %r for %r', field, pathname) return True # Treat xattr values None (no extended attributes) and '' @@ -766,13 +778,9 @@ # string) as equal values. xattr_current = current.xattr or None xattr_old = old.xattr or None - tracing.trace('xattr_current=%r', xattr_current) - tracing.trace('xattr_old=%r', xattr_old) if xattr_current != xattr_old: - tracing.trace('DIFFERENT xattr for %r', pathname) return True - tracing.trace('NOT DIFFERENT metadata for %r', pathname) return False def get_metadata_from_generation(self, gen, pathname): @@ -984,6 +992,15 @@ for old in old_pathnames: if old not in new_pathnames: self.repo.remove_file(self.new_generation, old) + else: + try: + st = self.fs.lstat(old) + except OSError: + pass + else: + if not self.can_be_backed_up(old, st): + self.repo.remove_file(self.new_generation, old) + # Files that are created after the previous generation will be # added to the directory when they are backed up, so we don't # need to worry about them here. @@ -1019,7 +1036,8 @@ helper(pathname) else: tracing.trace('is extra and removed: %s' % dirname) - self.progress.what('removing %s from new generation' % dirname) + self.progress.what( + 'removing %s from new generation' % dirname) self.repo.remove_file(self.new_generation, dirname) self.progress.what(msg) diff -Nru obnam-1.7.4/obnamlib/plugins/compression_plugin.py obnam-1.8/obnamlib/plugins/compression_plugin.py --- obnam-1.7.4/obnamlib/plugins/compression_plugin.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/plugins/compression_plugin.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2011 Lars Wirzenius +# Copyright (C) 2011-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnamlib/plugins/dump_repo_plugin.py obnam-1.8/obnamlib/plugins/dump_repo_plugin.py --- obnam-1.7.4/obnamlib/plugins/dump_repo_plugin.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/plugins/dump_repo_plugin.py 2014-05-13 07:05:26.000000000 +0000 @@ -30,6 +30,15 @@ 'dump metadata about files?') def cmd_dump_repo(self, args): + '''Dump (some) data structures from a repository. + + This is a debugging aid for Obnam developers. It writes out + some of the binary data structures in a repository in JSON + format so they can be inspected by the developer for problems. + The dump is not complete, however. + + ''' + repo = self.app.get_repository_object() for obj in self.dump_repository(repo): json.dump(obj, self.app.output, indent=4) diff -Nru obnam-1.7.4/obnamlib/plugins/encryption_plugin.py obnam-1.8/obnamlib/plugins/encryption_plugin.py --- obnam-1.7.4/obnamlib/plugins/encryption_plugin.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/plugins/encryption_plugin.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2011 Lars Wirzenius +# Copyright (C) 2011-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnamlib/plugins/force_lock_plugin.py obnam-1.8/obnamlib/plugins/force_lock_plugin.py --- obnam-1.7.4/obnamlib/plugins/force_lock_plugin.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/plugins/force_lock_plugin.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010, 2011 Lars Wirzenius +# Copyright (C) 2009-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -29,36 +29,57 @@ def enable(self): self.app.add_subcommand('force-lock', self.force_lock) + self.app.add_subcommand('_lock', self.lock, hidden=True) def force_lock(self, args): '''Force a locked repository to be open.''' + self.app.settings.require('repository') - self.app.settings.require('client-name') repourl = self.app.settings['repository'] client_name = self.app.settings['client-name'] logging.info('Forcing lock') logging.info('Repository: %s' % repourl) - logging.info('Client: %s' % client_name) try: repo = self.app.get_repository_object() except OSError, e: raise RepositoryAccessError(error=str(e)) - all_clients = repo.get_client_names() - if client_name not in all_clients: - msg = 'Client does not exist in repository.' - logging.warning(msg) - self.app.output.write('Warning: %s\n' % msg) - return - - all_dirs = ['clientlist', 'chunksums', 'chunklist', 'chunks', '.'] repo.force_client_list_lock() - for x in all_clients: - repo.force_client_lock(x) + for client_name in repo.get_client_names(): + repo.force_client_lock(client_name) repo.force_chunk_indexes_lock() repo.close() + return 0 + + def lock(self, args): + '''Add locks to the repository. + + This is a hidden command meant for use in testing only. + + ''' + + self.app.settings.require('repository') + + repourl = self.app.settings['repository'] + client_name = self.app.settings['client-name'] + logging.info('Creating lock') + logging.info('Repository: %s' % repourl) + logging.info('Client: %s' % client_name) + + try: + repo = self.app.get_repository_object() + except OSError, e: + raise RepositoryAccessError(error=str(e)) + + repo.lock_client_list() + if client_name: + repo.lock_client(client_name) + repo.lock_chunk_indexes() + + repo.close() + return 0 diff -Nru obnam-1.7.4/obnamlib/plugins/forget_plugin.py obnam-1.8/obnamlib/plugins/forget_plugin.py --- obnam-1.7.4/obnamlib/plugins/forget_plugin.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/plugins/forget_plugin.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010 Lars Wirzenius +# Copyright (C) 2010-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnamlib/plugins/fsck_plugin.py obnam-1.8/obnamlib/plugins/fsck_plugin.py --- obnam-1.7.4/obnamlib/plugins/fsck_plugin.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/plugins/fsck_plugin.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010 Lars Wirzenius +# Copyright (C) 2010-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -132,12 +132,14 @@ n = self.repo.get_generation_key( self.genid, obnamlib.REPO_GENERATION_FILE_COUNT) if n is None: - self.error('%s:%s: no file count' % (self.client_name, self.genid)) + self.error( + '%s:%s: no file count' % (self.client_name, self.genid)) n = self.repo.get_generation_key( self.genid, obnamlib.REPO_GENERATION_TOTAL_DATA) if n is None: - self.error('%s:%s: no total data' % (self.client_name, self.genid)) + self.error( + '%s:%s: no total data' % (self.client_name, self.genid)) if self.settings['fsck-skip-dirs']: return [] diff -Nru obnam-1.7.4/obnamlib/plugins/fuse_plugin.py obnam-1.8/obnamlib/plugins/fuse_plugin.py --- obnam-1.7.4/obnamlib/plugins/fuse_plugin.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/plugins/fuse_plugin.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2013 Valery Yundin +# Copyright (C) 2013-2014 Valery Yundin # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnamlib/plugins/__init__.py obnam-1.8/obnamlib/plugins/__init__.py --- obnam-1.7.4/obnamlib/plugins/__init__.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/plugins/__init__.py 2014-05-13 07:05:26.000000000 +0000 @@ -0,0 +1,22 @@ +# Copyright 2014 Lars Wirzenius +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# =*= License: GPL-3+ =*= + +# So as to not to claim copyright on an empty file, this file contains +# a joke. +# +# Weekends are when other people take two days to reply to e-mail. +# Vacations are when other people take two weeks to reply to e-mail. diff -Nru obnam-1.7.4/obnamlib/plugins/restore_plugin.py obnam-1.8/obnamlib/plugins/restore_plugin.py --- obnam-1.7.4/obnamlib/plugins/restore_plugin.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/plugins/restore_plugin.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Lars Wirzenius +# Copyright (C) 2009-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -79,7 +79,7 @@ arg_synopsis='[DIRECTORY]...') self.app.settings.string( ['to'], - 'where to restore') + 'where to restore or FUSE mount') self.app.settings.string_list( ['generation'], 'which generation to restore', @@ -134,6 +134,14 @@ if self.write_ok: self.fs = self.app.fsf.new(self.app.settings['to'], create=True) self.fs.connect() + + # Set permissions on this directory to be quite + # restrictive, so that nobody else can access the files + # while the restore is happening. The directory named by + # --to is set to have the permissions of the filesystem + # root directory (/) in the backup as the final step, so + # the permissions will eventually be correct. + self.fs.chmod_not_symlink('.', 0700) else: self.fs = None # this will trigger error if we try to really write @@ -233,11 +241,8 @@ obnamlib.set_metadata( self.fs, './' + pathname, metadata, always_set_id_bits=always) - except (IOError, OSError), e: - msg = ('Could not set metadata: %s: %d: %s' % - (pathname, e.errno, e.strerror)) - logging.error(msg) - self.app.ts.error(msg) + except obnamlib.SetMetadataError as e: + self.app.ts.error(str(e)) self.errors = True except Exception, e: # Reaching this code path means we've hit a bug, so we log @@ -252,7 +257,8 @@ if self.write_ok: if not self.fs.exists('./' + root): self.fs.mkdir('./' + root) - self.app.dump_memory_profile('after recursing through %s' % repr(root)) + self.app.dump_memory_profile( + 'after recursing through %s' % repr(root)) def restore_hardlink(self, filename, link, metadata): logging.debug('restoring hardlink %s to %s' % (filename, link)) diff -Nru obnam-1.7.4/obnamlib/plugins/sftp_plugin.py obnam-1.8/obnamlib/plugins/sftp_plugin.py --- obnam-1.7.4/obnamlib/plugins/sftp_plugin.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/plugins/sftp_plugin.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2009 Lars Wirzenius +# Copyright (C) 2009-2014 Lars Wirzenius # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -110,8 +110,10 @@ try: return os.read(self.proc.stdout.fileno(), count) except socket.error, e: - if e.args[0] in (errno.EPIPE, errno.ECONNRESET, errno.ECONNABORTED, - errno.EBADF): + errnos = ( + errno.EPIPE, errno.ECONNRESET, errno.ECONNABORTED, + errno.EBADF) + if e.args[0] in errnos: # Connection has closed. Paramiko expects an empty string in # this case, not an exception. return '' @@ -469,16 +471,16 @@ raise NotImplementedError('mknod on SFTP: %s' % pathname) @ioerror_to_oserror - def mkdir(self, pathname): + def mkdir(self, pathname, mode=obnamlib.NEW_DIR_MODE): self._delay() - self.sftp.mkdir(pathname) + self.sftp.mkdir(pathname, mode) @ioerror_to_oserror def makedirs(self, pathname): parent = os.path.dirname(pathname) if parent and parent != pathname and not self.exists(parent): self.makedirs(parent) - self.mkdir(pathname) + self.mkdir(pathname, obnamlib.NEW_DIR_MODE) @ioerror_to_oserror def rmdir(self, pathname): @@ -563,7 +565,7 @@ def cat(self, pathname): self._delay() - f = self.open(pathname, 'r') + f = self.open(pathname, 'rb') f.prefetch() chunks = [] while True: @@ -578,7 +580,7 @@ @ioerror_to_oserror def write_file(self, pathname, contents): try: - f = self.open(pathname, 'wx') + f = self.open(pathname, 'wbx') except (IOError, OSError), e: # When the path to the file to be written does not # exist, we try to create the directories below. Note that @@ -596,7 +598,7 @@ f.close() def _tempfile(self, dirname): - '''Create a new file with a random name, return file handle and name.''' + '''Create a new file with a random name, return handle and name.''' if dirname: try: @@ -613,7 +615,11 @@ basename = 'tmp.%x' % i pathname = os.path.join(dirname, basename) try: - f = self.open(pathname, 'wx', bufsize=self.chunk_size) + f = self.open(pathname, 'wbx', bufsize=self.chunk_size) + # paramiko.SFTPClient doesn't allow setting the mode + # on creation, so we set it separately. This leaves a + # short window where the file is possible to open. + self.chmod_not_symlink(pathname, obnamlib.NEW_FILE_MODE) except OSError: pass else: @@ -641,54 +647,55 @@ ssh_group = obnamlib.option_group['ssh'] = 'SSH/SFTP' devel_group = obnamlib.option_group['devel'] - self.app.settings.integer(['sftp-delay'], - 'add an artificial delay (in milliseconds) ' - 'to all SFTP transfers', - group=devel_group) - - self.app.settings.string(['ssh-key'], - 'use FILENAME as the ssh RSA private key for ' - 'sftp access (default is using keys known ' - 'to ssh-agent)', - metavar='FILENAME', - group=ssh_group) - - self.app.settings.boolean(['strict-ssh-host-keys'], - 'DEPRECATED, use --ssh-host-keys-check ' - 'instead', - group=ssh_group) - - self.app.settings.choice(['ssh-host-keys-check'], - ['ssh-config', 'yes', 'no', 'ask'], - 'If "yes", require that the ssh host key must ' - 'be known and correct to be accepted. If ' - '"no", do not require that. If "ask", the ' - 'user is interactively asked to accept new ' - 'hosts. The default ("ssh-config") is to ' - 'rely on the settings of the underlying ' - 'SSH client', - metavar='VALUE', - group=ssh_group) - - self.app.settings.string(['ssh-known-hosts'], - 'filename of the user\'s known hosts file ' - '(default: %default)', - metavar='FILENAME', - default= - os.path.expanduser('~/.ssh/known_hosts'), - group=ssh_group) - - self.app.settings.string(['ssh-command'], - 'alternative executable to be used instead ' - 'of "ssh" (full path is allowed, no ' - 'arguments may be added)', - metavar='EXECUTABLE', - group=ssh_group) - - self.app.settings.boolean(['pure-paramiko'], - 'do not use openssh even if available, ' - 'use paramiko only instead', - group=ssh_group) + self.app.settings.integer( + ['sftp-delay'], + 'add an artificial delay (in milliseconds) to all SFTP transfers', + group=devel_group) + + self.app.settings.string( + ['ssh-key'], + 'use FILENAME as the ssh RSA private key for sftp access ' + '(default is using keys known to ssh-agent)', + metavar='FILENAME', + group=ssh_group) + + self.app.settings.boolean( + ['strict-ssh-host-keys'], + 'DEPRECATED, use --ssh-host-keys-check instead', + group=ssh_group) + + self.app.settings.choice( + ['ssh-host-keys-check'], + ['ssh-config', 'yes', 'no', 'ask'], + 'If "yes", require that the ssh host key must ' + 'be known and correct to be accepted. If ' + '"no", do not require that. If "ask", the ' + 'user is interactively asked to accept new ' + 'hosts. The default ("ssh-config") is to ' + 'rely on the settings of the underlying ' + 'SSH client', + metavar='VALUE', + group=ssh_group) + + self.app.settings.string( + ['ssh-known-hosts'], + 'filename of the user\'s known hosts file', + metavar='FILENAME', + default=os.path.expanduser('~/.ssh/known_hosts'), + group=ssh_group) + + self.app.settings.string( + ['ssh-command'], + 'alternative executable to be used instead ' + 'of "ssh" (full path is allowed, no ' + 'arguments may be added)', + metavar='EXECUTABLE', + group=ssh_group) + + self.app.settings.boolean( + ['pure-paramiko'], + 'do not use openssh even if available, ' + 'use paramiko only instead', + group=ssh_group) self.app.fsf.register('sftp', SftpFS, settings=self.app.settings) - diff -Nru obnam-1.7.4/obnamlib/plugins/show_plugin.py obnam-1.8/obnamlib/plugins/show_plugin.py --- obnam-1.7.4/obnamlib/plugins/show_plugin.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/plugins/show_plugin.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Lars Wirzenius +# Copyright (C) 2009-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -60,24 +60,26 @@ self.app.add_subcommand('nagios-last-backup-age', self.nagios_last_backup_age) - self.app.settings.string(['warn-age'], - 'for nagios-last-backup-age: maximum age (by ' - 'default in hours) for the most recent ' - 'backup before status is warning. ' - 'Accepts one char unit specifier ' - '(s,m,h,d for seconds, minutes, hours, ' - 'and days.', - metavar='AGE', - default=obnamlib.DEFAULT_NAGIOS_WARN_AGE) - self.app.settings.string(['critical-age'], - 'for nagios-last-backup-age: maximum age ' - '(by default in hours) for the most ' - 'recent backup before statis is critical. ' - 'Accepts one char unit specifier ' - '(s,m,h,d for seconds, minutes, hours, ' - 'and days.', - metavar='AGE', - default=obnamlib.DEFAULT_NAGIOS_WARN_AGE) + self.app.settings.string( + ['warn-age'], + 'for nagios-last-backup-age: maximum age (by ' + 'default in hours) for the most recent ' + 'backup before status is warning. ' + 'Accepts one char unit specifier ' + '(s,m,h,d for seconds, minutes, hours, ' + 'and days.', + metavar='AGE', + default=obnamlib.DEFAULT_NAGIOS_WARN_AGE) + self.app.settings.string( + ['critical-age'], + 'for nagios-last-backup-age: maximum age ' + '(by default in hours) for the most ' + 'recent backup before statis is critical. ' + 'Accepts one char unit specifier ' + '(s,m,h,d for seconds, minutes, hours, ' + 'and days.', + metavar='AGE', + default=obnamlib.DEFAULT_NAGIOS_WARN_AGE) def open_repository(self, require_client=True): self.app.settings.require('repository') @@ -129,7 +131,7 @@ def nagios_last_backup_age(self, args): '''Check if the most recent generation is recent enough.''' - try: + try: self.open_repository() except obnamlib.ObnamError as e: self.app.output.write('CRITICAL: %s\n' % e) diff -Nru obnam-1.7.4/obnamlib/plugins/verify_plugin.py obnam-1.8/obnamlib/plugins/verify_plugin.py --- obnam-1.7.4/obnamlib/plugins/verify_plugin.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/plugins/verify_plugin.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2010 Lars Wirzenius +# Copyright (C) 2010-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnamlib/plugins/vfs_local_plugin.py obnam-1.8/obnamlib/plugins/vfs_local_plugin.py --- obnam-1.7.4/obnamlib/plugins/vfs_local_plugin.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/plugins/vfs_local_plugin.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright 2010 Lars Wirzenius +# Copyright 2010-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnamlib/repo_dummy.py obnam-1.8/obnamlib/repo_dummy.py --- obnam-1.7.4/obnamlib/repo_dummy.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/repo_dummy.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright 2013 Lars Wirzenius +# Copyright 2013-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -105,7 +105,8 @@ def lock(self): if self.data.locked: - raise obnamlib.RepositoryClientLockingFailed(client_name=self.name) + raise obnamlib.RepositoryClientLockingFailed( + client_name=self.name) self.data.lock() def _require_lock(self): diff -Nru obnam-1.7.4/obnamlib/repo_factory.py obnam-1.8/obnamlib/repo_factory.py --- obnam-1.7.4/obnamlib/repo_factory.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/repo_factory.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright 2013 Lars Wirzenius +# Copyright 2013-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -32,6 +32,11 @@ msg = 'Unknown format {format} requested' +class NotARepository(obnamlib.ObnamError): + + msg = '{url} does not seem to be an Obnam repository' + + class RepositoryFactory(object): '''Create new objects implementing obnamlib.RepositoryInterface.''' @@ -66,7 +71,13 @@ ''' - existing_format = self._read_existing_format(fs) + try: + existing_format = self._read_existing_format(fs) + except EnvironmentError as e: # pragma: no cover + if e.errno == errno.ENOENT: + raise NotARepository(url=fs.baseurl) + raise + for impl in self._implementations: if impl.format == existing_format: return self._open_repo(impl, fs, kwargs) diff -Nru obnam-1.7.4/obnamlib/repo_interface.py obnam-1.8/obnamlib/repo_interface.py --- obnam-1.7.4/obnamlib/repo_interface.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/repo_interface.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,6 +1,6 @@ # repo_interface.py -- interface class for repository access # -# Copyright 2013 Lars Wirzenius +# Copyright 2013-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -148,7 +148,9 @@ class RepositoryGenerationDoesNotExist(obnamlib.ObnamError): - msg = 'Cannot find requested generation {gen_id!r} for client {client_name}' + msg = ( + 'Cannot find requested generation {gen_id!r} ' + 'for client {client_name}') class RepositoryClientHasNoGenerations(obnamlib.ObnamError): @@ -346,7 +348,7 @@ # Client list. def get_client_names(self): - '''Return list of client names currently existing in the repository.''' + '''Return client names currently existing in the repository.''' raise NotImplementedError() def lock_client_list(self): diff -Nru obnam-1.7.4/obnamlib/sizeparse.py obnam-1.8/obnamlib/sizeparse.py --- obnam-1.7.4/obnamlib/sizeparse.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/sizeparse.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright 2010 Lars Wirzenius +# Copyright 2010-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnamlib/sizeparse_tests.py obnam-1.8/obnamlib/sizeparse_tests.py --- obnam-1.7.4/obnamlib/sizeparse_tests.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/sizeparse_tests.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright 2010 Lars Wirzenius +# Copyright 2010-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnamlib/vfs_local.py obnam-1.8/obnamlib/vfs_local.py --- obnam-1.7.4/obnamlib/vfs_local.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/vfs_local.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2008 Lars Wirzenius +# Copyright (C) 2008-2014 Lars Wirzenius # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -277,12 +277,12 @@ def mkdir(self, pathname): tracing.trace('mkdir %s', pathname) - os.mkdir(self.join(pathname)) + os.mkdir(self.join(pathname), obnamlib.NEW_DIR_MODE) self.maybe_crash() def makedirs(self, pathname): tracing.trace('makedirs %s', pathname) - os.makedirs(self.join(pathname)) + os.makedirs(self.join(pathname), obnamlib.NEW_DIR_MODE) self.maybe_crash() def rmdir(self, pathname): @@ -335,7 +335,9 @@ # Nope, didn't work. Now try with O_EXCL instead. try: - fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0666) + fd = os.open( + path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, + obnamlib.NEW_FILE_MODE) os.close(fd) os.rename(tempname, path) except OSError, e: @@ -359,7 +361,7 @@ if not os.path.exists(dirname): tracing.trace('os.makedirs(%s)' % dirname) try: - os.makedirs(dirname) + os.makedirs(dirname, mode=obnamlib.NEW_DIR_MODE) except OSError as e: # pragma: no cover # This avoids a race condition: another Obnam process # may have created the directory between our check and @@ -369,6 +371,7 @@ raise fd, tempname = tempfile.mkstemp(dir=dirname) + os.fchmod(fd, obnamlib.NEW_FILE_MODE) os.close(fd) f = self.open(tempname, 'wb') diff -Nru obnam-1.7.4/obnamlib/vfs_local_tests.py obnam-1.8/obnamlib/vfs_local_tests.py --- obnam-1.7.4/obnamlib/vfs_local_tests.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/vfs_local_tests.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2008 Lars Wirzenius +# Copyright (C) 2008-2014 Lars Wirzenius # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnamlib/vfs.py obnam-1.8/obnamlib/vfs.py --- obnam-1.7.4/obnamlib/vfs.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnamlib/vfs.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (C) 2008, 2010 Lars Wirzenius +# Copyright (C) 2008-2014 Lars Wirzenius # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -24,6 +24,27 @@ import obnamlib +# Modes (permissions) for new directories and files. We use minimal +# permissions to avoid allowing access to files being restored until +# their original permissions have been set. +# +# Directories are RWX for owner, nothing for anyone else. We need +# the R to be able to do listdir, W to create and remove files, and +# X to access anything in the directory. We _could_ do without R, +# but then everything that needs to later read needs to add the R +# manually, and that's most things, so it's much easier to just add +# R to everything. We can't do without W and X. +# +# Files are RW for owner, nothing for anyone else. Again, we could +# do without R in some cases, but they're few enough that it's easier +# to give R to everything. In any case, if an attacker has gained +# access to our UID, they can already chmod to add R and then they +# can read anyway. + +NEW_DIR_MODE = 0700 +NEW_FILE_MODE = 0600 + + class URLSchemeAlreadyRegisteredError(obnamlib.ObnamError): msg = 'VFS URL scheme {scheme} already registered' @@ -545,7 +566,8 @@ st = self.fs.lstat('.') for field in obnamlib.metadata_fields: if field.startswith('st_'): - self.assert_(hasattr(st, field), 'stat must return %s' % field) + self.assertTrue( + hasattr(st, field), 'stat must return %s' % field) def test_lstat_returns_right_filetype_for_directory(self): st = self.fs.lstat('.') diff -Nru obnam-1.7.4/_obnammodule.c obnam-1.8/_obnammodule.c --- obnam-1.7.4/_obnammodule.c 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/_obnammodule.c 2014-05-13 07:05:26.000000000 +0000 @@ -1,7 +1,7 @@ /* * _obnammodule.c -- Python extensions for Obna * - * Copyright (C) 2008, 2009 Lars Wirzenius + * Copyright (C) 2008-2014 Lars Wirzenius * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -194,11 +194,11 @@ size_t i = 0; while (i < n) { unsigned char length = (unsigned char) buf[i]; - memmove(buf + i, buf + i + 1, length); - buf[i + length] = '\0'; - i += length + 1; - } - o = Py_BuildValue("s#", buf, (int) n); + memmove(buf + i, buf + i + 1, length); + buf[i + length] = '\0'; + i += length + 1; + } + o = Py_BuildValue("s#", buf, (int) n); } else { o = Py_BuildValue("i", errno); } @@ -247,7 +247,8 @@ return Py_None; } #ifdef __FreeBSD__ - int n = extattr_get_link(filename, EXTATTR_NAMESPACE_USER, attrname, buf, bufsize); + int n = extattr_get_link(filename, EXTATTR_NAMESPACE_USER, + attrname, buf, bufsize); #else ssize_t n = lgetxattr(filename, attrname, buf, bufsize); #endif @@ -275,7 +276,8 @@ return NULL; #ifdef __FreeBSD__ - ret = extattr_set_link(filename, EXTATTR_NAMESPACE_USER, name, value, size); + ret = extattr_set_link(filename, EXTATTR_NAMESPACE_USER, + name, value, size); #else ret = lsetxattr(filename, name, value, size, 0); #endif diff -Nru obnam-1.7.4/obnam-viewprof obnam-1.8/obnam-viewprof --- obnam-1.7.4/obnam-viewprof 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnam-viewprof 2014-05-13 07:05:26.000000000 +0000 @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright 2010 Lars Wirzenius +# Copyright 2010-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/obnam-viewprof.1 obnam-1.8/obnam-viewprof.1 --- obnam-1.7.4/obnam-viewprof.1 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/obnam-viewprof.1 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -.\" Copyright 2012 Lars Wirzenius +.\" Copyright 2012-2014 Lars Wirzenius .\" .\" This program is free software: you can redistribute it and/or modify .\" it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/publish obnam-1.8/publish --- obnam-1.7.4/publish 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/publish 2014-05-13 07:05:26.000000000 +0000 @@ -6,6 +6,23 @@ # # It is assumed that the docs have been built already. +# Copyright 2014 Lars Wirzenius +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# =*= License: GPL-3+ =*= + host="pieni.net" path="code.liw.fi/obnam" diff -Nru obnam-1.7.4/read-live-data-with-sftp obnam-1.8/read-live-data-with-sftp --- obnam-1.7.4/read-live-data-with-sftp 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/read-live-data-with-sftp 1970-01-01 00:00:00.000000000 +0000 @@ -1,46 +0,0 @@ -#!/usr/bin/env python -# Copyright 2012 Lars Wirzenius -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -import stat -import sys -import ttystatus - -from obnamlib.plugins.sftp_plugin import SftpFS - - -ts = ttystatus.TerminalStatus(period=0.1) -ts['bytes'] = 0 -ts.format( - '%ElapsedTime() %Counter(pathname) %ByteSize(bytes) ' - '%ByteSpeed(bytes) %Pathname(pathname)') - -url = sys.argv[1] -fs = SftpFS(url) -fs.connect() - -for pathname, st in fs.scan_tree('.'): - ts['pathname'] = pathname - if stat.S_ISREG(st.st_mode): - f = fs.open(pathname, 'rb') - while True: - data = f.read(1024**2) - if not data: - break - ts['bytes'] += len(data) - f.close() - -ts.finish() diff -Nru obnam-1.7.4/README obnam-1.8/README --- obnam-1.7.4/README 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/README 2014-05-13 07:05:26.000000000 +0000 @@ -149,7 +149,7 @@ This entire work is covered by the GNU General Public License, version 3 or later. -> Copyright 2010-2013 Lars Wirzenius +> Copyright 2010-2014 Lars Wirzenius > > This program is free software: you can redistribute it and/or modify > it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/run-benchmarks obnam-1.8/run-benchmarks --- obnam-1.7.4/run-benchmarks 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/run-benchmarks 1970-01-01 00:00:00.000000000 +0000 @@ -1,27 +0,0 @@ -#!/bin/sh -# Copyright 2012 Lars Wirzenius -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -set -ue - -for x in confs/*.conf -do - if [ "$x" != confs/common.conf ] - then - ./obnam-benchmark --no-default-config --config=confs/common.conf \ - --config="$x" "$@" - fi -done diff -Nru obnam-1.7.4/sed-in-place obnam-1.8/sed-in-place --- obnam-1.7.4/sed-in-place 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/sed-in-place 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ -#!/bin/sh -# -# Do a sed in place for a set of files. This is like GNU sed -i, but -# we can't assume GNU sed. - -set -eu - -sedcmd="$1" -shift - -for filename in "$@" -do - temp="$(mktemp)" - sed "$sedcmd" "$filename" > "$temp" - mv "$temp" "$filename" -done \ No newline at end of file diff -Nru obnam-1.7.4/setup.py obnam-1.8/setup.py --- obnam-1.7.4/setup.py 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/setup.py 2014-05-13 07:05:26.000000000 +0000 @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (C) 2008-2012 Lars Wirzenius +# Copyright (C) 2008-2014 Lars Wirzenius # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -21,11 +21,14 @@ from distutils.command.clean import clean import glob import os +import re import shutil import subprocess import sys import tempfile +import cliapp + # We need to know whether we can run yarn. We do this by checking # the python-markdown version: if it's new enough, we assume yarn @@ -111,6 +114,7 @@ ('network-lock-tests', 'L', 'run lock tests against localhost?'), ('crash-tests', 'c', 'run crash tests?'), ('sftp-tests', 's', 'run sftp tests against localhost?'), + ('nitpick', 'n', 'check copyright statements, line lengths, etc'), ] def set_all_options(self, new_value): @@ -120,6 +124,7 @@ self.network_lock_tests = new_value self.crash_tests = new_value self.sftp_tests = new_value + self.nitpick = new_value def initialize_options(self): self.set_all_options(False) @@ -131,7 +136,8 @@ self.lock_tests or self.network_lock_tests or self.crash_tests or - self.sftp_tests) + self.sftp_tests or + self.nitpick) if not any_set: self.set_all_options(True) @@ -173,11 +179,63 @@ num_generations, repo_url, test_repo]) shutil.rmtree(test_repo) + if self.nitpick: + sources = self.find_all_source_files() + self.check_sources_for_nitpicks(sources) + self.check_copyright_statements(sources) + print "setup.py check done" + def check_sources_for_nitpicks(self, sources): + cliapp.runcmd(['./nitpicker'] + sources, stdout=None, stderr=None) + + def check_copyright_statements(self, sources): + if self.copylint_is_available(): + print 'check copyright statements in source files' + cliapp.runcmd(['copyright-statement-lint'] + sources) + else: + print 'no copyright-statement-lint: no copyright checks' + + def copylint_is_available(self): + returncode, stdout, stderr = cliapp.runcmd_unchecked( + ['copyright-statement-lint', '--help']) + return returncode == 0 + + def find_all_source_files(self): + exclude = [ + r'\.gpg$', + r'/random_seed$', + r'\.gz$', + r'\.xz$', + r'\.yarn$', + r'\.mdwn$', + r'\.css$', + r'\.conf$', + r'^without-tests$', + r'^test-plugins/.*\.py$', + r'^debian/', + r'^README\.', + r'^NEWS$', + r'^COPYING$', + r'^CC-BY-SA-4\.0\.txt$', + r'^\.gitignore$', + ] + + pats = [re.compile(x) for x in exclude] + + output = cliapp.runcmd(['git', 'ls-files']) + result = [] + for line in output.splitlines(): + for pat in pats: + if pat.search(line): + break + else: + result.append(line) + return result + setup(name='obnam', - version='1.7.4', + version='1.8', description='Backup software', author='Lars Wirzenius', author_email='liw@liw.fi', diff -Nru obnam-1.7.4/test-lock-files obnam-1.8/test-lock-files --- obnam-1.7.4/test-lock-files 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/test-lock-files 2014-05-13 07:05:26.000000000 +0000 @@ -2,7 +2,7 @@ # # Obnam test: test lock files. # -# Copyright 2012 Lars Wirzenius +# Copyright 2012-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/test-locking obnam-1.8/test-locking --- obnam-1.7.4/test-locking 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/test-locking 2014-05-13 07:05:26.000000000 +0000 @@ -2,7 +2,7 @@ # # Obnam test: test locking with multiple clients accessing the same repo. # -# Copyright 2012 Lars Wirzenius +# Copyright 2012-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/test-many-generations obnam-1.8/test-many-generations --- obnam-1.7.4/test-many-generations 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/test-many-generations 2014-05-13 07:05:26.000000000 +0000 @@ -2,7 +2,7 @@ # # Obnam test: backup and verify many generations of data. # -# Copyright 2012 Lars Wirzenius +# Copyright 2012-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/test-paramiko obnam-1.8/test-paramiko --- obnam-1.7.4/test-paramiko 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/test-paramiko 1970-01-01 00:00:00.000000000 +0000 @@ -1,147 +0,0 @@ -# Copyright 2010 Lars Wirzenius -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -'''Test paramiko by doing an sftp copy from localhost.''' - - -import os -import paramiko -import pwd -import socket -import subprocess -import sys -import tempfile -import time - - -class SSHChannelAdapter(object): - - '''Take an ssh subprocess and pretend it is a paramiko Channel.''' - - def __init__(self, proc): - self.proc = proc - - def send(self, data): - return os.write(self.proc.stdin.fileno(), data) - - def recv(self, count): - try: - return os.read(self.proc.stdout.fileno(), count) - except socket.error, e: - if e.args[0] in (errno.EPIPE, errno.ECONNRESET, errno.ECONNABORTED, - errno.EBADF): - # Connection has closed. Paramiko expects an empty string in - # this case, not an exception. - return '' - raise - - def get_name(self): - return 'obnam SSHChannelAdapter' - - def close(self): - for func in [proc.stdin.close, proc.stdout.close, proc.wait]: - try: - func() - except OSError: - pass - - -username = pwd.getpwuid(os.getuid()).pw_name -host = 'localhost' -port = 22 -path = '/tmp/zeroes' - -subsystem = 'sftp' -args = ['ssh', - '-oForwardX11=no', '-oForwardAgent=no', - '-oClearAllForwardings=yes', '-oProtocol=2'] -if port is not None: - args.extend(['-p', str(port)]) -if username is not None: - args.extend(['-l', username]) -args.extend(['-s', host, subsystem]) - -proc = transport = None -try: - raise OSError(None, None, None) - proc = subprocess.Popen(args, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - close_fds=True) -except OSError: - transport = paramiko.Transport((host, port)) - transport.connect() - agent = paramiko.Agent() - agent_keys = agent.get_keys() - for key in agent_keys: - try: - transport.auth_publickey(username, key) - break - except paramiko.SSHException: - pass - else: - raise Exception('no auth') - - try: - keys = paramiko.util.load_host_keys( - os.path.expanduser('~/.ssh/known_hosts')) - except IOError: - print '*** Unable to open host keys file' - sys.exit(1) - - t = transport - hostname = host - key = t.get_remote_server_key() - - k = keys.lookup(hostname) - if k is None: - print 'unknown host %s (using it anyway)' % hostname - else: - key_type = key.get_name() - if not k.has_key(key_type): - print 'no key of type %s for %s' % (key_type, hostname) - print 'accepting host key anyway' - else: - host_key = k[key_type] - if host_key != key: - print 'wrong host key for %s' % hostname - sys.exit(1) - else: - print 'good host key for %s' % hostname - - sftp = paramiko.SFTPClient.from_transport(transport) -else: - sftp = paramiko.SFTPClient(SSHChannelAdapter(proc)) - -n = 0 -f = sftp.open(path) -start = time.time() -while True: - data = f.read(32*1024) - if not data: - break - n += len(data) -end = time.time() -f.close() -sftp.close() -if transport: - transport.close() -if proc: - proc.close() - -duration = end - start -n = float(n) -print duration, n/1024/1024, 8 * n / duration / 1024 / 1024 diff -Nru obnam-1.7.4/test-sftpfs obnam-1.8/test-sftpfs --- obnam-1.7.4/test-sftpfs 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/test-sftpfs 2014-05-13 07:05:26.000000000 +0000 @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright 2010 Lars Wirzenius +# Copyright 2010-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/try-node-chunk-sizes obnam-1.8/try-node-chunk-sizes --- obnam-1.7.4/try-node-chunk-sizes 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/try-node-chunk-sizes 1970-01-01 00:00:00.000000000 +0000 @@ -1,43 +0,0 @@ -#!/bin/sh -# -# Run obnam with various node and chunk sizes, in order to see which ones -# are optimal. - -set -e - -datadir="$1" - -sizes="8kib 16kib 32kib 64kib 128kib 256kib 512kib 1024kib" -sizes="8kib 16kib" - -conf="$(mktemp)" - -rm -rf try-sizes -mkdir try-sizes -for node_size in $sizes -do - for chunk_size in $sizes - do - echo -------------------------------- - echo "node=$node_size chunk=$chunk_size" - cat << eof > "$conf" -[config] -chunk-size = $chunk_size -node-size = $node_size -eof - output="try-sizes/${node_size}-${chunk_size}.seivot" - $HOME/seivot/trunk/seivot \ - --output="$output" \ - --drop-caches \ - --obnam-config="$conf" \ - --generations=2 \ - --incremental=1 \ - --use-existing="$datadir" \ - --obnam-branch=. \ - --larch-branch=$HOME/larch/trunk \ - --description="node=$node_size chunk=$chunk_size" \ - --profile-name="real data" - done -done - -rm -f "$conf" diff -Nru obnam-1.7.4/try-node-chunk-sizes-plot-results obnam-1.8/try-node-chunk-sizes-plot-results --- obnam-1.7.4/try-node-chunk-sizes-plot-results 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/try-node-chunk-sizes-plot-results 1970-01-01 00:00:00.000000000 +0000 @@ -1,62 +0,0 @@ -#!/usr/bin/env python - -import ConfigParser -import re -import subprocess -import sys - - -ops = ['backup', 'restore', 'forget'] - - -def parse_size(spec): - m = re.search('\d+', spec) - return int(m.group()) - - -def dat_filename(op, node_size): - return '%s-%s-%d.dat' % (sys.argv[1], op, node_size) - - -def plotfile(op, node_size): - return ('"%s" with lines title "node size %d KiB"' % - (dat_filename(op, node_size), node_size)) - - -for op in ops: - data = {} - - for filename in sys.argv[2:]: - cp = ConfigParser.ConfigParser() - cp.read(filename) - desc = cp.get('meta', 'description') - words = desc.split() - node_size = parse_size(words[0]) - chunk_size = parse_size(words[1]) - secs = cp.getfloat('0', '%s.real' % op) - data[node_size] = data.get(node_size, []) + [(chunk_size, secs)] - - for node_size in data: - points = sorted(data[node_size]) - name = dat_filename(op, node_size) - with open(name, 'w') as f: - for chunk_size, secs in points: - f.write('%d %f\n' % (chunk_size, secs)) - - gnuplot = '''\ -set terminal svg dynamic -set title "%s %s" -set xlabel "chunk size (KiB)" -set ylabel "time (s)" -''' % (sys.argv[1], op) - - gnuplot += ('plot' + - ', '.join(plotfile(op, x) for x in sorted(data.keys())) + - '\n') - - script_name = '%s-%s.gnuplot' % (sys.argv[1], op) - with open(script_name, 'w') as f: - f.write(gnuplot) - with open('%s-%s.svg' % (sys.argv[1], op), 'wb') as outp: - subprocess.check_call(['gnuplot', script_name], stdout=outp) - diff -Nru obnam-1.7.4/verification-test obnam-1.8/verification-test --- obnam-1.7.4/verification-test 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/verification-test 2014-05-13 07:05:26.000000000 +0000 @@ -34,7 +34,7 @@ # fail even though Obnam was working ine: it's just that some file changed # between Obnam backing it up and summain including it in the manifest. # -# Copyright 2011 Lars Wirzenius +# Copyright 2011-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff -Nru obnam-1.7.4/without-tests obnam-1.8/without-tests --- obnam-1.7.4/without-tests 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/without-tests 2014-05-13 07:05:26.000000000 +0000 @@ -1,6 +1,7 @@ setup.py obnamlib/__init__.py obnamlib/app.py +obnamlib/humanise.py obnamlib/fsck_work_item.py obnamlib/repo_interface.py obnamlib/vfs.py diff -Nru obnam-1.7.4/yarns/0030-basics.yarn obnam-1.8/yarns/0030-basics.yarn --- obnam-1.7.4/yarns/0030-basics.yarn 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/yarns/0030-basics.yarn 2014-05-13 07:05:26.000000000 +0000 @@ -157,6 +157,26 @@ THEN the attempt succeeded +Backup root is not a directory +------------------------------ + +Obnam does not, currently, support backing up individual files. The +backup root must be a directory. This scenario verifies that Obnam +gives the right error if the root is not a directory. The error code +is R79ED6X, for "Backup root does not exist or is not a directory". + + SCENARIO backup root is not a directory + + GIVEN a symlink L pointing at /dev/null + WHEN user U attempts to back up directory L to repository R + THEN the attempt failed with exit code 1 + THEN the error message matches "R79ED6X" + + GIVEN a file F in ., with data + WHEN user U attempts to back up directory F to repository R + THEN the attempt failed with exit code 1 + THEN the error message matches "R79ED6X" + Backup to roots at once ----------------------- @@ -247,6 +267,25 @@ AND user U restores their latest generation in repository R into X THEN L, restored to X, matches manifest M +Excluded, already backed up files, are not included in next generation +---------------------------------------------------------------------- + +Until Obnam version 1.7.4, but fixed after that, Obnam had a bug where +a file that was not excluded became excluded was not removed from new +backup generations. In other words, if file `foo` exists and is backed +up, and the user then makes a new backup with `--exclude=foo`, the new +backup generation still contains `foo`. This is clearly a bug. This +scenario verifies that the bug no longer exists, and prevents it from +recurring. + + SCENARIO new generation drops excluded, previously backed up files + GIVEN a file foo in L, with data + WHEN user U backs up directory L to repository R + GIVEN user U sets configuration exclude to foo + WHEN user U backs up directory L to repository R + AND user U restores their latest generation in repository R into X + THEN L, restored to X, is empty + Changing backup roots --------------------- diff -Nru obnam-1.7.4/yarns/0090-lock-handling.yarn obnam-1.8/yarns/0090-lock-handling.yarn --- obnam-1.7.4/yarns/0090-lock-handling.yarn 1970-01-01 00:00:00.000000000 +0000 +++ obnam-1.8/yarns/0090-lock-handling.yarn 2014-05-13 07:05:26.000000000 +0000 @@ -0,0 +1,65 @@ +Lock handling +============= + +This chapter contains scenarios for testing Obnam's lock handling, +specifically the forcing of locks to become open when lock files have +been left by Obnam for whatever reason. + + +Basic forcing of a lock +----------------------- + +In this scenario, we force a repository to be locked, and force the +lock open. To do this, we use an Obnam command that locks the desired +parts of the repository, and does nothing else; this is a testing aid. + + SCENARIO force repository open + +We first create the repository and back up some data. + + GIVEN 1kB of new data in directory L + WHEN user U backs up directory L to repository R + +We then lock the repository, and verify that a backup now fails. + + AND user U locks repository R + AND user U attempts to back up directory L to repository R + THEN the attempt failed with exit code 1 + AND the error message matches "R681AEX" + +Now we can force the lock open and verify that a backup now succeeds. + + WHEN user U forces open the lock on repository R + AND user U attempts to back up directory L to repository R + THEN the attempt succeeded + + +Forcing of someone else's lock +------------------------------ + +We also need to force a lock by someone else. This is otherwise +similar to the basic lock forcing scenario, but the lock is held by a +different client. The lock is created before the second client even +gets added to the client list, to maximise the difficulty. + + SCENARIO force someone else's lock + +We first create the repository and back up some data as the first +client. + + GIVEN 1kB of new data in directory L + WHEN user U1 backs up directory L to repository R + +We then lock the repository as the first user, and verify that a +backup now fails when run as the second client. + + AND user U1 locks repository R + AND user U2 attempts to back up directory L to repository R + THEN the attempt failed with exit code 1 + AND the error message matches "R681AEX" + +The second client can force the lock open and successfully back up. + + WHEN user U2 forces open the lock on repository R + AND user U2 attempts to back up directory L to repository R + THEN the attempt succeeded diff -Nru obnam-1.7.4/yarns/9000-implements.yarn obnam-1.8/yarns/9000-implements.yarn --- obnam-1.7.4/yarns/9000-implements.yarn 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/yarns/9000-implements.yarn 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ - implementation +Test implementation =================== This chapter documents the generic, shared IMPLEMENTS sections for @@ -104,6 +104,11 @@ mkdir -p $(dirname "$DATADIR/$MATCH_1") setfattr --name="$MATCH_2" --value "$MATCH_3" "$DATADIR/$MATCH_1" +Create a symlink. + + IMPLEMENTS GIVEN a symlink (\S+) pointing at (\S+) + ln -s "$MATCH_2" "$DATADIR/$MATCH_1" + Sometimes we need to remove a file. IMPLEMENTS WHEN user (\S+) removes file (\S+) @@ -451,6 +456,21 @@ fingerprint="$(get_fingerprint "$MATCH_2")" gpg --batch --delete-secret-key "$fingerprint" + +Lock management +--------------- + +We need to lock parts of the repository, and force those locks open. + + IMPLEMENTS WHEN user (\S+) locks repository (\S+) + run_obnam "$MATCH_1" -r "$DATADIR/$MATCH_2" _lock + +Force it open. + + IMPLEMENTS WHEN user (\S+) forces open the lock on repository (\S+) + run_obnam "$MATCH_1" -r "$DATADIR/$MATCH_2" force-lock + + Client management ----------------- @@ -533,6 +553,14 @@ new=$(stat -c %b "$DATADIR/$MATCH_3/$DATADIR/$MATCH_2/$MATCH_1") test "$old" -lt "$new" +Check that a restored directory is empty. + + IMPLEMENTS THEN (\S+), restored to (\S+), is empty + if find "$DATADIR/$MATCH_2/$DATADIR/$MATCH_1" -mindepth 1 | grep . + then + die "$DATADIR/$MATCH_2/$DATADIR/$MATCH_1 isn't empty" + fi + Checks on contents of files --------------------------- diff -Nru obnam-1.7.4/yarns/Makefile obnam-1.8/yarns/Makefile --- obnam-1.7.4/yarns/Makefile 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/yarns/Makefile 2014-05-13 07:05:26.000000000 +0000 @@ -1,3 +1,21 @@ +# Copyright 2013-2014 Lars Wirzenius +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# =*= License: GPL-3+ =*= + + yarns = $(shell ls [0-9][0-9][0-9][0-9]-*.yarn) all: yarns.pdf yarns.html diff -Nru obnam-1.7.4/yarns/obnam.sh obnam-1.8/yarns/obnam.sh --- obnam-1.7.4/yarns/obnam.sh 2014-04-01 11:46:42.000000000 +0000 +++ obnam-1.8/yarns/obnam.sh 2014-05-13 07:05:26.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright 2013 Lars Wirzenius +# Copyright 2013-2014 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,6 +16,15 @@ # =*= License: GPL-3+ =*= +# A helper to print an error message and terminate. + +die() +{ + printf '%s\n' -- "$*" 1>&2 + exit 1 +} + + # Run Obnam in a safe way that ignore's any configuration files # outside the test. The first argument MUST be the client name. The # configuration file $DATADIR/$1.conf is used, if it exists. In addition, @@ -39,6 +48,12 @@ # numbers for tests. add_to_config "$name" weak-random yes + # Make lock timeout be very short, to make test suite not take + # long. Since the test suite only does one thing at a time, and + # does not run things in parallel, there's no way a lock will go + # away, so a zero timeout is OK. + add_to_config "$name" lock-timeout 0 + ( if [ -e "$DATADIR/$name.env" ] then