Test Failed
Push — beta ( 0deaa1...735b0d )
by Dean
03:31
created

SystemHelper.attributes_linux()   B

Complexity

Conditions 4

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 14.0742

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 24
rs 8.6845
ccs 2
cts 14
cp 0.1429
cc 4
crap 14.0742
1 1
from plugin.core.configuration import Configuration
2 1
from plugin.core.libraries.helpers.android import AndroidHelper
3
from plugin.core.libraries.helpers.arm import ArmHelper
4 1
5 1
from elftools.elf.attributes import AttributesSection
6 1
from elftools.elf.elffile import ELFFile
7 1
import logging
8 1
import os
9 1
import platform
10
import re
11 1
import sys
12
13 1
log = logging.getLogger(__name__)
14
15
_distributor_id_file_re = re.compile("(?:DISTRIB_ID\s*=)\s*(.*)", re.I)
16
_release_file_re = re.compile("(?:DISTRIB_RELEASE\s*=)\s*(.*)", re.I)
17
_codename_file_re = re.compile("(?:DISTRIB_CODENAME\s*=)\s*(.*)", re.I)
18 1
19
BITS_MAP = {
20
    '32bit': 'i386',
21
    '64bit': 'x86_64'
22
}
23 1
24
MACHINE_MAP = {
25
    ('32bit', 'i686'):  'i686',
26
    ('32bit', 'ppc' ):  'PowerPC'
27
}
28 1
29
MSVCR_MAP = {
30
    'msvcr110.dll': 'vc11',
31
    'msvcr120.dll': 'vc12',
32 1
    'msvcr130.dll': 'vc14'
33
}
34
35 1
NAME_MAP = {
36 1
    'Darwin': 'MacOSX'
37
}
38
39
FALLBACK_EXECUTABLE = '/bin/ls'
40
41 1
42
class SystemHelper(object):
43 1
    @classmethod
44
    def name(cls):
45
        """Retrieve system name (Windows, Linux, FreeBSD, MacOSX)"""
46
47
        system = platform.system()
48 1
49 1
        # Check for android platform
50
        if system == 'Linux' and AndroidHelper.is_android():
51
            system = 'Android'
52 1
53
        # Apply system name map
54
        if system in NAME_MAP:
55
            system = NAME_MAP[system]
56 1
57
        return system
58 1
59
    @classmethod
60
    def attributes(cls):
61
        # Retrieve platform attributes
62 1
        system = cls.name()
63 1
64
        release = platform.release()
65
        version = platform.version()
66
67
        # Build attributes dictionary
68 1
        result = {
69
            'cpu.architecture': cls.architecture(),
70
            'cpu.name': cls.cpu_name(),
71
72 1
            'os.system': system,
73
74
            'os.name': system,
75 1
            'os.release': release,
76
            'os.version': version
77
        }
78 1
79
        if system == 'Linux':
80 1
            # Update with linux distribution attributes
81
            result.update(cls.attributes_linux(
82
                release, version
83
            ))
84
85
        return result
86
87
    @classmethod
88
    def attributes_linux(cls, release=None, version=None):
89
        d_name, d_version, d_id = cls.distribution()
90
91
        # Build linux attributes dictionary
92
        result = {
93
            'os.name': None,
94
            'os.release': None,
95
            'os.version': None,
96
97
            'linux.release': release,
98 1
            'linux.version': version
99 1
        }
100
        
101
        if d_name:
102
            result['os.name'] = d_name
103
104
        if d_version:
105
            result['os.version'] = d_version
106
107
        if d_id:
108
            result['os.release'] = d_id
109
110
        return result
111
112
    @classmethod
113
    def architecture(cls):
114
        """Retrieve system architecture (i386, i686, x86_64)"""
115
116
        # Use `cpu_architecture` value from advanced configuration (if defined)
117
        cpu_architecture = Configuration.advanced['libraries'].get('cpu_architecture')
118
119
        if cpu_architecture:
120
            log.info('Using CPU Architecture from advanced configuration: %r', cpu_architecture)
121
            return cpu_architecture
122 1
123 1
        # Determine architecture from platform attributes
124
        bits, _ = platform.architecture()
125
        machine = platform.machine()
126
127
        # Check for ARM machine
