Repository: borgbackup/borg

Issue:
Confusing error message from borg init
If user doesn't have access to write to `/path`, this happens:
```
$ borg init -e keyfile /path
A repository already exists at /path.
```
borg 1.1.3


Discussion:
```
(borg-env) tw@tux:~/w/borg$ sudo mkdir inaccessible
(borg-env) tw@tux:~/w/borg$ sudo chmod 700 inaccessible

(borg-env) tw@tux:~/w/borg$ borg init -e keyfile inaccessible/repo-kf
Local Exception
Traceback (most recent call last):
  File "/home/tw/w/borg/src/borg/archiver.py", line 4157, in main
    exit_code = archiver.run(args)
  File "/home/tw/w/borg/src/borg/archiver.py", line 4089, in run
    return set_ec(func(args))
  File "/home/tw/w/borg/src/borg/archiver.py", line 136, in wrapper
    with repository:
  File "/home/tw/w/borg/src/borg/repository.py", line 176, in __enter__
    self.create(self.path)
  File "/home/tw/w/borg/src/borg/repository.py", line 239, in create
    os.mkdir(path)
PermissionError: [Errno 13] Permission denied: '/home/tw/w/borg/inaccessible/repo-kf'

Platform: Linux tux 4.14.4-tux3 #4 SMP Sun Dec 10 00:14:27 CET 2017 x86_64 x86_64
Linux: Ubuntu 16.04 xenial
Borg: 1.1.3  Python: CPython 3.5.2
PID: 7025  CWD: /home/tw/w/borg
sys.argv: ['/home/tw/w/borg-env/bin/borg', 'init', '-e', 'keyfile', 'inaccessible/repo-kf']
SSH_ORIGINAL_COMMAND: None
```

Can't reproduce.
Ah, I found that error too now, when trying to reproduce this. Here are more complete steps:
```
$ cd /tmp
$ sudo mkdir inaccessible
$ ls -ld inaccessible
drwxr-xr-x 2 root root 4096 Dec 20 13:47 inaccessible
$ borg init --encryption=keyfile /tmp/inaccessible
Local Exception
Traceback (most recent call last):
  File "/usr/lib64/python3.5/site-packages/borg/archiver.py", line 4157, in main
    exit_code = archiver.run(args)
  File "/usr/lib64/python3.5/site-packages/borg/archiver.py", line 4089, in run
    return set_ec(func(args))
  File "/usr/lib64/python3.5/site-packages/borg/archiver.py", line 136, in wrapper
    with repository:
  File "/usr/lib64/python3.5/site-packages/borg/repository.py", line 176, in __enter__
    self.create(self.path)
  File "/usr/lib64/python3.5/site-packages/borg/repository.py", line 240, in create
    with open(os.path.join(path, 'README'), 'w') as fd:
PermissionError: [Errno 13] Permission denied: '/tmp/inaccessible/README'

Platform: Linux komputer 4.12.12-gentoo #1 SMP Sat Oct 14 18:42:35 IST 2017 x86_64 AMD FX(tm)-8350 Eight-Core Processor
Linux: Gentoo Base System 2.4.1
Borg: 1.1.3  Python: CPython 3.5.4
PID: 25508  CWD: /var/tmp
sys.argv: ['/usr/lib/python-exec/python3.5/borg', 'init', '--encryption=keyfile', '/tmp/inaccessible']
SSH_ORIGINAL_COMMAND: None
$ sudo touch inaccessible/foo
$ borg init --encryption=keyfile /tmp/inaccessible
A repository already exists at /tmp/inaccessible.
```
So, accessibility doesn't matter, sorry. At first I got this message when there was something in the directory (I thought it's empty), and it was inaccessible.
It still not "repository" though.
that is not a bug. if you tell borg to initialize a repository somewhere, it will reject to do so if there is already something at that place.

Maybe the error message could be clearer by saying "Something already exists at /thatpath.".
Yep, half an hour wasted with such misleading error text.
Please guys, change it to something more meaningful!
Currently the checks for creating a repository, from [`check_can_create_repository`], are:

1. if the target directory exists,
    1. is it a directory? if not, raise an error
    2. does it contain anything? if so, raise an error
2. are any of the parent directories Borg repositories? (detected via contents of `README` file)

The error raised says "A repository already exists at &lt;path&gt;", which is correct for check 2, but not for check 1.

