move a file in the safest way possible from django core files move import filemovesafe filemovesafetmpoldfile tmpnewfile macintosh unix all other platforms check for same pathname move a file from one location to another in the safest way possible first try os rename which is simple but will break across filesystems if that fails stream manually from one file to another in pure python if the destination file exists and allowoverwrite is false raise fileexistserror there s no reason to move if we don t have to oserror happens with os rename if moving to another filesystem or when moving opened files on certain operating systems first open the old file so that it won t go away now open the new file not forgetting allowoverwrite certain filesystems e g cifs fail to copy the file s metadata if the type of the destination filesystem isn t the same as the source filesystem this also happens with some selinuxenabled systems ignore that but try to set basic permissions certain operating systems cygwin and windows fail when deleting opened files ignore it for the systems where this happens temporary files will be autodeleted on close anyway macintosh unix all other platforms check for same pathname move a file from one location to another in the safest way possible first try os rename which is simple but will break across filesystems if that fails stream manually from one file to another in pure python if the destination file exists and allow_overwrite is false raise fileexistserror there s no reason to move if we don t have to oserror happens with os rename if moving to another filesystem or when moving opened files on certain operating systems first open the old file so that it won t go away now open the new file not forgetting allow_overwrite certain filesystems e g cifs fail to copy the file s metadata if the type of the destination filesystem isn t the same as the source filesystem this also happens with some selinux enabled systems ignore that but try to set basic permissions certain operating systems cygwin and windows fail when deleting opened files ignore it for the systems where this happens temporary files will be auto deleted on close anyway
import os
from shutil import copymode, copystat

from django.core.files import locks

__all__ = ["file_move_safe"]


def _samefile(src, dst):
    
    if hasattr(os.path, "samefile"):
        try:
            return os.path.samefile(src, dst)
        except OSError:
            return False

    
    return os.path.normcase(os.path.abspath(src)) == os.path.normcase(
        os.path.abspath(dst)
    )


def file_move_safe(
    old_file_name, new_file_name, chunk_size=1024 * 64, allow_overwrite=False
):
    
    
    if _samefile(old_file_name, new_file_name):
        return

    try:
        if not allow_overwrite and os.access(new_file_name, os.F_OK):
            raise FileExistsError(
                "Destination file %s exists and allow_overwrite is False."
                % new_file_name
            )

        os.rename(old_file_name, new_file_name)
        return
    except OSError:
        
        
        pass

    
    with open(old_file_name, "rb") as old_file:
        
        fd = os.open(
            new_file_name,
            (
                os.O_WRONLY
                | os.O_CREAT
                | getattr(os, "O_BINARY", 0)
                | (os.O_EXCL if not allow_overwrite else 0)
            ),
        )
        try:
            locks.lock(fd, locks.LOCK_EX)
            current_chunk = None
            while current_chunk != b"":
                current_chunk = old_file.read(chunk_size)
                os.write(fd, current_chunk)
        finally:
            locks.unlock(fd)
            os.close(fd)

    try:
        copystat(old_file_name, new_file_name)
    except PermissionError:
        
        
        
        
        try:
            copymode(old_file_name, new_file_name)
        except PermissionError:
            pass

    try:
        os.remove(old_file_name)
    except PermissionError as e:
        
        
        
        
        if getattr(e, "winerror", 0) != 32:
            raise