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