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

Resolving patch:
diff --git a/src/borg/archiver.py b/src/borg/archiver.py
--- a/src/borg/archiver.py
+++ b/src/borg/archiver.py
@@ -1850,7 +1850,10 @@ def do_with_lock(self, args, repository):
         env = prepare_subprocess_env(system=True)
         try:
             # we exit with the return code we get from the subprocess
-            return subprocess.call([args.command] + args.args, env=env)
+            ret = subprocess.call([args.command] + args.args, env=env)
+        except (FileNotFoundError, OSError, ValueError) as e:
+            self.print_error(f"Error while trying to run '{args.command}': {e}")
+            ret = EXIT_ERROR
         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
@@ -1859,6 +1862,7 @@ def do_with_lock(self, args, repository):
             # any other mechanism relying on existing segment data not changing).
             # see issue #1867.
             repository.commit(compact=False)
+            return ret
 
     @with_repository(manifest=False, exclusive=True)
     def do_compact(self, args, repository):
diff --git a/src/borg/testsuite/archiver.py b/src/borg/testsuite/archiver.py
--- a/src/borg/testsuite/archiver.py
+++ b/src/borg/testsuite/archiver.py
@@ -3186,6 +3186,11 @@ def test_with_lock(self):
         cmd = 'python3', '-c', 'import os, sys; sys.exit(42 if os.path.exists("%s") else 23)' % lock_path
         self.cmd('with-lock', self.repository_location, *cmd, fork=True, exit_code=42)
 
+    def test_with_lock_non_existent_command(self):
+        self.cmd('init', '--encryption=repokey', self.repository_location)
+        cmd = ['non_existent_command', ]
+        self.cmd('with-lock', self.repository_location, *cmd, fork=True, exit_code=EXIT_ERROR)
+
     def test_recreate_list_output(self):
         self.cmd('init', '--encryption=repokey', self.repository_location)
         self.create_regular_file('file1', size=0)


Test patch: