Repository: borgbackup/borg

Issue:
PermissionError when initializing repository
My backup script should automatically initialize a new Borg repo in case it's not there yet. It executes this command:
`borg init --encryption=repokey-blake2 /mnt/nas/backup-desktop`

I remember that a few months/years back the script worked fine. Now, I get the following error:

```
Local Exception
Traceback (most recent call last):
  File "/usr/lib/python3.7/site-packages/borg/archiver.py", line 4501, in main
    exit_code = archiver.run(args)
  File "/usr/lib/python3.7/site-packages/borg/archiver.py", line 4433, in run
    return set_ec(func(args))
  File "/usr/lib/python3.7/site-packages/borg/archiver.py", line 166, in wrapper
    return method(self, args, repository=repository, **kwargs)
  File "/usr/lib/python3.7/site-packages/borg/archiver.py", line 280, in do_init
    key = key_creator(repository, args)
  File "/usr/lib/python3.7/site-packages/borg/crypto/key.py", line 110, in key_creator
    return key.create(repository, args)
  File "/usr/lib/python3.7/site-packages/borg/crypto/key.py", line 690, in create
    key.save(target, passphrase)
  File "/usr/lib/python3.7/site-packages/borg/crypto/key.py", line 800, in save
    target.save_key(key_data)
  File "/usr/lib/python3.7/site-packages/borg/repository.py", line 310, in save_key
    self.save_config(self.path, self.config)
  File "/usr/lib/python3.7/site-packages/borg/repository.py", line 292, in save_config
    os.link(config_path, old_config_path)
PermissionError: [Errno 13] Permission denied: '/mnt/nas/backup-desktop/config' -> '/mnt/nas/backup-desktop/config.old'
```

Also, the `backup-desktop` directory looks as follows after the exception is raised:

```
.rwxrwxrwx 209 root 25 Aug 14:17 config
drwxrwxrwx   - root 25 Aug 14:17 data
.rwxrwxrwx  73 root 25 Aug 14:17 README
```

It's run as daily cron, so with `root` privileges. I wonder why I get the PermissionError. Can someone give me a hint?

**Platform:** Linux 5.2.9-arch1-1-ARCH
**Borg:** 1.1.10
**Python:**: CPython 3.7.4 msgpack 0.5.6



Discussion:
Can you manually create a hardlink at that location?

Like: `# ln source target`
@ThomasWaldmann: It does not work manually because of permission error.

I wonder if the problem is that I mount the NAS file system using `mount -t cifs -o credentials=/path/to/credentials,vers=1.0 //nas /mnt/nas`?
Does the on-disk fs you use support hardlinks (on the server)?
Does cifs usually support hardlinks?

Are you sure you run 1.1.10? This is the code of 1.1.10 and you see that `EPERM` should be catched there, so you should not see a `PermissionError` exception traceback:

```
    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("Failed to securely erase old repository config file (hardlinks not supported>). "
                                   "Old repokey data, if any, might persist on physical storage.")
                else:
                    raise

        with SaveFile(config_path) as fd:
            config.write(fd)

        if os.path.isfile(old_config_path):
            secure_erase(old_config_path)
```
13 == `errno.EACCES` - that explains that the exception handler does not catch it (but re-raises it in the "else" branch).
Still not sure where the bug is, though:
- Is a hardlink expected to work on your base fs (which is what precisely?)?
- Is a hardlink expected to be supported by your cifs server?
- Is a hardlink expected to be supported by your cifs client?
- Is EACCES the correct errno for this problem or is the cifs client (server?) using the wrong errno?
https://stackoverflow.com/questions/35023667/difference-between-eacces-and-eperm

So, can you do anything there (like `cp source target`)?
I'm using the NAS functionality provided by FRITZ!Box 7430. So, I have a USB hard drive connected to the router.

> Is a hardlink expected to work on your base fs (which is what precisely?)?

The hard drive itself is using NTFS which should support hardlinks as far as I know.

When I plug in the hard drive directly to my desktop, I can create hard links. So, the problem is probably related to CIFS/Samba. Unfortunately, I am not that experienced with it. Looks like I have to do some further investigations. Sorry for the issue.
I am considering to just add errno.EACCES to the list of ignored errors.
It will not crash then if the hardlink fails due to "access denied", but will emit a warning.

If there really is a permissions issue (that is not related to hardlinking), the code will just fail a few lines later and we do not have to wonder about whether it is due to the hardlink operation.


Resolving patch:
diff --git a/src/borg/repository.py b/src/borg/repository.py
--- a/src/borg/repository.py
+++ b/src/borg/repository.py
@@ -298,7 +298,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, errno.ENOTSUP):
+                if e.errno in (errno.EMLINK, errno.ENOSYS, errno.EPERM, errno.EACCES, errno.ENOTSUP):
                     logger.warning("Failed to securely erase old repository config file (hardlinks not supported>). "
                                    "Old repokey data, if any, might persist on physical storage.")
                 else:


Test patch: