xref: /openbmc/openbmc-test-automation/lib/gen_misc.py (revision 67f929627191976964e8dbf88c44faa901ca215e)
1#!/usr/bin/env python
2
3r"""
4This module provides many valuable functions such as my_parm_file.
5"""
6
7# sys and os are needed to get the program dir path and program name.
8import sys
9import errno
10import os
11import collections
12try:
13    import ConfigParser
14except ImportError:
15    import configparser
16try:
17    import StringIO
18except ImportError:
19    import io
20import re
21import socket
22import tempfile
23try:
24    import psutil
25    psutil_imported = True
26except ImportError:
27    psutil_imported = False
28
29import gen_print as gp
30import gen_cmd as gc
31
32robot_env = gp.robot_env
33if robot_env:
34    from robot.libraries.BuiltIn import BuiltIn
35    from robot.utils import DotDict
36
37
38def add_trailing_slash(dir_path):
39    r"""
40    Add a trailing slash to the directory path if it doesn't already have one
41    and return it.
42
43    Description of arguments:
44    dir_path                        A directory path.
45    """
46
47    return os.path.normpath(dir_path) + os.path.sep
48
49
50def which(file_path):
51    r"""
52    Find the full path of an executable file and return it.
53
54    The PATH environment variable dictates the results of this function.
55
56    Description of arguments:
57    file_path                       The relative file path (e.g. "my_file" or
58                                    "lib/my_file").
59    """
60
61    shell_rc, out_buf = gc.cmd_fnc_u("which " + file_path, quiet=1,
62                                     print_output=0, show_err=0)
63    if shell_rc != 0:
64        error_message = "Failed to find complete path for file \"" +\
65                        file_path + "\".\n"
66        error_message += gp.sprint_var(shell_rc, 1)
67        error_message += out_buf
68        if robot_env:
69            BuiltIn().fail(gp.sprint_error(error_message))
70        else:
71            gp.print_error_report(error_message)
72            return False
73
74    file_path = out_buf.rstrip("\n")
75
76    return file_path
77
78
79def dft(value, default):
80    r"""
81    Return default if value is None.  Otherwise, return value.
82
83    This is really just shorthand as shown below.
84
85    dft(value, default)
86
87    vs
88
89    default if value is None else value
90
91    Description of arguments:
92    value                           The value to be returned.
93    default                         The default value to return if value is
94                                    None.
95    """
96
97    return default if value is None else value
98
99
100def get_mod_global(var_name,
101                   default=None,
102                   mod_name="__main__"):
103    r"""
104    Get module global variable value and return it.
105
106    If we are running in a robot environment, the behavior will default to
107    calling get_variable_value.
108
109    Description of arguments:
110    var_name                        The name of the variable whose value is
111                                    sought.
112    default                         The value to return if the global does not
113                                    exist.
114    mod_name                        The name of the module containing the
115                                    global variable.
116    """
117
118    if robot_env:
119        return BuiltIn().get_variable_value("${" + var_name + "}", default)
120
121    try:
122        module = sys.modules[mod_name]
123    except KeyError:
124        gp.print_error_report("Programmer error - The mod_name passed to"
125                              + " this function is invalid:\n"
126                              + gp.sprint_var(mod_name))
127        raise ValueError('Programmer error.')
128
129    if default is None:
130        return getattr(module, var_name)
131    else:
132        return getattr(module, var_name, default)
133
134
135def global_default(var_value,
136                   default=0):
137    r"""
138    If var_value is not None, return it.  Otherwise, return the global
139    variable of the same name, if it exists.  If not, return default.
140
141    This is meant for use by functions needing help assigning dynamic default
142    values to their parms.  Example:
143
144    def func1(parm1=None):
145
146        parm1 = global_default(parm1, 0)
147
148    Description of arguments:
149    var_value                       The value being evaluated.
150    default                         The value to be returned if var_value is
151                                    None AND the global variable of the same
152                                    name does not exist.
153    """
154
155    var_name = gp.get_arg_name(0, 1, stack_frame_ix=2)
156
157    return dft(var_value, get_mod_global(var_name, 0))
158
159
160def set_mod_global(var_value,
161                   mod_name="__main__",
162                   var_name=None):
163    r"""
164    Set a global variable for a given module.
165
166    Description of arguments:
167    var_value                       The value to set in the variable.
168    mod_name                        The name of the module whose variable is
169                                    to be set.
170    var_name                        The name of the variable to set.  This
171                                    defaults to the name of the variable used
172                                    for var_value when calling this function.
173    """
174
175    try:
176        module = sys.modules[mod_name]
177    except KeyError:
178        gp.print_error_report("Programmer error - The mod_name passed to"
179                              + " this function is invalid:\n"
180                              + gp.sprint_var(mod_name))
181        raise ValueError('Programmer error.')
182
183    if var_name is None:
184        var_name = gp.get_arg_name(None, 1, 2)
185
186    setattr(module, var_name, var_value)
187
188
189def my_parm_file(prop_file_path):
190    r"""
191    Read a properties file, put the keys/values into a dictionary and return
192    the dictionary.
193
194    The properties file must have the following format:
195    var_name<= or :>var_value
196    Comment lines (those beginning with a "#") and blank lines are allowed and
197    will be ignored.  Leading and trailing single or double quotes will be
198    stripped from the value.  E.g.
199    var1="This one"
200    Quotes are stripped so the resulting value for var1 is:
201    This one
202
203    Description of arguments:
204    prop_file_path                  The caller should pass the path to the
205                                    properties file.
206    """
207
208    # ConfigParser expects at least one section header in the file (or you
209    # get ConfigParser.MissingSectionHeaderError).  Properties files don't
210    # need those so I'll write a dummy section header.
211
212    try:
213        string_file = StringIO.StringIO()
214    except NameError:
215        string_file = io.StringIO()
216
217    # Write the dummy section header to the string file.
218    string_file.write('[dummysection]\n')
219    # Write the entire contents of the properties file to the string file.
220    string_file.write(open(prop_file_path).read())
221    # Rewind the string file.
222    string_file.seek(0, os.SEEK_SET)
223
224    # Create the ConfigParser object.
225    try:
226        config_parser = ConfigParser.ConfigParser()
227    except NameError:
228        config_parser = configparser.ConfigParser()
229    # Make the property names case-sensitive.
230    config_parser.optionxform = str
231    # Read the properties from the string file.
232    config_parser.readfp(string_file)
233    # Return the properties as a dictionary.
234    if robot_env:
235        return DotDict(config_parser.items('dummysection'))
236    else:
237        return collections.OrderedDict(config_parser.items('dummysection'))
238
239
240def file_to_list(file_path,
241                 newlines=0,
242                 comments=1,
243                 trim=0):
244    r"""
245    Return the contents of a file as a list.  Each element of the resulting
246    list is one line from the file.
247
248    Description of arguments:
249    file_path                       The path to the file (relative or
250                                    absolute).
251    newlines                        Include newlines from the file in the
252                                    results.
253    comments                        Include comment lines and blank lines in
254                                    the results.  Comment lines are any that
255                                    begin with 0 or more spaces followed by
256                                    the pound sign ("#").
257    trim                            Trim white space from the beginning and
258                                    end of each line.
259    """
260
261    lines = []
262    file = open(file_path)
263    for line in file:
264        if not comments:
265            if re.match(r"[ ]*#|^$", line):
266                continue
267        if not newlines:
268            line = line.rstrip("\n")
269        if trim:
270            line = line.strip()
271        lines.append(line)
272
273    return lines
274
275
276def return_path_list():
277    r"""
278    This function will split the PATH environment variable into a PATH_LIST
279    and return it.  Each element in the list will be normalized and have a
280    trailing slash added.
281    """
282
283    PATH_LIST = os.environ['PATH'].split(":")
284    PATH_LIST = [os.path.normpath(path) + os.sep for path in PATH_LIST]
285
286    return PATH_LIST
287
288
289def escape_bash_quotes(buffer):
290    r"""
291    Escape quotes in string and return it.
292
293    The escape style implemented will be for use on the bash command line.
294
295    Example:
296    That's all.
297
298    Result:
299    That'\''s all.
300
301    The result may then be single quoted on a bash command.  Example:
302
303    echo 'That'\''s all.'
304
305    Description of argument(s):
306    buffer                          The string whose quotes are to be escaped.
307    """
308
309    return re.sub("\'", "\'\\\'\'", buffer)
310
311
312def quote_bash_parm(parm):
313    r"""
314    Return the bash command line parm with single quotes if they are needed.
315
316    Description of arguments:
317    parm                            The string to be quoted.
318    """
319
320    # If any of these characters are found in the parm string, then the
321    # string should be quoted.  This list is by no means complete and should
322    # be expanded as needed by the developer of this function.
323    bash_special_chars = set(' $')
324
325    if any((char in bash_special_chars) for char in parm):
326        return "'" + parm + "'"
327
328    return parm
329
330
331def get_host_name_ip(host=None,
332                     short_name=0):
333    r"""
334    Get the host name and the IP address for the given host and return them as
335    a tuple.
336
337    Description of argument(s):
338    host                            The host name or IP address to be obtained.
339    short_name                      Include the short host name in the
340                                    returned tuple, i.e. return host, ip and
341                                    short_host.
342    """
343
344    host = dft(host, socket.gethostname())
345    host_name = socket.getfqdn(host)
346    try:
347        host_ip = socket.gethostbyname(host)
348    except socket.gaierror as my_gaierror:
349        message = "Unable to obtain the host name for the following host:" +\
350                  "\n" + gp.sprint_var(host)
351        gp.print_error_report(message)
352        raise my_gaierror
353
354    if short_name:
355        host_short_name = host_name.split(".")[0]
356        return host_name, host_ip, host_short_name
357    else:
358        return host_name, host_ip
359
360
361def pid_active(pid):
362    r"""
363    Return true if pid represents an active pid and false otherwise.
364
365    Description of argument(s):
366    pid                             The pid whose status is being sought.
367    """
368
369    try:
370        os.kill(int(pid), 0)
371    except OSError as err:
372        if err.errno == errno.ESRCH:
373            # ESRCH == No such process
374            return False
375        elif err.errno == errno.EPERM:
376            # EPERM clearly means there's a process to deny access to
377            return True
378        else:
379            # According to "man 2 kill" possible error values are
380            # (EINVAL, EPERM, ESRCH)
381            raise
382
383    return True
384
385
386def to_signed(number,
387              bit_width=None):
388    r"""
389    Convert number to a signed number and return the result.
390
391    Examples:
392
393    With the following code:
394
395    var1 = 0xfffffffffffffff1
396    print_var(var1)
397    print_var(var1, 1)
398    var1 = to_signed(var1)
399    print_var(var1)
400    print_var(var1, 1)
401
402    The following is written to stdout:
403    var1:  18446744073709551601
404    var1:  0x00000000fffffffffffffff1
405    var1:  -15
406    var1:  0xfffffffffffffff1
407
408    The same code but with var1 set to 0x000000000000007f produces the
409    following:
410    var1:  127
411    var1:  0x000000000000007f
412    var1:  127
413    var1:  0x000000000000007f
414
415    Description of argument(s):
416    number                          The number to be converted.
417    bit_width                       The number of bits that defines a complete
418                                    hex value.  Typically, this would be a
419                                    multiple of 32.
420    """
421
422    if bit_width is None:
423        try:
424            bit_width = gp.bit_length(long(sys.maxsize)) + 1
425        except NameError:
426            bit_width = gp.bit_length(int(sys.maxsize)) + 1
427
428    if number < 0:
429        return number
430    neg_bit_mask = 2**(bit_width - 1)
431    if number & neg_bit_mask:
432        return ((2**bit_width) - number) * -1
433    else:
434        return number
435
436
437def get_child_pids(quiet=1):
438
439    r"""
440    Get and return a list of pids representing all first-generation processes
441    that are the children of the current process.
442
443    Example:
444
445    children = get_child_pids()
446    print_var(children)
447
448    Output:
449    children:
450      children[0]:           9123
451
452    Description of argument(s):
453    quiet                           Display output to stdout detailing how
454                                    this child pids are obtained.
455    """
456
457    if psutil_imported:
458        # If "import psutil" worked, find child pids using psutil.
459        current_process = psutil.Process()
460        return [x.pid for x in current_process.children(recursive=False)]
461    else:
462        # Otherwise, find child pids using shell commands.
463        print_output = not quiet
464
465        ps_cmd_buf = "ps --no-headers --ppid " + str(os.getpid()) +\
466            " -o pid,args"
467        # Route the output of ps to a temporary file for later grepping.
468        # Avoid using " | grep" in the ps command string because it creates
469        # yet another process which is of no interest to the caller.
470        temp = tempfile.NamedTemporaryFile()
471        temp_file_path = temp.name
472        gc.shell_cmd(ps_cmd_buf + " > " + temp_file_path,
473                     print_output=print_output)
474        # Sample contents of the temporary file:
475        # 30703 sleep 2
476        # 30795 /bin/bash -c ps --no-headers --ppid 30672 -o pid,args >
477        # /tmp/tmpqqorWY
478        # Use egrep to exclude the "ps" process itself from the results
479        # collected with the prior shell_cmd invocation.  Only the other
480        # children are of interest to the caller.  Use cut on the grep results
481        # to obtain only the pid column.
482        rc, output = \
483            gc.shell_cmd("egrep -v '" + re.escape(ps_cmd_buf) + "' "
484                         + temp_file_path + " | cut -c1-5",
485                         print_output=print_output)
486        # Split the output buffer by line into a list.  Strip each element of
487        # extra spaces and convert each element to an integer.
488        return map(int, map(str.strip, filter(None, output.split("\n"))))
489