128
        if (bits == '32bit' and machine.startswith('armv')) or machine.startswith('aarch64'):
129
            return cls.arm(machine)
130
131
        # Check (bits, machine) map
132
        machine_key = (bits, machine)
133
134
        if machine_key in MACHINE_MAP:
135
            return MACHINE_MAP[machine_key]
136
137
        # Check (bits) map
138
        if bits in BITS_MAP:
139
            return BITS_MAP[bits]
140
141
        log.error('Unable to determine system architecture - bits: %r, machine: %r', bits, machine)
142
        return None
143
144
    @classmethod
145
    def arm(cls, machine, float_type=None):
146
        # Determine ARM version
147
        floats, architecture = cls.arm_architecture(machine)
148
149
        if architecture is None:
150
            log.warn('Unable to use ARM libraries, unsupported ARM architecture (%r)?' % machine)
151
            return None
152
153
        if not floats:
154 1
            return architecture
155 1
156
        # Determine floating-point type
157 1
        float_type = float_type or cls.arm_float_type()
158
159 1
        if float_type is None:
160 1
            log.warn('Unable to use ARM libraries, unsupported floating-point type?')
161
            return None
162
163
        return '%s_%s' % (architecture, float_type)
164
165
    @classmethod
166
    def arm_architecture(cls, machine=None):
167
        # Read `machine` name if not provided
168
        if machine is None:
169 1
            machine = platform.machine()
170 1
171
        # Ensure `machine` is valid
172 1
        if not machine:
173
            return False, None
174 1
175
        # ARMv5
176
        if machine.startswith('armv5'):
177
            return True, 'armv5'
178
179 1
        # ARMv6
180 1
        if machine.startswith('armv6'):
181
            return True, 'armv6'
182 1
183
        # ARMv7
184
        if machine.startswith('armv7'):
185
            return True, 'armv7'
186
187
        # ARMv8 / AArch64
188 1
        if machine.startswith('armv8') or machine.startswith('aarch64'):
189
            return False, 'aarch64'
190 1
191 1
        return False, None
192 1
193
    @classmethod
194
    def arm_float_type(cls, executable_path=sys.executable):
195
        # Use `arm_float_type` value from advanced configuration (if defined)
196
        arm_float_type = Configuration.advanced['libraries'].get('arm_float_type')
197 1
198
        if arm_float_type:
199
            log.info('Using ARM Float Type from advanced configuration: %r', arm_float_type)
200
            return arm_float_type
201 1
202
        # Try determine float-type from "/lib" directories
203 1
        if os.path.exists('/lib/arm-linux-gnueabihf'):
204
            return 'hf'
205
206 1
        if os.path.exists('/lib/arm-linux-gnueabi'):
207
            return 'sf'
208 1
209
        # Determine system float-type from python executable
210
        section, attributes = cls.elf_attributes(executable_path)
211
212 1
        if not section or not attributes:
213
            return None
214
215 1
        if section.name != 'aeabi':
216
            log.warn('Unknown attributes section name: %r', section.name)
217
            return None
218 1
219
        # Assume hard-float if "tag_abi_vfp_args" is present
220 1
        if attributes.get('abi_vfp_args'):
221 1
            return 'hf'
222 1
223
        return 'sf'
224
225
    @classmethod
226
    def cpu_name(cls, executable_path=sys.executable):
227
        if cls.name() == 'Windows':
228
            return None
229
230
        # Retrieve CPU name from ELF
231
        section, attributes = cls.elf_attributes(executable_path)
232
233
        if not section or not attributes:
234
            return None
235
236 1
        name = attributes.get('cpu_name')
237
238 1
        if not name:
239 1
            return None
240 1
241
        return name.lower()
242 1
243
    @classmethod
244
    def cpu_type(cls, executable_path=sys.executable):
245 1
        # Use `cpu_type` value from advanced configuration (if defined)
246
        cpu_type = Configuration.advanced['libraries'].get('cpu_type')
247
248
        if cpu_type:
249
            log.info('Using CPU Type from advanced configuration: %r', cpu_type)
250 1
            return cpu_type
251
252 1
        # Try retrieve cpu type via "/proc/cpuinfo"
253 1
        try:
254
            _, _, cpu_type = ArmHelper.identifier()
255
256 1
            if cpu_type:
257
                return cpu_type
