Repository: borgbackup/borg

Issue:
Hardlink failed, cannot securely erase old config file
```
mount -t davfs https://owncloud.provider.com/remote.php/webdav/ ~/owncloud/
borg init -e repokey-blake2 ~/owncloud/backup
```
results in
```
Hardlink failed, cannot securely erase old config file
```

What does that mean and indicate?

Arch Linux 4.14.15, borg 1.1.4


Discussion:
```
    def save_config(self, path, config):
        config_path = os.path.join(path, 'config')
        old_config_path = os.path.join(path, 'config.old')

        if os.path.isfile(old_config_path):
            logger.warning("Old config file not securely erased on previous config update")
            secure_erase(old_config_path)

        if os.path.isfile(config_path):
            try:
                os.link(config_path, old_config_path)
            except OSError as e:
                if e.errno in (errno.EMLINK, errno.ENOSYS, errno.EPERM, errno.ENOTSUP):
                    logger.warning("Hardlink failed, cannot securely erase old config file")
                else:
                    raise

        with SaveFile(config_path) as fd:
            config.write(fd)

        if os.path.isfile(old_config_path):
            secure_erase(old_config_path)
```

This is what borg does to update the config in a safe and secure way.

If hardlinks are not supported by the fs you use, it will still work, but it won't be totally safe any more, e.g. if the system crashes in the moment when borg starts writing the config file, then it could be lost.

When hardlinks work, you would still have the config file's content hardlinked to `config.old` if that happens.
Hmm, I just had a look at `SaveFile` and that also does a good effort on not losing the config file in case updating it goes wrong:

```
class SaveFile:
    """
    Update file contents atomically.

    Must be used as a context manager (defining the scope of the transaction).

    On a journaling file system the file contents are always updated
    atomically and won't become corrupted, even on power failures or
    crashes (for caveats see SyncFile).
    """

    SUFFIX = '.tmp'

    def __init__(self, path, binary=False):
        self.binary = binary
        self.path = path
        self.tmppath = self.path + self.SUFFIX

    def __enter__(self):
        from .. import platform
        try:
            truncate_and_unlink(self.tmppath)
        except FileNotFoundError:
            pass
        self.fd = platform.SyncFile(self.tmppath, self.binary)
        return self.fd

    def __exit__(self, exc_type, exc_val, exc_tb):
        from .. import platform
        self.fd.close()
        if exc_type is not None:
            truncate_and_unlink(self.tmppath)
            return
        os.replace(self.tmppath, self.path)
        platform.sync_dir(os.path.dirname(self.path))
```

@enkore can you clarify why we need both mechanisms and what can go wrong if hardlinks don't work?
@smoneck the short answer is that you may ignore that warning. if nothing crashes and nobody is forensically analyzing your filesystem, nothing special will happen.
Ah, now I see it. As the warning message says, it can't securely erase the contents of the old config file. The old config file will be deleted, but the disc sectors its data had occupied are still there on disk (logically freed, but not overwritten by zeros, thus still having the old config file contents).
Thanks a lot for the clarification! Is it just a thing while initializing a repository? Or in other words: When do I have to be careful apart from the initialization. During normal `borg create` the config is not touched, right?
That code is always used when the config is written. Could be also when you change the passphrase of your encrypted key.
Seems to me like the error message should be more clear what this means.

Instead of

> Hardlink failed, cannot securely erase old config file

try something like

> Failed to securely erase old repository config file (\<not supported\>). Old repokey data, if any, might persist on physical storage.

Resolving patch:
diff --git a/src/borg/repository.py b/src/borg/repository.py
--- a/src/borg/repository.py
+++ b/src/borg/repository.py
@@ -278,7 +278,8 @@ def save_config(self, path, config):
                 os.link(config_path, old_config_path)
             except OSError as e:
                 if e.errno in (errno.EMLINK, errno.ENOSYS, errno.EPERM, errno.ENOTSUP):
-                    logger.warning("Hardlink failed, cannot securely erase old config file")
+                    logger.warning("Failed to securely erase old repository config file (hardlinks not supported>). "
+                                   "Old repokey data, if any, might persist on physical storage.")
                 else:
                     raise
 


Test patch: