You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							183 lines
						
					
					
						
							5.8 KiB
						
					
					
				
			
		
		
	
	
							183 lines
						
					
					
						
							5.8 KiB
						
					
					
				import fnmatch
 | 
						|
import os
 | 
						|
import os.path
 | 
						|
import random
 | 
						|
import shutil
 | 
						|
import stat
 | 
						|
import sys
 | 
						|
from contextlib import contextmanager
 | 
						|
from tempfile import NamedTemporaryFile
 | 
						|
from typing import Any, BinaryIO, Iterator, List, Union, cast
 | 
						|
 | 
						|
from pip._vendor.tenacity import retry, stop_after_delay, wait_fixed
 | 
						|
 | 
						|
from pip._internal.utils.compat import get_path_uid
 | 
						|
from pip._internal.utils.misc import format_size
 | 
						|
 | 
						|
 | 
						|
def check_path_owner(path: str) -> bool:
 | 
						|
    # If we don't have a way to check the effective uid of this process, then
 | 
						|
    # we'll just assume that we own the directory.
 | 
						|
    if sys.platform == "win32" or not hasattr(os, "geteuid"):
 | 
						|
        return True
 | 
						|
 | 
						|
    assert os.path.isabs(path)
 | 
						|
 | 
						|
    previous = None
 | 
						|
    while path != previous:
 | 
						|
        if os.path.lexists(path):
 | 
						|
            # Check if path is writable by current user.
 | 
						|
            if os.geteuid() == 0:
 | 
						|
                # Special handling for root user in order to handle properly
 | 
						|
                # cases where users use sudo without -H flag.
 | 
						|
                try:
 | 
						|
                    path_uid = get_path_uid(path)
 | 
						|
                except OSError:
 | 
						|
                    return False
 | 
						|
                return path_uid == 0
 | 
						|
            else:
 | 
						|
                return os.access(path, os.W_OK)
 | 
						|
        else:
 | 
						|
            previous, path = path, os.path.dirname(path)
 | 
						|
    return False  # assume we don't own the path
 | 
						|
 | 
						|
 | 
						|
def copy2_fixed(src: str, dest: str) -> None:
 | 
						|
    """Wrap shutil.copy2() but map errors copying socket files to
 | 
						|
    SpecialFileError as expected.
 | 
						|
 | 
						|
    See also https://bugs.python.org/issue37700.
 | 
						|
    """
 | 
						|
    try:
 | 
						|
        shutil.copy2(src, dest)
 | 
						|
    except OSError:
 | 
						|
        for f in [src, dest]:
 | 
						|
            try:
 | 
						|
                is_socket_file = is_socket(f)
 | 
						|
            except OSError:
 | 
						|
                # An error has already occurred. Another error here is not
 | 
						|
                # a problem and we can ignore it.
 | 
						|
                pass
 | 
						|
            else:
 | 
						|
                if is_socket_file:
 | 
						|
                    raise shutil.SpecialFileError(f"`{f}` is a socket")
 | 
						|
 | 
						|
        raise
 | 
						|
 | 
						|
 | 
						|
def is_socket(path: str) -> bool:
 | 
						|
    return stat.S_ISSOCK(os.lstat(path).st_mode)
 | 
						|
 | 
						|
 | 
						|
@contextmanager
 | 
						|
def adjacent_tmp_file(path: str, **kwargs: Any) -> Iterator[BinaryIO]:
 | 
						|
    """Return a file-like object pointing to a tmp file next to path.
 | 
						|
 | 
						|
    The file is created securely and is ensured to be written to disk
 | 
						|
    after the context reaches its end.
 | 
						|
 | 
						|
    kwargs will be passed to tempfile.NamedTemporaryFile to control
 | 
						|
    the way the temporary file will be opened.
 | 
						|
    """
 | 
						|
    with NamedTemporaryFile(
 | 
						|
        delete=False,
 | 
						|
        dir=os.path.dirname(path),
 | 
						|
        prefix=os.path.basename(path),
 | 
						|
        suffix=".tmp",
 | 
						|
        **kwargs,
 | 
						|
    ) as f:
 | 
						|
        result = cast(BinaryIO, f)
 | 
						|
        try:
 | 
						|
            yield result
 | 
						|
        finally:
 | 
						|
            result.flush()
 | 
						|
            os.fsync(result.fileno())
 | 
						|
 | 
						|
 | 
						|
