tox-dev / filelock

A platform-independent file lock for Python.
https://py-filelock.readthedocs.io
The Unlicense
760 stars 107 forks source link

[BugFix] fix permission denied error when lock file is placed in `/tmp` #317

Closed kota-iizuka closed 7 months ago

kota-iizuka commented 7 months ago

When you put the lock file in a directory with sticky bit (like /tmp), you will get a permission denied error if you use a lock file created by someone else.

Work around this issue by excluding os.O_CREAT from open_flags if the lock file already exists.

cf. https://github.com/vllm-project/vllm/pull/3599#issuecomment-2017125950

sample code

# sample.py
import filelock
with filelock.FileLock("tmp/a.lock", mode=0o666):
    pass
# create tmp file with sticky bit
$ mkdir tmp
$ sudo chmod 1777 tmp
$ sudo chown root:root tmp

# add lock file by another user
$ touch tmp/a.lock
$ sudo chmod 666 tmp/a.lock 
$ sudo chown 1111:1111 tmp/a.lock

# run sample code
$ python sample.py 
Traceback (most recent call last):
  File "/workspaces/filelock/sample.py", line 4, in <module>
    with filelock.FileLock(lock_path, mode=0o666):
  File "/workspaces/filelock/src/filelock/_api.py", line 301, in __enter__
    self.acquire()
  File "/workspaces/filelock/src/filelock/_api.py", line 257, in acquire
    self._acquire()
  File "/workspaces/filelock/src/filelock/_unix.py", line 41, in _acquire
    fd = os.open(self.lock_file, open_flags, self._context.mode)
PermissionError: [Errno 13] Permission denied: 'tmp/a.lock'
kota-iizuka commented 7 months ago

P.S.: When I tried to implement this test with pytest, an error occurred inside pytest when deleting a file with another user's uid, so I could not implement the unit test.

@pytest.mark.parametrize("lockfile_init", ["not_exist", "owned", "sudo"])
def test_lock_mode(lockfile_init: str, tmp_path: Path) -> None:
    # test file lock permissions are independent of umask
    lock_dir = tmp_path / "lock_dir"
    lock_dir.mkdir()
    lock_path = lock_dir / "a.lock"
    if lockfile_init == "not_exist":
        assert not lock_path.exists()
    else:
        lock_path.touch(mode=0o666)
        if lockfile_init == "sudo":
            chown_cmd = f"sudo chown -R root:root {lock_dir}"
            subprocess.run(chown_cmd.split(" "))
            chmod_cmd = f"sudo chmod 1777 {lock_dir}"
            subprocess.run(chmod_cmd.split(" "))
            chmod_cmd = f"sudo chmod 0666 {lock_path}"
            subprocess.run(chmod_cmd.split(" "))
        assert lock_path.exists()
    lock = FileLock(str(lock_path), mode=0o666)
/workspaces/filelock/.tox/py312/lib/python3.12/site-packages/_pytest/pathlib.py:97: PytestWarning: (rm_rf) error removing /tmp/pytest-of-codespace/garbage-6b156c4c-bd92-4cc6-a723-4e7b297d1777
<class 'OSError'>: [Errno 39] Directory not empty: '/tmp/pytest-of-codespace/garbage-6b156c4c-bd92-4cc6-a723-4e7b297d1777'
  warnings.warn(
/workspaces/filelock/.tox/py312/lib/python3.12/site-packages/_pytest/pathlib.py:97: PytestWarning: (rm_rf) error removing /tmp/pytest-of-codespace/garbage-665dc500-a17f-4c58-a7e7-739a86a5aad8
<class 'OSError'>: [Errno 39] Directory not empty: '/tmp/pytest-of-codespace/garbage-665dc500-a17f-4c58-a7e7-739a86a5aad8'
  warnings.warn(
/workspaces/filelock/.tox/py312/lib/python3.12/site-packages/_pytest/pathlib.py:97: PytestWarning: (rm_rf) error removing /tmp/pytest-of-codespace/garbage-99d186db-dc63-446c-a7b5-328258482c9f
<class 'OSError'>: [Errno 39] Directory not empty: '/tmp/pytest-of-codespace/garbage-99d186db-dc63-446c-a7b5-328258482c9f'
  warnings.warn(
/workspaces/filelock/.tox/py312/lib/python3.12/site-packages/_pytest/pathlib.py:97: PytestWarning: (rm_rf) error removing /tmp/pytest-of-codespace/garbage-b15a352d-e955-4c5c-be84-236c0020e5bd
<class 'OSError'>: [Errno 39] Directory not empty: '/tmp/pytest-of-codespace/garbage-b15a352d-e955-4c5c-be84-236c0020e5bd'
  warnings.warn(
/workspaces/filelock/.tox/py312/lib/python3.12/site-packages/_pytest/pathlib.py:97: PytestWarning: (rm_rf) error removing /tmp/pytest-of-codespace/garbage-a17e3e1c-846e-42ff-85c6-92d00ea5d328
<class 'OSError'>: [Errno 39] Directory not empty: '/tmp/pytest-of-codespace/garbage-a17e3e1c-846e-42ff-85c6-92d00ea5d328'
  warnings.warn(
/workspaces/filelock/.tox/py312/lib/python3.12/site-packages/_pytest/pathlib.py:97: PytestWarning: (rm_rf) error removing /tmp/pytest-of-codespace/garbage-98fc84c5-5184-41d9-b8dd-9dfe1b9cae20
<class 'OSError'>: [Errno 39] Directory not empty: '/tmp/pytest-of-codespace/garbage-98fc84c5-5184-41d9-b8dd-9dfe1b9cae20'
  warnings.warn(
/workspaces/filelock/.tox/py312/lib/python3.12/site-packages/_pytest/pathlib.py:97: PytestWarning: (rm_rf) error removing /tmp/pytest-of-codespace/garbage-ca6dbecb-8810-43f5-b0f1-0413c9d34e0e
<class 'OSError'>: [Errno 39] Directory not empty: '/tmp/pytest-of-codespace/garbage-ca6dbecb-8810-43f5-b0f1-0413c9d34e0e'
  warnings.warn(
/workspaces/filelock/.tox/py312/lib/python3.12/site-packages/_pytest/pathlib.py:97: PytestWarning: (rm_rf) error removing /tmp/pytest-of-codespace/garbage-94f79960-5b08-4f5b-a4f4-a22e3ba11226
<class 'OSError'>: [Errno 39] Directory not empty: '/tmp/pytest-of-codespace/garbage-94f79960-5b08-4f5b-a4f4-a22e3ba11226'
  warnings.warn(
/workspaces/filelock/.tox/py312/lib/python3.12/site-packages/_pytest/pathlib.py:97: PytestWarning: (rm_rf) error removing /tmp/pytest-of-codespace/garbage-67b2ebfa-eefe-4d26-ad9b-4b00106cf38a
<class 'OSError'>: [Errno 39] Directory not empty: '/tmp/pytest-of-codespace/garbage-67b2ebfa-eefe-4d26-ad9b-4b00106cf38a'
  warnings.warn(