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
6
7import os
8import struct
9import subprocess
10from mozpack.errors import errors
11
12MACHO_SIGNATURES = [
13    0xfeedface,  # mach-o 32-bits big endian
14    0xcefaedfe,  # mach-o 32-bits little endian
15    0xfeedfacf,  # mach-o 64-bits big endian
16    0xcffaedfe,  # mach-o 64-bits little endian
17]
18
19FAT_SIGNATURE = 0xcafebabe  # mach-o FAT binary
20
21ELF_SIGNATURE = 0x7f454c46  # Elf binary
22
23UNKNOWN = 0
24MACHO = 1
25ELF = 2
26
27def get_type(path):
28    '''
29    Check the signature of the give file and returns what kind of executable
30    matches.
31    '''
32    with open(path, 'rb') as f:
33        signature = f.read(4)
34        if len(signature) < 4:
35            return UNKNOWN
36        signature = struct.unpack('>L', signature)[0]
37        if signature == ELF_SIGNATURE:
38            return ELF
39        if signature in MACHO_SIGNATURES:
40            return MACHO
41        if signature != FAT_SIGNATURE:
42            return UNKNOWN
43        # We have to sanity check the second four bytes, because Java class
44        # files use the same magic number as Mach-O fat binaries.
45        # This logic is adapted from file(1), which says that Mach-O uses
46        # these bytes to count the number of architectures within, while
47        # Java uses it for a version number. Conveniently, there are only
48        # 18 labelled Mach-O architectures, and Java's first released
49        # class format used the version 43.0.
50        num = f.read(4)
51        if len(num) < 4:
52            return UNKNOWN
53        num = struct.unpack('>L', num)[0]
54        if num < 20:
55            return MACHO
56        return UNKNOWN
57
58
59def is_executable(path):
60    '''
61    Return whether a given file path points to an executable or a library,
62    where an executable or library is identified by:
63        - the file extension on OS/2 and WINNT
64        - the file signature on OS/X and ELF systems (GNU/Linux, Android, BSD,
65          Solaris)
66
67    As this function is intended for use to choose between the ExecutableFile
68    and File classes in FileFinder, and choosing ExecutableFile only matters
69    on OS/2, OS/X, ELF and WINNT (in GCC build) systems, we don't bother
70    detecting other kind of executables.
71    '''
72    from buildconfig import substs
73    if not os.path.exists(path):
74        return False
75
76    if substs['OS_ARCH'] == 'WINNT':
77        return path.lower().endswith((substs['DLL_SUFFIX'],
78                                      substs['BIN_SUFFIX']))
79
80    return get_type(path) != UNKNOWN
81
82
83def may_strip(path):
84    '''
85    Return whether strip() should be called
86    '''
87    from buildconfig import substs
88    return not substs['PKG_SKIP_STRIP']
89
90
91def strip(path):
92    '''
93    Execute the STRIP command with STRIP_FLAGS on the given path.
94    '''
95    from buildconfig import substs
96    strip = substs['STRIP']
97    flags = substs['STRIP_FLAGS'].split() if 'STRIP_FLAGS' in substs else []
98    cmd = [strip] + flags + [path]
99    if subprocess.call(cmd) != 0:
100        errors.fatal('Error executing ' + ' '.join(cmd))
101
102
103def may_elfhack(path):
104    '''
105    Return whether elfhack() should be called
106    '''
107    # elfhack only supports libraries. We should check the ELF header for
108    # the right flag, but checking the file extension works too.
109    from buildconfig import substs
110    return ('USE_ELF_HACK' in substs and substs['USE_ELF_HACK'] and
111        path.endswith(substs['DLL_SUFFIX']) and
112        'COMPILE_ENVIRONMENT' in substs and substs['COMPILE_ENVIRONMENT'])
113
114
115def elfhack(path):
116    '''
117    Execute the elfhack command on the given path.
118    '''
119    from buildconfig import topobjdir
120    cmd = [os.path.join(topobjdir, 'build/unix/elfhack/elfhack'), path]
121    if 'ELF_HACK_FLAGS' in os.environ:
122        cmd[1:0] = os.environ['ELF_HACK_FLAGS'].split()
123    if subprocess.call(cmd) != 0:
124        errors.fatal('Error executing ' + ' '.join(cmd))
125