# Tenacity raises RetryError by default, explicitly raise the original exception
 | 
						|
_replace_retry = retry(reraise=True, stop=stop_after_delay(1), wait=wait_fixed(0.25))
 | 
						|
 | 
						|
replace = _replace_retry(os.replace)
 | 
						|
 | 
						|
 | 
						|
# test_writable_dir and _test_writable_dir_win are copied from Flit,
 | 
						|
# with the author's agreement to also place them under pip's license.
 | 
						|
def test_writable_dir(path: str) -> bool:
 | 
						|
    """Check if a directory is writable.
 | 
						|
 | 
						|
    Uses os.access() on POSIX, tries creating files on Windows.
 | 
						|
    """
 | 
						|
    # If the directory doesn't exist, find the closest parent that does.
 | 
						|
    while not os.path.isdir(path):
 | 
						|
        parent = os.path.dirname(path)
 | 
						|
        if parent == path:
 | 
						|
            break  # Should never get here, but infinite loops are bad
 | 
						|
        path = parent
 | 
						|
 | 
						|
    if os.name == "posix":
 | 
						|
        return os.access(path, os.W_OK)
 | 
						|
 | 
						|
    return _test_writable_dir_win(path)
 | 
						|
 | 
						|
 | 
						|
def _test_writable_dir_win(path: str) -> bool:
 | 
						|
    # os.access doesn't work on Windows: http://bugs.python.org/issue2528
 | 
						|
    # and we can't use tempfile: http://bugs.python.org/issue22107
 | 
						|
    basename = "accesstest_deleteme_fishfingers_custard_"
 | 
						|
    alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
 | 
						|
    for _ in range(10):
 | 
						|
        name = basename + "".join(random.choice(alphabet) for _ in range(6))
 | 
						|
        file = os.path.join(path, name)
 | 
						|
        try:
 | 
						|
            fd = os.open(file, os.O_RDWR | os.O_CREAT | os.O_EXCL)
 | 
						|
        except FileExistsError:
 | 
						|
            pass
 | 
						|
        except PermissionError:
 | 
						|
            # This could be because there's a directory with the same name.
 | 
						|
            # But it's highly unlikely there's a directory called that,
 | 
						|
            # so we'll assume it's because the parent dir is not writable.
 | 
						|
            # This could as well be because the parent dir is not readable,
 | 
						|
            # due to non-privileged user access.
 | 
						|
            return False
 | 
						|
        else:
 | 
						|
            os.close(fd)
 | 
						|
            os.unlink(file)
 | 
						|
            return True
 | 
						|
 | 
						|
    # This should never be reached
 | 
						|
    raise OSError("Unexpected condition testing for writable directory")
 | 
						|
 | 
						|
 | 
						|
def find_files(path: str, pattern: str) -> List[str]:
 | 
						|
    """Returns a list of absolute paths of files beneath path, recursively,
 | 
						|
    with filenames which match the UNIX-style shell glob pattern."""
 | 
						|
    result: List[str] = []
 | 
						|
    for root, _, files in os.walk(path):
 | 
						|
        matches = fnmatch.filter(files, pattern)
 | 
						|
        result.extend(os.path.join(root, f) for f in matches)
 | 
						|
    return result
 | 
						|
 | 
						|
 | 
						|
def file_size(path: str) -> Union[int, float]:
 | 
						|
    # If it's a symlink, return 0.
 | 
						|
    if os.path.islink(path):
 | 
						|
        return 0
 | 
						|
    return os.path.getsize(path)
 | 
						|
 | 
						|
 | 
						|
def format_file_size(path: str) -> str:
 | 
						|
    return format_size(file_size(path))
 | 
						|
 | 
						|
 | 
						|
def directory_size(path: str) -> Union[int, float]:
 | 
						|
    size = 0.0
 | 
						|
    for root, _dirs, files in os.walk(path):
 | 
						|
        for filename in files:
 | 
						|
            file_path = os.path.join(root, filename)
 | 
						|
            size += file_size(file_path)
 | 
						|
    return size
 | 
						|
 | 
						|
 | 
						|
def format_directory_size(path: str) -> str:
 | 
						|
    return format_size(directory_size(path))
 |