0

I'm trying to create Skype patcher with replaced font (stock is incorrectly displaying japanese kanji as chinese equivalent) for Windows. Maybe Python is not the best options, but current problem is not related with Python itself: asar utility produces output with readonly permissions, and changing permission attempt can't do anything. But, maybe, os.chmod is just not working on Windows?


import tkinter as tk
from tkinter import filedialog
import subprocess
import os
import shutil


def change_permissions_recursive(path, mode):
    for root, dirs, files in os.walk(path, topdown=False):
        for dir in [os.path.join(root,d) for d in dirs]:
            os.chmod(dir, mode)
    for file in [os.path.join(root, f) for f in files]:
            os.chmod(file, mode)


root = tk.Tk()
root.withdraw()
dir_path = filedialog.askdirectory(initialdir="C:\Program Files (x86)\Microsoft\Skype for Desktop")
cmd = "asar.cmd extract \"" + dir_path + "/resources/app.asar\" tmp/app"
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, creationflags=0x08000000, cwd="utils")
process.wait()
change_permissions_recursive("utils/tmp/app/fonts", 0o777)
shutil.copyfile("utils/meiryo.ttf", "utils/tmp/app/fonts")

wingear
  • 819
  • 9
  • 21
  • 1
    Can you show a [mcve]? I'm not sure tkinter is required for this problem – OneCricketeer Feb 09 '20 at 17:53
  • And PowerShell or batch would be "better" than python, yes – OneCricketeer Feb 09 '20 at 17:54
  • https://www.codepicky.com/hacking-electron-restyle-skype/ I'm following this guide, but asar utility output is seems to be written with wrong permissions. But, maybe this is shutil.copy error itself.Will rewrite with powershell – wingear Feb 09 '20 at 20:00
  • Yes, that's was something related to Python itself: all works as expected with powershell – wingear Feb 09 '20 at 20:27

3 Answers3

2

Yes, chmod is usually not working on Windows.

It is better to use the proper tools from command line: cacls for old versions of windows (Windows XP or below), or icacls for most versions of Windows.

The following code could allow to change permissions (with an additional context manager to change temporarily if needed):

"""
Prototype of script to modify access rights with "icacls" in Windows
"""
from contextlib import contextmanager
from enum import Enum
from subprocess import check_output
from pathlib import Path
from typing import Generator, List


class AccessRight(Enum):
    """Access Rights for files/folders"""

    DELETE = "D"
    FULL = "F"  # Edit Permissions + Create + Delete + Read + Write
    NO_ACCESS = "N"
    MODIFY = "M"  # Create + Delete + Read + Write
    READ_EXECUTE = "RX"
    READ_ONLY = "R"
    WRITE_ONLY = "W"


@contextmanager
def set_access_right(file: Path, access: AccessRight) -> Generator[Path, None, None]:
    """Context Manager to temporarily set a given access rights to a file and reset"""

    def cmd(access_right: AccessRight) -> List[str]:
        return [
            "icacls",
            str(file),
            "/inheritance:r",
            "/grant:r",
            f"Everyone:{access_right.value}",
        ]

    try:
        check_output(cmd(access))
        yield file

    finally:
        check_output(cmd(AccessRight.FULL))


# We create a file (if it does not exist) with empty content
toto = Path("toto.txt")
toto.touch()

# We temporarily set access rights of the
with set_access_right(toto, AccessRight.WRITE_ONLY) as path:
    path.write_text("My name is Toto")
    try:
        content = path.read_text()
        print(f":( Should not be able to read content of file but read: {content}")
    except PermissionError:
        print("Cannot read toto: YEAH!")

# We check that access rights have been restored (and delete the file to stay clean)
print(path.read_text())
toto.unlink()

[Edit March 2023] -> Alternate solution using os.chmod, which might be more complex but is cross-platform

Based on a remark from @L4ZZA and from this answer, we could also use the permissions defined in stat:

import os
import stat

# If you want to set new permisions (i.e. replace)
os.chmod('my_file.txt', <stat mask>)

