import errno
import hashlib
import logging
import os
import stat

logger = logging.getLogger(__name__)


def _hash_and_size_from_fd(fd, hash_func, chunksize=4096):
    """Read an open file descriptor in chunks; return (hexdigest, size)."""
    hash_ = hash_func()
    size = 0
    while True:
        chunk = os.read(fd, chunksize)
        if not chunk:
            break
        hash_.update(chunk)
        size += len(chunk)
    return hash_.hexdigest(), size


def migrate(migrator, database, fake=False, **kwargs):
    MalwareHit = migrator.orm["malware_hits"]
    try:
        for hit in MalwareHit.select():
            path = hit.orig_file.encode("utf-8", errors="surrogateescape")
            # Open with O_NOFOLLOW so a symlinked malware path cannot redirect
            # the subsequent fchown/read to an attacker-chosen target file.
            # O_NONBLOCK guards against accidentally hanging on a FIFO that
            # an attacker may have substituted for the recorded file.
            try:
                fd = os.open(
                    path,
                    os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK,
                )
            except FileNotFoundError:
                logger.warning(
                    "Malware file %s does not exist, skipping", path
                )
                continue
            except OSError as e:
                if e.errno == errno.ELOOP:
                    logger.warning(
                        "Malware file %s is a symlink, skipping", path
                    )
                    continue
                raise
            try:
                st = os.fstat(fd)
                if not stat.S_ISREG(st.st_mode):
                    logger.warning(
                        "Malware file %s is not a regular file, skipping",
                        path,
                    )
                    continue

                if hit.mode is not None and not hit.restored:
                    os.fchown(fd, 0, 0)
                    hit.uid, hit.gid = st.st_uid, st.st_gid

                hit.hash, hit.size = _hash_and_size_from_fd(fd, hashlib.sha256)
            finally:
                os.close(fd)
            hit.save()
    except Exception as e:
        logger.exception(e)


def rollback(migrator, database, fake=False, **kwargs):
    pass
