Browse Source

Merge branch '1.0-maint'

Thomas Waldmann 9 years ago
parent
commit
4de14fdd28
12 changed files with 481 additions and 182 deletions
  1. 3 3
      README.rst
  2. 2 3
      Vagrantfile
  3. 23 2
      borg/_chunker.c
  4. 0 48
      borg/_hashindex.c
  5. 1 2
      borg/archive.py
  6. 17 16
      borg/archiver.py
  7. 12 12
      borg/cache.py
  8. 145 30
      borg/hashindex.pyx
  9. 176 0
      borg/testsuite/hashindex.py
  10. 36 0
      docs/changes.rst
  11. 16 16
      docs/quickstart.rst
  12. 50 50
      docs/usage.rst

+ 3 - 3
README.rst

@@ -84,12 +84,12 @@ Easy to use
 
 
 Initialize a new backup repository and create a backup archive::
 Initialize a new backup repository and create a backup archive::
 
 
-    $ borg init /mnt/backup
-    $ borg create /mnt/backup::Saturday1 ~/Documents
+    $ borg init /path/to/repo
+    $ borg create /path/to/repo::Saturday1 ~/Documents
 
 
 Now doing another backup, just to show off the great deduplication::
 Now doing another backup, just to show off the great deduplication::
 
 
-    $ borg create -v --stats /mnt/backup::Saturday2 ~/Documents
+    $ borg create -v --stats /path/to/repo::Saturday2 ~/Documents
     -----------------------------------------------------------------------------
     -----------------------------------------------------------------------------
     Archive name: Saturday2
     Archive name: Saturday2
     Archive fingerprint: 622b7c53c...
     Archive fingerprint: 622b7c53c...

+ 2 - 3
Vagrantfile

@@ -132,7 +132,7 @@ def packages_netbsd
     touch /etc/openssl/openssl.cnf  # avoids a flood of "can't open ..."
     touch /etc/openssl/openssl.cnf  # avoids a flood of "can't open ..."
     mozilla-rootcerts install
     mozilla-rootcerts install
     pkg_add pkg-config  # avoids some "pkg-config missing" error msg, even without fuse
     pkg_add pkg-config  # avoids some "pkg-config missing" error msg, even without fuse
-    # pkg_add fuse  # llfuse 0.41.1 supports netbsd, but is still buggy.
+    # pkg_add fuse  # llfuse supports netbsd, but is still buggy.
     # https://bitbucket.org/nikratio/python-llfuse/issues/70/perfuse_open-setsockopt-no-buffer-space
     # https://bitbucket.org/nikratio/python-llfuse/issues/70/perfuse_open-setsockopt-no-buffer-space
     pkg_add python34 py34-setuptools
     pkg_add python34 py34-setuptools
     ln -s /usr/pkg/bin/python3.4 /usr/pkg/bin/python
     ln -s /usr/pkg/bin/python3.4 /usr/pkg/bin/python
