@@ -281,3 +281,62 @@ def test_do_backup_for_restic_adapts_ownership(
281281 }
282282 assert found_user == {expected_user }
283283 assert found_group == {expected_group }
284+
285+
286+ def test_do_backup_for_btrfs_with_empty_files_creates_directory (
287+ mounted_device ,
288+ ) -> None :
289+ """Test that reproduces the permission error when Files is empty.
290+
291+ This test exercises the code path that causes a PermissionError in production
292+ when creating a backup with empty Files set.
293+
294+ The issue in production:
295+ 1. First backup works (initial subvolume is owned by user after chown)
296+ 2. Second backup fails with PermissionError because:
297+ - A new snapshot is created with 'sudo btrfs subvolume snapshot'
298+ - In production, the snapshot is owned by root
299+ - files_dest.mkdir() is called without sudo (line 53 in backup_backends.py)
300+ - mkdir fails with PermissionError when trying to create directory in
301+ root-owned snapshot
302+
303+ Note: This test may pass in CI environments where sudo preserves user
304+ ownership, but it reproduces the production error scenario where Files is
305+ empty and multiple backups are performed.
306+ """
307+ empty_config , device = mounted_device
308+ if not isinstance (empty_config , cp .BtrFSRsyncConfig ):
309+ # This test is specific to BtrFS backend
310+ return
311+
312+ # Create config with empty Files set (this is the key to reproducing the bug)
313+ folder_dest_dir = "some-folder-name"
314+ config = empty_config .model_copy (
315+ update = {
316+ "Folders" : {FIRST_BACKUP : folder_dest_dir },
317+ "Files" : set (), # Empty Files set triggers the bug
318+ "FilesDest" : "Einzeldateien" ,
319+ }
320+ )
321+
322+ # First backup - creates first snapshot
323+ backend = bb .BtrFSRsyncBackend (config )
324+ backend .do_backup (device )
325+
326+ # Verify the FilesDest directory was created in the first snapshot
327+ backup_repository = device / config .BackupRepositoryFolder
328+ first_snapshot = sorted (backup_repository .iterdir ())[- 1 ]
329+ files_dest_first = first_snapshot / config .FilesDest
330+ assert files_dest_first .exists ()
331+ assert files_dest_first .is_dir ()
332+
333+ # Second backup - creates snapshot from first snapshot
334+ # In production with root-owned snapshots, this would raise PermissionError
335+ # at the files_dest.mkdir() call because the snapshot is owned by root
336+ backend .do_backup (device )
337+
338+ # Verify the FilesDest directory was created in the second snapshot
339+ second_snapshot = sorted (backup_repository .iterdir ())[- 1 ]
340+ files_dest_second = second_snapshot / config .FilesDest
341+ assert files_dest_second .exists ()
342+ assert files_dest_second .is_dir ()
0 commit comments