@@ -311,6 +311,67 @@ def _chflags_raiser(path, flags):
311311 finally :
312312 os .chflags = old_chflags
313313
314+ @support .skip_unless_xattr
315+ def test_copyxattr (self ):
316+ tmp_dir = self .mkdtemp ()
317+ src = os .path .join (tmp_dir , 'foo' )
318+ write_file (src , 'foo' )
319+ dst = os .path .join (tmp_dir , 'bar' )
320+ write_file (dst , 'bar' )
321+
322+ # no xattr == no problem
323+ shutil ._copyxattr (src , dst )
324+ # common case
325+ os .setxattr (src , 'user.foo' , b'42' )
326+ os .setxattr (src , 'user.bar' , b'43' )
327+ shutil ._copyxattr (src , dst )
328+ self .assertEqual (os .listxattr (src ), os .listxattr (dst ))
329+ self .assertEqual (
330+ os .getxattr (src , 'user.foo' ),
331+ os .getxattr (dst , 'user.foo' ))
332+ # check errors don't affect other attrs
333+ os .remove (dst )
334+ write_file (dst , 'bar' )
335+ os_error = OSError (errno .EPERM , 'EPERM' )
336+
337+ def _raise_on_user_foo (fname , attr , val ):
338+ if attr == 'user.foo' :
339+ raise os_error
340+ else :
341+ orig_setxattr (fname , attr , val )
342+ try :
343+ orig_setxattr = os .setxattr
344+ os .setxattr = _raise_on_user_foo
345+ shutil ._copyxattr (src , dst )
346+ self .assertEqual (['user.bar' ], os .listxattr (dst ))
347+ finally :
348+ os .setxattr = orig_setxattr
349+
350+ @support .skip_unless_symlink
351+ @support .skip_unless_xattr
352+ @unittest .skipUnless (hasattr (os , 'geteuid' ) and os .geteuid () == 0 ,
353+ 'root privileges required' )
354+ def test_copyxattr_symlinks (self ):
355+ # On Linux, it's only possible to access non-user xattr for symlinks;
356+ # which in turn require root privileges. This test should be expanded
357+ # as soon as other platforms gain support for extended attributes.
358+ tmp_dir = self .mkdtemp ()
359+ src = os .path .join (tmp_dir , 'foo' )
360+ src_link = os .path .join (tmp_dir , 'baz' )
361+ write_file (src , 'foo' )
362+ os .symlink (src , src_link )
363+ os .setxattr (src , 'trusted.foo' , b'42' )
364+ os .lsetxattr (src_link , 'trusted.foo' , b'43' )
365+ dst = os .path .join (tmp_dir , 'bar' )
366+ dst_link = os .path .join (tmp_dir , 'qux' )
367+ write_file (dst , 'bar' )
368+ os .symlink (dst , dst_link )
369+ shutil ._copyxattr (src_link , dst_link , symlinks = True )
370+ self .assertEqual (os .lgetxattr (dst_link , 'trusted.foo' ), b'43' )
371+ self .assertRaises (OSError , os .getxattr , dst , 'trusted.foo' )
372+ shutil ._copyxattr (src_link , dst , symlinks = True )
373+ self .assertEqual (os .getxattr (dst , 'trusted.foo' ), b'43' )
374+
314375 @support .skip_unless_symlink
315376 def test_copy_symlinks (self ):
316377 tmp_dir = self .mkdtemp ()
@@ -369,6 +430,19 @@ def test_copy2_symlinks(self):
369430 if hasattr (os , 'lchflags' ) and hasattr (src_link_stat , 'st_flags' ):
370431 self .assertEqual (src_link_stat .st_flags , dst_stat .st_flags )
371432
433+ @support .skip_unless_xattr
434+ def test_copy2_xattr (self ):
435+ tmp_dir = self .mkdtemp ()
436+ src = os .path .join (tmp_dir , 'foo' )
437+ dst = os .path .join (tmp_dir , 'bar' )
438+ write_file (src , 'foo' )
439+ os .setxattr (src , 'user.foo' , b'42' )
440+ shutil .copy2 (src , dst )
441+ self .assertEqual (
442+ os .getxattr (src , 'user.foo' ),
443+ os .getxattr (dst , 'user.foo' ))
444+ os .remove (dst )
445+
372446 @support .skip_unless_symlink
373447 def test_copyfile_symlinks (self ):
374448 tmp_dir = self .mkdtemp ()
0 commit comments