@@ -200,9 +200,8 @@ def install_borg(boxname)
     rm -f borg/*.so borg/*.cpy*
     rm -f borg/*.so borg/*.cpy*
     rm -f borg/{chunker,crypto,compress,hashindex,platform_linux}.c
     rm -f borg/{chunker,crypto,compress,hashindex,platform_linux}.c
     rm -rf borg/__pycache__ borg/support/__pycache__ borg/testsuite/__pycache__
     rm -rf borg/__pycache__ borg/support/__pycache__ borg/testsuite/__pycache__
-    pip install 'llfuse<0.41'  # 0.41.1 throws UnicodeDecodeError at install time:
-    # https://bitbucket.org/nikratio/python-llfuse/issues/69/unicode-exception-at-install-time
     pip install -r requirements.d/development.txt
     pip install -r requirements.d/development.txt
+    pip install -r requirements.d/fuse.txt
     pip install -e .
     pip install -e .
   EOF
   EOF
 end
 end

+ 23 - 2
borg/_chunker.c

@@ -41,6 +41,7 @@ static uint32_t table_base[] =
 
 
 #define BARREL_SHIFT(v, shift) ( ((v) << shift) | ((v) >> (32 - shift)) )
 #define BARREL_SHIFT(v, shift) ( ((v) << shift) | ((v) >> (32 - shift)) )
 
 
+size_t pagemask;
 
 
 static uint32_t *
 static uint32_t *
 buzhash_init_table(uint32_t seed)
 buzhash_init_table(uint32_t seed)
@@ -130,6 +131,7 @@ chunker_fill(Chunker *c)
 {
 {
     ssize_t n;
     ssize_t n;
     off_t offset, length;
     off_t offset, length;
+    int overshoot;
     PyObject *data;
     PyObject *data;
     memmove(c->data, c->data + c->last, c->position + c->remaining - c->last);
     memmove(c->data, c->data + c->last, c->position + c->remaining - c->last);
     c->position -= c->last;
     c->position -= c->last;
@@ -157,14 +159,33 @@ chunker_fill(Chunker *c)
         }
         }
         length = c->bytes_read - offset;
         length = c->bytes_read - offset;
         #if ( ( _XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L ) && defined(POSIX_FADV_DONTNEED) )
         #if ( ( _XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L ) && defined(POSIX_FADV_DONTNEED) )
+
+	// Only do it once per run.
+	if (pagemask == 0)
+		pagemask = getpagesize() - 1;
+
         // We tell the OS that we do not need the data that we just have read any
         // We tell the OS that we do not need the data that we just have read any
         // more (that it maybe has in the cache). This avoids that we spoil the
         // more (that it maybe has in the cache). This avoids that we spoil the
         // complete cache with data that we only read once and (due to cache
         // complete cache with data that we only read once and (due to cache
         // size limit) kick out data from the cache that might be still useful
         // size limit) kick out data from the cache that might be still useful
         // for the OS or other processes.
         // for the OS or other processes.
+        // We rollback the initial offset back to the start of the page,
+        // to avoid it not being truncated as a partial page request.
         if (length > 0) {
         if (length > 0) {
-            posix_fadvise(c->fh, offset, length, POSIX_FADV_DONTNEED);
-        }
+            // Linux kernels prior to 4.7 have a bug where they truncate
+            // last partial page of POSIX_FADV_DONTNEED request, so we need
+            // to page-align it ourselves. We'll need the rest of this page
+            // on the next read (assuming this was not EOF)
+            overshoot = (offset + length) & pagemask;
+        } else {
+            // For length == 0 we set overshoot 0, so the below
+            // length - overshoot is 0, which means till end of file for
+            // fadvise. This will cancel the final page and is not part
+            // of the above workaround.
+            overshoot = 0;
+	}
+
+        posix_fadvise(c->fh, offset & ~pagemask, length - overshoot, POSIX_FADV_DONTNEED);
         #endif
         #endif
     }
     }
     else {
     else {

+ 0 - 48
borg/_hashindex.c

@@ -439,51 +439,3 @@ hashindex_get_size(HashIndex *index)
 {
 {
     return index->num_entries;
     return index->num_entries;
 }
 }
-
-static void
-hashindex_summarize(HashIndex *index, long long *total_size, long long *total_csize,
-                    long long *total_unique_size, long long *total_unique_csize,
-                    long long *total_unique_chunks, long long *total_chunks)
-{
-    int64_t size = 0, csize = 0, unique_size = 0, unique_csize = 0, chunks = 0, unique_chunks = 0;
-    const int32_t *values;
-    void *key = NULL;
-
-    while((key = hashindex_next_key(index, key))) {
-        values = key + index->key_size;
-        unique_chunks++;
-        chunks += values[0];
-        unique_size += values[1];
-        unique_csize += values[2];
-        size += (int64_t) values[0] * values[1];
-        csize += (int64_t) values[0] * values[2];
-    }
-    *total_size = size;
-    *total_csize = csize;
-    *total_unique_size = unique_size;
-    *total_unique_csize = unique_csize;
-    *total_unique_chunks = unique_chunks;
-    *total_chunks = chunks;
-}
-
-static void
-hashindex_add(HashIndex *index, const void *key, int32_t *other_values)
-{
-    int32_t *my_values = (int32_t *)hashindex_get(index, key);
-    if(my_values == NULL) {
-        hashindex_set(index, key, other_values);
-    } else {
-        *my_values += *other_values;
-    }
-}
-
-static void
-hashindex_merge(HashIndex *index, HashIndex *other)
-{
-    int32_t key_size = index->key_size;
-    void *key = NULL;
-
-    while((key = hashindex_next_key(other, key))) {
-        hashindex_add(index, key, key + key_size);
-    }
-}

+ 1 - 2
borg/archive.py

@@ -786,8 +786,7 @@ class ArchiveChecker:
 
 
         def add_reference(id_, size, csize, cdata=None):
         def add_reference(id_, size, csize, cdata=None):
             try:
             try:
-                count, _, _ = self.chunks[id_]
-                self.chunks[id_] = count + 1, size, csize
+                self.chunks.incref(id_)
             except KeyError:
             except KeyError:
                 assert cdata is not None
                 assert cdata is not None
                 self.chunks[id_] = 1, size, csize
                 self.chunks[id_] = 1, size, csize

+ 17 - 16
borg/archiver.py

@@ -306,7 +306,7 @@ class Archiver:
         if (st.st_ino, st.st_dev) in skip_inodes:
         if (st.st_ino, st.st_dev) in skip_inodes:
             return
             return
         # Entering a new filesystem?
         # Entering a new filesystem?
-        if restrict_dev and st.st_dev != restrict_dev:
+        if restrict_dev is not None and st.st_dev != restrict_dev:
             return
             return
         status = None
         status = None
         # Ignore if nodump flag is set
         # Ignore if nodump flag is set
@@ -628,22 +628,23 @@ class Archiver:
         cache.commit()
         cache.commit()
         return self.exit_code
         return self.exit_code
 
 
-    @with_repository(exclusive=True, cache=True)
-    def do_delete(self, args, repository, manifest, key, cache):
+    @with_repository(exclusive=True)
+    def do_delete(self, args, repository, manifest, key):
         """Delete an existing repository or archive"""
         """Delete an existing repository or archive"""
         if args.location.archive:
         if args.location.archive:
-            archive = Archive(repository, key, manifest, args.location.archive, cache=cache)
-            stats = Statistics()
-            archive.delete(stats, progress=args.progress)
-            manifest.write()
-            repository.commit(save_space=args.save_space)
-            cache.commit()
-            logger.info("Archive deleted.")
-            if args.stats:
-                log_multi(DASHES,
-                          stats.summary.format(label='Deleted data:', stats=stats),
-                          str(cache),
-                          DASHES)
+            with Cache(repository, key, manifest, lock_wait=self.lock_wait) as cache:
+                archive = Archive(repository, key, manifest, args.location.archive, cache=cache)
+                stats = Statistics()
+                archive.delete(stats, progress=args.progress)
+                manifest.write()
+                repository.commit(save_space=args.save_space)
+                cache.commit()
+                logger.info("Archive deleted.")
+                if args.stats:
+                    log_multi(DASHES,
+                              stats.summary.format(label='Deleted data:', stats=stats),
+                              str(cache),
+                              DASHES)
         else:
         else:
             if not args.cache_only:
             if not args.cache_only:
                 msg = []
                 msg = []
@@ -658,7 +659,7 @@ class Archiver:
                     return self.exit_code
                     return self.exit_code
                 repository.destroy()
                 repository.destroy()
                 logger.info("Repository deleted.")
                 logger.info("Repository deleted.")
-            cache.destroy()
+            Cache.destroy(repository)
             logger.info("Cache deleted.")
             logger.info("Cache deleted.")
         return self.exit_code
         return self.exit_code
 
 

+ 12 - 12
borg/cache.py

@@ -37,6 +37,15 @@ class Cache:
         path = path or os.path.join(get_cache_dir(), hexlify(repository.id).decode('ascii'))
         path = path or os.path.join(get_cache_dir(), hexlify(repository.id).decode('ascii'))
         UpgradableLock(os.path.join(path, 'lock'), exclusive=True).break_lock()
         UpgradableLock(os.path.join(path, 'lock'), exclusive=True).break_lock()
 
 
+    @staticmethod
+    def destroy(repository, path=None):
+        """destroy the cache for ``repository`` or at ``path``"""
+        path = path or os.path.join(get_cache_dir(), hexlify(repository.id).decode('ascii'))
+        config = os.path.join(path, 'config')
+        if os.path.exists(config):
+            os.remove(config)  # kill config first
+            shutil.rmtree(path)
+
     def __init__(self, repository, key, manifest, path=None, sync=True, do_files=False, warn_if_unencrypted=True,
     def __init__(self, repository, key, manifest, path=None, sync=True, do_files=False, warn_if_unencrypted=True,
                  lock_wait=None):
                  lock_wait=None):
         """
         """
@@ -131,13 +140,6 @@ Chunk index:    {0.total_unique_chunks:20d} {0.total_chunks:20d}"""
         with open(os.path.join(self.path, 'files'), 'wb') as fd:
         with open(os.path.join(self.path, 'files'), 'wb') as fd:
             pass  # empty file
             pass  # empty file
 
 
-    def destroy(self):
-        """destroy the cache at `self.path`
-        """
-        self.close()
-        os.remove(os.path.join(self.path, 'config'))  # kill config first
-        shutil.rmtree(self.path)
-
     def _do_open(self):
     def _do_open(self):
         self.config = configparser.ConfigParser(interpolation=None)
         self.config = configparser.ConfigParser(interpolation=None)
         config_path = os.path.join(self.path, 'config')
         config_path = os.path.join(self.path, 'config')
@@ -389,21 +391,19 @@ Chunk index:    {0.total_unique_chunks:20d} {0.total_chunks:20d}"""
     def chunk_incref(self, id, stats):
     def chunk_incref(self, id, stats):
         if not self.txn_active:
         if not self.txn_active:
             self.begin_txn()
             self.begin_txn()
-        count, size, csize = self.chunks[id]
-        self.chunks[id] = (count + 1, size, csize)
+        count, size, csize = self.chunks.incref(id)
         stats.update(size, csize, False)
         stats.update(size, csize, False)
         return id, size, csize
         return id, size, csize
 
 
     def chunk_decref(self, id, stats):
     def chunk_decref(self, id, stats):
         if not self.txn_active:
         if not self.txn_active:
             self.begin_txn()
             self.begin_txn()
-        count, size, csize = self.chunks[id]
-        if count == 1:
+        count, size, csize = self.chunks.decref(id)
+        if count == 0:
             del self.chunks[id]
             del self.chunks[id]
             self.repository.delete(id, wait=False)
             self.repository.delete(id, wait=False)
             stats.update(-size, -csize, True)
             stats.update(-size, -csize, True)
         else:
         else:
-            self.chunks[id] = (count - 1, size, csize)
             stats.update(-size, -csize, False)
             stats.update(-size, -csize, False)
 
 
     def file_known_and_unchanged(self, path_hash, st, ignore_inode=False):
     def file_known_and_unchanged(self, path_hash, st, ignore_inode=False):

+ 145 - 30
borg/hashindex.pyx

@@ -1,6 +1,9 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 import os
 import os
 
 
+cimport cython
+from libc.stdint cimport uint32_t, UINT32_MAX, uint64_t
+
 API_VERSION = 2
 API_VERSION = 2
 
 
 
 
@@ -11,9 +14,6 @@ cdef extern from "_hashindex.c":
     HashIndex *hashindex_read(char *path)
     HashIndex *hashindex_read(char *path)
     HashIndex *hashindex_init(int capacity, int key_size, int value_size)
     HashIndex *hashindex_init(int capacity, int key_size, int value_size)
     void hashindex_free(HashIndex *index)
     void hashindex_free(HashIndex *index)
-    void hashindex_summarize(HashIndex *index, long long *total_size, long long *total_csize,
-                             long long *unique_size, long long *unique_csize,
-                             long long *total_unique_chunks, long long *total_chunks)
     void hashindex_merge(HashIndex *index, HashIndex *other)
     void hashindex_merge(HashIndex *index, HashIndex *other)
     void hashindex_add(HashIndex *index, void *key, void *value)
     void hashindex_add(HashIndex *index, void *key, void *value)
     int hashindex_get_size(HashIndex *index)
     int hashindex_get_size(HashIndex *index)
@@ -22,13 +22,34 @@ cdef extern from "_hashindex.c":
     void *hashindex_next_key(HashIndex *index, void *key)
     void *hashindex_next_key(HashIndex *index, void *key)
     int hashindex_delete(HashIndex *index, void *key)
     int hashindex_delete(HashIndex *index, void *key)
     int hashindex_set(HashIndex *index, void *key, void *value)
     int hashindex_set(HashIndex *index, void *key, void *value)
-    int _htole32(int v)
-    int _le32toh(int v)
+    uint32_t _htole32(uint32_t v)
+    uint32_t _le32toh(uint32_t v)
 
 
 
 
 cdef _NoDefault = object()
 cdef _NoDefault = object()
 
 
-cimport cython
+"""
+The HashIndex is *not* a general purpose data structure. The value size must be at least 4 bytes, and these
+first bytes are used for in-band signalling in the data structure itself.
+
+The constant MAX_VALUE defines the valid range for these 4 bytes when interpreted as an uint32_t from 0
+to MAX_VALUE (inclusive). The following reserved values beyond MAX_VALUE are currently in use
+(byte order is LE)::
+
+    0xffffffff marks empty entries in the hashtable
+    0xfffffffe marks deleted entries in the hashtable
+
+None of the publicly available classes in this module will accept nor return a reserved value;
+AssertionError is raised instead.
+"""
+
+assert UINT32_MAX == 2**32-1
+
+# module-level constant because cdef's in classes can't have default values
+cdef uint32_t _MAX_VALUE = 2**32-1025
+MAX_VALUE = _MAX_VALUE
+
+assert _MAX_VALUE % 2 == 1
 
 
 @cython.internal
 @cython.internal
 cdef class IndexBase:
 cdef class IndexBase:
@@ -101,22 +122,30 @@ cdef class NSIndex(IndexBase):
 
 
     def __getitem__(self, key):
     def __getitem__(self, key):
         assert len(key) == self.key_size
         assert len(key) == self.key_size
-        data = <int *>hashindex_get(self.index, <char *>key)
+        data = <uint32_t *>hashindex_get(self.index, <char *>key)
         if not data:
         if not data:
-            raise KeyError
-        return _le32toh(data[0]), _le32toh(data[1])
+            raise KeyError(key)
+        cdef uint32_t segment = _le32toh(data[0])
+        assert segment <= _MAX_VALUE, "maximum number of segments reached"
+        return segment, _le32toh(data[1])
 
 
     def __setitem__(self, key, value):
     def __setitem__(self, key, value):
         assert len(key) == self.key_size
         assert len(key) == self.key_size
-        cdef int[2] data
-        data[0] = _htole32(value[0])
+        cdef uint32_t[2] data
+        cdef uint32_t segment = value[0]
+        assert segment <= _MAX_VALUE, "maximum number of segments reached"
+        data[0] = _htole32(segment)
         data[1] = _htole32(value[1])
         data[1] = _htole32(value[1])
         if not hashindex_set(self.index, <char *>key, data):
         if not hashindex_set(self.index, <char *>key, data):
             raise Exception('hashindex_set failed')
             raise Exception('hashindex_set failed')
 
 
     def __contains__(self, key):
     def __contains__(self, key):
+        cdef uint32_t segment
         assert len(key) == self.key_size
         assert len(key) == self.key_size
-        data = <int *>hashindex_get(self.index, <char *>key)
+        data = <uint32_t *>hashindex_get(self.index, <char *>key)
+        if data != NULL:
+            segment = _le32toh(data[0])
+            assert segment <= _MAX_VALUE, "maximum number of segments reached"
         return data != NULL
         return data != NULL
 
 
     def iteritems(self, marker=None):
     def iteritems(self, marker=None):
@@ -149,25 +178,46 @@ cdef class NSKeyIterator:
         self.key = hashindex_next_key(self.index, <char *>self.key)
         self.key = hashindex_next_key(self.index, <char *>self.key)
         if not self.key:
         if not self.key:
             raise StopIteration
             raise StopIteration
-        cdef int *value = <int *>(self.key + self.key_size)
-        return (<char *>self.key)[:self.key_size], (_le32toh(value[0]), _le32toh(value[1]))
+        cdef uint32_t *value = <uint32_t *>(self.key + self.key_size)
+        cdef uint32_t segment = _le32toh(value[0])
+        assert segment <= _MAX_VALUE, "maximum number of segments reached"
+        return (<char *>self.key)[:self.key_size], (segment, _le32toh(value[1]))
 
 
 
 
 cdef class ChunkIndex(IndexBase):
 cdef class ChunkIndex(IndexBase):
+    """
+    Mapping of 32 byte keys to (refcount, size, csize), which are all 32-bit unsigned.
+
+    The reference count cannot overflow. If an overflow would occur, the refcount
+    is fixed to MAX_VALUE and will neither increase nor decrease by incref(), decref()
+    or add().
+
+    Prior signed 32-bit overflow is handled correctly for most cases: All values
+    from UINT32_MAX (2**32-1, inclusive) to MAX_VALUE (exclusive) are reserved and either
+    cause silent data loss (-1, -2) or will raise an AssertionError when accessed.
+    Other values are handled correctly. Note that previously the refcount could also reach
+    0 by *increasing* it.
+
+    Assigning refcounts in this reserved range is an invalid operation and raises AssertionError.
+    """
 
 
     value_size = 12
     value_size = 12
 
 
     def __getitem__(self, key):
     def __getitem__(self, key):
         assert len(key) == self.key_size
         assert len(key) == self.key_size
-        data = <int *>hashindex_get(self.index, <char *>key)
+        data = <uint32_t *>hashindex_get(self.index, <char *>key)
         if not data:
         if not data:
-            raise KeyError
-        return _le32toh(data[0]), _le32toh(data[1]), _le32toh(data[2])
+            raise KeyError(key)
+        cdef uint32_t refcount = _le32toh(data[0])
+        assert refcount <= _MAX_VALUE
+        return refcount, _le32toh(data[1]), _le32toh(data[2])
 
 
     def __setitem__(self, key, value):
     def __setitem__(self, key, value):
         assert len(key) == self.key_size
         assert len(key) == self.key_size
-        cdef int[3] data
-        data[0] = _htole32(value[0])
+        cdef uint32_t[3] data
+        cdef uint32_t refcount = value[0]
+        assert refcount <= _MAX_VALUE, "invalid reference count"
+        data[0] = _htole32(refcount)
         data[1] = _htole32(value[1])
         data[1] = _htole32(value[1])
         data[2] = _htole32(value[2])
         data[2] = _htole32(value[2])
         if not hashindex_set(self.index, <char *>key, data):
         if not hashindex_set(self.index, <char *>key, data):
@@ -175,9 +225,38 @@ cdef class ChunkIndex(IndexBase):
 
 
     def __contains__(self, key):
     def __contains__(self, key):
         assert len(key) == self.key_size
         assert len(key) == self.key_size
-        data = <int *>hashindex_get(self.index, <char *>key)
+        data = <uint32_t *>hashindex_get(self.index, <char *>key)
+        if data != NULL:
+            assert data[0] <= _MAX_VALUE
         return data != NULL
         return data != NULL
 
 
+    def incref(self, key):
+        """Increase refcount for 'key', return (refcount, size, csize)"""
+        assert len(key) == self.key_size
+        data = <uint32_t *>hashindex_get(self.index, <char *>key)
+        if not data:
+            raise KeyError(key)
+        cdef uint32_t refcount = _le32toh(data[0])
+        assert refcount <= _MAX_VALUE, "invalid reference count"
+        if refcount != _MAX_VALUE:
+            refcount += 1
+        data[0] = _htole32(refcount)
+        return refcount, _le32toh(data[1]), _le32toh(data[2])
+
+    def decref(self, key):
+        """Decrease refcount for 'key', return (refcount, size, csize)"""
+        assert len(key) == self.key_size
+        data = <uint32_t *>hashindex_get(self.index, <char *>key)
+        if not data:
+            raise KeyError(key)
+        cdef uint32_t refcount = _le32toh(data[0])
+        # Never decrease a reference count of zero
+        assert 0 < refcount <= _MAX_VALUE, "invalid reference count"
+        if refcount != _MAX_VALUE:
+            refcount -= 1
+        data[0] = _htole32(refcount)
+        return refcount, _le32toh(data[1]), _le32toh(data[2])
+
     def iteritems(self, marker=None):
     def iteritems(self, marker=None):
         cdef const void *key
         cdef const void *key
         iter = ChunkKeyIterator(self.key_size)
         iter = ChunkKeyIterator(self.key_size)
@@ -191,22 +270,56 @@ cdef class ChunkIndex(IndexBase):
         return iter
         return iter
 
 
     def summarize(self):
     def summarize(self):
-        cdef long long total_size, total_csize, unique_size, unique_csize, total_unique_chunks, total_chunks
-        hashindex_summarize(self.index, &total_size, &total_csize,
-                            &unique_size, &unique_csize,
-                            &total_unique_chunks, &total_chunks)
-        return total_size, total_csize, unique_size, unique_csize, total_unique_chunks, total_chunks
+        cdef uint64_t size = 0, csize = 0, unique_size = 0, unique_csize = 0, chunks = 0, unique_chunks = 0
+        cdef uint32_t *values
+        cdef uint32_t refcount
+        cdef void *key = NULL
+
+        while True:
+            key = hashindex_next_key(self.index, key)
+            if not key:
+                break
+            unique_chunks += 1
+            values = <uint32_t*> (key + self.key_size)
+            refcount = _le32toh(values[0])
+            assert refcount <= MAX_VALUE, "invalid reference count"
+            chunks += refcount
+            unique_size += _le32toh(values[1])
+            unique_csize += _le32toh(values[2])
+            size += <uint64_t> _le32toh(values[1]) * _le32toh(values[0])
+            csize += <uint64_t> _le32toh(values[2]) * _le32toh(values[0])
+
+        return size, csize, unique_size, unique_csize, unique_chunks, chunks
 
 
     def add(self, key, refs, size, csize):
     def add(self, key, refs, size, csize):
         assert len(key) == self.key_size
         assert len(key) == self.key_size
-        cdef int[3] data
+        cdef uint32_t[3] data
         data[0] = _htole32(refs)
         data[0] = _htole32(refs)
         data[1] = _htole32(size)
         data[1] = _htole32(size)
         data[2] = _htole32(csize)
         data[2] = _htole32(csize)
-        hashindex_add(self.index, <char *>key, data)
+        self._add(<char*> key, data)
+
+    cdef _add(self, void *key, uint32_t *data):
+        cdef uint64_t refcount1, refcount2, result64
+        values = <uint32_t*> hashindex_get(self.index, key)
+        if values:
+            refcount1 = _le32toh(values[0])
+            refcount2 = _le32toh(data[0])
+            assert refcount1 <= _MAX_VALUE
+            assert refcount2 <= _MAX_VALUE
+            result64 = refcount1 + refcount2
+            values[0] = _htole32(min(result64, _MAX_VALUE))
+        else:
+            hashindex_set(self.index, key, data)
 
 
     def merge(self, ChunkIndex other):
     def merge(self, ChunkIndex other):
-        hashindex_merge(self.index, other.index)
+        cdef void *key = NULL
+
+        while True:
+            key = hashindex_next_key(other.index, key)
+            if not key:
+                break
+            self._add(key, <uint32_t*> (key + self.key_size))
 
 
 
 
 cdef class ChunkKeyIterator:
 cdef class ChunkKeyIterator:
@@ -226,5 +339,7 @@ cdef class ChunkKeyIterator:
         self.key = hashindex_next_key(self.index, <char *>self.key)
         self.key = hashindex_next_key(self.index, <char *>self.key)
         if not self.key:
         if not self.key:
             raise StopIteration
             raise StopIteration
-        cdef int *value = <int *>(self.key + self.key_size)
-        return (<char *>self.key)[:self.key_size], (_le32toh(value[0]), _le32toh(value[1]), _le32toh(value[2]))
+        cdef uint32_t *value = <uint32_t *>(self.key + self.key_size)
+        cdef uint32_t refcount = _le32toh(value[0])
+        assert refcount <= MAX_VALUE, "invalid reference count"
+        return (<char *>self.key)[:self.key_size], (refcount, _le32toh(value[1]), _le32toh(value[2]))

+ 176 - 0
borg/testsuite/hashindex.py

@@ -1,8 +1,13 @@
+import base64
 import hashlib
 import hashlib
 import os
 import os
+import struct
 import tempfile
 import tempfile
+import zlib
 
 
+import pytest
 from ..hashindex import NSIndex, ChunkIndex
 from ..hashindex import NSIndex, ChunkIndex
+from .. import hashindex
 from . import BaseTestCase
 from . import BaseTestCase
 
 
 
 
@@ -100,3 +105,174 @@ class HashIndexTestCase(BaseTestCase):
         assert idx1[H(2)] == (7, 200, 200)
         assert idx1[H(2)] == (7, 200, 200)
         assert idx1[H(3)] == (3, 300, 300)
         assert idx1[H(3)] == (3, 300, 300)
         assert idx1[H(4)] == (6, 400, 400)
         assert idx1[H(4)] == (6, 400, 400)
+
+    def test_chunkindex_summarize(self):
+        idx = ChunkIndex()
+        idx[H(1)] = 1, 1000, 100
+        idx[H(2)] = 2, 2000, 200
+        idx[H(3)] = 3, 3000, 300
+
+        size, csize, unique_size, unique_csize, unique_chunks, chunks = idx.summarize()
+        assert size == 1000 + 2 * 2000 + 3 * 3000
+        assert csize == 100 + 2 * 200 + 3 * 300
+        assert unique_size == 1000 + 2000 + 3000
+        assert unique_csize == 100 + 200 + 300
+        assert chunks == 1 + 2 + 3
+        assert unique_chunks == 3
+
+
+class HashIndexRefcountingTestCase(BaseTestCase):
+    def test_chunkindex_limit(self):
+        idx = ChunkIndex()
+        idx[H(1)] = hashindex.MAX_VALUE - 1, 1, 2
+
+        # 5 is arbitray, any number of incref/decrefs shouldn't move it once it's limited
+        for i in range(5):
+            # first incref to move it to the limit
+            refcount, *_ = idx.incref(H(1))
+            assert refcount == hashindex.MAX_VALUE
+        for i in range(5):
+            refcount, *_ = idx.decref(H(1))
+            assert refcount == hashindex.MAX_VALUE
+
+    def _merge(self, refcounta, refcountb):
+        def merge(refcount1, refcount2):
+            idx1 = ChunkIndex()
+            idx1[H(1)] = refcount1, 1, 2
+            idx2 = ChunkIndex()
+            idx2[H(1)] = refcount2, 1, 2
+            idx1.merge(idx2)
+            refcount, *_ = idx1[H(1)]
+            return refcount
+        result = merge(refcounta, refcountb)
+        # check for commutativity
+        assert result == merge(refcountb, refcounta)
+        return result
+
+    def test_chunkindex_merge_limit1(self):
+        # Check that it does *not* limit at MAX_VALUE - 1
+        # (MAX_VALUE is odd)
+        half = hashindex.MAX_VALUE // 2
+        assert self._merge(half, half) == hashindex.MAX_VALUE - 1
+
+    def test_chunkindex_merge_limit2(self):
+        # 3000000000 + 2000000000 > MAX_VALUE
+        assert self._merge(3000000000, 2000000000) == hashindex.MAX_VALUE
+
+    def test_chunkindex_merge_limit3(self):
+        # Crossover point: both addition and limit semantics will yield the same result
+        half = hashindex.MAX_VALUE // 2
+        assert self._merge(half + 1, half) == hashindex.MAX_VALUE
+
+    def test_chunkindex_merge_limit4(self):
+        # Beyond crossover, result of addition would be 2**31
+        half = hashindex.MAX_VALUE // 2
+        assert self._merge(half + 2, half) == hashindex.MAX_VALUE
+        assert self._merge(half + 1, half + 1) == hashindex.MAX_VALUE
+
+    def test_chunkindex_add(self):
+        idx1 = ChunkIndex()
+        idx1.add(H(1), 5, 6, 7)
+        assert idx1[H(1)] == (5, 6, 7)
+        idx1.add(H(1), 1, 0, 0)
+        assert idx1[H(1)] == (6, 6, 7)
+
+    def test_incref_limit(self):
+        idx1 = ChunkIndex()
+        idx1[H(1)] = (hashindex.MAX_VALUE, 6, 7)
+        idx1.incref(H(1))
+        refcount, *_ = idx1[H(1)]
+        assert refcount == hashindex.MAX_VALUE
+
+    def test_decref_limit(self):
+        idx1 = ChunkIndex()
+        idx1[H(1)] = hashindex.MAX_VALUE, 6, 7
+        idx1.decref(H(1))
+        refcount, *_ = idx1[H(1)]
+        assert refcount == hashindex.MAX_VALUE
+
+    def test_decref_zero(self):
+        idx1 = ChunkIndex()
+        idx1[H(1)] = 0, 0, 0
+        with pytest.raises(AssertionError):
+            idx1.decref(H(1))
+
+    def test_incref_decref(self):
+        idx1 = ChunkIndex()
+        idx1.add(H(1), 5, 6, 7)
+        assert idx1[H(1)] == (5, 6, 7)
+        idx1.incref(H(1))
+        assert idx1[H(1)] == (6, 6, 7)
+        idx1.decref(H(1))
+        assert idx1[H(1)] == (5, 6, 7)
+
+    def test_setitem_raises(self):
+        idx1 = ChunkIndex()
+        with pytest.raises(AssertionError):
+            idx1[H(1)] = hashindex.MAX_VALUE + 1, 0, 0
+
+    def test_keyerror(self):
+        idx = ChunkIndex()
+        with pytest.raises(KeyError):
+            idx.incref(H(1))
+        with pytest.raises(KeyError):
+            idx.decref(H(1))
+        with pytest.raises(KeyError):
+            idx[H(1)]
+        with pytest.raises(OverflowError):
+            idx.add(H(1), -1, 0, 0)
+
+
+class HashIndexDataTestCase(BaseTestCase):
+    # This bytestring was created with 1.0-maint at c2f9533
+    HASHINDEX = b'eJzt0L0NgmAUhtHLT0LDEI6AuAEhMVYmVnSuYefC7AB3Aj9KNedJbnfyFne6P67P27w0EdG1Eac+Cm1ZybAsy7Isy7Isy7Isy7I' \
+                b'sy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7LsL9nhc+cqTZ' \
+                b'3XlO2Ys++Du5fX+l1/YFmWZVmWZVmWZVmWZVmWZVmWZVmWZVmWZVmWZVmWZVmWZVmWZVmWZVmWZVmWZVmWZVn2/+0O2rYccw=='
+
+    def _serialize_hashindex(self, idx):
+        with tempfile.TemporaryDirectory() as tempdir:
+            file = os.path.join(tempdir, 'idx')
+            idx.write(file)
+            with open(file, 'rb') as f:
+                return self._pack(f.read())
+
+    def _deserialize_hashindex(self, bytestring):
+        with tempfile.TemporaryDirectory() as tempdir:
+            file = os.path.join(tempdir, 'idx')
+            with open(file, 'wb') as f:
+                f.write(self._unpack(bytestring))
+            return ChunkIndex.read(file)
+
+    def _pack(self, bytestring):
+        return base64.b64encode(zlib.compress(bytestring))
+
+    def _unpack(self, bytestring):
+        return zlib.decompress(base64.b64decode(bytestring))
+
+    def test_identical_creation(self):
+        idx1 = ChunkIndex()
+        idx1[H(1)] = 1, 2, 3
+        idx1[H(2)] = 2**31 - 1, 0, 0
+        idx1[H(3)] = 4294962296, 0, 0  # 4294962296 is -5000 interpreted as an uint32_t
+
+        assert self._serialize_hashindex(idx1) == self.HASHINDEX
+
+    def test_read_known_good(self):
+        idx1 = self._deserialize_hashindex(self.HASHINDEX)
+        assert idx1[H(1)] == (1, 2, 3)
+        assert idx1[H(2)] == (2**31 - 1, 0, 0)
+        assert idx1[H(3)] == (4294962296, 0, 0)
+
+        idx2 = ChunkIndex()
+        idx2[H(3)] = 2**32 - 123456, 6, 7
+        idx1.merge(idx2)
+        assert idx1[H(3)] == (hashindex.MAX_VALUE, 0, 0)
+
+
+def test_nsindex_segment_limit():
+    idx = NSIndex()
+    with pytest.raises(AssertionError):
+        idx[H(1)] = hashindex.MAX_VALUE + 1, 0
+    assert H(1) not in idx
+    idx[H(2)] = hashindex.MAX_VALUE, 0
+    assert H(2) in idx

+ 36 - 0
docs/changes.rst

@@ -1,6 +1,42 @@
 Changelog
 Changelog
 =========
 =========
 
 
+Version 1.0.2
+-------------
+
+Bug fixes:
+
+- fix malfunction and potential corruption on (nowadays rather rare) big-endian
+  architectures or bi-endian archs in (rare) BE mode. #886, #889
+
+  cache resync / index merge was malfunctioning due to this, potentially
+  leading to data loss. borg info had cosmetic issues (displayed wrong values).
+
+  note: all (widespread) little-endian archs (like x86/x64) or bi-endian archs
+  in (widespread) LE mode (like ARMEL, MIPSEL, ...) were NOT affected.
+- add overflow and range checks for 1st (special) uint32 of the hashindex
+  values, switch from int32 to uint32.
+- fix so that refcount will never overflow, but just stick to max. value after
+  a overflow would have occured.
+- borg delete: fix --cache-only for broken caches, #874
+
+  Makes --cache-only idempotent: it won't fail if the cache is already deleted.
+- fixed borg create --one-file-system erroneously traversing into other
+  filesystems (if starting fs device number was 0), #873
+- workround a bug in Linux fadvise FADV_DONTNEED, #907
+
+Other changes:
+
+- better test coverage for hashindex, incl. overflow testing, checking correct
+  computations so endianness issues would be discovered.
+- reproducible doc for ProgressIndicator*,  make the build reproducible.
+- use latest llfuse for vagrant machines
+- docs:
+
+  - use /path/to/repo in examples, fixes #901
+  - fix confusing usage of "repo" as archive name (use "arch")
+
+
 Version 1.0.1
 Version 1.0.1
 -------------
 -------------
 
 

+ 16 - 16
docs/quickstart.rst

@@ -37,16 +37,16 @@ A step by step example
 
 
 1. Before a backup can be made a repository has to be initialized::
 1. Before a backup can be made a repository has to be initialized::
 
 
-    $ borg init /mnt/backup
+    $ borg init /path/to/repo
 
 
 2. Backup the ``~/src`` and ``~/Documents`` directories into an archive called
 2. Backup the ``~/src`` and ``~/Documents`` directories into an archive called
    *Monday*::
    *Monday*::
 
 
-    $ borg create /mnt/backup::Monday ~/src ~/Documents
+    $ borg create /path/to/repo::Monday ~/src ~/Documents
 
 
 3. The next day create a new archive called *Tuesday*::
 3. The next day create a new archive called *Tuesday*::
 
 
-    $ borg create -v --stats /mnt/backup::Tuesday ~/src ~/Documents
+    $ borg create -v --stats /path/to/repo::Tuesday ~/src ~/Documents
 
 
    This backup will be a lot quicker and a lot smaller since only new never
    This backup will be a lot quicker and a lot smaller since only new never
    before seen data is stored. The ``--stats`` option causes |project_name| to
    before seen data is stored. The ``--stats`` option causes |project_name| to
@@ -72,24 +72,24 @@ A step by step example
 
 
 4. List all archives in the repository::
 4. List all archives in the repository::
 
 
-    $ borg list /mnt/backup
+    $ borg list /path/to/repo
     Monday                               Mon, 2016-02-15 19:14:44
     Monday                               Mon, 2016-02-15 19:14:44
     Tuesday                              Tue, 2016-02-16 19:15:11
     Tuesday                              Tue, 2016-02-16 19:15:11
 
 
 5. List the contents of the *Monday* archive::
 5. List the contents of the *Monday* archive::
 
 
-    $ borg list /mnt/backup::Monday
+    $ borg list /path/to/repo::Monday
     drwxr-xr-x user   group          0 Mon, 2016-02-15 18:22:30 home/user/Documents
     drwxr-xr-x user   group          0 Mon, 2016-02-15 18:22:30 home/user/Documents
     -rw-r--r-- user   group       7961 Mon, 2016-02-15 18:22:30 home/user/Documents/Important.doc
     -rw-r--r-- user   group       7961 Mon, 2016-02-15 18:22:30 home/user/Documents/Important.doc
     ...
     ...
 
 
 6. Restore the *Monday* archive::
 6. Restore the *Monday* archive::
 
 
-    $ borg extract /mnt/backup::Monday
+    $ borg extract /path/to/repo::Monday
 
 
 7. Recover disk space by manually deleting the *Monday* archive::
 7. Recover disk space by manually deleting the *Monday* archive::
 
 
-    $ borg delete /mnt/backup::Monday
+    $ borg delete /path/to/repo::Monday
 
 
 .. Note::
 .. Note::
     Borg is quiet by default (it works on WARNING log level).
     Borg is quiet by default (it works on WARNING log level).
@@ -134,17 +134,17 @@ or high compression:
 
 
 If you have a fast repo storage and you want some compression: ::
 If you have a fast repo storage and you want some compression: ::
 
 
-    $ borg create --compression lz4 /mnt/backup::repo ~
+    $ borg create --compression lz4 /path/to/repo::arch ~
 
 
 If you have a less fast repo storage and you want a bit more compression (N=0..9,
 If you have a less fast repo storage and you want a bit more compression (N=0..9,
 0 means no compression, 9 means high compression): ::
 0 means no compression, 9 means high compression): ::
 
 
-    $ borg create --compression zlib,N /mnt/backup::repo ~
+    $ borg create --compression zlib,N /path/to/repo::arch ~
 
 
 If you have a very slow repo storage and you want high compression (N=0..9, 0 means
 If you have a very slow repo storage and you want high compression (N=0..9, 0 means
 low compression, 9 means high compression): ::
 low compression, 9 means high compression): ::
 
 
-    $ borg create --compression lzma,N /mnt/backup::repo ~
+    $ borg create --compression lzma,N /path/to/repo::arch ~
 
 
 You'll need to experiment a bit to find the best compression for your use case.
 You'll need to experiment a bit to find the best compression for your use case.
 Keep an eye on CPU load and throughput.
 Keep an eye on CPU load and throughput.
@@ -208,23 +208,23 @@ Remote repositories
 host is accessible using SSH.  This is fastest and easiest when |project_name|
 host is accessible using SSH.  This is fastest and easiest when |project_name|
 is installed on the remote host, in which case the following syntax is used::
 is installed on the remote host, in which case the following syntax is used::
 
 
-  $ borg init user@hostname:/mnt/backup
+  $ borg init user@hostname:/path/to/repo
 
 
 or::
 or::
 
 
-  $ borg init ssh://user@hostname:port//mnt/backup
+  $ borg init ssh://user@hostname:port//path/to/repo
 
 
 Remote operations over SSH can be automated with SSH keys. You can restrict the
 Remote operations over SSH can be automated with SSH keys. You can restrict the
 use of the SSH keypair by prepending a forced command to the SSH public key in
 use of the SSH keypair by prepending a forced command to the SSH public key in
 the remote server's `authorized_keys` file. This example will start |project_name|
 the remote server's `authorized_keys` file. This example will start |project_name|
 in server mode and limit it to a specific filesystem path::
 in server mode and limit it to a specific filesystem path::
 
 
-  command="borg serve --restrict-to-path /mnt/backup",no-pty,no-agent-forwarding,no-port-forwarding,no-X11-forwarding,no-user-rc ssh-rsa AAAAB3[...]
+  command="borg serve --restrict-to-path /path/to/repo",no-pty,no-agent-forwarding,no-port-forwarding,no-X11-forwarding,no-user-rc ssh-rsa AAAAB3[...]
 
 
 If it is not possible to install |project_name| on the remote host,
 If it is not possible to install |project_name| on the remote host,
 it is still possible to use the remote host to store a repository by
 it is still possible to use the remote host to store a repository by
 mounting the remote filesystem, for example, using sshfs::
 mounting the remote filesystem, for example, using sshfs::
 
 
-  $ sshfs user@hostname:/mnt /mnt
-  $ borg init /mnt/backup
-  $ fusermount -u /mnt
+  $ sshfs user@hostname:/path/to /path/to
+  $ borg init /path/to/repo
+  $ fusermount -u /path/to

+ 50 - 50
docs/usage.rst

@@ -207,10 +207,10 @@ Examples
 ::
 ::
 
 
     # Local repository (default is to use encryption in repokey mode)
     # Local repository (default is to use encryption in repokey mode)
-    $ borg init /mnt/backup
+    $ borg init /path/to/repo
 
 
     # Local repository (no encryption)
     # Local repository (no encryption)
-    $ borg init --encryption=none /mnt/backup
+    $ borg init --encryption=none /path/to/repo
 
 
     # Remote repository (accesses a remote borg via ssh)
     # Remote repository (accesses a remote borg via ssh)
     $ borg init user@hostname:backup
     $ borg init user@hostname:backup
@@ -265,54 +265,54 @@ Examples
 ::
 ::
 
 
     # Backup ~/Documents into an archive named "my-documents"
     # Backup ~/Documents into an archive named "my-documents"
-    $ borg create /mnt/backup::my-documents ~/Documents
+    $ borg create /path/to/repo::my-documents ~/Documents
 
 
     # same, but verbosely list all files as we process them
     # same, but verbosely list all files as we process them
-    $ borg create -v --list /mnt/backup::my-documents ~/Documents
+    $ borg create -v --list /path/to/repo::my-documents ~/Documents
 
 
     # Backup ~/Documents and ~/src but exclude pyc files
     # Backup ~/Documents and ~/src but exclude pyc files
-    $ borg create /mnt/backup::my-files   \
+    $ borg create /path/to/repo::my-files \
         ~/Documents                       \
         ~/Documents                       \
         ~/src                             \
         ~/src                             \
         --exclude '*.pyc'
         --exclude '*.pyc'
 
 
     # Backup home directories excluding image thumbnails (i.e. only
     # Backup home directories excluding image thumbnails (i.e. only
     # /home/*/.thumbnails is excluded, not /home/*/*/.thumbnails)
     # /home/*/.thumbnails is excluded, not /home/*/*/.thumbnails)
