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 70e3b32..f454cdc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,3 @@ -# oschmod -Python chmod that works on Windows and Linux -

@@ -13,37 +10,40 @@ 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. +# oschmod -* 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). +***oschmod*** sets consistent file permissions across Windows, Linux and macOS. -## 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*** 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. -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. +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! -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: +***oschmod*** allows you to set consistent file permissions in a consistent way across platforms. -```python -import os -import stat -os.chmod('myfile', stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) +* 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). + +## Installation + +```console +$ pip install 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. +## GNU Documentation -However, using ***oschmod*** you can use the same command on Windows, macOS or Linux and get the same results: +***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. -```python -import oschmod -oschmod.set_mode('myfile', stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) -``` +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.)* -## 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`, ***oschmod*** works similarly, albeit with fewer options. ```console $ oschmod -h @@ -52,7 +52,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: @@ -60,20 +60,100 @@ optional arguments: -R apply mode recursively ``` -For example, to open up a file to the world, 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 777 file_name +$ oschmod +x +$ oschmod a+x +$ oschmod ugo+x ``` -As another example, you can lock down a file to just the file owner: +**Example 2:** To remove read, write, and execute permissions from the file group and all others (these are equivalent): ```console -$ oschmod 700 file_name +$ oschmod go-rwx +$ oschmod go= ``` -## Installation +**Example 3:** To give the file owner read and execute permissions, and remove execute permissions from the group and all others: ```console -$ pip install oschmod +$ 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 777 +``` + +**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: + +```console +$ 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: + +```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: + +```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 d360530..ea29275 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,10 +28,10 @@ import os import platform import random +import re import stat import string - IS_WINDOWS = platform.system() == 'Windows' HAS_PYWIN32 = False try: @@ -153,7 +154,7 @@ "S_IXOTH" ) -__version__ = "0.2.7" +__version__ = "0.3.0" def get_mode(path): @@ -164,10 +165,26 @@ 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: + new_mode = get_effective_mode(get_mode(path), mode) + else: + 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 +220,51 @@ 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') + + 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('bad format of symbolic representation modifier') + + 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 +364,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..d36fb03 100644 --- a/oschmod/cli.py +++ b/oschmod/cli.py @@ -14,11 +14,12 @@ 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() - 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/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 diff --git a/tests/test_oschmod.py b/tests/test_oschmod.py index 29ac22b..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,12 +86,331 @@ def test_set_recursive(): oschmod.set_mode_recursive(topdir, file_mode, dir_mode) time.sleep(1) # modes aren't always ready to go immediately + 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 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 + 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.""" + 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 + + +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 + 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 + + # 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.""" + # 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=", "u=rwx,go=") + time.sleep(1) # modes aren't always ready to go immediately + + 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