diff --git a/NEWS b/NEWS index 88c70cdd..1980b313 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,7 @@ * Deprecate the "store_config_files" option at the global scope and move it under the "bootstrap" hook. See the documentation for more information: https://torsion.org/borgmatic/docs/how-to/extract-a-backup/#extract-the-configuration-files-used-to-create-an-archive + * Require the runtime directory to be an absolute path. * Add a "--deleted" flag to the "repo-list" action for listing deleted archives that haven't yet been compacted (Borg 2 only). diff --git a/borgmatic/config/paths.py b/borgmatic/config/paths.py index 799b3d17..7401e1c3 100644 --- a/borgmatic/config/paths.py +++ b/borgmatic/config/paths.py @@ -96,9 +96,16 @@ class Runtime_directory: ) if runtime_directory: + if not runtime_directory.startswith(os.path.sep): + raise ValueError('The runtime directory must be an absolute path') + self.temporary_directory = None else: base_directory = os.environ.get('TMPDIR') or os.environ.get('TEMP') or '/tmp' + + if not base_directory.startswith(os.path.sep): + raise ValueError('The runtime directory must be an absolute path') + os.makedirs(base_directory, mode=0o700, exist_ok=True) self.temporary_directory = tempfile.TemporaryDirectory( prefix=TEMPORARY_DIRECTORY_PREFIX, diff --git a/tests/unit/config/test_paths.py b/tests/unit/config/test_paths.py index d9597cea..fa067fa8 100644 --- a/tests/unit/config/test_paths.py +++ b/tests/unit/config/test_paths.py @@ -63,6 +63,15 @@ def test_runtime_directory_uses_config_option_without_adding_duplicate_borgmatic assert borgmatic_runtime_directory == '/run/./borgmatic' +def test_runtime_directory_with_relative_config_option_errors(): + flexmock(module.os).should_receive('makedirs').never() + config = {'user_runtime_directory': 'run', 'borgmatic_source_directory': '/nope'} + + with pytest.raises(ValueError): + with module.Runtime_directory(config, 'prefix') as borgmatic_runtime_directory: + pass + + def test_runtime_directory_falls_back_to_xdg_runtime_dir(): flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path) flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return( @@ -85,6 +94,15 @@ def test_runtime_directory_falls_back_to_xdg_runtime_dir_without_adding_duplicat assert borgmatic_runtime_directory == '/run/./borgmatic' +def test_runtime_directory_with_relative_xdg_runtime_dir_errors(): + flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return('run') + flexmock(module.os).should_receive('makedirs').never() + + with pytest.raises(ValueError): + with module.Runtime_directory({}, 'prefix') as borgmatic_runtime_directory: + pass + + def test_runtime_directory_falls_back_to_runtime_directory(): flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path) flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None) @@ -109,6 +127,18 @@ def test_runtime_directory_falls_back_to_runtime_directory_without_adding_duplic assert borgmatic_runtime_directory == '/run/./borgmatic' +def test_runtime_directory_falls_back_to_runtime_directory(): + flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None) + flexmock(module.os.environ).should_receive('get').with_args('RUNTIME_DIRECTORY').and_return( + 'run' + ) + flexmock(module.os).should_receive('makedirs').never() + + with pytest.raises(ValueError): + with module.Runtime_directory({}, 'prefix') as borgmatic_runtime_directory: + pass + + def test_runtime_directory_falls_back_to_tmpdir_and_adds_temporary_subdirectory_that_get_cleaned_up(): flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path) flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None) @@ -127,6 +157,20 @@ def test_runtime_directory_falls_back_to_tmpdir_and_adds_temporary_subdirectory_ assert borgmatic_runtime_directory == '/run/borgmatic-1234/./borgmatic' +def test_runtime_directory_with_relative_tmpdir_errors(): + flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None) + flexmock(module.os.environ).should_receive('get').with_args('RUNTIME_DIRECTORY').and_return( + None + ) + flexmock(module.os.environ).should_receive('get').with_args('TMPDIR').and_return('run') + flexmock(module.tempfile).should_receive('TemporaryDirectory').never() + flexmock(module.os).should_receive('makedirs').never() + + with pytest.raises(ValueError): + with module.Runtime_directory({}, 'prefix') as borgmatic_runtime_directory: + pass + + def test_runtime_directory_falls_back_to_temp_and_adds_temporary_subdirectory_that_get_cleaned_up(): flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path) flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None) @@ -146,6 +190,21 @@ def test_runtime_directory_falls_back_to_temp_and_adds_temporary_subdirectory_th assert borgmatic_runtime_directory == '/run/borgmatic-1234/./borgmatic' +def test_runtime_directory_with_relative_temp_errors(): + flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None) + flexmock(module.os.environ).should_receive('get').with_args('RUNTIME_DIRECTORY').and_return( + None + ) + flexmock(module.os.environ).should_receive('get').with_args('TMPDIR').and_return(None) + flexmock(module.os.environ).should_receive('get').with_args('TEMP').and_return('run') + flexmock(module.tempfile).should_receive('TemporaryDirectory').never() + flexmock(module.os).should_receive('makedirs') + + with pytest.raises(ValueError): + with module.Runtime_directory({}, 'prefix') as borgmatic_runtime_directory: + pass + + def test_runtime_directory_falls_back_to_hard_coded_tmp_path_and_adds_temporary_subdirectory_that_get_cleaned_up(): flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path) flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None)