SystemHelper.attributes()   B
last analyzed

Complexity

Conditions 2

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 4.6796

Importance

Changes 0
Metric Value
dl 0
loc 27
ccs 1
cts 8
cp 0.125
rs 8.8571
c 0
b 0
f 0
cc 2
crap 4.6796
1 1
from plugin.core.configuration import Configuration
2 1
from plugin.core.libraries.helpers.android import AndroidHelper
3 1
from plugin.core.libraries.helpers.arm import ArmHelper
4
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 1
import re
11 1
import sys
12
13 1
log = logging.getLogger(__name__)
14
15 1
_distributor_id_file_re = re.compile("(?:DISTRIB_ID\s*=)\s*(.*)", re.I)
0 ignored issues
show
Bug introduced by
A suspicious escape sequence \s was found. Did you maybe forget to add an r prefix?

Escape sequences in Python are generally interpreted according to rules similar to standard C. Only if strings are prefixed with r or R are they interpreted as regular expressions.

The escape sequence that was used indicates that you might have intended to write a regular expression.

Learn more about the available escape sequences. in the Python documentation.

Loading history...
16 1
_release_file_re = re.compile("(?:DISTRIB_RELEASE\s*=)\s*(.*)", re.I)
0 ignored issues
show
Bug introduced by
A suspicious escape sequence \s was found. Did you maybe forget to add an r prefix?

Escape sequences in Python are generally interpreted according to rules similar to standard C. Only if strings are prefixed with r or R are they interpreted as regular expressions.

The escape sequence that was used indicates that you might have intended to write a regular expression.

Learn more about the available escape sequences. in the Python documentation.

Loading history...
17 1
_codename_file_re = re.compile("(?:DISTRIB_CODENAME\s*=)\s*(.*)", re.I)
0 ignored issues
show
Bug introduced by
A suspicious escape sequence \s was found. Did you maybe forget to add an r prefix?

Escape sequences in Python are generally interpreted according to rules similar to standard C. Only if strings are prefixed with r or R are they interpreted as regular expressions.

The escape sequence that was used indicates that you might have intended to write a regular expression.

Learn more about the available escape sequences. in the Python documentation.

Loading history...
18
19 1
BITS_MAP = {
20
    '32bit': 'i386',
21
    '64bit': 'x86_64'
22
}
23
24 1
MACHINE_MAP = {
25
    ('32bit', 'i686'):  'i686',
26
    ('32bit', 'ppc' ):  'PowerPC'
0 ignored issues
show
Coding Style introduced by
No space allowed before bracket
('32bit', 'ppc' ): 'PowerPC'
^
Loading history...
27
}
28
29 1
MSVCR_MAP = {
30
    'msvcr110.dll': 'vc11',
31
    'msvcr120.dll': 'vc12',
32
    'msvcr130.dll': 'vc14'
33
}
34
35 1
NAME_MAP = {
36
    'Darwin': 'MacOSX'
37
}
38
39 1
FALLBACK_EXECUTABLE = '/bin/ls'
40
41
42 1
class SystemHelper(object):
43 1
    @classmethod
44
    def name(cls):
45
        """Retrieve system name (Windows, Linux, FreeBSD, MacOSX)"""
46
47 1
        system = platform.system()
48
49
        # Check for android platform
50 1
        if system == 'Linux' and AndroidHelper.is_android():
51
            system = 'Android'
52
53
        # Apply system name map
54 1
        if system in NAME_MAP:
55
            system = NAME_MAP[system]
56
57 1
        return system
58
59 1
    @classmethod
60
    def attributes(cls):
61
        # Retrieve platform attributes
62
        system = cls.name()
63
64
        release = platform.release()
65
        version = platform.version()
66
67
        # Build attributes dictionary
68
        result = {
69
            'cpu.architecture': cls.architecture(),
70
            'cpu.name': cls.cpu_name(),
71
72
            'os.system': system,
73
74
            'os.name': system,
75
            'os.release': release,
76
            'os.version': version
77
        }
78
79
        if system == 'Linux':
80
            # Update with linux distribution attributes
81
            result.update(cls.attributes_linux(
82
                release, version
83
            ))
84
85
        return result
86
87 1
    @classmethod
88 1
    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
            'linux.version': version
99
        }
100
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
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 1
    @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 1
        cpu_architecture = Configuration.advanced['libraries'].get('cpu_architecture')
118
119 1
        if cpu_architecture:
120
            log.info('Using CPU Architecture from advanced configuration: %r', cpu_architecture)
121
            return cpu_architecture
122
123
        # Determine architecture from platform attributes
124 1
        bits, _ = platform.architecture()
125 1
        machine = platform.machine()
126
127
        # Check for ARM machine
128 1
        if (bits == '32bit' and machine.startswith('armv')) or machine.startswith('aarch64'):
129
            return cls.arm(machine)
130
131
        # Check (bits, machine) map
132 1
        machine_key = (bits, machine)
133
134 1
        if machine_key in MACHINE_MAP:
135
            return MACHINE_MAP[machine_key]
136
137
        # Check (bits) map
138 1
        if bits in BITS_MAP:
139 1
            return BITS_MAP[bits]
140
141
        log.error('Unable to determine system architecture - bits: %r, machine: %r', bits, machine)
142
        return None
143
144 1
    @classmethod
145 1
    def arm(cls, machine, float_type=None):
146
        # Determine ARM version
