Repository: borgbackup/borg

Issue:
`borg with-lock` raises `FileNotFoundError`
Hey,
I just want to find out what `with-lock` does. I tried some things and I ended up with an exception :)

```bash
kmille@linbox:~ borg --verbose with-lock /home/kmille/tmp/borgrepo list
Local Exception
Traceback (most recent call last):
  File "/usr/lib/python3.11/site-packages/borg/archiver.py", line 5343, in main
    exit_code = archiver.run(args)
                ^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/borg/archiver.py", line 5263, in run
    return set_ec(func(args))
                  ^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/borg/archiver.py", line 189, in wrapper
    return method(self, args, repository=repository, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/borg/archiver.py", line 1853, in do_with_lock
    return subprocess.call([args.command] + args.args, env=env)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/subprocess.py", line 389, in call
    with Popen(*popenargs, **kwargs) as p:
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/subprocess.py", line 1026, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/usr/lib/python3.11/subprocess.py", line 1950, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'list'

Platform: Linux linbox 6.6.8-hardened1-1-hardened #1 SMP PREEMPT_DYNAMIC Thu, 21 Dec 2023 23:45:24 +0000 x86_64
Linux: Unknown Linux  
Borg: 1.2.7  Python: CPython 3.11.6 msgpack: 1.0.5 fuse: llfuse 1.5.0 [pyfuse3,llfuse]
PID: 186644  CWD: /home/kmille
sys.argv: ['/usr/bin/borg', '--verbose', 'with-lock', '/home/kmille/tmp/borgrepo', 'list']
SSH_ORIGINAL_COMMAND: None
```

I don't understand the [docs](https://borgbackup.readthedocs.io/en/stable/usage/lock.html). After reading, I ask myself:
- What's the purpose of a lock if I can run other borg commands in the meantime
- "It will first try to acquire the lock": Does this just mean: wait until it's free?
- 

Maybe it makes sense to update the docs :)


Discussion:
The purpose of the lock is to lock the repo against other usage, e.g. if you want to run some command on it and make sure it is in a steady state and does not change.

E.g.:
```
borg with-lock /mnt/borgrepo rsync -av /mnt/borgrepo /somewhere/else
```

Yes, it will wait if the repo is already locked, see the `--lock-wait` option (default is to only wait for rather short though).
Can you make a PR and add the example to the docs?
Another useful change would be to catch the `FileNotFoundError` here and emit an error message without traceback.
Thanks for the explanation! I will try tomorrow!
I fail to reproduce the issue with the master branch. I'm not able to use borg at all...?
I can not use my current repo.
```bash
(borg-env) kmille@linbox:borg borg-env/bin/borg -r /home/kmille/tmp/borgrepo list
usage: borg [-V] [-h] [--critical] [--error] [--warning] [--info] [--debug] [--debug-topic TOPIC] [-p] [--iec] [--log-json] [--lock-wait SECONDS] [--bypass-lock] [--show-version] [--show-rc] [--umask M] [--remote-path PATH]
            [--upload-ratelimit RATE] [--upload-buffer UPLOAD_BUFFER] [--debug-profile FILE] [--rsh RSH] [--socket [PATH]] [-r REPO]
            <command> ...
borg: error: argument -r/--repo: invalid validator value: '/home/kmille/tmp/borgrepo'
```
And can't create a new one ...?
```bash
(borg-env) kmille@linbox:borg borg-env/bin/borg -r ~/tmp/borgrepo2 rcreate
usage: borg [-V] [-h] [--critical] [--error] [--warning] [--info] [--debug] [--debug-topic TOPIC] [-p] [--iec] [--log-json] [--lock-wait SECONDS] [--bypass-lock] [--show-version] [--show-rc] [--umask M] [--remote-path PATH]
            [--upload-ratelimit RATE] [--upload-buffer UPLOAD_BUFFER] [--debug-profile FILE] [--rsh RSH] [--socket [PATH]] [-r REPO]
            <command> ...
borg: error: argument -r/--repo: invalid validator value: '/home/kmille/tmp/borgrepo2'
```
I'm following the quickstart ...
master branch will be borg2 at some time and is very different.

1.2.x code is in 1.2-maint branch, upcoming 1.4 (will supersede 1.2 in the near future) code is in 1.4-maint branch.
Okay? 
1) How can I run borg without running into the `invalid validator value` issue?
2) Which branch should I use for my PR? That's still not clear to me.
3) I need to close this PR and open a new for a different branch, do I?
1. use 1.2-maint branch (that is closest to what you practically use)
2. 1.2-maint
3. yes
Sorry for the confusion

Resolving patch:
diff --git a/src/borg/archiver/lock_cmds.py b/src/borg/archiver/lock_cmds.py
--- a/src/borg/archiver/lock_cmds.py
+++ b/src/borg/archiver/lock_cmds.py
@@ -4,7 +4,7 @@
 from ._common import with_repository
 from ..cache import Cache
 from ..constants import *  # NOQA
-from ..helpers import prepare_subprocess_env, set_ec
+from ..helpers import prepare_subprocess_env, set_ec, CommandError
 from ..manifest import Manifest
 
 from ..logger import create_logger
@@ -35,6 +35,8 @@ def do_with_lock(self, args, repository):
             # we exit with the return code we get from the subprocess
             rc = subprocess.call([args.command] + args.args, env=env)
             set_ec(rc)
+        except (FileNotFoundError, OSError, ValueError) as e:
+            raise CommandError(f"Error while trying to run '{args.command}': {e}")
         finally:
             # we need to commit the "no change" operation we did to the manifest
             # because it created a new segment file in the repository. if we would
diff --git a/src/borg/testsuite/archiver/lock_cmds.py b/src/borg/testsuite/archiver/lock_cmds.py
--- a/src/borg/testsuite/archiver/lock_cmds.py
+++ b/src/borg/testsuite/archiver/lock_cmds.py
@@ -18,3 +18,10 @@ def test_with_lock(archivers, request):
     lock_path = os.path.join(archiver.repository_path, "lock.exclusive")
     command = "python3", "-c", 'import os, sys; sys.exit(42 if os.path.exists("%s") else 23)' % lock_path
     cmd(archiver, "with-lock", *command, fork=True, exit_code=42)
+
+
+def test_with_lock_non_existent_command(archivers, request):
+    archiver = request.getfixturevalue(archivers)
+    cmd(archiver, "rcreate", RK_ENCRYPTION)
+    command = ["non_existent_command"]
+    cmd(archiver, "with-lock", *command, fork=True, exit_code=EXIT_ERROR)


Test patch: