@@ -308,21 +308,25 @@ def copymode(src, dst, *, follow_symlinks=True):
308308 chmod_func (dst , stat .S_IMODE (st .st_mode ))
309309
310310if hasattr (os , 'listxattr' ):
311- def _copyxattr (src , dst , * , follow_symlinks = True ):
311+ def _copyxattr (src , dst , * , follow_symlinks = True , names_filter = None ):
312312 """Copy extended filesystem attributes from `src` to `dst`.
313313
314314 Overwrite existing attributes.
315315
316316 If `follow_symlinks` is false, symlinks won't be followed.
317317
318+ `names_filter` is a callback to filter xattr names.
318319 """
319-
320320 try :
321321 names = os .listxattr (src , follow_symlinks = follow_symlinks )
322322 except OSError as e :
323323 if e .errno not in (errno .ENOTSUP , errno .ENODATA , errno .EINVAL ):
324324 raise
325325 return
326+ if not names :
327+ return
328+ if names_filter is not None :
329+ names = names_filter (names )
326330 for name in names :
327331 try :
328332 value = os .getxattr (src , name , follow_symlinks = follow_symlinks )
@@ -335,7 +339,8 @@ def _copyxattr(src, dst, *, follow_symlinks=True):
335339 def _copyxattr (* args , ** kwargs ):
336340 pass
337341
338- def copystat (src , dst , * , follow_symlinks = True ):
342+ def copystat (src , dst , * , follow_symlinks = True ,
343+ preserve_security_context = False ):
339344 """Copy file metadata
340345
341346 Copy the permission bits, last access time, last modification time, and
@@ -346,6 +351,14 @@ def copystat(src, dst, *, follow_symlinks=True):
346351
347352 If the optional flag `follow_symlinks` is not set, symlinks aren't
348353 followed if and only if both `src` and `dst` are symlinks.
354+
355+ If the optional flag `preserve_security_context` is set, extended
356+ attributes in the security namespace are copied as well. By default,
357+ attributes like `security.selinux` are ignored.
358+
359+ `preserve_security_context=False` is equivalent to
360+ `cp -p --preserve=xattr`. `preserve_security_context=True` behaves like
361+ `cp -p --preserve=xattr,context`.
349362 """
350363 sys .audit ("shutil.copystat" , src , dst )
351364
@@ -367,6 +380,16 @@ def lookup(name):
367380 return fn
368381 return _nop
369382
383+ # don't copy security xattr by default
384+ # see coreutils src/copy.c:check_selinux_attr()
385+ if not preserve_security_context :
386+ def names_filter (names ):
387+ return [
388+ name for name in names if not name .startswith ("security." )
389+ ]
390+ else :
391+ names_filter = None
392+
370393 if isinstance (src , os .DirEntry ):
371394 st = src .stat (follow_symlinks = follow )
372395 else :
@@ -376,7 +399,7 @@ def lookup(name):
376399 follow_symlinks = follow )
377400 # We must copy extended attributes before the file is (potentially)
378401 # chmod()'ed read-only, otherwise setxattr() will error with -EACCES.
379- _copyxattr (src , dst , follow_symlinks = follow )
402+ _copyxattr (src , dst , follow_symlinks = follow , names_filter = names_filter )
380403 try :
381404 lookup ("chmod" )(dst , mode , follow_symlinks = follow )
382405 except NotImplementedError :
@@ -419,7 +442,7 @@ def copy(src, dst, *, follow_symlinks=True):
419442 copymode (src , dst , follow_symlinks = follow_symlinks )
420443 return dst
421444
422- def copy2 (src , dst , * , follow_symlinks = True ):
445+ def copy2 (src , dst , * , follow_symlinks = True , preserve_security_context = False ):
423446 """Copy data and metadata. Return the file's destination.
424447
425448 Metadata is copied with copystat(). Please see the copystat function
@@ -429,11 +452,16 @@ def copy2(src, dst, *, follow_symlinks=True):
429452
430453 If follow_symlinks is false, symlinks won't be followed. This
431454 resembles GNU's "cp -P src dst".
455+
456+ If the optional flag `preserve_security_context` is set, extended
457+ attributes in the security namespace are copied as well. By default,
458+ attributes like `security.selinux` are ignored.
432459 """
433460 if os .path .isdir (dst ):
434461 dst = os .path .join (dst , os .path .basename (src ))
435462 copyfile (src , dst , follow_symlinks = follow_symlinks )
436- copystat (src , dst , follow_symlinks = follow_symlinks )
463+ copystat (src , dst , follow_symlinks = follow_symlinks ,
464+ preserve_security_context = preserve_security_context )
437465 return dst
438466
439467def ignore_patterns (* patterns ):
@@ -449,7 +477,8 @@ def _ignore_patterns(path, names):
449477 return _ignore_patterns
450478
451479def _copytree (entries , src , dst , symlinks , ignore , copy_function ,
452- ignore_dangling_symlinks , dirs_exist_ok = False ):
480+ ignore_dangling_symlinks , dirs_exist_ok = False ,
481+ preserve_security_context = False ):
453482 if ignore is not None :
454483 ignored_names = ignore (os .fspath (src ), [x .name for x in entries ])
455484 else :
@@ -480,7 +509,8 @@ def _copytree(entries, src, dst, symlinks, ignore, copy_function,
480509 # code with a custom `copy_function` may rely on copytree
481510 # doing the right thing.
482511 os .symlink (linkto , dstname )
483- copystat (srcobj , dstname , follow_symlinks = not symlinks )
512+ copystat (srcobj , dstname , follow_symlinks = not symlinks ,
513+ preserve_security_context = preserve_security_context )
484514 else :
485515 # ignore dangling symlink if the flag is on
486516 if not os .path .exists (linkto ) and ignore_dangling_symlinks :
@@ -493,7 +523,8 @@ def _copytree(entries, src, dst, symlinks, ignore, copy_function,
493523 copy_function (srcobj , dstname )
494524 elif srcentry .is_dir ():
495525 copytree (srcobj , dstname , symlinks , ignore , copy_function ,
496- dirs_exist_ok = dirs_exist_ok )
526+ dirs_exist_ok = dirs_exist_ok ,
527+ preserve_security_context = preserve_security_context )
497528 else :
498529 # Will raise a SpecialFileError for unsupported file types
499530 copy_function (srcobj , dstname )
@@ -504,7 +535,7 @@ def _copytree(entries, src, dst, symlinks, ignore, copy_function,
504535 except OSError as why :
505536 errors .append ((srcname , dstname , str (why )))
506537 try :
507- copystat (src , dst )
538+ copystat (src , dst , preserve_security_context = preserve_security_context )
508539 except OSError as why :
509540 # Copying file access times may fail on Windows
510541 if getattr (why , 'winerror' , None ) is None :
@@ -514,7 +545,8 @@ def _copytree(entries, src, dst, symlinks, ignore, copy_function,
514545 return dst
515546
516547def copytree (src , dst , symlinks = False , ignore = None , copy_function = copy2 ,
517- ignore_dangling_symlinks = False , dirs_exist_ok = False ):
548+ ignore_dangling_symlinks = False , dirs_exist_ok = False ,
549+ preserve_security_context = False ):
518550 """Recursively copy a directory tree and return the destination directory.
519551
520552 dirs_exist_ok dictates whether to raise an exception in case dst or any
@@ -550,14 +582,18 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
550582 destination path as arguments. By default, copy2() is used, but any
551583 function that supports the same signature (like copy()) can be used.
552584
585+ If the optional flag `preserve_security_context` is set, extended
586+ attributes in the security namespace are copied as well. By default,
587+ attributes like `security.selinux` are ignored.
553588 """
554589 sys .audit ("shutil.copytree" , src , dst )
555590 with os .scandir (src ) as itr :
556591 entries = list (itr )
557592 return _copytree (entries = entries , src = src , dst = dst , symlinks = symlinks ,
558593 ignore = ignore , copy_function = copy_function ,
559594 ignore_dangling_symlinks = ignore_dangling_symlinks ,
560- dirs_exist_ok = dirs_exist_ok )
595+ dirs_exist_ok = dirs_exist_ok ,
596+ preserve_security_context = preserve_security_context )
561597
562598if hasattr (os .stat_result , 'st_file_attributes' ):
563599 # Special handling for directory junctions to make them behave like
@@ -761,7 +797,7 @@ def _basename(path):
761797 sep = os .path .sep + (os .path .altsep or '' )
762798 return os .path .basename (path .rstrip (sep ))
763799
764- def move (src , dst , copy_function = copy2 ):
800+ def move (src , dst , copy_function = copy2 , preserve_security_context = False ):
765801 """Recursively move a file or directory to another location. This is
766802 similar to the Unix "mv" command. Return the file or directory's
767803 destination.
@@ -814,10 +850,17 @@ def move(src, dst, copy_function=copy2):
814850 raise Error ("Cannot move a directory '%s' into itself"
815851 " '%s'." % (src , dst ))
816852 copytree (src , real_dst , copy_function = copy_function ,
817- symlinks = True )
853+ symlinks = True ,
854+ preserve_security_context = preserve_security_context )
818855 rmtree (src )
819856 else :
820- copy_function (src , real_dst )
857+ if preserve_security_context :
858+ copy_function (
859+ src , real_dst ,
860+ preserve_security_context = preserve_security_context
861+ )
862+ else :
863+ copy_function (src , real_dst )
821864 os .unlink (src )
822865 return real_dst
823866
0 commit comments