Repository: borgbackup/borg

Issue:
failure with eatmydata "tried to call a logger before setup_logging() was called"
As said on Debian bug 825729, with eatmydata borg fails.
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=825729

```
==================================== ERRORS ====================================
___ ERROR collecting .pybuild/pythonX.Y_3.5/build/borg/testsuite/__init__.py ___
borg/testsuite/__init__.py:10: in <module>
    from ..xattr import get_all
borg/xattr.py:71: in <module>
    logger.error(msg)
borg/logger.py:173: in error
    return self.__logger.error(*args, **kw)
borg/logger.py:150: in __logger
    raise Exception("tried to call a logger before setup_logging() was called")
E   Exception: tried to call a logger before setup_logging() was called
--------------------------------------------------------------------------------
```



Discussion:
I've  no idea what eatmydata is, but it seems to invoke borg / tests in an unusual way.
grep the borg source for `setup_logging` to see what you need to add?

package description:

```
Library and utilities designed to disable fsync and friends
This package contains a small LD_PRELOAD library (libeatmydata) and a couple of helper utilities designed to transparently disable fsync and friends (like open(O_SYNC)). This has two side-effects: making software that writes data safely to disk a lot quicker and making this software no longer crash safe.
You will find eatmydata useful if particular software calls fsync(), sync() etc. frequently but the data it stores is not that valuable to you and you may afford losing it in case of system crash. Data-to-disk synchronization calls are typically very slow on modern file systems and their extensive usage might slow down software significantly. It does not make sense to accept such a hit in performance if data being manipulated is not very important.
On the other hand, do not use eatmydata when you care about what software stores or it manipulates important components of your system. The library is called libEAT-MY-DATA for a reason. 
```

Not sure if fixable or worth a fix :)

> Not sure if fixable or worth a fix :)

Not worth a fix. fsync()iness is "fixed" in 1.1.

Edit: Disabling fsync() is a _really_ bad idea for Borg.

~~-- Does it crash at runtime? If not we might want to add a little code to make it...~~ Traceback suggests just that, nothing to do there.

Close?


Resolving patch:
diff --git a/src/borg/testsuite/archiver.py b/src/borg/testsuite/archiver.py
--- a/src/borg/testsuite/archiver.py
+++ b/src/borg/testsuite/archiver.py
@@ -23,7 +23,7 @@
     pass
 
 from .. import xattr, helpers, platform
-from ..archive import Archive, ChunkBuffer, ArchiveRecreater
+from ..archive import Archive, ChunkBuffer, ArchiveRecreater, flags_noatime, flags_normal
 from ..archiver import Archiver
 from ..cache import Cache
 from ..constants import *  # NOQA
@@ -390,8 +390,20 @@ def test_symlink_extract(self):
             assert os.readlink('input/link1') == 'somewhere'
 
     def test_atime(self):
+        def has_noatime(some_file):
+            atime_before = os.stat(some_file).st_atime_ns
+            try:
+                os.close(os.open(some_file, flags_noatime))
+            except PermissionError:
+                return False
+            else:
+                atime_after = os.stat(some_file).st_atime_ns
+                noatime_used = flags_noatime != flags_normal
+                return noatime_used and atime_before == atime_after
+
         self.create_test_files()
         atime, mtime = 123456780, 234567890
+        have_noatime = has_noatime('input/file1')
         os.utime('input/file1', (atime, mtime))
         self.cmd('init', self.repository_location)
         self.cmd('create', self.repository_location + '::test', 'input')
@@ -400,7 +412,7 @@ def test_atime(self):
         sti = os.stat('input/file1')
         sto = os.stat('output/input/file1')
         assert sti.st_mtime_ns == sto.st_mtime_ns == mtime * 1e9
-        if hasattr(os, 'O_NOATIME'):
+        if have_noatime:
             assert sti.st_atime_ns == sto.st_atime_ns == atime * 1e9
         else:
             # it touched the input file's atime while backing it up
@@ -420,12 +432,30 @@ def _set_repository_id(self, path, id):
             return repository.id
 
     def test_sparse_file(self):
