def split_and_strip_files(d):
    import stat, errno

    dvar = d.getVar('PKGD')
    pn = d.getVar('PN')
    targetos = d.getVar('TARGET_OS')

    oldcwd = os.getcwd()
    os.chdir(dvar)

    # We default to '.debug' style
    if d.getVar('PACKAGE_DEBUG_SPLIT_STYLE') == 'debug-file-directory':
        # Single debug-file-directory style debug info
        debugappend = ".debug"
        debugdir = ""
        debuglibdir = "/usr/lib/debug"
        debugsrcdir = "/usr/src/debug"
    elif d.getVar('PACKAGE_DEBUG_SPLIT_STYLE') == 'debug-without-src':
        # Original OE-core, a.k.a. ".debug", style debug info, but without sources in /usr/src/debug
        debugappend = ""
        debugdir = "/.debug"
        debuglibdir = ""
        debugsrcdir = ""
    elif d.getVar('PACKAGE_DEBUG_SPLIT_STYLE') == 'debug-with-srcpkg':
        debugappend = ""
        debugdir = "/.debug"
        debuglibdir = ""
        debugsrcdir = "/usr/src/debug"
    else:
        # Original OE-core, a.k.a. ".debug", style debug info
        debugappend = ""
        debugdir = "/.debug"
        debuglibdir = ""
        debugsrcdir = "/usr/src/debug"

    sourcefile = d.expand("${WORKDIR}/debugsources.list")
    bb.utils.remove(sourcefile)

    # Return type (bits):
    # 0 - not elf
    # 1 - ELF
    # 2 - stripped
    # 4 - executable
    # 8 - shared library
    # 16 - kernel module
    def isELF(path):
        type = 0
        ret, result = oe.utils.getstatusoutput("file -b '%s'" % path)

        if ret:
            msg = "split_and_strip_files: 'file %s' failed" % path
            package_qa_handle_error("split-strip", msg, d)
            return type

        # Not stripped
        if "ELF" in result:
            type |= 1
            if "not stripped" not in result:
                type |= 2
            if "executable" in result:
                type |= 4
            if "shared" in result:
                type |= 8
        return type

    def isStaticLib(path):
        if path.endswith('.a') and not os.path.islink(path):
            with open(path, 'rb') as fh:
                # The magic must include the first slash to avoid
                # matching golang static libraries
                magic = b'!<arch>\x0a/'
                start = fh.read(len(magic))
                return start == magic
        return False

    #
    # First lets figure out all of the files we may have to process ... do this only once!
    #
    elffiles = {}
    symlinks = {}
    kernmods = []
    staticlibs = []
    inodes = {}
    libdir = os.path.abspath(dvar + os.sep + d.getVar("libdir"))
    baselibdir = os.path.abspath(dvar + os.sep + d.getVar("base_libdir"))
    skipfiles = (d.getVar("INHIBIT_PACKAGE_STRIP_FILES") or "").split()
    if (d.getVar('INHIBIT_PACKAGE_STRIP') != '1' or \
            d.getVar('INHIBIT_PACKAGE_DEBUG_SPLIT') != '1'):
        for root, dirs, files in cpath.walk(dvar):
            for f in files:
                file = os.path.join(root, f)
                if file.endswith(".ko") and file.find("/lib/modules/") != -1:
                    kernmods.append(file)
                    continue
                if isStaticLib(file):
                    staticlibs.append(file)
                    continue

                # Skip debug files
                if debugappend and file.endswith(debugappend):
                    continue
                if debugdir and debugdir in os.path.dirname(file[len(dvar):]):
                    continue

                if file in skipfiles:
                    continue

                try:
                    ltarget = cpath.realpath(file, dvar, False)
                    s = cpath.lstat(ltarget)
                except OSError as e:
                    (err, strerror) = e.args
                    if err != errno.ENOENT:
                        raise
                    # Skip broken symlinks
                    continue
                if not s:
                    continue
                # Check its an executable
                if (s[stat.ST_MODE] & stat.S_IXUSR) or (s[stat.ST_MODE] & stat.S_IXGRP) or (s[stat.ST_MODE] & stat.S_IXOTH) \
                        or ((file.startswith(libdir) or file.startswith(baselibdir)) and (".so" in f or ".node" in f)):
                    # If it's a symlink, and points to an ELF file, we capture the readlink target
                    if cpath.islink(file):
                        target = os.readlink(file)
                        if isELF(ltarget):
                            #bb.note("Sym: %s (%d)" % (ltarget, isELF(ltarget)))
                            symlinks[file] = target
                        continue

                    # It's a file (or hardlink), not a link
                    # ...but is it ELF, and is it already stripped?
                    elf_file = isELF(file)
                    if elf_file & 1:
                        if elf_file & 2:
                            if 'already-stripped' in (d.getVar('INSANE_SKIP_' + pn) or "").split():
                                bb.note("Skipping file %s from %s for already-stripped QA test" % (file[len(dvar):], pn))
                            else:
                                msg = "File '%s' from %s was already stripped, this will prevent future debugging!" % (file[len(dvar):], pn)
                                package_qa_handle_error("already-stripped", msg, d)
                            continue

                        # At this point we have an unstripped elf file. We need to:
                        #  a) Make sure any file we strip is not hardlinked to anything else outside this tree
                        #  b) Only strip any hardlinked file once (no races)
                        #  c) Track any hardlinks between files so that we can reconstruct matching debug file hardlinks

                        # Use a reference of device ID and inode number to identify files
                        file_reference = "%d_%d" % (s.st_dev, s.st_ino)
                        if file_reference in inodes:
                            os.unlink(file)
                            os.link(inodes[file_reference][0], file)
                            inodes[file_reference].append(file)
                        else:
                            inodes[file_reference] = [file]
                            # break hardlink
                            bb.utils.copyfile(file, file)
                            elffiles[file] = elf_file
                        # Modified the file so clear the cache
                        cpath.updatecache(file)

    #
    # First lets process debug splitting
    #
    if (d.getVar('INHIBIT_PACKAGE_DEBUG_SPLIT') != '1'):
        for file in elffiles:
            src = file[len(dvar):]
            dest = debuglibdir + os.path.dirname(src) + debugdir + "/" + os.path.basename(src) + debugappend
            fpath = dvar + dest

            # Split the file...
            bb.utils.mkdirhier(os.path.dirname(fpath))
            #bb.note("Split %s -> %s" % (file, fpath))
            # Only store off the hard link reference if we successfully split!
            splitdebuginfo(file, fpath, debugsrcdir, sourcefile, d)

        if debugsrcdir and not targetos.startswith("mingw"):
            for file in staticlibs:
                append_source_info(file, sourcefile, d, fatal=False)

        # Hardlink our debug symbols to the other hardlink copies
        for ref in inodes:
            if len(inodes[ref]) == 1:
                continue
            for file in inodes[ref][1:]:
                src = file[len(dvar):]
                dest = debuglibdir + os.path.dirname(src) + debugdir + "/" + os.path.basename(src) + debugappend
                fpath = dvar + dest
                target = inodes[ref][0][len(dvar):]
                ftarget = dvar + debuglibdir + os.path.dirname(target) + debugdir + "/" + os.path.basename(target) + debugappend
                bb.utils.mkdirhier(os.path.dirname(fpath))
                #bb.note("Link %s -> %s" % (fpath, ftarget))
                os.link(ftarget, fpath)

        # Create symlinks for all cases we were able to split symbols
        for file in symlinks:
            src = file[len(dvar):]
            dest = debuglibdir + os.path.dirname(src) + debugdir + "/" + os.path.basename(src) + debugappend
            fpath = dvar + dest
            # Skip it if the target doesn't exist
            try:
                s = os.stat(fpath)
            except OSError as e:
                (err, strerror) = e.args
                if err != errno.ENOENT:
                    raise
                continue

            ltarget = symlinks[file]
            lpath = os.path.dirname(ltarget)
            lbase = os.path.basename(ltarget)
            ftarget = ""
            if lpath and lpath != ".":
                ftarget += lpath + debugdir + "/"
            ftarget += lbase + debugappend
            if lpath.startswith(".."):
                ftarget = os.path.join("..", ftarget)
            bb.utils.mkdirhier(os.path.dirname(fpath))
            #bb.note("Symlink %s -> %s" % (fpath, ftarget))
            os.symlink(ftarget, fpath)

        # Process the debugsrcdir if requested...
        # This copies and places the referenced sources for later debugging...
        copydebugsources(debugsrcdir, d)
    #
    # End of debug splitting
    #

    #
    # Now lets go back over things and strip them
    #
    if (d.getVar('INHIBIT_PACKAGE_STRIP') != '1'):
        strip = d.getVar("STRIP")
        sfiles = []
        for file in elffiles:
            elf_file = int(elffiles[file])
            #bb.note("Strip %s" % file)
            sfiles.append((file, elf_file, strip))
        for f in kernmods:
            sfiles.append((f, 16, strip))

        oe.utils.multiprocess_exec(sfiles, oe.package.runstrip)

    #
    # End of strip
    #
    os.chdir(oldcwd)