-    $ borg create /mnt/backup::my-files /home \
+    $ borg create /path/to/repo::my-files /home \
         --exclude 're:^/home/[^/]+/\.thumbnails/'
         --exclude 're:^/home/[^/]+/\.thumbnails/'
 
 
     # Do the same using a shell-style pattern
     # Do the same using a shell-style pattern
-    $ borg create /mnt/backup::my-files /home \
+    $ borg create /path/to/repo::my-files /home \
         --exclude 'sh:/home/*/.thumbnails'
         --exclude 'sh:/home/*/.thumbnails'
 
 
     # Backup the root filesystem into an archive named "root-YYYY-MM-DD"
     # Backup the root filesystem into an archive named "root-YYYY-MM-DD"
     # use zlib compression (good, but slow) - default is no compression
     # use zlib compression (good, but slow) - default is no compression
-    $ borg create -C zlib,6 /mnt/backup::root-{now:%Y-%m-%d} / --one-file-system
+    $ borg create -C zlib,6 /path/to/repo::root-{now:%Y-%m-%d} / --one-file-system
 
 
     # Make a big effort in fine granular deduplication (big chunk management
     # Make a big effort in fine granular deduplication (big chunk management
     # overhead, needs a lot of RAM and disk space, see formula in internals
     # overhead, needs a lot of RAM and disk space, see formula in internals
     # docs - same parameters as borg < 1.0 or attic):
     # docs - same parameters as borg < 1.0 or attic):