147 1
        floats, architecture = cls.arm_architecture(machine)
148
149 1
        if architecture is None:
150
            log.warn('Unable to use ARM libraries, unsupported ARM architecture (%r)?' % machine)
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
151
            return None
152
153 1
        if not floats:
154 1
            return architecture
155
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
            log.warn('Unable to use ARM libraries, unsupported floating-point type?')
161
            return None
162
163 1
        return '%s_%s' % (architecture, float_type)
164
165 1
    @classmethod
166 1
    def arm_architecture(cls, machine=None):
167
        # Read `machine` name if not provided
168 1
        if machine is None:
169
            machine = platform.machine()
170
171
        # Ensure `machine` is valid
172 1
        if not machine:
173
            return False, None
174
175
        # ARMv5
176 1
        if machine.startswith('armv5'):
177 1
            return True, 'armv5'
178
179
        # ARMv6
180 1
        if machine.startswith('armv6'):
181 1
            return True, 'armv6'
182
183
        # ARMv7
184 1
        if machine.startswith('armv7'):
185 1
            return True, 'armv7'
186
187
        # ARMv8 / AArch64
188 1
        if machine.startswith('armv8') or machine.startswith('aarch64'):
189 1
            return False, 'aarch64'
190
191
        return False, None
192
193 1
    @classmethod
194 1
    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
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
202
        # Try determine float-type from "/lib" directories
203
        if os.path.exists('/lib/arm-linux-gnueabihf'):
204
            return 'hf'
205
206
        if os.path.exists('/lib/arm-linux-gnueabi'):
207
            return 'sf'
208
209
        # Determine system float-type from python executable
210
        section, attributes = cls.elf_attributes(executable_path)
211
212
        if not section or not attributes:
213
            return None
214
215
        if section.name != 'aeabi':
216
            log.warn('Unknown attributes section name: %r', section.name)
217
            return None
218
219
        # Assume hard-float if "tag_abi_vfp_args" is present
220
        if attributes.get('abi_vfp_args'):
221
            return 'hf'
222
223
        return 'sf'
224
225 1
    @classmethod
226 1
    def cpu_name(cls, executable_path=sys.executable):
227 1
        if cls.name() == 'Windows':
228
            return None
229
230
        # Retrieve CPU name from ELF
231 1
        section, attributes = cls.elf_attributes(executable_path)
232
233 1
        if not section or not attributes:
234 1
            return None
235
236
        name = attributes.get('cpu_name')
237
238
        if not name:
239
            return None
240
241
        return name.lower()
242
243 1
    @classmethod
244 1
    def cpu_type(cls, executable_path=sys.executable):
245
        # Use `cpu_type` value from advanced configuration (if defined)
246 1
        cpu_type = Configuration.advanced['libraries'].get('cpu_type')
247
248 1
        if cpu_type:
249
            log.info('Using CPU Type from advanced configuration: %r', cpu_type)
250
            return cpu_type
251
252
        # Try retrieve cpu type via "/proc/cpuinfo"
253 1
        try:
254 1
            _, _, cpu_type = ArmHelper.identifier()
255
256 1
            if cpu_type:
257
                return cpu_type
258
        except Exception as ex:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
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 1
        return cls.cpu_name(executable_path)
263
264 1
    @classmethod
265 1
    def distribution(cls, distname='', version='', id='',
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
266
                     supported_dists=platform._supported_dists,
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _supported_dists was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
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):
0 ignored issues
show
Unused Code introduced by
This except handler seems to be unused and could be removed.

Except handlers which only contain pass and do not have an else clause can usually simply be removed:

try:
    raises_exception()
except:  # Could be removed
    pass
Loading history...
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 1
    @classmethod
310 1
    def elf_attributes(cls, executable_path=sys.executable):
311 1
        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 1
        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 1
        try:
321
            # Open executable stream
322 1
            stream = open(executable_path, 'rb')
323
324
            # Retrieve magic number (header)
325 1
            magic = stream.read(4)
326
327 1
            if magic != b'\x7fELF':
328
                log.warn('Unknown ELF format for %r (magic: %r)', executable_path, magic)
329
                return None, None
330
331 1
            stream.seek(0)
332
333
            # Parse ELF
334 1
            elf = ELFFile(stream)
335
336
            # Find attributes section
337 1
            section = cls._find_elf_section(elf, AttributesSection)
338
339 1
            if section is None:
340 1
                log.info('Unable to find attributes section in ELF: %r', executable_path)
341 1
                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 as ex:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
351
            log.warn('Unable to retrieve attributes from ELF %r: %s', executable_path, ex, exc_info=True)
352
353
        return None, None
354
355 1
    @classmethod
356
    def page_size(cls):
357 1
        try:
358 1
            import resource
359 1
            page_size = resource.getpagesize()
360
361 1
            if not page_size:
362
                return None
363
364 1
            return '%dk' % (page_size / 1024)
365
        except Exception as ex:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
366
            log.warn('Unable to retrieve memory page size: %s', ex, exc_info=True)
367
            return None
368
369 1
    @staticmethod
370
    def _find_elf_section(elf, cls):
371 1
        for section in elf.iter_sections():
372 1
            if isinstance(section, cls):
373
                return section
374
375 1
        return None
376
377 1
    @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:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
392
            log.error('Unable to retrieve VC++ runtime version', exc_info=True)
393
            return None
394