From a73ecbf0f32d298123ace96ebf5136aa446db55a Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Tue, 26 May 2020 22:34:55 -0400 Subject: [PATCH 1/7] Add symbolic representation of mode modifier --- README.md | 64 +++++++------- oschmod/__init__.py | 71 ++++++++++++++- oschmod/cli.py | 2 +- tests/test_oschmod.py | 195 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 296 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 70e3b32..58e1b41 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,3 @@ -# oschmod -Python chmod that works on Windows and Linux -

@@ -13,37 +10,24 @@ Python chmod that works on Windows and Linux Mergify

-Use ***oschmod*** to set permissions for files and directories on Windows, Linux and macOS. While Python's standard libraries include a simple command to do this on Linux and macOS (`os.chmod()`), the same command does not work on Windows. - -* Read more about [oschmod](https://medium.com/@dirk.avery/securing-files-on-windows-macos-and-linux-7b2b9899992) on Medium -* For more background, have a look at the [oschmod Wiki](https://github.com/YakDriver/oschmod/wiki). - -## Usage +# oschmod TL;DR -The problem is that on Linux and macOS, you can easily set distinct permissions for a file's owner, group, and all others. It takes one command and one mode, or, in other words, a number representing bitwise permissions for reading, writing, and executing. On Linux and macOS, you use the `os` module and `os.chmod()`. +***oschmod*** sets consistent file permissions across Windows, Linux and macOS. -Misleadingly, on Windows, `os.chmod()` does not have the same effect and does not give a warning or error. You think you've protected a file but you have not. - -For example, on Linux or macOS, to give a file owner read, write, and execute permissions and deny the group and others any permissions (i.e., equivalent of `700`), you can make a single call: - -```python -import os -import stat -os.chmod('myfile', stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) -``` +Python includes `os.chmod()` to set read, write, and execute file permissions. However, on Windows, Python's `os.chmod()` basically has no effect. Even worse, Windows Python does not give a warning or error -- you think you've protected a file but you have not. In order to set the same file permissions across platforms, use ***oschmod***. -Running the same command on Windows does not achieve the same results. The owner isn't given any permissions and the group and others are not denied any permissions. All you can do is restrict anyone from deleting, changing or renaming the file. That's nothing like what `os.chmod()` does. +* Read more about [oschmod](https://medium.com/@dirk.avery/securing-files-on-windows-macos-and-linux-7b2b9899992) on Medium +* For more background, have a look at the [oschmod Wiki](https://github.com/YakDriver/oschmod/wiki). -However, using ***oschmod*** you can use the same command on Windows, macOS or Linux and get the same results: +## Installation -```python -import oschmod -oschmod.set_mode('myfile', stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) +```console +$ pip install oschmod ``` -## CLI +## Command line interface -The `oschmod` CLI works similarly to `chmod` albeit with fewer options: +***oschmod*** brings the ability to set consistent file permissions using the command line to Windows, macOS, and Linux platforms. If you are familiar with `chmod` on Unix, Linux and/or macOS, ***oschmod*** works similarly, albeit with fewer options. ```console $ oschmod -h @@ -60,20 +44,38 @@ optional arguments: -R apply mode recursively ``` -For example, to open up a file to the world, you can run this command: +For example, to give everyone read, write, and execute permissions on a file, you can run this command: ```console $ oschmod 777 file_name ``` -As another example, you can lock down a file to just the file owner: +You can also lock down a file to just give the file owner read, write, and execute permissions and deny any permissions to everyone else: ```console $ oschmod 700 file_name ``` -## Installation +## Python Usage -```console -$ pip install oschmod +You can use ***oschmod*** from Python code. + +Replacing `os.chmod()` with ***oschmod*** is straightforward and you will get consistent file permissions on Windows, macOS, and Linux: + +For example, this is an example of using `os.chmod()` in Python: + +```python +import os +import stat +os.chmod('myfile', stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) +``` + +On Linux or macOS, this gives a file's owner read, write, and execute permissions and denies the group and others any permissions (i.e., equivalent of `700`). On Windows, the best this command may have done is set the read-only attribute of the file. The read-only attribute restricts anyone from deleting, changing or renaming the file. The owner isn't given any permissions and the group and others are not denied any permissions. There is no consistency between the platforms. + +However, using ***oschmod*** you can use the same command on Windows, macOS or Linux and get the same results: + +```python +import oschmod +import stat +oschmod.set_mode('myfile', stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) ``` diff --git a/oschmod/__init__.py b/oschmod/__init__.py index d360530..5d52512 100644 --- a/oschmod/__init__.py +++ b/oschmod/__init__.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- """oschmod module. -Module for working with file permissions. +Module for working with file permissions that are consistent across Windows, +macOS, and Linux. These bitwise permissions from the stat module can be used with this module: stat.S_IRWXU # Mask for file owner permissions @@ -27,6 +28,7 @@ import os import platform import random +import re import stat import string @@ -164,10 +166,30 @@ def get_mode(path): def set_mode(path, mode): - """Get bitwise mode (stat) of object (dir or file).""" + """ + Set bitwise mode (stat) of object (dir or file). + + Three types of modes can be used: + 1. Decimal mode - an integer representation of set bits (eg, 512) + 2. Octal mode - a string expressing an octal number (eg, "777") + 3. Symbolic representation - a string with modifier symbols (eg, "+x") + """ + new_mode = 0 + if isinstance(mode, int): + new_mode = mode + elif isinstance(mode, str): + if '+' in mode or '-' in mode or '=' in mode: + current_mode = get_mode(path) + for mode_piece in mode.split(","): + set_mode(path, get_effective_mode( + current_mode, mode_piece.strip())) + return True + + new_mode = int(mode, 8) + if IS_WINDOWS: - return win_set_permissions(path, mode) - return os.chmod(path, mode) + return win_set_permissions(path, new_mode) + return os.chmod(path, new_mode) def set_mode_recursive(path, mode, dir_mode=None): @@ -203,6 +225,40 @@ def set_mode_recursive(path, mode, dir_mode=None): return set_mode(path, dir_mode) +def get_effective_mode(current_mode, symbolic): + """Get octal mode, given current mode and symbolic mode modifier.""" + if not isinstance(symbolic, str): + raise AttributeError('symbolic must be a string') + + result = re.search(r'^([ugoa]*)([-+=])([rwx]+)$', symbolic) + if result is None: + raise AttributeError('symbolic bad format') + + whom = result.group(1) or "ugo" + operation = result.group(2) + perm = result.group(3) + + if "a" in whom: + whom = "ugo" + + # bitwise magic + bit_perm = _get_basic_symbol_to_mode(perm) + mask_mode = ("u" in whom and bit_perm << 6) | \ + ("g" in whom and bit_perm << 3) | \ + ("o" in whom and bit_perm << 0) + + if operation == "=": + original = ("u" not in whom and current_mode & 448) | \ + ("g" not in whom and current_mode & 56) | \ + ("o" not in whom and current_mode & 7) + return mask_mode | original + + if operation == "+": + return current_mode | mask_mode + + return current_mode & ~mask_mode + + def get_object_type(path): """Get whether object is file or directory.""" object_type = DIRECTORY @@ -302,6 +358,13 @@ def win_get_permissions(path): return _win_get_permissions(path, get_object_type(path)) +def _get_basic_symbol_to_mode(symbol): + """Calculate numeric value of set of 'rwx'.""" + return ("r" in symbol and 1 << 2) | \ + ("w" in symbol and 1 << 1) | \ + ("x" in symbol and 1 << 0) + + def _win_get_permissions(path, object_type): """Get the permissions.""" sec_des = win32security.GetNamedSecurityInfo( diff --git a/oschmod/cli.py b/oschmod/cli.py index 7ec3354..8522c2a 100644 --- a/oschmod/cli.py +++ b/oschmod/cli.py @@ -18,7 +18,7 @@ def main(): parser.add_argument('object', nargs=1, help='file or directory') args = parser.parse_args() - mode = int(args.mode[0], 8) + mode = args.mode[0] obj = args.object[0] if args.R: oschmod.set_mode_recursive(obj, mode) diff --git a/tests/test_oschmod.py b/tests/test_oschmod.py index 29ac22b..bcacc2d 100644 --- a/tests/test_oschmod.py +++ b/tests/test_oschmod.py @@ -93,3 +93,198 @@ def test_set_recursive(): # clean up shutil.rmtree(topdir) + + +def test_symbolic_effective_add(): + """Check calculation of effective mode from symbolic.""" + assert oschmod.get_effective_mode(0b111000000, "g+x") == 0b111001000 + assert oschmod.get_effective_mode(0b111000000, "o+x") == 0b111000001 + assert oschmod.get_effective_mode(0b111000000, "u+x") == 0b111000000 + assert oschmod.get_effective_mode(0b111000000, "+x") == 0b111001001 + assert oschmod.get_effective_mode(0b111000000, "ugo+x") == 0b111001001 + assert oschmod.get_effective_mode(0b111000000, "a+x") == 0b111001001 + + assert oschmod.get_effective_mode(0b111000000, "g+wx") == 0b111011000 + assert oschmod.get_effective_mode(0b111000000, "o+wx") == 0b111000011 + assert oschmod.get_effective_mode(0b111000000, "u+wx") == 0b111000000 + assert oschmod.get_effective_mode(0b111000000, "+wx") == 0b111011011 + assert oschmod.get_effective_mode(0b111000000, "a+wx") == 0b111011011 + + assert oschmod.get_effective_mode(0b111000000, "g+rwx") == 0b111111000 + assert oschmod.get_effective_mode(0b111000000, "o+rwx") == 0b111000111 + assert oschmod.get_effective_mode(0b111000000, "u+rwx") == 0b111000000 + assert oschmod.get_effective_mode(0b111000000, "+rwx") == 0b111111111 + assert oschmod.get_effective_mode(0b111000000, "a+rwx") == 0b111111111 + + # randomly chosen starting permission = 394 + assert oschmod.get_effective_mode(0b110001010, "g+x") == 0b110001010 + assert oschmod.get_effective_mode(0b110001010, "o+x") == 0b110001011 + assert oschmod.get_effective_mode(0b110001010, "u+x") == 0b111001010 + assert oschmod.get_effective_mode(0b110001010, "+x") == 0b111001011 + assert oschmod.get_effective_mode(0b110001010, "ugo+x") == 0b111001011 + assert oschmod.get_effective_mode(0b110001010, "a+x") == 0b111001011 + + assert oschmod.get_effective_mode(0b110001010, "g+wx") == 0b110011010 + assert oschmod.get_effective_mode(0b110001010, "o+wx") == 0b110001011 + assert oschmod.get_effective_mode(0b110001010, "u+wx") == 0b111001010 + assert oschmod.get_effective_mode(0b110001010, "+wx") == 0b111011011 + assert oschmod.get_effective_mode(0b110001010, "a+wx") == 0b111011011 + + assert oschmod.get_effective_mode(0b110001010, "g+rwx") == 0b110111010 + assert oschmod.get_effective_mode(0b110001010, "o+rwx") == 0b110001111 + assert oschmod.get_effective_mode(0b110001010, "u+rwx") == 0b111001010 + assert oschmod.get_effective_mode(0b110001010, "+rwx") == 0b111111111 + assert oschmod.get_effective_mode(0b110001010, "a+rwx") == 0b111111111 + + # randomly chosen starting permission = 53 + assert oschmod.get_effective_mode(0b000110101, "g+x") == 0b000111101 + assert oschmod.get_effective_mode(0b000110101, "o+x") == 0b000110101 + assert oschmod.get_effective_mode(0b000110101, "u+x") == 0b001110101 + assert oschmod.get_effective_mode(0b000110101, "+x") == 0b001111101 + assert oschmod.get_effective_mode(0b000110101, "ugo+x") == 0b001111101 + assert oschmod.get_effective_mode(0b000110101, "a+x") == 0b001111101 + + assert oschmod.get_effective_mode(0b000110101, "g+wx") == 0b000111101 + assert oschmod.get_effective_mode(0b000110101, "o+wx") == 0b000110111 + assert oschmod.get_effective_mode(0b000110101, "u+wx") == 0b011110101 + assert oschmod.get_effective_mode(0b000110101, "+wx") == 0b011111111 + assert oschmod.get_effective_mode(0b000110101, "a+wx") == 0b011111111 + + assert oschmod.get_effective_mode(0b000110101, "g+rwx") == 0b000111101 + assert oschmod.get_effective_mode(0b000110101, "o+rwx") == 0b000110111 + assert oschmod.get_effective_mode(0b000110101, "u+rwx") == 0b111110101 + + # randomly chosen starting permission = 372 + assert oschmod.get_effective_mode(0b101110100, "g+x") == 0b101111100 + assert oschmod.get_effective_mode(0b101110100, "o+x") == 0b101110101 + assert oschmod.get_effective_mode(0b101110100, "u+x") == 0b101110100 + assert oschmod.get_effective_mode(0b101110100, "+x") == 0b101111101 + assert oschmod.get_effective_mode(0b101110100, "ugo+x") == 0b101111101 + assert oschmod.get_effective_mode(0b101110100, "a+x") == 0b101111101 + + assert oschmod.get_effective_mode(0b101110100, "g+rx") == 0b101111100 + assert oschmod.get_effective_mode(0b101110100, "o+rx") == 0b101110101 + assert oschmod.get_effective_mode(0b101110100, "u+rx") == 0b101110100 + assert oschmod.get_effective_mode(0b101110100, "+rx") == 0b101111101 + assert oschmod.get_effective_mode(0b101110100, "a+rx") == 0b101111101 + + assert oschmod.get_effective_mode(0b101110100, "g+rwx") == 0b101111100 + assert oschmod.get_effective_mode(0b101110100, "o+rwx") == 0b101110111 + assert oschmod.get_effective_mode(0b101110100, "u+rwx") == 0b111110100 + + # randomly chosen starting permission = 501 + assert oschmod.get_effective_mode(0b111110101, "g+x") == 0b111111101 + assert oschmod.get_effective_mode(0b111110101, "o+x") == 0b111110101 + assert oschmod.get_effective_mode(0b111110101, "u+x") == 0b111110101 + assert oschmod.get_effective_mode(0b111110101, "+x") == 0b111111101 + assert oschmod.get_effective_mode(0b111110101, "ugo+x") == 0b111111101 + assert oschmod.get_effective_mode(0b111110101, "a+x") == 0b111111101 + + assert oschmod.get_effective_mode(0b111110101, "g+rw") == 0b111110101 + assert oschmod.get_effective_mode(0b111110101, "o+rw") == 0b111110111 + assert oschmod.get_effective_mode(0b111110101, "u+rw") == 0b111110101 + assert oschmod.get_effective_mode(0b111110101, "+rw") == 0b111110111 + assert oschmod.get_effective_mode(0b111110101, "a+rw") == 0b111110111 + + assert oschmod.get_effective_mode(0b111110101, "g+rwx") == 0b111111101 + assert oschmod.get_effective_mode(0b111110101, "o+rwx") == 0b111110111 + assert oschmod.get_effective_mode(0b111110101, "u+rwx") == 0b111110101 + + +def test_symbolic_effective_sub(): + """Check calculation of effective mode from symbolic.""" + # randomly chosen starting permission = 328 + assert oschmod.get_effective_mode(0b101001000, "g-x") == 0b101000000 + assert oschmod.get_effective_mode(0b101001000, "o-x") == 0b101001000 + assert oschmod.get_effective_mode(0b101001000, "u-x") == 0b100001000 + assert oschmod.get_effective_mode(0b101001000, "uo-x") == 0b100001000 + assert oschmod.get_effective_mode(0b101001000, "-x") == 0b100000000 + assert oschmod.get_effective_mode(0b101001000, "ugo-x") == 0b100000000 + assert oschmod.get_effective_mode(0b101001000, "a-x") == 0b100000000 + + # randomly chosen starting permission = 256 + assert oschmod.get_effective_mode(0b100000000, "g-r") == 0b100000000 + assert oschmod.get_effective_mode(0b100000000, "o-r") == 0b100000000 + assert oschmod.get_effective_mode(0b100000000, "u-r") == 0b000000000 + assert oschmod.get_effective_mode(0b100000000, "uo-r") == 0b000000000 + assert oschmod.get_effective_mode(0b100000000, "-r") == 0b000000000 + assert oschmod.get_effective_mode(0b100000000, "ugo-r") == 0b000000000 + assert oschmod.get_effective_mode(0b100000000, "a-r") == 0b000000000 + + # randomly chosen starting permission = 166 + assert oschmod.get_effective_mode(0b010100110, "g-x") == 0b010100110 + assert oschmod.get_effective_mode(0b010100110, "o-w") == 0b010100100 + assert oschmod.get_effective_mode(0b010100110, "u-rw") == 0b000100110 + assert oschmod.get_effective_mode(0b010100110, "uo-rw") == 0b000100000 + assert oschmod.get_effective_mode(0b010100110, "-wx") == 0b000100100 + assert oschmod.get_effective_mode(0b010100110, "ugo-rwx") == 0b000000000 + assert oschmod.get_effective_mode(0b010100110, "a-r") == 0b010000010 + + # randomly chosen starting permission = 174 + assert oschmod.get_effective_mode(0b010101110, "ug-w") == 0b000101110 + assert oschmod.get_effective_mode(0b010101110, "u-r") == 0b010101110 + assert oschmod.get_effective_mode(0b010101110, "u-rx") == 0b010101110 + assert oschmod.get_effective_mode(0b010101110, "ug-rwx") == 0b000000110 + assert oschmod.get_effective_mode(0b010101110, "g-rx") == 0b010000110 + assert oschmod.get_effective_mode(0b010101110, "go-rw") == 0b010001000 + assert oschmod.get_effective_mode(0b010101110, "ug-x") == 0b010100110 + + +def test_symbolic_effective_eq(): + """Check calculation of effective mode from symbolic.""" + # randomly chosen starting permission = 494 + assert oschmod.get_effective_mode(0b111101110, "go=rx") == 0b111101101 + assert oschmod.get_effective_mode(0b111101110, "=r") == 0b100100100 + assert oschmod.get_effective_mode(0b111101110, "ugo=rw") == 0b110110110 + assert oschmod.get_effective_mode(0b111101110, "ugo=rx") == 0b101101101 + assert oschmod.get_effective_mode(0b111101110, "uo=r") == 0b100101100 + assert oschmod.get_effective_mode(0b111101110, "o=rw") == 0b111101110 + assert oschmod.get_effective_mode(0b111101110, "=x") == 0b001001001 + + # randomly chosen starting permission = 417 + assert oschmod.get_effective_mode(0b110100001, "ugo=x") == 0b001001001 + assert oschmod.get_effective_mode(0b110100001, "ug=rw") == 0b110110001 + assert oschmod.get_effective_mode(0b110100001, "ugo=rw") == 0b110110110 + assert oschmod.get_effective_mode(0b110100001, "u=wx") == 0b011100001 + assert oschmod.get_effective_mode(0b110100001, "=rx") == 0b101101101 + assert oschmod.get_effective_mode(0b110100001, "u=r") == 0b100100001 + assert oschmod.get_effective_mode(0b110100001, "uo=wx") == 0b011100011 + + +def test_symbolic_use(): + """Check file permissions are recursively set.""" + # create dirs + topdir = 'testdir1' + testdir = os.path.join(topdir, 'testdir2', 'testdir3') + os.makedirs(testdir) + + # create files + fileh = open(os.path.join(topdir, 'file1'), "w+") + fileh.write("contents") + fileh.close() + + fileh = open(os.path.join(testdir, 'file2'), "w+") + fileh.write("contents") + fileh.close() + + # set permissions to badness + triple7 = "+rwx" + oschmod.set_mode(topdir, triple7) + oschmod.set_mode(testdir, triple7) + oschmod.set_mode(os.path.join(topdir, 'file1'), triple7) + oschmod.set_mode(os.path.join(testdir, 'file2'), triple7) + time.sleep(1) # modes aren't always ready to go immediately + + # set permissions - the test + oschmod.set_mode_recursive(topdir, "u=rw,go-rwx", "u+rwx,go-rwx") + time.sleep(1) # modes aren't always ready to go immediately + + # check it out + assert oschmod.get_mode(topdir) == 0o700 + assert oschmod.get_mode(os.path.join(topdir, 'testdir2')) == 0o700 + assert oschmod.get_mode(testdir) == 0o700 + assert oschmod.get_mode(os.path.join(topdir, 'file1')) == 0o600 + assert oschmod.get_mode(os.path.join(testdir, 'file2')) == 0o600 + + # clean up + shutil.rmtree(topdir) From 44ede8115c6542cc2c3c47546c95b0103ebb38b6 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Tue, 26 May 2020 22:37:55 -0400 Subject: [PATCH 2/7] Fix docs --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 58e1b41..733a534 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,12 @@ Mergify