-    $ borg create --chunker-params 10,23,16,4095 /mnt/backup::small /smallstuff
+    $ borg create --chunker-params 10,23,16,4095 /path/to/repo::small /smallstuff
 
 
     # Backup a raw device (must not be active/in use/mounted at that time)
     # Backup a raw device (must not be active/in use/mounted at that time)
-    $ dd if=/dev/sdx bs=10M | borg create /mnt/backup::my-sdx -
+    $ dd if=/dev/sdx bs=10M | borg create /path/to/repo::my-sdx -
 
 
     # No compression (default)
     # No compression (default)
-    $ borg create /mnt/backup::repo ~
+    $ borg create /path/to/repo::arch ~
 
 
     # Super fast, low compression
     # Super fast, low compression
-    $ borg create --compression lz4 /mnt/backup::repo ~
+    $ borg create --compression lz4 /path/to/repo::arch ~
 
 
     # Less fast, higher compression (N = 0..9)
     # Less fast, higher compression (N = 0..9)
-    $ borg create --compression zlib,N /mnt/backup::repo ~
+    $ borg create --compression zlib,N /path/to/repo::arch ~
 
 
     # Even slower, even higher compression (N = 0..9)
     # Even slower, even higher compression (N = 0..9)
-    $ borg create --compression lzma,N /mnt/backup::repo ~
+    $ borg create --compression lzma,N /path/to/repo::arch ~
 
 
     # Format tags available for archive name:
     # Format tags available for archive name:
     # {now}, {utcnow}, {fqdn}, {hostname}, {user}, {pid}
     # {now}, {utcnow}, {fqdn}, {hostname}, {user}, {pid}
     # add short hostname, backup username and current unixtime (seconds from epoch)
     # add short hostname, backup username and current unixtime (seconds from epoch)