Borg needs to handle the cases where:

- the target directory doesn't exist (already done, by creating it)
- the target directory exists, but isn't a directory (done, but the error message is wrong)
- the target directory exists, but already contains files (done, but the error message is wrong)
- a parent directory is a repository (done, raises the correct error)

so it would seem like there need to be two new errors: "&lt;path&gt; is not a directory", and "&lt;path&gt; already contains files".

I'm happy to implement this, with one question: if there are already files in the directory, should Borg check if it is already a repository and report a more accurate error if so, or just say "there are already files there"?

[`check_can_create_repository`]: https://github.com/borgbackup/borg/blob/79b2f2f6537aaa8ab4f8a71f13b0e294cd2b5c38/src/borg/repository.py#L198
@anowlcalledjosh good analysis. if it is easy to check for being a repo, it could be done for the target dir also (like in a loop over thatdir, parent(thatdir)).

the most simple fix of the confusing message is to just say "there is already something at the target location" (while borg's expectation / requirement is that there is nothing yet and it can create a dir there).

Resolving patch:
diff --git a/src/borg/repository.py b/src/borg/repository.py
--- a/src/borg/repository.py
+++ b/src/borg/repository.py
@@ -116,6 +116,9 @@ class DoesNotExist(Error):
     class AlreadyExists(Error):
         """A repository already exists at {}."""
 
+    class PathAlreadyExists(Error):
+        """There is already something at {}."""
+
     class InvalidRepository(Error):
         """{} is not a valid repository. Check repo config."""
 
@@ -195,9 +198,24 @@ def __exit__(self, exc_type, exc_val, exc_tb):
     def id_str(self):
         return bin_to_hex(self.id)
 
+    @staticmethod
+    def is_repository(path):
+        """Check whether there is already a Borg repository at *path*."""
+        try:
+            # Use binary mode to avoid troubles if a README contains some stuff not in our locale
+            with open(os.path.join(path, 'README'), 'rb') as fd:
+                # Read only the first ~100 bytes (if any), in case some README file we stumble upon is large.
+                readme_head = fd.read(100)
+                # The first comparison captures our current variant (REPOSITORY_README), the second comparison
+                # is an older variant of the README file (used by 1.0.x).
+                return b'Borg Backup repository' in readme_head or b'Borg repository' in readme_head
+        except OSError:
+            # Ignore FileNotFound, PermissionError, ...
+            return False
+
     def check_can_create_repository(self, path):
         """
-        Raise self.AlreadyExists if a repository already exists at *path* or any parent directory.
+        Raise an exception if a repository already exists at *path* or any parent directory.
 
         Checking parent directories is done for two reasons:
         (1) It's just a weird thing to do, and usually not intended. A Borg using the "parent" repository
@@ -207,8 +225,11 @@ def check_can_create_repository(self, path):
             repository, user's can only use the quota'd repository, when their --restrict-to-path points
             at the user's repository.
         """
-        if os.path.exists(path) and (not os.path.isdir(path) or os.listdir(path)):
-            raise self.AlreadyExists(path)
+        if os.path.exists(path):
+            if self.is_repository(path):
+                raise self.AlreadyExists(path)
+            if not os.path.isdir(path) or os.listdir(path):
+                raise self.PathAlreadyExists(path)
 
         while True:
             # Check all parent directories for Borg's repository README
@@ -218,18 +239,8 @@ def check_can_create_repository(self, path):
             if path == previous_path:
                 # We reached the root of the directory hierarchy (/.. = / and C:\.. = C:\).
                 break
-            try:
-                # Use binary mode to avoid troubles if a README contains some stuff not in our locale
-                with open(os.path.join(path, 'README'), 'rb') as fd:
-                    # Read only the first ~100 bytes (if any), in case some README file we stumble upon is large.
-                    readme_head = fd.read(100)
-                    # The first comparison captures our current variant (REPOSITORY_README), the second comparison
-                    # is an older variant of the README file (used by 1.0.x).
-                    if b'Borg Backup repository' in readme_head or b'Borg repository' in readme_head:
-                        raise self.AlreadyExists(path)
-            except OSError:
-                # Ignore FileNotFound, PermissionError, ...
-                pass
+            if self.is_repository(path):
+                raise self.AlreadyExists(path)
 
     def create(self, path):
         """Create a new empty repository at `path`


Test patch: