Chronaxis用户手册English
本页目录

chronaxis.results.platform_files

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""按平台原语不覆盖地移动一个文件或目录,不复制内容或重读分析输入。"""2from functools import lru_cache3from pathlib import Path4import ctypes5import errno6import os7import sys8from .budget import _cleanup_owner910@lru_cache(maxsize=1)11def _linux_rename():12    """取得libc的正式renameat2接口。1314    参数: 无。15    返回: 已声明参数类型的C函数。16    异常: libc缺少该接口时抛NotImplementedError,不退回可覆盖的rename。17    """18    try:19        function = ctypes.CDLL(None, use_errno=True).renameat220    except AttributeError as error:21        raise NotImplementedError('Linux发布需要libc renameat2,不退回覆盖式rename') from error22    function.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_uint]23    function.restype = ctypes.c_int24    return function2526def require_publication_platform():27    """确认本机提供不覆盖发布的调用接口,不创建探测文件。2829    参数: 无。30    返回: None。31    异常: 未支持的平台或缺少接口时抛NotImplementedError;文件系统能力在实际移动时核验。32    """33    if os.name == 'nt':34        return35    if sys.platform.startswith('linux'):36        _linux_rename()37        return38    raise NotImplementedError('不覆盖发布目前支持Windows与提供renameat2的Linux')3940class _DirectoryHandles:41    """短暂持有本次移动的两个父目录fd;遵循Linux关闭后不重试同一编号。"""4243    def __init__(self):44        """初始化两个空目录fd槽。4546        参数: 无。47        返回: 新的目录句柄对象。48        异常: 无主动异常。49        """50        self.source = self.target = None5152    @property53    def closed(self):54        """查询两个目录fd是否都已交给Linux关闭。5556        参数: 无。57        返回: bool;关闭报错仍由close方法向外报告。58        异常: 无。59        """60        return self.source is None and self.target is None6162    def open(self, name, path):63        """打开并登记本次移动的一侧父目录。6465        参数: name为source或target内部槽名;path为父目录Path。66        返回: 实际打开的目录fd。67        异常: 链接、父目录身份替换或IO错误;已经打开的fd仍归本对象。68        """69        before = path.stat(follow_symlinks=False)70        descriptor = os.open(path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)71        setattr(self, name, descriptor)72        actual = os.fstat(descriptor)73        if (before.st_dev, before.st_ino) != (actual.st_dev, actual.st_ino):74            raise OSError('发布父目录身份已变化')75        return descriptor7677    def close(self):78        """对两个实际目录fd各调用一次Linux close。7980        参数: 无。81        返回: None;已关闭的槽不重复操作。82        异常: 报告首个关闭错误,其余写入notes;不重试可能已被复用的fd编号。83        """84        errors = []85        for name in ('target', 'source'):86            descriptor = getattr(self, name)87            if descriptor is None:88                continue89            # Linux close报告错误前可能已释放编号,不能保存数字供第二次关闭。90            setattr(self, name, None)91            try:92                os.close(descriptor)93            except BaseException as error:94                errors.append(error)95        if errors:96            for error in errors[1:]:97                errors[0].add_note('另一发布目录fd关闭失败: ' + type(error).__name__)98            raise errors[0]99100def move_no_replace(source, target):101    """在同一文件系统中不覆盖地移动一个文件或目录。102103    参数: source为来源路径;target为目标路径。104    返回: None;回滚也使用同一原语。105    异常: 已有目标、跨盘或IO错误原样失败;缺少不覆盖原语时明确拒绝。106    Linux使用已打开父目录的相对名称调用RENAME_NOREPLACE,并检查父目录身份。107    单次名称移动原子,不承诺多个文件构成事务或断电后内容持久。108    """109    require_publication_platform()110    if os.name == 'nt':111        os.rename(source, target)112        return113    source, target = (Path(source).absolute(), Path(target).absolute())114    handles = _DirectoryHandles()115    try:116        source_fd = handles.open('source', source.parent)117        target_fd = handles.open('target', target.parent)118        if _linux_rename()(source_fd, os.fsencode(source.name), target_fd, os.fsencode(target.name), 1):119            code = ctypes.get_errno()120            error = OSError(code, os.strerror(code), str(target))121            if code in (errno.ENOSYS, errno.EOPNOTSUPP):122                raise NotImplementedError('当前Linux内核或文件系统不支持不覆盖发布') from error123            if code == errno.EINVAL:124                # EINVAL也可能来自无效路径组合,不能一律说文件系统不支持,更不能覆盖重试。125                raise OSError(code, '不覆盖发布被拒绝:可能是文件系统不支持RENAME_NOREPLACE,'126                              '也可能是源/目标路径组合或调用参数无效;请检查路径和文件系统能力',127                              str(target)) from error128            raise error129        # fd仍指向原目录;路径若已被替换,拒绝把新路径当作本次结果归属。130        for path, descriptor in ((source.parent, source_fd), (target.parent, target_fd)):131            actual = os.fstat(descriptor)132            current = path.stat(follow_symlinks=False)133            if (actual.st_dev, actual.st_ino) != (current.st_dev, current.st_ino):134                raise OSError('发布期间父目录被替换,保留未知对象')135    except BaseException as error:136        _cleanup_owner(handles, error, 'publication_move_cleanup_owner', '发布目录句柄')137        raise138    else:139        handles.close()140
Chronaxis 用户手册 · 离线阅读