-    $ borg create  /mnt/backup::{hostname}-{user}-{now:%s} ~
+    $ borg create  /path/to/repo::{hostname}-{user}-{now:%s} ~
 
 
 .. include:: usage/extract.rst.inc
 .. include:: usage/extract.rst.inc
 
 
@@ -321,19 +321,19 @@ Examples
 ::
 ::
 
 
     # Extract entire archive
     # Extract entire archive
-    $ borg extract /mnt/backup::my-files
+    $ borg extract /path/to/repo::my-files
 
 
     # Extract entire archive and list files while processing
     # Extract entire archive and list files while processing
-    $ borg extract -v --list /mnt/backup::my-files
+    $ borg extract -v --list /path/to/repo::my-files
 
 
     # Extract the "src" directory
     # Extract the "src" directory
-    $ borg extract /mnt/backup::my-files home/USERNAME/src
+    $ borg extract /path/to/repo::my-files home/USERNAME/src
 
 
     # Extract the "src" directory but exclude object files
     # Extract the "src" directory but exclude object files
-    $ borg extract /mnt/backup::my-files home/USERNAME/src --exclude '*.o'
+    $ borg extract /path/to/repo::my-files home/USERNAME/src --exclude '*.o'
 
 
     # Restore a raw device (must not be active/in use/mounted at that time)
     # Restore a raw device (must not be active/in use/mounted at that time)