-# oschmod TL;DR +# oschmod ***oschmod*** sets consistent file permissions across Windows, Linux and macOS. +## TL;DR + Python includes `os.chmod()` to set read, write, and execute file permissions. However, on Windows, Python's `os.chmod()` basically has no effect. Even worse, Windows Python does not give a warning or error -- you think you've protected a file but you have not. In order to set the same file permissions across platforms, use ***oschmod***. * Read more about [oschmod](https://medium.com/@dirk.avery/securing-files-on-windows-macos-and-linux-7b2b9899992) on Medium From e3c2785f63d61da24218a5754daca226864d7e85 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Tue, 26 May 2020 22:40:53 -0400 Subject: [PATCH 3/7] Break tests in smaller functions --- tests/test_oschmod.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_oschmod.py b/tests/test_oschmod.py index bcacc2d..8ac58d1 100644 --- a/tests/test_oschmod.py +++ b/tests/test_oschmod.py @@ -136,6 +136,9 @@ def test_symbolic_effective_add(): assert oschmod.get_effective_mode(0b110001010, "+rwx") == 0b111111111 assert oschmod.get_effective_mode(0b110001010, "a+rwx") == 0b111111111 + +def test_symbolic_effective_add2(): + """Check calculation of effective mode from symbolic.""" # randomly chosen starting permission = 53 assert oschmod.get_effective_mode(0b000110101, "g+x") == 0b000111101 assert oschmod.get_effective_mode(0b000110101, "o+x") == 0b000110101 From 46de967220ba818ea5360501632a6903d668bc61 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Wed, 27 May 2020 09:32:26 -0400 Subject: [PATCH 4/7] Allow empty modifiers --- oschmod/__init__.py | 2 +- test.xyz | 0 tests/test_oschmod.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 test.xyz diff --git a/oschmod/__init__.py b/oschmod/__init__.py index 5d52512..66de977 100644 --- a/oschmod/__init__.py +++ b/oschmod/__init__.py @@ -230,7 +230,7 @@ def get_effective_mode(current_mode, symbolic): if not isinstance(symbolic, str): raise AttributeError('symbolic must be a string') - result = re.search(r'^([ugoa]*)([-+=])([rwx]+)$', symbolic) + result = re.search(r'^([ugoa]*)([-+=])([rwx]*)$', symbolic) if result is None: raise AttributeError('symbolic bad format') diff --git a/test.xyz b/test.xyz new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_oschmod.py b/tests/test_oschmod.py index 8ac58d1..b275965 100644 --- a/tests/test_oschmod.py +++ b/tests/test_oschmod.py @@ -279,7 +279,7 @@ def test_symbolic_use(): time.sleep(1) # modes aren't always ready to go immediately # set permissions - the test - oschmod.set_mode_recursive(topdir, "u=rw,go-rwx", "u+rwx,go-rwx") + oschmod.set_mode_recursive(topdir, "u=rw,go=", "u=rwx,go=") time.sleep(1) # modes aren't always ready to go immediately # check it out From 2342e7d953c237eb843ed215ec425d40779ec5d6 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Wed, 27 May 2020 13:36:22 -0400 Subject: [PATCH 5/7] Add test generator, fix effective calc --- oschmod/__init__.py | 26 +++++--- test.xyz | 0 tests/test_oschmod.py | 147 ++++++++++++++++++++++++++++++++++++++---- 3 files changed, 151 insertions(+), 22 deletions(-) delete mode 100644 test.xyz diff --git a/oschmod/__init__.py b/oschmod/__init__.py index 66de977..8242cac 100644 --- a/oschmod/__init__.py +++ b/oschmod/__init__.py @@ -32,7 +32,6 @@ import stat import string - IS_WINDOWS = platform.system() == 'Windows' HAS_PYWIN32 = False try: @@ -179,13 +178,9 @@ def set_mode(path, mode): new_mode = mode elif isinstance(mode, str): if '+' in mode or '-' in mode or '=' in mode: - current_mode = get_mode(path) - for mode_piece in mode.split(","): - set_mode(path, get_effective_mode( - current_mode, mode_piece.strip())) - return True - - new_mode = int(mode, 8) + new_mode = get_effective_mode(get_mode(path), mode) + else: + new_mode = int(mode, 8) if IS_WINDOWS: return win_set_permissions(path, new_mode) @@ -225,14 +220,25 @@ def set_mode_recursive(path, mode, dir_mode=None): return set_mode(path, dir_mode) +def _get_effective_mode_multiple(current_mode, modes): + """Get octal mode, given current mode and symbolic mode modifiers.""" + new_mode = current_mode + for mode in modes.split(","): + new_mode = get_effective_mode(new_mode, mode) + return new_mode + + def get_effective_mode(current_mode, symbolic): """Get octal mode, given current mode and symbolic mode modifier.""" if not isinstance(symbolic, str): raise AttributeError('symbolic must be a string') - result = re.search(r'^([ugoa]*)([-+=])([rwx]*)$', symbolic) + if "," in symbolic: + return _get_effective_mode_multiple(current_mode, symbolic) + + result = re.search(r'^\s*([ugoa]*)([-+=])([rwx]*)\s*$', symbolic) if result is None: - raise AttributeError('symbolic bad format') + raise AttributeError('bad format of symbolic representation modifier') whom = result.group(1) or "ugo" operation = result.group(2) diff --git a/test.xyz b/test.xyz deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_oschmod.py b/tests/test_oschmod.py index b275965..62b3697 100644 --- a/tests/test_oschmod.py +++ b/tests/test_oschmod.py @@ -9,6 +9,8 @@ import string import time +from random import randrange + import oschmod @@ -84,16 +86,22 @@ def test_set_recursive(): oschmod.set_mode_recursive(topdir, file_mode, dir_mode) time.sleep(1) # modes aren't always ready to go immediately - # check it out - assert oschmod.get_mode(topdir) == dir_mode - assert oschmod.get_mode(os.path.join(topdir, 'testdir2')) == dir_mode - assert oschmod.get_mode(testdir) == dir_mode - assert oschmod.get_mode(os.path.join(topdir, 'file1')) == file_mode - assert oschmod.get_mode(os.path.join(testdir, 'file2')) == file_mode + mode_dir1 = oschmod.get_mode(topdir) + mode_dir2 = oschmod.get_mode(os.path.join(topdir, 'testdir2')) + mode_dir3 = oschmod.get_mode(testdir) + mode_file1 = oschmod.get_mode(os.path.join(topdir, 'file1')) + mode_file2 = oschmod.get_mode(os.path.join(testdir, 'file2')) # clean up shutil.rmtree(topdir) + # check it out + assert mode_dir1 == dir_mode + assert mode_dir2 == dir_mode + assert mode_dir3 == dir_mode + assert mode_file1 == file_mode + assert mode_file2 == file_mode + def test_symbolic_effective_add(): """Check calculation of effective mode from symbolic.""" @@ -253,6 +261,112 @@ def test_symbolic_effective_eq(): assert oschmod.get_effective_mode(0b110100001, "u=r") == 0b100100001 assert oschmod.get_effective_mode(0b110100001, "uo=wx") == 0b011100011 + # randomly chosen starting permission = 359 + assert oschmod.get_effective_mode(0b101100111, "go=") == 0b101000000 + assert oschmod.get_effective_mode(0b101100111, "ugo=") == 0b000000000 + assert oschmod.get_effective_mode(0b101100111, "ugo=rwx") == 0b111111111 + assert oschmod.get_effective_mode(0b101100111, "uo=w") == 0b010100010 + assert oschmod.get_effective_mode(0b101100111, "=w") == 0b010010010 + assert oschmod.get_effective_mode(0b101100111, "uo=wx") == 0b011100011 + assert oschmod.get_effective_mode(0b101100111, "u=r") == 0b100100111 + + +def generate_symbolic(): + """Generate one symbolic representation of a mode modifier.""" + who = randrange(8) + whom = ((who & 0b100 > 0 and "u") or "") + \ + ((who & 0b010 > 0 and "g") or "") + \ + ((who & 0b001 > 0 and "o") or "") + oper = randrange(3) + operation = ((oper == 0 and "+") or "") + \ + ((oper == 1 and "-") or "") + \ + ((oper == 2 and "=") or "") + perm = randrange(8) + perms = ((perm & 0b100 > 0 and "r") or "") + \ + ((perm & 0b010 > 0 and "w") or "") + \ + ((perm & 0b001 > 0 and "x") or "") + return whom + operation + perms + + +def generate_case(prefix, suffix): + """Generate a test case to be solved manually.""" + symbolic = [generate_symbolic() for _ in range(randrange(1, 4))] + symbolics = ",".join(symbolic) + return "{0:s}0b{1:09b}, \"{2:s}\"{3:s} == 0b{1:09b}".format( + prefix, + randrange(512), + symbolics, + suffix + ) + + +def generate_cases(count): + """Generate test cases to be solved manually and added to tests.""" + prefix = "assert oschmod.get_effective_mode(" + suffix = ")" + cases = [generate_case(prefix, suffix) for _ in range(count)] + print("\n".join(cases)) + + +def test_symbolic_multiples(): + """Check calculation of effective mode from symbolic.""" + assert oschmod.get_effective_mode(0b000101010, "g-rwx,go=") == 0b000000000 + assert oschmod.get_effective_mode(0b011001011, "uo-wx,u=x") == 0b001001000 + assert oschmod.get_effective_mode(0b111101000, "u=rwx,o=x") == 0b111101001 + assert oschmod.get_effective_mode(0b110101001, "+r,ug=rx") == 0b101101101 + assert oschmod.get_effective_mode( + 0b010010000, "go-,go+,u+rw") == 0b110010000 + assert oschmod.get_effective_mode(0b000101110, "ug-rw,go=x") == 0b000001001 + assert oschmod.get_effective_mode( + 0b010110000, "=rwx,=rw,ug-") == 0b110110110 + assert oschmod.get_effective_mode( + 0b010001111, "o-rwx,o=rwx,ug-x") == 0b010000111 + assert oschmod.get_effective_mode( + 0b100111011, "u-r,o=rwx,ug-wx") == 0b000100111 + assert oschmod.get_effective_mode( + 0b111110101, "o=rwx,ugo-,g=rx") == 0b111101111 + assert oschmod.get_effective_mode(0b010010000, "u=rx") == 0b101010000 + assert oschmod.get_effective_mode(0b001011111, "=") == 0b000000000 + assert oschmod.get_effective_mode(0b100011010, "ug-w,uo=rw") == 0b110001110 + assert oschmod.get_effective_mode(0b111001001, "ug=rw,g-wx") == 0b110100001 + assert oschmod.get_effective_mode( + 0b111000000, "u-,uo+rx,go+x") == 0b111001101 + assert oschmod.get_effective_mode(0b000000000, "u=rx,uo-x") == 0b100000000 + assert oschmod.get_effective_mode(0b101110101, "uo=rx") == 0b101110101 + assert oschmod.get_effective_mode( + 0b111111010, "g-wx,ug=,-x") == 0b000000010 + assert oschmod.get_effective_mode(0b100011000, "uo+rw") == 0b110011110 + assert oschmod.get_effective_mode( + 0b011111000, "ugo+,uo+w,-rwx") == 0b000000000 + assert oschmod.get_effective_mode( + 0b000010100, "ug=x,ug=x,g-rx") == 0b001000100 + assert oschmod.get_effective_mode(0b110101101, "g=rwx") == 0b110111101 + assert oschmod.get_effective_mode(0b000010111, "=wx") == 0b011011011 + assert oschmod.get_effective_mode( + 0b000111011, "u-rw,uo-x,o+wx") == 0b000111011 + assert oschmod.get_effective_mode(0b010110000, "uo+,u+") == 0b010110000 + assert oschmod.get_effective_mode( + 0b000111110, "go=x,ug+x,uo=rx") == 0b101001101 + assert oschmod.get_effective_mode(0b011101111, "o+wx") == 0b011101111 + assert oschmod.get_effective_mode( + 0b001001011, "u-,go+w,ugo=w") == 0b010010010 + assert oschmod.get_effective_mode(0b110110100, "u=w,=x") == 0b001001001 + assert oschmod.get_effective_mode( + 0b110011100, "u=w,ug-rwx,uo+rwx") == 0b111000111 + assert oschmod.get_effective_mode(0b100101001, "go-r") == 0b100001001 + assert oschmod.get_effective_mode( + 0b110100110, "uo=r,ug+rx,ugo=") == 0b000000000 + assert oschmod.get_effective_mode(0b101100000, "go=wx,o-") == 0b101011011 + assert oschmod.get_effective_mode(0b111111101, "-r,o=r,o-w") == 0b011011100 + assert oschmod.get_effective_mode(0b110101000, "uo-rx,+rwx") == 0b111111111 + assert oschmod.get_effective_mode(0b101011111, "go=wx") == 0b101011011 + assert oschmod.get_effective_mode( + 0b110110010, "go+x,ugo-w,u+rwx") == 0b111101001 + assert oschmod.get_effective_mode(0b001100101, "=rx,-rwx") == 0b000000000 + assert oschmod.get_effective_mode(0b001010011, "uo=x") == 0b001010001 + assert oschmod.get_effective_mode( + 0b011101110, "ugo-rx,uo+rw,uo-rw") == 0b000000000 + def test_symbolic_use(): """Check file permissions are recursively set.""" @@ -282,12 +396,21 @@ def test_symbolic_use(): oschmod.set_mode_recursive(topdir, "u=rw,go=", "u=rwx,go=") time.sleep(1) # modes aren't always ready to go immediately - # check it out - assert oschmod.get_mode(topdir) == 0o700 - assert oschmod.get_mode(os.path.join(topdir, 'testdir2')) == 0o700 - assert oschmod.get_mode(testdir) == 0o700 - assert oschmod.get_mode(os.path.join(topdir, 'file1')) == 0o600 - assert oschmod.get_mode(os.path.join(testdir, 'file2')) == 0o600 + dir_mode = 0o700 + file_mode = 0o600 + + mode_dir1 = oschmod.get_mode(topdir) + mode_dir2 = oschmod.get_mode(os.path.join(topdir, 'testdir2')) + mode_dir3 = oschmod.get_mode(testdir) + mode_file1 = oschmod.get_mode(os.path.join(topdir, 'file1')) + mode_file2 = oschmod.get_mode(os.path.join(testdir, 'file2')) # clean up shutil.rmtree(topdir) + + # check it out + assert mode_dir1 == dir_mode + assert mode_dir2 == dir_mode + assert mode_dir3 == dir_mode + assert mode_file1 == file_mode + assert mode_file2 == file_mode From ddd9227a8d57f6732c8a2eb245e65171d6e6be6d Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Wed, 27 May 2020 14:52:52 -0400 Subject: [PATCH 6/7] Bump version, update docs --- .bumpversion.cfg | 2 +- CHANGELOG.md | 10 +++++ README.md | 101 ++++++++++++++++++++++++++++++++++++-------- oschmod/__init__.py | 2 +- oschmod/cli.py | 2 +- setup.cfg | 2 +- 6 files changed, 97 insertions(+), 22 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 808dfb5..da5c969 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.2.7 +current_version = 0.3.0 commit = False tag = False tag_name = {new_version} diff --git a/CHANGELOG.md b/CHANGELOG.md index 71e7acc..f2363bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ ## Changelog +### 0.3.0 + +**Commit Delta**: [Change from 0.2.4 release](https://github.com/YakDriver/oschmod/compare/0.2.4...0.3.0) + +**Released**: 2020.05.27 + +**Summary**: + +* Adds symbolic representation for mode changes (e.g., `+x`, `u+rwx`, `ugo-x`). + ### 0.2.4 **Commit Delta**: [Change from 0.2.2 release](https://github.com/YakDriver/oschmod/compare/0.2.2...0.2.4) diff --git a/README.md b/README.md index 733a534..ba492b3 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,17 @@ Mergify

