From 285c7257ba6b9652b10b96b2ac3cb8c7c6686072 Mon Sep 17 00:00:00 2001 From: abejgonzalez Date: Fri, 14 Jun 2024 23:45:37 -0700 Subject: [PATCH 01/10] Use FireSim sudo scripts if available --- wlutil/wlutil.py | 101 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 70 insertions(+), 31 deletions(-) diff --git a/wlutil/wlutil.py b/wlutil/wlutil.py index 23441006..bb0b90b4 100644 --- a/wlutil/wlutil.py +++ b/wlutil/wlutil.py @@ -569,45 +569,80 @@ def waitpid(pid): time.sleep(0.25) -if sp.run(['/usr/bin/sudo', '-ln', 'true'], stderr=sp.DEVNULL, stdout=sp.DEVNULL).returncode == 0: - # User has passwordless sudo available, use the mount command (much faster) - sudoCmd = ["/usr/bin/sudo"] +sudoCmd = ["/usr/bin/sudo"] +pwdlessSudoCmd = [] # set if pwdless sudo is enabled - @contextmanager - def mountImg(imgPath, mntPath): - run(sudoCmd + ["mount", "-o", "loop", imgPath, mntPath]) - try: - yield mntPath - finally: - run_with_retries(sudoCmd + ['umount', mntPath]) -else: - # User doesn't have sudo (use guestmount, slow but reliable) - sudoCmd = [] - - @contextmanager - def mountImg(imgPath, mntPath): - run(['guestmount', '--pid-file', 'guestmount.pid', '-a', imgPath, '-m', '/dev/sda', mntPath]) + +def runnableWithSudo(cmd): + global sudoCmd + return sp.run(sudoCmd + ['-ln', cmd], stderr=sp.DEVNULL, stdout=sp.DEVNULL).returncode == 0 + + +if runnableWithSudo('true'): + # User has passwordless sudo available + pwdlessSudoCmd = sudoCmd + + +def existsAndRunnableWithSudo(cmd): + global sudoCmd + return os.path.exists(cmd) and runnableWithSudo(cmd) + + +@contextmanager +def mountImg(imgPath, mntPath): + global sudoCmd + global pwdlessSudoCmd + if pwdlessSudoCmd: + # use faster mount without firesim script since we have pwdless sudo + run(pwdlessSudoCmd + ["mount", "-o", "loop", imgPath, mntPath]) + username = sp.run(['whoami'], capture_output=True, text=True) + run(pwdlessSudoCmd + ["chown", "-R", username, mntPath]) try: - with open('./guestmount.pid', 'r') as pidFile: - mntPid = int(pidFile.readline()) yield mntPath finally: - run(['guestunmount', mntPath]) - os.remove('./guestmount.pid') + run_with_retries(pwdlessSudoCmd + ['umount', mntPath]) + else: + # use either firesim-*mount* cmds if available/useable or default to guestmount (slower but reliable) + fsimMountCmd = '/usr/local/bin/firesim-mount' + fsimUnmountCmd = '/usr/local/bin/firesim-unmount' + + if existsAndRunnableWithSudo(fsimMountCmd) and existsAndRunnableWithSudo(fsimUnmountCmd): + run(sudoCmd + [fsimMountCmd, imgPath, mntPath]) + try: + yield mntPath + finally: + run_with_retries(sudoCmd + [fsimUnmountCmd, mntPath]) + else: + run(['guestmount', '--pid-file', 'guestmount.pid', '-a', imgPath, '-m', '/dev/sda', mntPath]) + try: + with open('./guestmount.pid', 'r') as pidFile: + mntPid = int(pidFile.readline()) + yield mntPath + finally: + run(['guestunmount', mntPath]) + os.remove('./guestmount.pid') - # There is a race-condition in guestmount where a background task keeps - # modifying the image for a period after unmount. This is the documented - # best-practice (see man guestmount). - waitpid(mntPid) + # There is a race-condition in guestmount where a background task keeps + # modifying the image for a period after unmount. This is the documented + # best-practice (see man guestmount). + waitpid(mntPid) def toCpio(src, dst): + global sudoCmd + global pwdlessSudoCmd + log = logging.getLogger() log.debug("Creating Cpio archive from " + str(src)) - with open(dst, 'wb') as outCpio: - p = sp.run(sudoCmd + ["sh", "-c", "find -print0 | cpio --owner root:root --null -ov --format=newc"], - stderr=sp.PIPE, stdout=outCpio, cwd=src) - log.debug(p.stderr.decode('utf-8')) + + fsimCpioCmd = '/usr/local/bin/firesim-cpio' + if existsAndRunnableWithSudo(fsimCpioCmd): + run(sudoCmd + [fsimCpioCmd, src, dst]) + else: + with open(dst, 'wb') as outCpio: + p = sp.run(pwdlessSudoCmd + ["sh", "-c", "find -print0 | cpio --owner root:root --null -ov --format=newc"], + stderr=sp.PIPE, stdout=outCpio, cwd=src) + log.debug(p.stderr.decode('utf-8')) def resizeFS(img, newSize=0): @@ -649,14 +684,18 @@ def copyImgFiles(img, files, direction): files - list of FileSpecs to use direction - "in" or "out" for copying files into or out of the image (respectively) """ + # TODO: unsure if you need sudo anymore for this if chown is happening correctly above + global pwdlessSudoCmd + with mountImg(img, getOpt('mnt-dir')): + for f in files: if direction == 'in': dst = str(getOpt('mnt-dir') / f.dst.relative_to('/')) - run(sudoCmd + ['cp', '-a', str(f.src), dst]) + run(pwdlessSudoCmd + ['cp', '-a', str(f.src), dst]) elif direction == 'out': src = str(getOpt('mnt-dir') / f.src.relative_to('/')) - run(sudoCmd + ['cp', '-a', src, str(f.dst)]) + run(pwdlessSudoCmd + ['cp', '-a', src, str(f.dst)]) else: raise ValueError("direction option must be either 'in' or 'out'") From 5bc9bf85c86c414507ee07e0c4f6692e8293eca7 Mon Sep 17 00:00:00 2001 From: abejgonzalez Date: Mon, 17 Jun 2024 11:13:40 -0700 Subject: [PATCH 02/10] Misc. fixes for CI --- .github/workflows/linter.yml | 2 +- .github/workflows/run-tests.yml | 7 ++++--- .github/workflows/weekly-build.yml | 2 +- conda-reqs.yaml | 6 +++--- wlutil/wlutil.py | 2 +- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index b9f0439a..ecce3d79 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -36,7 +36,7 @@ jobs: # Checkout the code base # ########################## - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Full git history is needed to get a proper list of changed files within `super-linter` fetch-depth: 0 diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 98fcc60c..4a08cb1d 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Cancel previous workflow runs - uses: styfle/cancel-workflow-action@0.11.0 + uses: styfle/cancel-workflow-action@0.12.1 with: access_token: ${{ github.token }} @@ -38,7 +38,7 @@ jobs: outputs: run-core: ${{ steps.filter.outputs.all_count != steps.filter.outputs.non-core-files_count }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dorny/paths-filter@v2 id: filter with: @@ -71,7 +71,7 @@ jobs: rm -rf ${{ github.workspace }}/* || true rm -rf ${{ github.workspace }}/.* || true - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup repo copy run: | git clone $GITHUB_WORKSPACE ${{ env.REMOTE_WORK_DIR }} @@ -86,6 +86,7 @@ jobs: cd ${{ env.REMOTE_WORK_DIR }} eval "$(conda shell.bash hook)" conda activate $PWD/.conda-env + conda env list git clone https://github.com/riscv-software-src/riscv-isa-sim.git cd riscv-isa-sim mkdir build diff --git a/.github/workflows/weekly-build.yml b/.github/workflows/weekly-build.yml index a240fef9..16f72276 100644 --- a/.github/workflows/weekly-build.yml +++ b/.github/workflows/weekly-build.yml @@ -25,7 +25,7 @@ jobs: rm -rf ${{ env.REMOTE_WORK_DIR }}/.* || true rm -rf ${{ github.workspace }}/* || true rm -rf ${{ github.workspace }}/.* || true - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup repo copy run: | git clone $GITHUB_WORKSPACE ${{ env.REMOTE_WORK_DIR }} diff --git a/conda-reqs.yaml b/conda-reqs.yaml index a0cd4e70..3c1e22e8 100644 --- a/conda-reqs.yaml +++ b/conda-reqs.yaml @@ -9,7 +9,7 @@ platforms: dependencies: - qemu # from ucb-bar channel - https://github.com/ucb-bar/qemu-feedstock - - python>=3.8 + - python>=3.9,<3.11 - rsync - psutil - doit>=0.34.0 @@ -27,8 +27,8 @@ dependencies: - sphinx - sphinx_rtd_theme - unzip - - gcc<13 - - gxx<13 + - gcc=13.2 + - gxx=13.2 - conda-gcc-specs - binutils - sysroot_linux-64=2.17 diff --git a/wlutil/wlutil.py b/wlutil/wlutil.py index bb0b90b4..aa866c87 100644 --- a/wlutil/wlutil.py +++ b/wlutil/wlutil.py @@ -570,7 +570,7 @@ def waitpid(pid): sudoCmd = ["/usr/bin/sudo"] -pwdlessSudoCmd = [] # set if pwdless sudo is enabled +pwdlessSudoCmd = [] # set if pwdless sudo is enabled def runnableWithSudo(cmd): From 22c40804c40d47e77ddefe8b525188ba289ec081 Mon Sep 17 00:00:00 2001 From: abejgonzalez Date: Mon, 17 Jun 2024 11:17:29 -0700 Subject: [PATCH 03/10] Use CY version of Spike --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 4a08cb1d..2dba6f31 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -86,9 +86,9 @@ jobs: cd ${{ env.REMOTE_WORK_DIR }} eval "$(conda shell.bash hook)" conda activate $PWD/.conda-env - conda env list git clone https://github.com/riscv-software-src/riscv-isa-sim.git cd riscv-isa-sim + git checkout 4d8651b mkdir build cd build ../configure --prefix=$RISCV --with-boost=no --with-boost-asio=no --with-boost-regex=no From 2ea226d6f56aba6b22062d32391343049996025d Mon Sep 17 00:00:00 2001 From: abejgonzalez Date: Mon, 17 Jun 2024 13:58:34 -0700 Subject: [PATCH 04/10] Bump RISC-V tools --- riscv-tools.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/riscv-tools.yaml b/riscv-tools.yaml index 35f89fe6..c7ac4ae2 100644 --- a/riscv-tools.yaml +++ b/riscv-tools.yaml @@ -8,4 +8,4 @@ platforms: - linux-64 dependencies: - - riscv-tools==1.0.4 # from ucb-bar channel - https://github.com/ucb-bar/riscv-tools-feedstock + - riscv-tools==1.0.6 # from ucb-bar channel - https://github.com/ucb-bar/riscv-tools-feedstock From abc8d76c4bf0b1d0fa136616aac20d211a06e271 Mon Sep 17 00:00:00 2001 From: abejgonzalez Date: Mon, 17 Jun 2024 14:13:57 -0700 Subject: [PATCH 05/10] Change mount permissions --- wlutil/wlutil.py | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/wlutil/wlutil.py b/wlutil/wlutil.py index aa866c87..5b00e77e 100644 --- a/wlutil/wlutil.py +++ b/wlutil/wlutil.py @@ -592,35 +592,39 @@ def existsAndRunnableWithSudo(cmd): def mountImg(imgPath, mntPath): global sudoCmd global pwdlessSudoCmd + + uid = sp.run(['id -u'], capture_output=True, text=True) + gid = sp.run(['id -g'], capture_output=True, text=True) + if pwdlessSudoCmd: # use faster mount without firesim script since we have pwdless sudo run(pwdlessSudoCmd + ["mount", "-o", "loop", imgPath, mntPath]) - username = sp.run(['whoami'], capture_output=True, text=True) - run(pwdlessSudoCmd + ["chown", "-R", username, mntPath]) + run(pwdlessSudoCmd + ["chown", "-R", f"{uid}:{gid}", mntPath]) try: yield mntPath finally: run_with_retries(pwdlessSudoCmd + ['umount', mntPath]) else: # use either firesim-*mount* cmds if available/useable or default to guestmount (slower but reliable) - fsimMountCmd = '/usr/local/bin/firesim-mount' + fsimMountCmd = '/usr/local/bin/firesim-mount-with-uid-gid' fsimUnmountCmd = '/usr/local/bin/firesim-unmount' if existsAndRunnableWithSudo(fsimMountCmd) and existsAndRunnableWithSudo(fsimUnmountCmd): - run(sudoCmd + [fsimMountCmd, imgPath, mntPath]) + run(sudoCmd + [fsimMountCmd, imgPath, mntPath, uid, gid]) try: yield mntPath finally: run_with_retries(sudoCmd + [fsimUnmountCmd, mntPath]) else: - run(['guestmount', '--pid-file', 'guestmount.pid', '-a', imgPath, '-m', '/dev/sda', mntPath]) + pidPath = './guestmount.pid' + run(['guestmount', '--pid-file', pidPath, '-o', f'uid={uid}', '-o', f'gid={gid}', '-a', imgPath, '-m', '/dev/sda', mntPath]) try: - with open('./guestmount.pid', 'r') as pidFile: + with open(pidPath, 'r') as pidFile: mntPid = int(pidFile.readline()) yield mntPath finally: run(['guestunmount', mntPath]) - os.remove('./guestmount.pid') + os.remove(pidPath) # There is a race-condition in guestmount where a background task keeps # modifying the image for a period after unmount. This is the documented @@ -639,10 +643,14 @@ def toCpio(src, dst): if existsAndRunnableWithSudo(fsimCpioCmd): run(sudoCmd + [fsimCpioCmd, src, dst]) else: - with open(dst, 'wb') as outCpio: - p = sp.run(pwdlessSudoCmd + ["sh", "-c", "find -print0 | cpio --owner root:root --null -ov --format=newc"], - stderr=sp.PIPE, stdout=outCpio, cwd=src) - log.debug(p.stderr.decode('utf-8')) + if pwdlessSudoCmd: + with open(dst, 'wb') as outCpio: + p = sp.run(pwdlessSudoCmd + ["sh", "-c", "find -print0 | cpio --owner root:root --null -ov --format=newc"], + stderr=sp.PIPE, stdout=outCpio, cwd=src) + log.debug(p.stderr.decode('utf-8')) + else: + # if the firesim script doesn't exist then users need 'sudo' access + raise ValueError("Need passwordless 'sudo' access for cpio script") def resizeFS(img, newSize=0): @@ -684,18 +692,14 @@ def copyImgFiles(img, files, direction): files - list of FileSpecs to use direction - "in" or "out" for copying files into or out of the image (respectively) """ - # TODO: unsure if you need sudo anymore for this if chown is happening correctly above - global pwdlessSudoCmd - with mountImg(img, getOpt('mnt-dir')): - for f in files: if direction == 'in': dst = str(getOpt('mnt-dir') / f.dst.relative_to('/')) - run(pwdlessSudoCmd + ['cp', '-a', str(f.src), dst]) + run(['cp', '-a', str(f.src), dst]) elif direction == 'out': src = str(getOpt('mnt-dir') / f.src.relative_to('/')) - run(pwdlessSudoCmd + ['cp', '-a', src, str(f.dst)]) + run(['cp', '-a', src, str(f.dst)]) else: raise ValueError("direction option must be either 'in' or 'out'") From 1ddac7b2e79b4260700057470bdf8b0492ba4dbb Mon Sep 17 00:00:00 2001 From: abejgonzalez Date: Mon, 17 Jun 2024 14:26:12 -0700 Subject: [PATCH 06/10] CPIO can run with{out} sudo if necessary --- wlutil/wlutil.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/wlutil/wlutil.py b/wlutil/wlutil.py index 5b00e77e..021b9a4e 100644 --- a/wlutil/wlutil.py +++ b/wlutil/wlutil.py @@ -643,14 +643,10 @@ def toCpio(src, dst): if existsAndRunnableWithSudo(fsimCpioCmd): run(sudoCmd + [fsimCpioCmd, src, dst]) else: - if pwdlessSudoCmd: - with open(dst, 'wb') as outCpio: - p = sp.run(pwdlessSudoCmd + ["sh", "-c", "find -print0 | cpio --owner root:root --null -ov --format=newc"], - stderr=sp.PIPE, stdout=outCpio, cwd=src) - log.debug(p.stderr.decode('utf-8')) - else: - # if the firesim script doesn't exist then users need 'sudo' access - raise ValueError("Need passwordless 'sudo' access for cpio script") + with open(dst, 'wb') as outCpio: + p = sp.run(pwdlessSudoCmd + ["sh", "-c", "find -print0 | cpio --owner root:root --null -ov --format=newc"], + stderr=sp.PIPE, stdout=outCpio, cwd=src) + log.debug(p.stderr.decode('utf-8')) def resizeFS(img, newSize=0): From 43ead7ed29a9d39a789f5378ba10928f3a3eacf7 Mon Sep 17 00:00:00 2001 From: abejgonzalez Date: Mon, 17 Jun 2024 14:31:43 -0700 Subject: [PATCH 07/10] Fix id -* cmds --- wlutil/wlutil.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wlutil/wlutil.py b/wlutil/wlutil.py index 021b9a4e..52e57523 100644 --- a/wlutil/wlutil.py +++ b/wlutil/wlutil.py @@ -593,8 +593,8 @@ def mountImg(imgPath, mntPath): global sudoCmd global pwdlessSudoCmd - uid = sp.run(['id -u'], capture_output=True, text=True) - gid = sp.run(['id -g'], capture_output=True, text=True) + uid = sp.run(['id', '-u'], capture_output=True, text=True).stdout.strip() + gid = sp.run(['id', '-g'], capture_output=True, text=True).stdout.strip() if pwdlessSudoCmd: # use faster mount without firesim script since we have pwdless sudo From 7bc79d8d335722a7ab978b4138952693955fca0f Mon Sep 17 00:00:00 2001 From: abejgonzalez Date: Mon, 17 Jun 2024 15:57:54 -0700 Subject: [PATCH 08/10] More bumps --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 2dba6f31..a55f9885 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -39,7 +39,7 @@ jobs: run-core: ${{ steps.filter.outputs.all_count != steps.filter.outputs.non-core-files_count }} steps: - uses: actions/checkout@v4 - - uses: dorny/paths-filter@v2 + - uses: dorny/paths-filter@v3 id: filter with: filters: | From 4fc1997b758520712c2ef0240284c097e81b65d1 Mon Sep 17 00:00:00 2001 From: abejgonzalez Date: Mon, 17 Jun 2024 16:53:38 -0700 Subject: [PATCH 09/10] Bump --- wlutil/wlutil.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wlutil/wlutil.py b/wlutil/wlutil.py index 52e57523..7ad68b68 100644 --- a/wlutil/wlutil.py +++ b/wlutil/wlutil.py @@ -692,10 +692,10 @@ def copyImgFiles(img, files, direction): for f in files: if direction == 'in': dst = str(getOpt('mnt-dir') / f.dst.relative_to('/')) - run(['cp', '-a', str(f.src), dst]) + run(['cp', '-a', '-f', str(f.src), dst]) elif direction == 'out': src = str(getOpt('mnt-dir') / f.src.relative_to('/')) - run(['cp', '-a', src, str(f.dst)]) + run(['cp', '-a', '-f', src, str(f.dst)]) else: raise ValueError("direction option must be either 'in' or 'out'") From c51076844b50ac407cb5b0329b0898de7d55c87b Mon Sep 17 00:00:00 2001 From: abejgonzalez Date: Tue, 18 Jun 2024 13:43:53 -0700 Subject: [PATCH 10/10] Change permissions of mountpoint before copies --- wlutil/wlutil.py | 54 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/wlutil/wlutil.py b/wlutil/wlutil.py index 7ad68b68..e9c5caab 100644 --- a/wlutil/wlutil.py +++ b/wlutil/wlutil.py @@ -593,6 +593,10 @@ def mountImg(imgPath, mntPath): global sudoCmd global pwdlessSudoCmd + assert imgPath.is_file(), f"Unable to find {imgPath} to mount" + ret = run(["mountpoint", mntPath], check=False).returncode + assert ret == 1, f"{mntPath} already mounted. Somethings wrong" + uid = sp.run(['id', '-u'], capture_output=True, text=True).stdout.strip() gid = sp.run(['id', '-g'], capture_output=True, text=True).stdout.strip() @@ -688,16 +692,50 @@ def copyImgFiles(img, files, direction): files - list of FileSpecs to use direction - "in" or "out" for copying files into or out of the image (respectively) """ + log = logging.getLogger() + assert direction in ['in', 'out'], f"direction={direction} must be either 'in' or 'out'" with mountImg(img, getOpt('mnt-dir')): for f in files: - if direction == 'in': - dst = str(getOpt('mnt-dir') / f.dst.relative_to('/')) - run(['cp', '-a', '-f', str(f.src), dst]) - elif direction == 'out': - src = str(getOpt('mnt-dir') / f.src.relative_to('/')) - run(['cp', '-a', '-f', src, str(f.dst)]) - else: - raise ValueError("direction option must be either 'in' or 'out'") + cpSrcMaybeRelPath = f.src if direction == 'in' else f.src.relative_to('/') + cpDstMaybeRelPath = f.dst.relative_to('/') if direction == 'in' else f.dst + cpSrcResPath = cpSrcMaybeRelPath if direction == 'in' else getOpt('mnt-dir') / cpSrcMaybeRelPath + cpDstResPath = getOpt('mnt-dir') / cpDstMaybeRelPath if direction == 'in' else cpDstMaybeRelPath + + # modify perms for dirs to always be able to copy in/out + oldPerms = {} # store old permissions + relaxedPerms = 0o777 # arb. chosen to be very permissive + dirsToModify = [] + + # irrespective if the mountpoint is src/dst, modify all dirs up to mountpoint (including the src/dst dir) + withinMountRelPath = cpDstMaybeRelPath if direction == 'in' else cpSrcMaybeRelPath + withinMountPath = getOpt('mnt-dir') / withinMountRelPath + parents = withinMountRelPath.parents if withinMountRelPath.parents else '.' + dirsToModify.extend([getOpt('mnt-dir') / e for e in reversed(parents)]) + dirsToModify.extend([withinMountPath] if withinMountPath.is_dir() else []) + # also ensure that if copying a directory into a mountpoint, that directory can be written in the mountpoint + dirsToModify.extend([cpDstResPath / cpSrcResPath.name] if direction == 'in' and cpSrcResPath.is_dir() else []) + + # remove duplicates but keep order + dirsToModify = list(dict.fromkeys(dirsToModify)) + + for dirPath in dirsToModify: + perms = int(oct(os.stat(dirPath).st_mode)[-3:], 8) + log.debug(f"Changing permissions of {dirPath} from {oct(perms)}:{type(perms)} to {oct(relaxedPerms)} temporarily") + assert dirPath not in oldPerms, f"Something went wrong. Expected that {dirPath}'s permissions aren't already set" + oldPerms[dirPath] = perms + os.chmod(dirPath, relaxedPerms) + newPerms = int(oct(os.stat(dirPath).st_mode)[-3:], 8) + assert newPerms == relaxedPerms, f"Unable to set perms of {dirPath} to {oct(relaxedPerms)}" + + try: + run(['cp', '-a', '-f', cpSrcResPath, cpDstResPath]) + finally: + for dirPath in dirsToModify: + perms = oldPerms[dirPath] + log.debug(f"Changing permissions of {dirPath} back from {oct(relaxedPerms)} to {oct(perms)}:{type(perms)}") + os.chmod(dirPath, perms) + # verify it's right + assert int(oct(os.stat(dirPath).st_mode)[-3:], 8) == perms, "Unable to revert permissions" def applyOverlay(img, overlay):