-    $ borg extract --stdout /mnt/backup::my-sdx | dd of=/dev/sdx bs=10M
+    $ borg extract --stdout /path/to/repo::my-sdx | dd of=/dev/sdx bs=10M
 
 
 
 
 .. Note::
 .. Note::
@@ -349,12 +349,12 @@ Examples
 ~~~~~~~~
 ~~~~~~~~
 ::
 ::
 
 
-    $ borg create /mnt/backup::archivename ~
-    $ borg list /mnt/backup
+    $ borg create /path/to/repo::archivename ~
+    $ borg list /path/to/repo
     archivename                          Mon, 2016-02-15 19:50:19
     archivename                          Mon, 2016-02-15 19:50:19
 
 
-    $ borg rename /mnt/backup::archivename newname
-    $ borg list /mnt/backup
+    $ borg rename /path/to/repo::archivename newname
+    $ borg list /path/to/repo
     newname                              Mon, 2016-02-15 19:50:19
     newname                              Mon, 2016-02-15 19:50:19
 
 
 
 
@@ -364,14 +364,14 @@ Examples
 ~~~~~~~~
 ~~~~~~~~
 ::
 ::
 
 
-    $ borg list /mnt/backup
+    $ borg list /path/to/repo
     Monday                               Mon, 2016-02-15 19:15:11
     Monday                               Mon, 2016-02-15 19:15:11
     repo                                 Mon, 2016-02-15 19:26:54
     repo                                 Mon, 2016-02-15 19:26:54
     root-2016-02-15                      Mon, 2016-02-15 19:36:29
     root-2016-02-15                      Mon, 2016-02-15 19:36:29
     newname                              Mon, 2016-02-15 19:50:19
     newname                              Mon, 2016-02-15 19:50:19
     ...
     ...
 
 
