@@ -197,7 +197,8 @@ def load_config_file(configuration_file=None, expand=True):
197197 exclude_list = split (items .get ('exclude-list' , '' )),
198198 io_scheduling_class = items .get ('ionice' ),
199199 strict = coerce_boolean (items .get ('strict' , 'yes' )),
200- prefer_recent = coerce_boolean (items .get ('prefer-recent' , 'no' )))
200+ prefer_recent = coerce_boolean (items .get ('prefer-recent' , 'no' )),
201+ rmdir = items .get ('use-rmdir' ))
201202 # Expand filename patterns?
202203 if expand and location .have_wildcards :
203204 logger .verbose ("Expanding filename pattern %s on %s .." , location .directory , location .context )
@@ -241,8 +242,9 @@ def __init__(self, rotation_scheme, **options):
241242 :param rotation_scheme: Used to set :attr:`rotation_scheme`.
242243 :param options: Any keyword arguments are used to set the values of the
243244 properties :attr:`config_file`, :attr:`dry_run`,
244- :attr:`exclude_list`, :attr:`include_list`,
245- :attr:`io_scheduling_class` and :attr:`strict`.
245+ :attr:`rmdir`, :attr:`exclude_list`,
246+ :attr:`include_list`, :attr:`io_scheduling_class` and
247+ :attr:`strict`.
246248 """
247249 options .update (rotation_scheme = rotation_scheme )
248250 super (RotateBackups , self ).__init__ (** options )
@@ -269,6 +271,18 @@ def dry_run(self):
269271 """
270272 return False
271273
274+ @mutable_property
275+ def rmdir (self ):
276+ """
277+ :data:`True` to use `rmdir` to remove the snapshots, :data:`False` to use `rm -r` (defaults to :data:`False`).
278+
279+ Normally the backups are removed one file at a file using the command `rm -r`.
280+ Some file-systems are capable of removing a whole directory with the command `rmdir`,
281+ even when the directory is not empty. For example, this is how CephFS snapshots are
282+ removed.
283+ """
284+ return False
285+
272286 @cached_property (writable = True )
273287 def exclude_list (self ):
274288 """
@@ -467,11 +481,18 @@ class together to implement backup rotation with an easy to use Python
467481 else :
468482 logger .info ("Deleting %s .." , friendly_name )
469483 if not self .dry_run :
470- command = location .context .prepare (
471- 'rm' , '-Rf' , backup .pathname ,
472- group_by = (location .ssh_alias , location .mount_point ),
473- ionice = self .io_scheduling_class ,
474- )
484+ if not self .rmdir :
485+ command = location .context .prepare (
486+ 'rm' , '-Rf' , backup .pathname ,
487+ group_by = (location .ssh_alias , location .mount_point ),
488+ ionice = self .io_scheduling_class ,
489+ )
490+ else :
491+ command = location .context .prepare (
492+ 'rmdir' , backup .pathname ,
493+ group_by = (location .ssh_alias , location .mount_point ),
494+ ionice = self .io_scheduling_class ,
495+ )
475496 rotation_commands .append (command )
476497 if not prepare :
477498 timer = Timer ()
0 commit comments