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