-        # Mac OS X has no sparse file support
-        # Solaris (ZFS) has sparse file support, but is less predictable about it
-        sparse_support = sys.platform not in ['darwin', 'sunos5']
+        def is_sparse(fn, total_size, hole_size):
+            st = os.stat(fn)
+            assert st.st_size == total_size
+            sparse = True
+            if sparse and hasattr(st, 'st_blocks') and st.st_blocks * 512 >= st.st_size:
+                sparse = False
+            if sparse and hasattr(os, 'SEEK_HOLE') and hasattr(os, 'SEEK_DATA'):
+                with open(fn, 'rb') as fd:
+                    # only check if the first hole is as expected, because the 2nd hole check
+                    # is problematic on xfs due to its "dynamic speculative EOF preallocation
+                    try:
+                        if fd.seek(0, os.SEEK_HOLE) != 0:
+                            sparse = False
+                        if fd.seek(0, os.SEEK_DATA) != hole_size:
+                            sparse = False
+                    except OSError:
+                        # OS/FS does not really support SEEK_HOLE/SEEK_DATA
+                        sparse = False
+            return sparse
+
         filename = os.path.join(self.input_path, 'sparse')
         content = b'foobar'
         hole_size = 5 * (1 << CHUNK_MAX_EXP)  # 5 full chunker buffers
+        total_size = hole_size + len(content) + hole_size
         with open(filename, 'wb') as fd:
             # create a file that has a hole at the beginning and end (if the
             # OS and filesystem supports sparse files)
@@ -434,39 +464,23 @@ def test_sparse_file(self):
             fd.seek(hole_size, 1)
             pos = fd.tell()
             fd.truncate(pos)
-        total_len = hole_size + len(content) + hole_size
-        st = os.stat(filename)
-        self.assert_equal(st.st_size, total_len)
-        if sparse_support and hasattr(st, 'st_blocks'):
-            self.assert_true(st.st_blocks * 512 < total_len)  # is input sparse?
-        self.cmd('init', self.repository_location)
-        self.cmd('create', self.repository_location + '::test', 'input')
-        with changedir('output'):
-            self.cmd('extract', '--sparse', self.repository_location + '::test')
-        self.assert_dirs_equal('input', 'output/input')
-        filename = os.path.join(self.output_path, 'input', 'sparse')
-        with open(filename, 'rb') as fd:
-            # check if file contents are as expected
-            self.assert_equal(fd.read(hole_size), b'\0' * hole_size)
-            self.assert_equal(fd.read(len(content)), content)
-            self.assert_equal(fd.read(hole_size), b'\0' * hole_size)
-        st = os.stat(filename)
-        self.assert_equal(st.st_size, total_len)
+        # we first check if we could create a sparse input file:
+        sparse_support = is_sparse(filename, total_size, hole_size)
         if sparse_support:
-            if hasattr(st, 'st_blocks'):
-                # do only check if it is less, do NOT check if it is much less
-                # as that causes troubles on xfs, zfs, ntfs:
-                self.assert_true(st.st_blocks * 512 < total_len)
-            if hasattr(os, 'SEEK_HOLE') and hasattr(os, 'SEEK_DATA'):
-                with open(filename, 'rb') as fd:
-                    # only check if the first hole is as expected, because the 2nd hole check
-                    # is problematic on xfs due to its "dynamic speculative EOF preallocation
-                    try:
-                        self.assert_equal(fd.seek(0, os.SEEK_HOLE), 0)
-                        self.assert_equal(fd.seek(0, os.SEEK_DATA), hole_size)
-                    except OSError:
-                        # does not really support SEEK_HOLE/SEEK_DATA
-                        pass
+            # we could create a sparse input file, so creating a backup of it and
+            # extracting it again (as sparse) should also work:
+            self.cmd('init', self.repository_location)
+            self.cmd('create', self.repository_location + '::test', 'input')
+            with changedir(self.output_path):
+                self.cmd('extract', '--sparse', self.repository_location + '::test')
+            self.assert_dirs_equal('input', 'output/input')
+            filename = os.path.join(self.output_path, 'input', 'sparse')
+            with open(filename, 'rb') as fd:
+                # check if file contents are as expected
+                self.assert_equal(fd.read(hole_size), b'\0' * hole_size)
+                self.assert_equal(fd.read(len(content)), content)
+                self.assert_equal(fd.read(hole_size), b'\0' * hole_size)
+            self.assert_true(is_sparse(filename, total_size, hole_size))
 
     def test_unusual_filenames(self):
         filenames = ['normal', 'with some blanks', '(with_parens)', ]
diff --git a/src/borg/xattr.py b/src/borg/xattr.py
--- a/src/borg/xattr.py
+++ b/src/borg/xattr.py
@@ -72,7 +72,6 @@ def get_all(path, follow_symlinks=True):
     libc = CDLL(libc_name, use_errno=True)
 except OSError as e:
     msg = "Can't find C library [%s]. Try installing ldconfig, gcc/cc or objdump." % e
-    logger.error(msg)
     raise Exception(msg)
 
 


Test patch: