import os
import shutil
import subprocess
from distutils.errors import LibError
from distutils.core import setup
from distutils.command.build import build as _build
from setuptools.command.develop import develop as _develop

AFL_UNIX_INSTALL_PATH = os.path.join("bin", "afl-unix")
AFL_UNIX_PATCH_FILE = os.path.join("patches", "afl-patch.diff")
BUILD_QEMU_PATCH_FILE = os.path.join("patches", "build_qemu.diff")
AFL_UNIX_GEN = os.path.join(os.curdir, "patches", "build.sh")
AFL_CGC_INSTALL_PATH = os.path.join("bin", "afl-cgc")
AFL_MULTI_CGC_INSTALL_PATH = os.path.join("bin", "afl-multi-cgc")
SUPPORTED_ARCHES = ["aarch64", "x86_64", "i386", "arm", "ppc", "ppc64", "mips", "mipsel", "mips64"]
QEMU_PATCH = "patches/memfd.diff"
MULTIARCH_LIBRARY_PATH = os.path.join("bin", "fuzzer-libs")
AFL_QEMU_MODE_PATCH = AFL_UNIX_INSTALL_PATH+"/qemu_mode/patches/"
AFL_UNIX_FUZZ = os.path.join(AFL_UNIX_INSTALL_PATH)
AFL_CGC_FUZZ  = os.path.join(AFL_CGC_INSTALL_PATH)
AFL_MULTI_CGC_FUZZ  = os.path.join(AFL_MULTI_CGC_INSTALL_PATH)


def _setup_other_arch():
    # revisiting the afl mirrorer repo
    if not os.path.exists(AFL_UNIX_INSTALL_PATH):
        AFL_UNIX_REPO = "https://github.com/mirrorer/afl"
        if subprocess.call(['git', 'clone','--depth=1', AFL_UNIX_REPO, AFL_UNIX_INSTALL_PATH]) != 0:
            raise LibError("Unable to retrieve afl-unix")

        with open(BUILD_QEMU_PATCH_FILE, "rb") as f:
            if subprocess.check_call(['patch', '-p0'],stdin=f, cwd=AFL_UNIX_INSTALL_PATH) != 0:
                raise LibError("Unable to apply patches to qemu build")

        if subprocess.call(['cp',AFL_UNIX_GEN, AFL_UNIX_INSTALL_PATH]) != 0:
            raise LibError("Build file doesn't exist")

        # patch for qemu to work with ubuntu 18.04 and above
        if subprocess.check_call(['cp',QEMU_PATCH,AFL_QEMU_MODE_PATCH]) != 0:
            raise LibError('Patch to work Qemu with Ubuntu 18 not found')

        if subprocess.check_call(['./build.sh'] + SUPPORTED_ARCHES, cwd=AFL_UNIX_INSTALL_PATH) != 0:
            raise LibError("Unable to build afl-other-arch")

def _setup_cgc():

    if not os.path.exists(AFL_CGC_INSTALL_PATH):
        AFL_CGC_REPO = "https://github.com/shellphish/driller-afl.git"
        if subprocess.call(['git', 'clone', AFL_CGC_REPO, AFL_CGC_INSTALL_PATH]) != 0:
            raise LibError("Unable to retrieve afl-cgc")

        if subprocess.call(['make', '-j'], cwd=AFL_CGC_INSTALL_PATH) != 0:
            raise LibError("Unable to make afl-cgc")

        if subprocess.call(['./build_qemu_support.sh'], cwd=os.path.join(AFL_CGC_INSTALL_PATH, "qemu_mode")) != 0:
            raise LibError("Unable to build afl-cgc-qemu")

    if not os.path.exists(AFL_MULTI_CGC_INSTALL_PATH):
        AFL_MULTI_CGC_REPO = "https://github.com/mechaphish/multiafl.git"
        if subprocess.call(['git', 'clone', AFL_MULTI_CGC_REPO, AFL_MULTI_CGC_INSTALL_PATH]) != 0:
            raise LibError("Unable to retrieve afl-multi-cgc")

        if subprocess.call(['make', '-j'], cwd=AFL_MULTI_CGC_INSTALL_PATH) != 0:
            raise LibError("Unable to make afl-multi-cgc")

def _setup_libs():
    if not os.path.exists(MULTIARCH_LIBRARY_PATH):
        if subprocess.call(["./fetchlibs.sh"], cwd=".") != 0:
            raise LibError("Unable to fetch libraries")

data_files = [ ]
def _datafiles():
    # for each lib export it into data_files
    for path,_,files in os.walk("bin/fuzzer-libs"):
        libs = [ os.path.join(path, f) for f in files if '.so' in f ]
        if libs:
            data_files.append((path, libs))

    # grab all the executables from afl
    for s in ('afl-multi-cgc', 'afl-cgc', 'afl-unix'):
        for path,_,files in os.walk(os.path.join("bin", s)):
            if 'qemu-2.' in path:
                continue
            paths = [ os.path.join(path, f) for f in files ]
            exes = [ f for f in paths if os.path.isfile(f) and os.access(f, os.X_OK) ]
            if exes:
                data_files.append((path, exes))

    return data_files

def get_patches():
    # get all patches
    for path,_,files in os.walk("patches"):
        patches = [os.path.join(path, f) for f in files]
        if patches:
            data_files.append((path, patches))

    return data_files

class build(_build):
    def run(self):
        self.execute(_setup_other_arch, (), msg="Setting up AFL-other-arch")
        self.execute(_setup_cgc, (), msg="Setting up AFL-cgc")
        self.execute(_setup_libs, (), msg="Getting libraries")
        _datafiles()
        _build.run(self)

class develop(_develop):
    def run(self):
        self.execute(_setup_other_arch, (), msg="Setting up AFL-other-arch")
        self.execute(_setup_cgc, (), msg="Setting up AFL-cgc")
        self.execute(_setup_libs, (), msg="Getting libraries")
        _datafiles()
        _develop.run(self)

get_patches()

setup(
    name='shellphish-afl', version='1.2.2', description="pip package for afl",
    packages=['shellphish_afl'],
    cmdclass={'build': build, 'develop': develop},
    data_files=data_files,
    scripts=['fetchlibs.sh'],
)