split_and_strip_files(d)

def package_qa_handle_error(error_class, error_msg, d):
    if error_class in (d.getVar("ERROR_QA") or "").split():
        package_qa_write_error(error_class, error_msg, d)
        bb.error("QA Issue: %s [%s]" % (error_msg, error_class))
        d.setVar("QA_SANE", False)
        return False
    elif error_class in (d.getVar("WARN_QA") or "").split():
        package_qa_write_error(error_class, error_msg, d)
        bb.warn("QA Issue: %s [%s]" % (error_msg, error_class))
    else:
        bb.note("QA Issue: %s [%s]" % (error_msg, error_class))
    return True

def splitdebuginfo(file, debugfile, debugsrcdir, sourcefile, d):
    # Function to split a single file into two components, one is the stripped
    # target system binary, the other contains any debugging information. The
    # two files are linked to reference each other.
    #
    # sourcefile is also generated containing a list of debugsources

    import stat

    dvar = d.getVar('PKGD')
    objcopy = d.getVar("OBJCOPY")

    # We ignore kernel modules, we don't generate debug info files.
    if file.find("/lib/modules/") != -1 and file.endswith(".ko"):
        return 1

    newmode = None
    if not os.access(file, os.W_OK) or os.access(file, os.R_OK):
        origmode = os.stat(file)[stat.ST_MODE]
        newmode = origmode | stat.S_IWRITE | stat.S_IREAD
        os.chmod(file, newmode)

    # We need to extract the debug src information here...
    if debugsrcdir:
        append_source_info(file, sourcefile, d)

    bb.utils.mkdirhier(os.path.dirname(debugfile))

    cmd = "'%s' --only-keep-debug '%s' '%s'" % (objcopy, file, debugfile)
    (retval, output) = oe.utils.getstatusoutput(cmd)
    if retval:
        bb.fatal("objcopy failed with exit code %s (cmd was %s)%s" % (retval, cmd, ":\n%s" % output if output else ""))

    # Set the debuglink to have the view of the file path on the target
    cmd = "'%s' --add-gnu-debuglink='%s' '%s'" % (objcopy, debugfile, file)
    (retval, output) = oe.utils.getstatusoutput(cmd)
    if retval:
        bb.fatal("objcopy failed with exit code %s (cmd was %s)%s" % (retval, cmd, ":\n%s" % output if output else ""))

    if newmode:
        os.chmod(file, origmode)

    return 0

