Repository: borgbackup/borg

Issue:
Issue on FS that does not support HardLinking
For some reason i have to use SMBFS that does not support hard linking as a repo storage.

it fails in file repository.py arround line 267:

```python
        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):
                    logger.warning("Hardlink failed, cannot securely erase old config file")
                else:
                    raise
```

The workaround is to replace os.link with os.rename

```python
        if os.path.isfile(config_path):
            try:
                os.rename(config_path, old_config_path)
            except OSError as e:
                if e.errno in (errno.EMLINK, errno.ENOSYS, errno.EPERM):
                    logger.warning("Hardlink failed, cannot securely erase old config file")
                else:
                    raise
```

Best Regards
willy.


Discussion:
What is the error message you got?


/tmp/borgtest is an ordinary FS
```shell
willy@browar: ~/borg # borg init -e repokey-blake2 /tmp/borgtest                                                                                                                                      
Enter new passphrase: 
Enter same passphrase again: 
Do you want your passphrase to be displayed for verification? [yN]: y
Your passphrase (between double-quotes): "test"
Make sure the passphrase displayed above is exactly what you wanted.

By default repositories initialized with this version will produce security
errors if written to with an older version (up to and including Borg 1.0.8).

If you want to use these older versions, you can disable the check by running:
borg upgrade --disable-tam '/tmp/borgtest'

See https://borgbackup.readthedocs.io/en/stable/changes.html#pre-1-0-9-manifest-spoofing-vulnerability for details about the security implications.
```

/var/backup/tmp/borgtest is a SMBFS mount 
```shell
willy@browar: ~/borg # borg init -e repokey-blake2 /var/backup/tmp/borgtest
Enter new passphrase: 
Enter same passphrase again: 
Do you want your passphrase to be displayed for verification? [yN]: y
Your passphrase (between double-quotes): "test"
Make sure the passphrase displayed above is exactly what you wanted.
Local Exception
Traceback (most recent call last):
  File "/root/borg/src/borg/archiver.py", line 4024, in main
    exit_code = archiver.run(args)
  File "/root/borg/src/borg/archiver.py", line 3952, in run
    return set_ec(func(args))
  File "/root/borg/src/borg/archiver.py", line 148, in wrapper
    return method(self, args, repository=repository, **kwargs)
  File "/root/borg/src/borg/archiver.py", line 248, in do_init
    key = key_creator(repository, args)
  File "/root/borg/src/borg/crypto/key.py", line 106, in key_creator
    return key.create(repository, args)
  File "/root/borg/src/borg/crypto/key.py", line 657, in create
    key.save(target, passphrase)
  File "/root/borg/src/borg/crypto/key.py", line 767, in save
    target.save_key(key_data)
  File "/root/borg/src/borg/repository.py", line 284, in save_key
    self.save_config(self.path, self.config)
  File "/root/borg/src/borg/repository.py", line 267, in save_config
    os.link(config_path, old_config_path)
OSError: [Errno 45] Operation not supported: '/var/backup/tmp/borgtest/config' -> '/var/backup/tmp/borgtest/config.old'

Platform: FreeBSD browar.XXXXXX.dk 10.3-RELEASE-p20 FreeBSD 10.3-RELEASE-p20 #0: Wed Jul 12 03:13:07 UTC 2017     root@amd64-builder.daemonology.net:/usr/obj/usr/src/sys/GENERIC amd64 amd64
Borg: 1.1.1.dev2+g85e3b405  Python: CPython 3.6.3
PID: 9440  CWD: /root/borg
sys.argv: ['/usr/local/bin/borg', 'init', '-e', 'repokey-blake2', '/var/backup/tmp/borgtest']
SSH_ORIGINAL_COMMAND: None
```
Some info
```shell
willy@browar: ~/borg # mount -t smbfs
//UXXXXXX@UXXXXXX/BACKUP on /var/backup (smbfs)
willy@browar: ~/borg # uname -a
FreeBSD browar.XXXXXX.dk 10.3-RELEASE-p20 FreeBSD 10.3-RELEASE-p20 #0: Wed Jul 12 03:13:07 UTC 2017     root@amd64-builder.daemonology.net:/usr/obj/usr/src/sys/GENERIC  amd64
willy@browar: ~/borg # borg -V
borg 1.1.1.dev2+g85e3b405
willy@browar: ~/borg # 
```

And with my patch/workaroud:

```shell
willy@browar: ~/borg # borg delete /var/backup/tmp/borgtest
You requested to completely DELETE the repository *including* all archives it may contain.
This repository seems to have no manifest, so we can't tell anything about its contents.
Type 'YES' if you understand this and want to continue: YES
willy@browar: ~/borg # borg init -e repokey-blake2 /var/backup/tmp/borgtest                                                                                                                           
Enter new passphrase: 
Enter same passphrase again: 
Do you want your passphrase to be displayed for verification? [yN]: y
Your passphrase (between double-quotes): "test"
Make sure the passphrase displayed above is exactly what you wanted.

By default repositories initialized with this version will produce security
errors if written to with an older version (up to and including Borg 1.0.8).

If you want to use these older versions, you can disable the check by running:
borg upgrade --disable-tam '/var/backup/tmp/borgtest'

See https://borgbackup.readthedocs.io/en/stable/changes.html#pre-1-0-9-manifest-spoofing-vulnerability for details about the security implications.
willy@browar: ~/borg # 
```

Hope you got all you need ;)
on freebsd:
```
#define	EOPNOTSUPP	45	/* Operation not supported */
```

```
Python 3.6.2 (default, Oct  3 2017, 01:18:34) 
[GCC 4.2.1 Compatible FreeBSD Clang 3.4.1 (tags/RELEASE_34/dot1-final 208032)] on freebsd10
Type "help", "copyright", "credits" or "license" for more information.
>>> import errno
>>> errno.ENOTSUP
45
```

Resolving patch:
diff --git a/src/borg/repository.py b/src/borg/repository.py
--- a/src/borg/repository.py
+++ b/src/borg/repository.py
@@ -266,7 +266,7 @@ def save_config(self, path, config):
             try:
                 os.link(config_path, old_config_path)
             except OSError as e:
-                if e.errno in (errno.EMLINK, errno.ENOSYS, errno.EPERM):
+                if e.errno in (errno.EMLINK, errno.ENOSYS, errno.EPERM, errno.ENOTSUP):
                     logger.warning("Hardlink failed, cannot securely erase old config file")
                 else:
                     raise


Test patch: