Открытие файла на Windows с эксклюзивной блокировкой на Python - PullRequest
1 голос
/ 16 марта 2020

У меня есть вопрос, очень похожий на этот вопрос, где мне нужно соблюдать следующие условия:

  • Если файл открыт для чтения, этот файл может быть только открыть для чтения любым другим процессом / программой
  • Если файл открыт для записи, этот файл может быть открыт только для чтения любым другим процессом / программой

Решение опубликовано в связанном вопросе используется сторонняя библиотека, которая добавляет произвольный файл .LOCK в тот же каталог, что и рассматриваемый файл. Это решение, которое работает только в отношении программы, в которой используется эта библиотека, и не препятствует использованию файла другими процессами / программами, поскольку они могут быть не реализованы для проверки связи .LOCK.

В сущности, я sh должен скопировать этот результат, используя только стандартную библиотеку Python.

BLUF : Нужен спецификация реализации стандартной библиотеки c до Windows для исключительной блокировки файлов

Чтобы привести пример решения проблемы, предположим, что на файле имеется:

  • 1 общая сеть / диск
  • 2 пользователя в отдельных процессах / программах

Предположим, что пользователь 1 запускает программу A для файла и в какой-то момент выполняется следующее:

with open(fp, 'rb') as f:
    while True:
        chunk = f.read(10)
        if chunk:
            # do something with chunk
        else:
            break 

Таким образом, они перебирают файл по 10 байт за раз.

Теперь пользователь 2 запускает программу B для того же файла через мгновение:

with open(fp, 'wb') as f:
    for b in data:  # some byte array
        f.write(b)

При Windows рассматриваемый файл немедленно усекается, и программа A прекращает итерацию (даже если она не было сделано) и программа B начинает запись в файл. Поэтому мне нужен способ гарантировать, что файл не может быть открыт в другом режиме, который изменил бы его содержимое при открытии ранее.

Я искал библиотеку msvcrt , а именно msvcrt.locking() интерфейс. Что мне удалось сделать, так это убедиться, что файл, открытый для чтения, может быть заблокирован для чтения, но никто другой не может прочитать файл (так как я блокирую весь файл):

>>> f1 = open(fp, 'rb')
>>> f2 = open(fp, 'rb')
>>> msvcrt.locking(f1.fileno(), msvcrt.LK_LOCK, os.stat(fp).st_size)
>>> next(f1)
b"\x00\x05'\n"
>>> next(f2)
PermissionError: [Errno 13] Permission denied

Это приемлемо Результат, просто не самый желанный.

В том же сценарии Пользователь 1 запускает Программу А, которая включает в себя:

with open(fp, 'rb') as f
    msvcrt.locking(f.fileno(), msvcrt.LK_LOCK, os.stat(fp).st_size)
    # repeat while block
    msvcrt.locking(f.fileno(), msvcrt.LK_UNLCK, os.stat(fp).st_size)

Затем Пользователь 2 запускает Программу В мгновение спустя, происходит тот же результат, и файл усекается.

На этом этапе мне бы хотелось, чтобы пользователь 2 выдал ошибку, в которой говорится, что файл открыт для чтения в другом месте и не может быть записан в данный момент. Но если пользователь 3 приедет и откроет файл для чтения, проблем не будет.

Обновление:

Потенциальное решение - изменить разрешения для file (за исключением перехвата, если файл уже используется):

>>> os.chmod(fp, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
>>> with open(fp, 'wb') as f:
        # do something
PermissionError: [Errno 13] Permission denied <fp>

Это не самое лучшее решение (особенно если у пользователей не было разрешения даже изменять разрешения). Все еще ищите правильное решение для блокировки, но msvcrt не препятствует усечению и записи, если файл заблокирован для чтения. По-видимому, до сих пор не существует способа создания эксклюзивной блокировки с помощью стандартной библиотеки Python.

1 Ответ

0 голосов
/ 18 марта 2020

Для тех, кто заинтересован в решении Windows speci c:

import os
import ctypes
import msvcrt
import pathlib

# Windows constants for file operations
NULL = 0x00000000
CREATE_ALWAYS = 0x00000002
OPEN_EXISTING = 0x00000003
FILE_SHARE_READ = 0x00000001
FILE_ATTRIBUTE_READONLY = 0x00000001  # strictly for file reading
FILE_ATTRIBUTE_NORMAL = 0x00000080  # strictly for file writing
FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000
GENERIC_READ = 0x80000000
GENERIC_WRITE = 0x40000000

_ACCESS_MASK = os.O_RDONLY | os.O_WRONLY
_ACCESS_MAP = {os.O_RDONLY: GENERIC_READ,
               os.O_WRONLY: GENERIC_WRITE
               }

_CREATE_MASK = os.O_CREAT | os.O_TRUNC
_CREATE_MAP = {NULL: OPEN_EXISTING,
               os.O_CREAT | os.O_TRUNC: CREATE_ALWAYS
               }

win32 = ctypes.WinDLL('kernel32.dll', use_last_error=True)
win32.CreateFileW.restype = ctypes.c_void_p
INVALID_FILE_HANDLE = ctypes.c_void_p(-1).value


def _opener(path: pathlib.Path, flags: int) -> int:

    access_flags = _ACCESS_MAP[flags & _ACCESS_MASK]
    create_flags = _CREATE_MAP[flags & _CREATE_MASK]

    if flags & os.O_WRONLY:
        share_flags = NULL
        attr_flags = FILE_ATTRIBUTE_NORMAL
    else:
        share_flags = FILE_SHARE_READ
        attr_flags = FILE_ATTRIBUTE_READONLY

    attr_flags |= FILE_FLAG_SEQUENTIAL_SCAN

    h = win32.CreateFileW(path, access_flags, share_flags, NULL, create_flags, attr_flags, NULL)

    if h == INVALID_FILE_HANDLE:
        raise ctypes.WinError(ctypes.get_last_error())

    return msvcrt.open_osfhandle(h, flags)


class _FileControlAccessor(pathlib._NormalAccessor):

    open = staticmethod(_opener)


_control_accessor = _FileControlAccessor()


class Path(pathlib.WindowsPath):

    def _init(self) -> None:

        self._closed = False
        self._accessor = _control_accessor

    def _opener(self, name, flags) -> int:

        return self._accessor.open(name, flags)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...