-    $ borg list /mnt/backup::root-2016-02-15
+    $ borg list /path/to/repo::root-2016-02-15
     drwxr-xr-x root   root          0 Mon, 2016-02-15 17:44:27 .
     drwxr-xr-x root   root          0 Mon, 2016-02-15 17:44:27 .
     drwxrwxr-x root   root          0 Mon, 2016-02-15 19:04:49 bin
     drwxrwxr-x root   root          0 Mon, 2016-02-15 19:04:49 bin
     -rwxr-xr-x root   root    1029624 Thu, 2014-11-13 00:08:51 bin/bash
     -rwxr-xr-x root   root    1029624 Thu, 2014-11-13 00:08:51 bin/bash
@@ -379,7 +379,7 @@ Examples
     -rwxr-xr-x root   root       2140 Fri, 2015-03-27 20:24:22 bin/bzdiff
     -rwxr-xr-x root   root       2140 Fri, 2015-03-27 20:24:22 bin/bzdiff
     ...
     ...
 
 
-    $ borg list /mnt/backup::archiveA --list-format="{mode} {user:6} {group:6} {size:8d} {isomtime} {path}{extra}{NEWLINE}"
+    $ borg list /path/to/repo::archiveA --list-format="{mode} {user:6} {group:6} {size:8d} {isomtime} {path}{extra}{NEWLINE}"
     drwxrwxr-x user   user          0 Sun, 2015-02-01 11:00:00 .
     drwxrwxr-x user   user          0 Sun, 2015-02-01 11:00:00 .
     drwxrwxr-x user   user          0 Sun, 2015-02-01 11:00:00 code
     drwxrwxr-x user   user          0 Sun, 2015-02-01 11:00:00 code
     drwxrwxr-x user   user          0 Sun, 2015-02-01 11:00:00 code/myproject
     drwxrwxr-x user   user          0 Sun, 2015-02-01 11:00:00 code/myproject
@@ -387,8 +387,8 @@ Examples
     ...
     ...
 
 
     # see what is changed between archives, based on file modification time, size and file path
     # see what is changed between archives, based on file modification time, size and file path
-    $ borg list /mnt/backup::archiveA --list-format="{mtime:%s}{TAB}{size}{TAB}{path}{LF}" |sort -n > /tmp/list.archiveA
-    $ borg list /mnt/backup::archiveB --list-format="{mtime:%s}{TAB}{size}{TAB}{path}{LF}" |sort -n > /tmp/list.archiveB
+    $ borg list /path/to/repo::archiveA --list-format="{mtime:%s}{TAB}{size}{TAB}{path}{LF}" |sort -n > /tmp/list.archiveA
+    $ borg list /path/to/repo::archiveB --list-format="{mtime:%s}{TAB}{size}{TAB}{path}{LF}" |sort -n > /tmp/list.archiveB
     $ diff -y /tmp/list.archiveA /tmp/list.archiveB
     $ diff -y /tmp/list.archiveA /tmp/list.archiveB
     1422781200      0       .                                       1422781200      0       .
     1422781200      0       .                                       1422781200      0       .
     1422781200      0       code                                    1422781200      0       code
     1422781200      0       code                                    1422781200      0       code
@@ -442,10 +442,10 @@ Examples
 ::
 ::
 
 
     # delete a single backup archive:
     # delete a single backup archive:
-    $ borg delete /mnt/backup::Monday
+    $ borg delete /path/to/repo::Monday
 
 
     # delete the whole repository and the related local cache:
     # delete the whole repository and the related local cache:
-    $ borg delete /mnt/backup
+    $ borg delete /path/to/repo
     You requested to completely DELETE the repository *including* all archives it contains:
     You requested to completely DELETE the repository *including* all archives it contains:
     repo                                 Mon, 2016-02-15 19:26:54
     repo                                 Mon, 2016-02-15 19:26:54
     root-2016-02-15                      Mon, 2016-02-15 19:36:29
     root-2016-02-15                      Mon, 2016-02-15 19:36:29
@@ -473,18 +473,18 @@ will see what it would do without it actually doing anything.
 
 
     # Keep 7 end of day and 4 additional end of week archives.
     # Keep 7 end of day and 4 additional end of week archives.
     # Do a dry-run without actually deleting anything.
     # Do a dry-run without actually deleting anything.