-# oschmod +# oschmod ***oschmod*** sets consistent file permissions across Windows, Linux and macOS. -## TL;DR +## oschmod TL;DR -Python includes `os.chmod()` to set read, write, and execute file permissions. However, on Windows, Python's `os.chmod()` basically has no effect. Even worse, Windows Python does not give a warning or error -- you think you've protected a file but you have not. In order to set the same file permissions across platforms, use ***oschmod***. +macOS and other Linux-based operating systems include a tool called `chmod`. ***oschmod*** provides consistent `chmod`-like functionality that works on **Windows**, macOS, and Linux! + +Prior to ***oschmod***, Windows file permissions couldn't be set in the familiar `chmod` way. Tools did not translate `chmod`-style permissions into Windows-style file permissions. Even though Python's `os.chmod()` sets read, write, and execute file permissions, on Windows, `os.chmod()` basically has no effect. Even worse, Python on Windows gives no warnings or errors. If you think you've protected a file on Windows with `os.chmod()`, you have not. + +***oschmod*** allows you to set consistent file permissions in a consistent way across platforms. * Read more about [oschmod](https://medium.com/@dirk.avery/securing-files-on-windows-macos-and-linux-7b2b9899992) on Medium * For more background, have a look at the [oschmod Wiki](https://github.com/YakDriver/oschmod/wiki). @@ -29,7 +33,7 @@ $ pip install oschmod ## Command line interface -***oschmod*** brings the ability to set consistent file permissions using the command line to Windows, macOS, and Linux platforms. If you are familiar with `chmod` on Unix, Linux and/or macOS, ***oschmod*** works similarly, albeit with fewer options. +***oschmod*** brings the ability to set consistent file permissions using the command line to Windows, macOS, and Linux platforms. If you are familiar with `chmod` on Unix, Linux and/or macOS, ***oschmod*** works similarly, albeit with fewer options. ```console $ oschmod -h @@ -38,7 +42,7 @@ usage: oschmod [-h] [-R] mode object Change the mode (permissions) of a file or directory positional arguments: - mode octal mode of the object + mode octal or symbolic mode of the object object file or directory optional arguments: @@ -46,38 +50,99 @@ optional arguments: -R apply mode recursively ``` -For example, to give everyone read, write, and execute permissions on a file, you can run this command: +## Command line examples + +You can use symbolic (e.g., "u+rw") or octal (e.g., "600) representations of modes. Multiple mode modifications can be made in a single call by separating modifiers with commas. + +### Symbolic representation examples + +Symbolic representation mode modifiers have three parts: + 1. **whom:** To whom does the modification apply? You can include zero or more of `[ugoa]*` where `a` is for all, `u` is for the file owner (i.e., "**u**ser"), `g` is for the file group, and `o` is for others. In other words, `ugo` is equivalent to `a`. Also, if you do not provide a "whom," ***oschmod*** assumes you mean `a` (everyone). + 2. **operation:** Which operation should be applied? You must include one and only one operation, `[+-=]{1}`, per modifier (although you can have multiple modifiers). `+` adds permissions, `-` removes permissions, and `=` sets permissions regardless of previous permissions. `+` and `-` modifications often depend on the current permissions. + 3. **permission:** Which permission or permissions will be affected? You can include zero or more of `[rwx]*` where `r` is for read, `w` is for write, and `x` is for execute. If you do not include a permission with `+` or `-` (e.g., `u-`), the modifier has no effect. However, if you use no permissions with `=` (e.g., `o=`), all permissions are removed. + +**Example 1:** To give everyone execute permissions on a file (all of these are equivalent): + +```console +$ oschmod +x +$ oschmod a+x +$ oschmod ugo+x +``` + +**Example 2:** To remove read, write, and execute permissions from the file group and all others (these are equivalent): ```console -$ oschmod 777 file_name +$ oschmod go-rwx +$ oschmod go= ``` -You can also lock down a file to just give the file owner read, write, and execute permissions and deny any permissions to everyone else: +**Example 3:** To give the file owner read and execute permissions, and remove execute permissions from the group and all others: + +```console +$ oschmod u+rx,go-x +``` + +**Example 4:** To give everyone all permissions, and then remove execute write from the group, and execute from all others: + +```console +$ oschmod a+rwx,g-w,o-x +``` + +### Octal representation examples + +For more about what octal representations mean, see [this article](https://medium.com/@dirk.avery/securing-files-on-windows-macos-and-linux-7b2b9899992) on Medium. + +**Example 5:** To give everyone read, write, and execute permissions on a file: ```console -$ oschmod 700 file_name +$ oschmod 777 ``` -## Python Usage +**Example 6:** To lock down a file to just give the file owner read, write, and execute permissions and deny all permissions to everyone else: -You can use ***oschmod*** from Python code. +```console +$ oschmod 700 +``` -Replacing `os.chmod()` with ***oschmod*** is straightforward and you will get consistent file permissions on Windows, macOS, and Linux: +## Python usage -For example, this is an example of using `os.chmod()` in Python: +You can use ***oschmod*** from Python code. Any of the command line examples above will work very similarly. For example, "Example 4" above, in Python code, would look like this: ```python -import os -import stat -os.chmod('myfile', stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) +import oschmod +oschmod.set_mode("myfile", "a+rwx,g-w,o-x") ``` -On Linux or macOS, this gives a file's owner read, write, and execute permissions and denies the group and others any permissions (i.e., equivalent of `700`). On Windows, the best this command may have done is set the read-only attribute of the file. The read-only attribute restricts anyone from deleting, changing or renaming the file. The owner isn't given any permissions and the group and others are not denied any permissions. There is no consistency between the platforms. +"Example 5" above, in Python code, could be done in two ways: -However, using ***oschmod*** you can use the same command on Windows, macOS or Linux and get the same results: +```python +import oschmod +oschmod.set_mode("myfile", "777") +oschmod.set_mode("myfile", 0o777) +``` + +***oschmod*** is compatible with bitwise permissions as defined in the `stat` module. To give a file's owner read, write, and execute permissions and deny the group and others any permissions (i.e., equivalent of `700`): ```python import oschmod import stat oschmod.set_mode('myfile', stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) ``` + +Replacing `os.chmod()` with ***oschmod*** should usually be an easy drop-in replacement. Replacement will allow you to get consistent file permission settings on Windows, macOS, and Linux: + +If this is your Python code using `os.chmod()`: + +```python +import os +os.chmod('myfile1', 'u+x') +os.chmod('myfile2', 0o777) +``` + +The replacement using ***oschmod*** is very similar: + +```python +import oschmod +oschmod.set_mode('myfile1', 'u+x') +oschmod.set_mode('myfile2', 0o777) +``` diff --git a/oschmod/__init__.py b/oschmod/__init__.py index 8242cac..ea29275 100644 --- a/oschmod/__init__.py +++ b/oschmod/__init__.py @@ -154,7 +154,7 @@ "S_IXOTH" ) -__version__ = "0.2.7" +__version__ = "0.3.0" def get_mode(path): diff --git a/oschmod/cli.py b/oschmod/cli.py index 8522c2a..04ce033 100644 --- a/oschmod/cli.py +++ b/oschmod/cli.py @@ -14,7 +14,7 @@ def main(): description='Change the mode (permissions) of a file or directory') parser.add_argument('-R', action='store_true', help='apply mode recursively') - parser.add_argument('mode', nargs=1, help='octal mode of the object') + parser.add_argument('mode', nargs=1, help='octal or symbolic mode of the object') parser.add_argument('object', nargs=1, help='file or directory') args = parser.parse_args() diff --git a/setup.cfg b/setup.cfg index 226649e..696d31d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,7 +3,7 @@ name = oschmod description = Windows and Linux compatible chmod long_description = file: README.md, CHANGELOG.md long_description_content_type = text/markdown -version = 0.2.7 +version = 0.3.0 author = YakDriver author_email = projects@plus3it.com url = https://github.com/yakdriver/oschmod From f9279cda32c207c7aed4c235608e60e69731acde Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Wed, 27 May 2020 14:52:52 -0400 Subject: [PATCH 7/7] Bump version, update docs --- README.md | 27 +++++++++++++++++++-------- oschmod/cli.py | 3 ++- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ba492b3..f454cdc 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,9 @@ ## oschmod TL;DR -macOS and other Linux-based operating systems include a tool called `chmod`. ***oschmod*** provides consistent `chmod`-like functionality that works on **Windows**, macOS, and Linux! +***oschmod*** brings `chmod` functionality to **Windows**, macOS, and Linux! If you're not familiar, `chmod` is a handy macOS and Linux-only tool for setting file permissions. -Prior to ***oschmod***, Windows file permissions couldn't be set in the familiar `chmod` way. Tools did not translate `chmod`-style permissions into Windows-style file permissions. Even though Python's `os.chmod()` sets read, write, and execute file permissions, on Windows, `os.chmod()` basically has no effect. Even worse, Python on Windows gives no warnings or errors. If you think you've protected a file on Windows with `os.chmod()`, you have not. +Prior to ***oschmod***, Windows file permissions couldn't be set in the familiar `chmod` way. Tools did not translate `chmod`-style permissions into Windows-style file permissions. Even though Python's `os.chmod()` sets read, write, and execute file permissions, on Windows, `os.chmod()` basically has no effect. Even worse, Python on Windows gives no warnings or errors. If you think you set file permissions on Windows with `os.chmod()`, you're wrong! ***oschmod*** allows you to set consistent file permissions in a consistent way across platforms. @@ -31,9 +31,19 @@ Prior to ***oschmod***, Windows file permissions couldn't be set in the familiar $ pip install oschmod ``` +## GNU Documentation + +***oschmod*** changes the file mode bits of each given file according to mode, which can be either a symbolic representation of changes to make, or an octal number representing the bit pattern for the new mode bits. + +The format of a symbolic mode is `[ugoa...][+-=][perms...]` where perms is zero or more letters from the set `rwx`. Multiple symbolic modes can be given, separated by commas. + +A combination of the letters `ugoa` controls which users' access to the file will be changed: the user who owns it (`u`), other users in the file's group (`g`), other users not in the file's group (`o`), or all users (`a`). If none of these are given, the effect is as if `a` were given. + +*(Modified from the GNU manpage for chmod.)* + ## Command line interface -***oschmod*** brings the ability to set consistent file permissions using the command line to Windows, macOS, and Linux platforms. If you are familiar with `chmod` on Unix, Linux and/or macOS, ***oschmod*** works similarly, albeit with fewer options. +***oschmod*** brings the ability to set consistent file permissions using the command line to Windows, macOS, and Linux platforms. If you are familiar with `chmod`, ***oschmod*** works similarly, albeit with fewer options. ```console $ oschmod -h @@ -57,9 +67,10 @@ You can use symbolic (e.g., "u+rw") or octal (e.g., "600) representations of mod ### Symbolic representation examples Symbolic representation mode modifiers have three parts: - 1. **whom:** To whom does the modification apply? You can include zero or more of `[ugoa]*` where `a` is for all, `u` is for the file owner (i.e., "**u**ser"), `g` is for the file group, and `o` is for others. In other words, `ugo` is equivalent to `a`. Also, if you do not provide a "whom," ***oschmod*** assumes you mean `a` (everyone). - 2. **operation:** Which operation should be applied? You must include one and only one operation, `[+-=]{1}`, per modifier (although you can have multiple modifiers). `+` adds permissions, `-` removes permissions, and `=` sets permissions regardless of previous permissions. `+` and `-` modifications often depend on the current permissions. - 3. **permission:** Which permission or permissions will be affected? You can include zero or more of `[rwx]*` where `r` is for read, `w` is for write, and `x` is for execute. If you do not include a permission with `+` or `-` (e.g., `u-`), the modifier has no effect. However, if you use no permissions with `=` (e.g., `o=`), all permissions are removed. + +1. **whom:** To whom does the modification apply? You can include zero or more of `[ugoa]*` where `a` is for all, `u` is for the file owner (i.e., "**u**ser"), `g` is for the file group, and `o` is for others. In other words, `ugo` is equivalent to `a`. Also, if you do not provide a "whom," ***oschmod*** assumes you mean `a` (everyone). +2. **operation:** Which operation should be applied? You must include one and only one operation, `[+-=]{1}`, per modifier (although you can have multiple modifiers). `+` adds permissions, `-` removes permissions, and `=` sets permissions regardless of previous permissions. `+` and `-` modifications often depend on the current permissions. +3. **permission:** Which permission or permissions will be affected? You can include zero or more of `[rwx]*` where `r` is for read, `w` is for write, and `x` is for execute. If you do not include a permission with `+` or `-` (e.g., `u-`), the modifier has no effect. However, if you use no permissions with `=` (e.g., `o=`), all permissions are removed. **Example 1:** To give everyone execute permissions on a file (all of these are equivalent): @@ -106,14 +117,14 @@ $ oschmod 700 ## Python usage -You can use ***oschmod*** from Python code. Any of the command line examples above will work very similarly. For example, "Example 4" above, in Python code, would look like this: +You can use ***oschmod*** from Python code. Any of the command line examples above will work very similarly. For example, *Example 4* above, in Python code, would look like this: ```python import oschmod oschmod.set_mode("myfile", "a+rwx,g-w,o-x") ``` -"Example 5" above, in Python code, could be done in two ways: +*Example 5* above, in Python code, could be done in two ways: ```python import oschmod diff --git a/oschmod/cli.py b/oschmod/cli.py index 04ce033..d36fb03 100644 --- a/oschmod/cli.py +++ b/oschmod/cli.py @@ -14,7 +14,8 @@ def main(): description='Change the mode (permissions) of a file or directory') parser.add_argument('-R', action='store_true', help='apply mode recursively') - parser.add_argument('mode', nargs=1, help='octal or symbolic mode of the object') + parser.add_argument( + 'mode', nargs=1, help='octal or symbolic mode of the object') parser.add_argument('object', nargs=1, help='file or directory') args = parser.parse_args()