def copydebugsources(debugsrcdir, d):
    # The debug src information written out to sourcefile is further processed
    # and copied to the destination here.

    import stat

    sourcefile = d.expand("${WORKDIR}/debugsources.list")
    if debugsrcdir and os.path.isfile(sourcefile):
        dvar = d.getVar('PKGD')
        strip = d.getVar("STRIP")
        objcopy = d.getVar("OBJCOPY")
        workdir = d.getVar("WORKDIR")
        workparentdir = os.path.dirname(os.path.dirname(workdir))
        workbasedir = os.path.basename(os.path.dirname(workdir)) + "/" + os.path.basename(workdir)

        # If build path exists in sourcefile, it means toolchain did not use
        # -fdebug-prefix-map to compile
        if checkbuildpath(sourcefile, d):
            localsrc_prefix = workparentdir + "/"
        else:
            localsrc_prefix = "/usr/src/debug/"

        nosuchdir = []
        basepath = dvar
        for p in debugsrcdir.split("/"):
            basepath = basepath + "/" + p
            if not cpath.exists(basepath):
                nosuchdir.append(basepath)
        bb.utils.mkdirhier(basepath)
        cpath.updatecache(basepath)

        # Ignore files from the recipe sysroots (target and native)
        processdebugsrc =  "LC_ALL=C ; sort -z -u '%s' | egrep -v -z '((<internal>|<built-in>)$|/.*recipe-sysroot.*/)' | "
        # We need to ignore files that are not actually ours
        # we do this by only paying attention to items from this package
        processdebugsrc += "fgrep -zw '%s' | "
        # Remove prefix in the source paths
        processdebugsrc += "sed 's#%s##g' | "
        processdebugsrc += "(cd '%s' ; cpio -pd0mlL --no-preserve-owner '%s%s' 2>/dev/null)"

        cmd = processdebugsrc % (sourcefile, workbasedir, localsrc_prefix, workparentdir, dvar, debugsrcdir)
        (retval, output) = oe.utils.getstatusoutput(cmd)
        # Can "fail" if internal headers/transient sources are attempted
        #if retval:
        #    bb.fatal("debug source copy failed with exit code %s (cmd was %s)" % (retval, cmd))

        # cpio seems to have a bug with -lL together and symbolic links are just copied, not dereferenced.
        # Work around this by manually finding and copying any symbolic links that made it through.
        cmd = "find %s%s -type l -print0 -delete | sed s#%s%s/##g | (cd '%s' ; cpio -pd0mL --no-preserve-owner '%s%s' 2>/dev/null)" % (dvar, debugsrcdir, dvar, debugsrcdir, workparentdir, dvar, debugsrcdir)
        (retval, output) = oe.utils.getstatusoutput(cmd)
        if retval:
            bb.fatal("debugsrc symlink fixup failed with exit code %s (cmd was %s)" % (retval, cmd))

        # The copy by cpio may have resulted in some empty directories!  Remove these
        cmd = "find %s%s -empty -type d -delete" % (dvar, debugsrcdir)
        (retval, output) = oe.utils.getstatusoutput(cmd)
        if retval:
            bb.fatal("empty directory removal failed with exit code %s (cmd was %s)%s" % (retval, cmd, ":\n%s" % output if output else ""))

        # Also remove debugsrcdir if its empty
        for p in nosuchdir[::-1]:
            if os.path.exists(p) and not os.listdir(p):
                os.rmdir(p)

