From a3b26f6aead3ebc87478a530ec5ca1522482da47 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 18 Sep 2024 19:21:12 +0200 Subject: [PATCH 1/2] implement test for copy_file to copy read-only file with extended attributes --- test/framework/filetools.py | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test/framework/filetools.py b/test/framework/filetools.py index 1a0294a02f..f6dd6fd7ac 100644 --- a/test/framework/filetools.py +++ b/test/framework/filetools.py @@ -32,6 +32,7 @@ @author: Maxime Boissonneault (Compute Canada, Universite Laval) """ import datetime +import filecmp import glob import logging import os @@ -51,6 +52,8 @@ from easybuild.tools.config import IGNORE, ERROR, build_option, update_build_option from easybuild.tools.multidiff import multidiff from easybuild.tools.py2vs3 import StringIO, std_urllib +from easybuild.tools.run import run_cmd +from easybuild.tools.systemtools import LINUX, get_os_type class FileToolsTest(EnhancedTestCase): @@ -1912,6 +1915,47 @@ def test_copy_file(self): # However, if we add 'force_in_dry_run=True' it should throw an exception self.assertErrorRegex(EasyBuildError, "Could not copy *", ft.copy_file, src, target, force_in_dry_run=True) + def test_copy_file_xattr(self): + """Test copying a file with extended attributes using copy_file.""" + # test copying a read-only files with extended attributes set + # first, create a special file with extended attributes + special_file = os.path.join(self.test_prefix, 'special.txt') + ft.write_file(special_file, 'special') + # make read-only, and set extended attributes + attr = ft.which('attr') + xattr = ft.which('xattr') + # try to attr (Linux) or xattr (macOS) to set extended attributes foo=bar + cmd = None + if attr: + cmd = "attr -s foo -V bar %s" % special_file + elif xattr: + cmd = "xattr -w foo bar %s" % special_file + + if cmd: + (_, ec) = run_cmd(cmd, simple=False, log_all=False, log_ok=False) + + # need to make file read-only after setting extended attribute + ft.adjust_permissions(special_file, stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH, add=False) + + # only proceed if setting extended attribute worked + if ec == 0: + target = os.path.join(self.test_prefix, 'copy.txt') + ft.copy_file(special_file, target) + self.assertTrue(os.path.exists(target)) + self.assertTrue(filecmp.cmp(special_file, target, shallow=False)) + + # only verify wheter extended attributes were also copied on Linux, + # since shutil.copy2 doesn't copy them on macOS; + # see warning at https://docs.python.org/3/library/shutil.html + if get_os_type() == LINUX: + if attr: + cmd = "attr -g foo %s" % target + else: + cmd = "xattr -l %s" % target + (out, ec) = run_cmd(cmd, simple=False, log_all=False, log_ok=False) + self.assertEqual(ec, 0) + self.assertTrue(out.endswith('\nbar\n')) + def test_copy_files(self): """Test copy_files function.""" test_ecs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs') From 23b5b787caac930d3b408fc03f19fa51ba9051c4 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 18 Sep 2024 20:57:59 +0200 Subject: [PATCH 2/2] fix condition in copy_file when hitting PermissionError when copying read-only file --- easybuild/tools/filetools.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/easybuild/tools/filetools.py b/easybuild/tools/filetools.py index 30d4ec34c4..973f83e1f5 100644 --- a/easybuild/tools/filetools.py +++ b/easybuild/tools/filetools.py @@ -2443,11 +2443,11 @@ def copy_file(path, target_path, force_in_dry_run=False): # see https://bugs.python.org/issue24538 shutil.copy2(path, target_path) _log.info("%s copied to %s", path, target_path) + # catch the more general OSError instead of PermissionError, + # since Python 2.7 doesn't support PermissionError except OSError as err: - # catch the more general OSError instead of PermissionError, since python2 doesn't support - # PermissionError - if not os.stat(path).st_mode & stat.S_IWUSR: - # failure not due to read-only file + # if file is writable (not read-only), then we give up since it's not a simple permission error + if os.path.exists(target_path) and os.stat(target_path).st_mode & stat.S_IWUSR: raise EasyBuildError("Failed to copy file %s to %s: %s", path, target_path, err) pyver = LooseVersion(platform.python_version())