258 1
        except Exception, ex:
259
            log.warn('Unable to retrieve cpu type from "/proc/cpuinfo": %s', ex, exc_info=True)
260
261
        # Fallback to using the ELF cpu name
262
        return cls.cpu_name(executable_path)
263
264
    @classmethod
265
    def distribution(cls, distname='', version='', id='',
266
                     supported_dists=platform._supported_dists,
267
                     full_distribution_name=1):
268
269
        # check for the Debian/Ubuntu /etc/lsb-release file first, needed so
270
        # that the distribution doesn't get identified as Debian.
271
        try:
272
            _distname = None
273
            _version = None
274
            _id = None
275
276
            with open("/etc/lsb-release", "rU") as fp:
277
                for line in fp:
278
                    # Distribution Name
279
                    m = _distributor_id_file_re.search(line)
280
281
                    if m:
282
                        _distname = m.group(1).strip()
283
284
                    # Release
285
                    m = _release_file_re.search(line)
286
287
                    if m:
288
                        _version = m.group(1).strip()
289
290
                    # ID
291
                    m = _codename_file_re.search(line)
292
293
                    if m:
294
                        _id = m.group(1).strip()
295
296
                if _distname and _version:
297
                    return _distname, _version, _id
298
299
        except (EnvironmentError, UnboundLocalError):
300
            pass
301
302
        # Fallback to using the "platform" module
303
        return platform.linux_distribution(
304
            distname, version, id,
305
            supported_dists=supported_dists,
306
            full_distribution_name=full_distribution_name
307
        )
308
309
    @classmethod
310
    def elf_attributes(cls, executable_path=sys.executable):
311
        if cls.name() == 'MacOSX':
312
            log.info('Unable to retrieve ELF attributes on Mac OSX (not supported)')
313
            return None, None
314
315
        # Read attributes from "/bin/ls" if `executable_path` doesn't exist
316
        if not executable_path or not os.path.exists(executable_path):
317
            log.info('Executable at %r doesn\'t exist, using %r instead', executable_path, FALLBACK_EXECUTABLE)
318
            executable_path = FALLBACK_EXECUTABLE
319
320
        try:
321
            # Open executable stream
322
            stream = open(executable_path, 'rb')
323
324
            # Retrieve magic number (header)
325
            magic = stream.read(4)
326
327
            if magic != b'\x7fELF':
328
                log.warn('Unknown ELF format for %r (magic: %r)', executable_path, magic)
329
                return None, None
330
331
            stream.seek(0)
332
333
            # Parse ELF
334
            elf = ELFFile(stream)
335
336
            # Find attributes section
337
            section = cls._find_elf_section(elf, AttributesSection)
338
339
            if section is None:
340
                log.info('Unable to find attributes section in ELF: %r', executable_path)
341
                return None, None
342
343
            # Build dictionary of attributes
344
            attributes = dict([
345
                (attr.tag.lower(), attr.value)
346
                for attr in section.iter_attributes()
347
            ])
348
349
            return section, attributes
350
        except Exception, ex:
351
            log.warn('Unable to retrieve attributes from ELF %r: %s', executable_path, ex, exc_info=True)
352
353
        return None, None
354
355
    @classmethod
356
    def page_size(cls):
357
        try:
358
            import resource
359
            page_size = resource.getpagesize()
360
361
            if not page_size:
362
                return None
363
364
            return '%dk' % (page_size / 1024)
365
        except Exception, ex:
366
            log.warn('Unable to retrieve memory page size: %s', ex, exc_info=True)
367
            return None
368
369
    @staticmethod
370
    def _find_elf_section(elf, cls):
371
        for section in elf.iter_sections():
372
            if isinstance(section, cls):
373
                return section
374
375
        return None
376
377
    @classmethod
378
    def vcr_version(cls):
379
        try:
380
            import ctypes.util
381
382
            # Retrieve linked msvcr dll
383
            name = ctypes.util.find_msvcrt()
384
385
            # Return VC++ version from map
386
            if name not in MSVCR_MAP:
387
                log.error('Unknown VC++ runtime: %r', name)
388
                return None
389
390
            return MSVCR_MAP[name]
391
        except Exception:
392
            log.error('Unable to retrieve VC++ runtime version', exc_info=True)
393
            return None
394