1# This Source Code Form is subject to the terms of the Mozilla Public
2# License, v. 2.0. If a copy of the MPL was not distributed with this
3# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5from __future__ import absolute_import, print_function, unicode_literals
6
7import os
8import struct
9import subprocess
10from io import BytesIO
11from mozpack.errors import errors
12
13MACHO_SIGNATURES = [
14    0xFEEDFACE,  # mach-o 32-bits big endian
15    0xCEFAEDFE,  # mach-o 32-bits little endian
16    0xFEEDFACF,  # mach-o 64-bits big endian
17    0xCFFAEDFE,  # mach-o 64-bits little endian
18]
19
20FAT_SIGNATURE = 0xCAFEBABE  # mach-o FAT binary
21
22ELF_SIGNATURE = 0x7F454C46  # Elf binary
23
24UNKNOWN = 0
25MACHO = 1
26ELF = 2
27
28
29def get_type(path_or_fileobj):
30    """
31    Check the signature of the give file and returns what kind of executable
32    matches.
33    """
34    if hasattr(path_or_fileobj, "peek"):
35        f = BytesIO(path_or_fileobj.peek(8))
36    elif hasattr(path_or_fileobj, "read"):
37        f = path_or_fileobj
38    else:
39        f = open(path_or_fileobj, "rb")
40    signature = f.read(4)
41    if len(signature) < 4:
42        return UNKNOWN
43    signature = struct.unpack(">L", signature)[0]
44    if signature == ELF_SIGNATURE:
45        return ELF
46    if signature in MACHO_SIGNATURES:
47        return MACHO
48    if signature != FAT_SIGNATURE:
49        return UNKNOWN
50    # We have to sanity check the second four bytes, because Java class
51    # files use the same magic number as Mach-O fat binaries.
52    # This logic is adapted from file(1), which says that Mach-O uses
53    # these bytes to count the number of architectures within, while
54    # Java uses it for a version number. Conveniently, there are only
55    # 18 labelled Mach-O architectures, and Java's first released
56    # class format used the version 43.0.
57    num = f.read(4)
58    if len(num) < 4:
59        return UNKNOWN
60    num = struct.unpack(">L", num)[0]
61    if num < 20:
62        return MACHO
63    return UNKNOWN
64
65
66def is_executable(path):
67    """
68    Return whether a given file path points to an executable or a library,
69    where an executable or library is identified by:
70        - the file extension on OS/2 and WINNT
71        - the file signature on OS/X and ELF systems (GNU/Linux, Android, BSD,
72          Solaris)
73
74    As this function is intended for use to choose between the ExecutableFile
75    and File classes in FileFinder, and choosing ExecutableFile only matters
76    on OS/2, OS/X, ELF and WINNT (in GCC build) systems, we don't bother
77    detecting other kind of executables.
78    """
79    from buildconfig import substs
80
81    if not os.path.exists(path):
82        return False
83
84    if substs["OS_ARCH"] == "WINNT":
85        return path.lower().endswith((substs["DLL_SUFFIX"], substs["BIN_SUFFIX"]))
86
87    return get_type(path) != UNKNOWN
88
89
90def may_strip(path):
91    """
92    Return whether strip() should be called
93    """
94    from buildconfig import substs
95
96    # Bug 1658632: clang-11-based strip complains about d3dcompiler_47.dll.
97    # It's not clear why this happens, but as a quick fix just avoid stripping
98    # this DLL. It's not from our build anyway.
99    if "d3dcompiler" in path:
100        return False
101    return bool(substs.get("PKG_STRIP"))
102
103
104def strip(path):
105    """
106    Execute the STRIP command with STRIP_FLAGS on the given path.
107    """
108    from buildconfig import substs
109
110    strip = substs["STRIP"]
111    flags = substs.get("STRIP_FLAGS", [])
112    cmd = [strip] + flags + [path]
113    if subprocess.call(cmd) != 0:
114        errors.fatal("Error executing " + " ".join(cmd))
115
116
117def may_elfhack(path):
118    """
119    Return whether elfhack() should be called
120    """
121    # elfhack only supports libraries. We should check the ELF header for
122    # the right flag, but checking the file extension works too.
123    from buildconfig import substs
124
125    return (
126        "USE_ELF_HACK" in substs
127        and substs["USE_ELF_HACK"]
128        and path.endswith(substs["DLL_SUFFIX"])
129        and "COMPILE_ENVIRONMENT" in substs
130        and substs["COMPILE_ENVIRONMENT"]
131    )
132
133
134def elfhack(path):
135    """
136    Execute the elfhack command on the given path.
137    """
138    from buildconfig import topobjdir
139
140    cmd = [os.path.join(topobjdir, "build/unix/elfhack/elfhack"), path]
141    if subprocess.call(cmd) != 0:
142        errors.fatal("Error executing " + " ".join(cmd))
143