-    $ borg prune --dry-run --keep-daily=7 --keep-weekly=4 /mnt/backup
+    $ borg prune --dry-run --keep-daily=7 --keep-weekly=4 /path/to/repo
 
 
     # Same as above but only apply to archive names starting with "foo":
     # Same as above but only apply to archive names starting with "foo":
-    $ borg prune --keep-daily=7 --keep-weekly=4 --prefix=foo /mnt/backup
+    $ borg prune --keep-daily=7 --keep-weekly=4 --prefix=foo /path/to/repo
 
 
     # Keep 7 end of day, 4 additional end of week archives,
     # Keep 7 end of day, 4 additional end of week archives,
     # and an end of month archive for every month:
     # and an end of month archive for every month:
-    $ borg prune --keep-daily=7 --keep-weekly=4 --keep-monthly=-1 /mnt/backup
+    $ borg prune --keep-daily=7 --keep-weekly=4 --keep-monthly=-1 /path/to/repo
 
 
     # Keep all backups in the last 10 days, 4 additional end of week archives,
     # Keep all backups in the last 10 days, 4 additional end of week archives,
     # and an end of month archive for every month:
     # and an end of month archive for every month:
-    $ borg prune --keep-within=10d --keep-weekly=4 --keep-monthly=-1 /mnt/backup
+    $ borg prune --keep-within=10d --keep-weekly=4 --keep-monthly=-1 /path/to/repo
 
 
 
 
 .. include:: usage/info.rst.inc
 .. include:: usage/info.rst.inc
@@ -493,14 +493,14 @@ Examples
 ~~~~~~~~
 ~~~~~~~~
 ::
 ::
 
 
-    $ borg info /mnt/backup::root-2016-02-15
+    $ borg info /path/to/repo::root-2016-02-15
     Name: root-2016-02-15
     Name: root-2016-02-15
     Fingerprint: 57c827621f21b000a8d363c1e163cc55983822b3afff3a96df595077a660be50
     Fingerprint: 57c827621f21b000a8d363c1e163cc55983822b3afff3a96df595077a660be50
     Hostname: myhostname
     Hostname: myhostname
     Username: root
     Username: root
     Time (start): Mon, 2016-02-15 19:36:29
     Time (start): Mon, 2016-02-15 19:36:29
     Time (end):   Mon, 2016-02-15 19:39:26
     Time (end):   Mon, 2016-02-15 19:39:26
-    Command line: /usr/local/bin/borg create -v --list -C zlib,6 /mnt/backup::root-2016-02-15 / --one-file-system
+    Command line: /usr/local/bin/borg create -v --list -C zlib,6 /path/to/repo::root-2016-02-15 / --one-file-system
     Number of files: 38100
     Number of files: 38100
 
 
                            Original size      Compressed size    Deduplicated size
                            Original size      Compressed size    Deduplicated size
@@ -519,7 +519,7 @@ borg mount/borgfs
 +++++++++++++++++
 +++++++++++++++++
 ::
 ::
 
 
-    $ borg mount /mnt/backup::root-2016-02-15 /tmp/mymountpoint
+    $ borg mount /path/to/repo::root-2016-02-15 /tmp/mymountpoint
     $ ls /tmp/mymountpoint
     $ ls /tmp/mymountpoint
     bin  boot  etc	home  lib  lib64  lost+found  media  mnt  opt  root  sbin  srv  tmp  usr  var
     bin  boot  etc	home  lib  lib64  lost+found  media  mnt  opt  root  sbin  srv  tmp  usr  var
     $ fusermount -u /tmp/mymountpoint
     $ fusermount -u /tmp/mymountpoint
@@ -551,8 +551,8 @@ Examples
 ::
 ::
 
 
     # Create a key file protected repository
     # Create a key file protected repository
-    $ borg init --encryption=keyfile -v /mnt/backup
-    Initializing repository at "/mnt/backup"
+    $ borg init --encryption=keyfile -v /path/to/repo
+    Initializing repository at "/path/to/repo"
     Enter new passphrase:
     Enter new passphrase:
     Enter same passphrase again:
     Enter same passphrase again:
     Remember your passphrase. Your data will be inaccessible without it.
     Remember your passphrase. Your data will be inaccessible without it.
@@ -563,7 +563,7 @@ Examples
     Done.
     Done.
 
 
     # Change key file passphrase
     # Change key file passphrase
-    $ borg change-passphrase -v /mnt/backup
+    $ borg change-passphrase -v /path/to/repo
     Enter passphrase for key /root/.config/borg/keys/mnt_backup:
     Enter passphrase for key /root/.config/borg/keys/mnt_backup:
     Enter new passphrase:
     Enter new passphrase:
     Enter same passphrase again:
     Enter same passphrase again:
@@ -586,11 +586,11 @@ forced command. That way, other options given by the client (like ``--info`` or
 
 
 ::
 ::
 
 
-    # Allow an SSH keypair to only run borg, and only have access to /mnt/backup.
+    # Allow an SSH keypair to only run borg, and only have access to /path/to/repo.
     # Use key options to disable unneeded and potentially dangerous SSH functionality.
     # Use key options to disable unneeded and potentially dangerous SSH functionality.
     # This will help to secure an automated remote backup system.
     # This will help to secure an automated remote backup system.
     $ cat ~/.ssh/authorized_keys
     $ cat ~/.ssh/authorized_keys
-    command="borg serve --restrict-to-path /mnt/backup",no-pty,no-agent-forwarding,no-port-forwarding,no-X11-forwarding,no-user-rc ssh-rsa AAAAB3[...]
+    command="borg serve --restrict-to-path /path/to/repo",no-pty,no-agent-forwarding,no-port-forwarding,no-X11-forwarding,no-user-rc ssh-rsa AAAAB3[...]
 
 
 
 
 .. include:: usage/upgrade.rst.inc
 .. include:: usage/upgrade.rst.inc
@@ -600,11 +600,11 @@ Examples
 ::
 ::
 
 
     # Upgrade the borg repository to the most recent version.
     # Upgrade the borg repository to the most recent version.
-    $ borg upgrade -v /mnt/backup
-    making a hardlink copy in /mnt/backup.upgrade-2016-02-15-20:51:55
+    $ borg upgrade -v /path/to/repo
+    making a hardlink copy in /path/to/repo.upgrade-2016-02-15-20:51:55
     opening attic repository with borg and converting
     opening attic repository with borg and converting
     no key file found for repository
     no key file found for repository
-    converting repo index /mnt/backup/index.0
+    converting repo index /path/to/repo/index.0
     converting 1 segments...
     converting 1 segments...
     converting borg 0.xx to borg current
     converting borg 0.xx to borg current
     no key file found for repository
     no key file found for repository
@@ -802,16 +802,16 @@ After the backup has completed, you remove the snapshots again. ::
 
 
     $ # create snapshots here
     $ # create snapshots here
     $ lvdisplay > lvdisplay.txt
     $ lvdisplay > lvdisplay.txt
-    $ borg create --read-special /mnt/backup::repo lvdisplay.txt /dev/vg0/*-snapshot
+    $ borg create --read-special /path/to/repo::arch lvdisplay.txt /dev/vg0/*-snapshot
     $ # remove snapshots here
     $ # remove snapshots here
 
 
 Now, let's see how to restore some LVs from such a backup. ::
 Now, let's see how to restore some LVs from such a backup. ::
 
 
-    $ borg extract /mnt/backup::repo lvdisplay.txt
+    $ borg extract /path/to/repo::arch lvdisplay.txt
     $ # create empty LVs with correct sizes here (look into lvdisplay.txt).
     $ # create empty LVs with correct sizes here (look into lvdisplay.txt).
     $ # we assume that you created an empty root and home LV and overwrite it now:
     $ # we assume that you created an empty root and home LV and overwrite it now:
-    $ borg extract --stdout /mnt/backup::repo dev/vg0/root-snapshot > /dev/vg0/root
-    $ borg extract --stdout /mnt/backup::repo dev/vg0/home-snapshot > /dev/vg0/home
+    $ borg extract --stdout /path/to/repo::arch dev/vg0/root-snapshot > /dev/vg0/root
+    $ borg extract --stdout /path/to/repo::arch dev/vg0/home-snapshot > /dev/vg0/home
 
 
 
 
 Append-only mode
 Append-only mode