# If you want to add new permissions
st = os.stat('somefile')
os.chmod('my_file.txt', st.st_mode | <stat mask>)

With the mask stat mask being one or several of the following (if several, join them with a |):

  • stat.S_ISUID: set UID bit
  • stat.S_ISGID: set GID bit
  • stat.S_ENFMT: file locking enforcement
  • stat.S_ISVTX: sticky bit
  • stat.S_IREAD: Unix V7 synonym for S_IRUSR
  • stat.S_IWRITE: Unix V7 synonym for S_IWUSR
  • stat.S_IEXEC: Unix V7 synonym for S_IXUSR
  • stat.S_IRWXU: mask for owner permissions
  • stat.S_IRUSR: read by owner
  • stat.S_IWUSR: write by owner
  • stat.S_IXUSR: execute by owner
  • stat.S_IRWXG: mask for group permissions
  • stat.S_IRGRP: read by group
  • stat.S_IWGRP: write by group
  • stat.S_IXGRP: execute by group
  • stat.S_IRWXO: mask for others (not in group) permissions
  • stat.S_IROTH: read by others
  • stat.S_IWOTH: write by others
  • stat.S_IXOTH: execute by others
Jean-Francois T.
  • 11,549
  • 7
  • 68
  • 107
  • this doesn't seem to work for me on Windows 11 with Python 3.9 – L4ZZA Feb 16 '23 at 12:18
  • @L4ZZA What does that mean "does not seem to work"? You need to be more specific – Jean-Francois T. Feb 17 '23 at 02:42
  • ... FYI I have Windows 11 and it's working on my computer with Python 3.9.10 64bits. So please detail what Exception or problem you have met. – Jean-Francois T. Feb 17 '23 at 07:52
  • it means that no matter how I tried it did not work. I ran the command both in normal cmd and in administrator cmd and I could not remove the read-only flag when specifying the `FULL` _AccesRight_. No errors no exeptions it just didn't do what I expected :/ – L4ZZA Feb 20 '23 at 21:40
  • I wonder if there's any reliable package for accomplishing simple things like read only for all mainstream operating systems. Don't have a Windows VM at hand to test out Pathlib. – matanster Apr 26 '23 at 14:47
1

I found a working solution on windows 11 with python 3.9! Just replace stat.S_IEXEC with the mask you need.

from stats.py


# Names for permission bits

S_ISUID = 0o4000  # set UID bit
S_ISGID = 0o2000  # set GID bit
S_ENFMT = S_ISGID # file locking enforcement
S_ISVTX = 0o1000  # sticky bit
S_IREAD = 0o0400  # Unix V7 synonym for S_IRUSR
S_IWRITE = 0o0200 # Unix V7 synonym for S_IWUSR
S_IEXEC = 0o0100  # Unix V7 synonym for S_IXUSR
S_IRWXU = 0o0700  # mask for owner permissions
S_IRUSR = 0o0400  # read by owner
S_IWUSR = 0o0200  # write by owner
S_IXUSR = 0o0100  # execute by owner
S_IRWXG = 0o0070  # mask for group permissions
S_IRGRP = 0o0040  # read by group
S_IWGRP = 0o0020  # write by group
S_IXGRP = 0o0010  # execute by group
S_IRWXO = 0o0007  # mask for others (not in group) permissions
S_IROTH = 0o0004  # read by others
S_IWOTH = 0o0002  # write by others
S_IXOTH = 0o0001  # execute by others
L4ZZA
  • 83
  • 12
  • Hi, you could improve your answer by 1. Writing working code 2. Explaining what the masks mean 3. How to set for example "Read-Only" or "ReadWrite" or "Write-Only", etc. Notably, you could just have `os.chmod(stat.)` if you want to overwrite the permissions. ... and actually you don't need to copy the values of each mask: this is confusing. – Jean-Francois T. Feb 17 '23 at 08:00
-1

Something wrong with shutil.copy itself: when rewrite same logic with powershell, all works as expected

wingear
  • 819
  • 9
  • 21