def append_source_info(file, sourcefile, d, fatal=True):
    cmd = "'dwarfsrcfiles' '%s'" % (file)
    (retval, output) = oe.utils.getstatusoutput(cmd)
    # 255 means a specific file wasn't fully parsed to get the debug file list, which is not a fatal failure
    if retval != 0 and retval != 255:
        msg = "dwarfsrcfiles failed with exit code %s (cmd was %s)%s" % (retval, cmd, ":\n%s" % output if output else "")
        if fatal:
            bb.fatal(msg)
        bb.note(msg)

    debugsources = parse_debugsources_from_dwarfsrcfiles_output(output)
    # filenames are null-separated - this is an artefact of the previous use
    # of rpm's debugedit, which was writing them out that way, and the code elsewhere
    # is still assuming that.
    debuglistoutput = '\0'.join(debugsources) + '\0'
    open(sourcefile, 'a').write(debuglistoutput)

def checkbuildpath(file, d):
    tmpdir = d.getVar('TMPDIR')
    with open(file) as f:
        file_content = f.read()
        if tmpdir in file_content:
            return True

    return False

def package_qa_write_error(type, error, d):
    logfile = d.getVar('QA_LOGFILE')
    if logfile:
        p = d.getVar('P')
        with open(logfile, "a+") as f:
            f.write("%s: %s [%s]\n" % (p, error, type))

def parse_debugsources_from_dwarfsrcfiles_output(dwarfsrcfiles_output):
    debugfiles = {}

    for line in dwarfsrcfiles_output.splitlines():
        if line.startswith("\t"):
            debugfiles[os.path.normpath(line.split()[0])] = ""

    return debugfiles.keys()

