blob: e2136c2b1f12b3fbbd4b7313a95fbc80a1fb6d10 [file] [log] [blame]
George Keishinge7e91712021-09-03 11:28:44 -05001#!/usr/bin/env python3
Michael Walshde791732016-09-06 14:25:24 -05002
Michael Walsh7423c012016-10-04 10:27:21 -05003r"""
4This module provides many valuable functions such as my_parm_file.
5"""
Michael Walshde791732016-09-06 14:25:24 -05006
Patrick Williams20f38712022-12-08 06:18:26 -06007import collections
8import errno
9import inspect
10import json
11import os
12import random
13import shutil
14
Michael Walshde791732016-09-06 14:25:24 -050015# sys and os are needed to get the program dir path and program name.
16import sys
Michael Walshc9cb9722018-10-01 17:56:20 -050017import time
Patrick Williams20f38712022-12-08 06:18:26 -060018
Michael Walshdece16c2018-08-07 15:01:05 -050019try:
20 import ConfigParser
21except ImportError:
22 import configparser
George Keishing3b7115a2018-08-02 10:48:17 -050023try:
Michael Walsh61f5e8f2018-08-03 11:16:00 -050024 import StringIO
George Keishing3b7115a2018-08-02 10:48:17 -050025except ImportError:
Michael Walshdece16c2018-08-07 15:01:05 -050026 import io
Patrick Williams20f38712022-12-08 06:18:26 -060027
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060028import re
Michael Walsh1c85bab2017-05-04 14:29:24 -050029import socket
Michael Walsh3b621fe2018-07-24 16:27:53 -050030import tempfile
Patrick Williams20f38712022-12-08 06:18:26 -060031
Michael Walsh3b621fe2018-07-24 16:27:53 -050032try:
33 import psutil
Patrick Williams20f38712022-12-08 06:18:26 -060034
Michael Walsh3b621fe2018-07-24 16:27:53 -050035 psutil_imported = True
36except ImportError:
37 psutil_imported = False
Michael Walshde791732016-09-06 14:25:24 -050038
George Keishinge635ddc2022-12-08 07:38:02 -060039import gen_cmd as gc
Patrick Williams20f38712022-12-08 06:18:26 -060040import gen_print as gp
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060041
Michael Walsh93a09f22017-11-13 15:34:46 -060042robot_env = gp.robot_env
43if robot_env:
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060044 from robot.libraries.BuiltIn import BuiltIn
Michael Walshdece16c2018-08-07 15:01:05 -050045 from robot.utils import DotDict
Michael Walsh7423c012016-10-04 10:27:21 -050046
Michael Walshde791732016-09-06 14:25:24 -050047
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060048def add_trailing_slash(dir_path):
Michael Walsh7db77942017-01-10 11:37:06 -060049 r"""
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060050 Add a trailing slash to the directory path if it doesn't already have one
51 and return it.
Michael Walsh7db77942017-01-10 11:37:06 -060052
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060053 Description of arguments:
54 dir_path A directory path.
Michael Walsh7db77942017-01-10 11:37:06 -060055 """
56
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060057 return os.path.normpath(dir_path) + os.path.sep
58
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060059
Michael Walshc4d6bce2020-02-18 14:25:44 -060060def makedirs(path, mode=0o777, quiet=None):
61 r"""
62 Call os.makedirs with the caller's arguments.
63
64 This function offers 2 advantages over the base os.makedirs function:
65 1) It will not fail if the directory already exists.
66 2) It will print an "Issuing: os.makedirs" message.
67
68 Description of argument(s):
69 path The path containing the directories to be created.
70 mode The mode or permissions to be granted to the created directories.
71 quiet Indicates whether this function should run the print_issuing() function.
72 """
Patrick Williams20f38712022-12-08 06:18:26 -060073 quiet = int(dft(quiet, gp.get_stack_var("quiet", 0)))
Michael Walshc4d6bce2020-02-18 14:25:44 -060074 gp.qprint_issuing("os.makedirs('" + path + "', mode=" + oct(mode) + ")")
75 try:
76 os.makedirs(path, mode)
Michael Walsh1435b352020-02-27 15:22:55 -060077 except OSError:
Michael Walshc4d6bce2020-02-18 14:25:44 -060078 pass
79
80
Michael Walsh37cade82020-03-26 17:32:27 -050081def rmtree(path, ignore_errors=False, onerror=None, quiet=None):
82 r"""
83 Call shutil.rmtree with the caller's arguments.
84
85 This function offers this advantage over the base function:
86 - It will print an "Issuing: shutil.rmtree" message.
87
88 Description of argument(s):
89 (All parms are passed directly to shutil.rmtree. See its prolog for details)
90 quiet Indicates whether this function should run the print_issuing() function.
91 """
Patrick Williams20f38712022-12-08 06:18:26 -060092 quiet = int(dft(quiet, gp.get_stack_var("quiet", 0)))
Michael Walsh37cade82020-03-26 17:32:27 -050093 print_string = gp.sprint_executing(max_width=2000)
Patrick Williams20f38712022-12-08 06:18:26 -060094 print_string = re.sub(
95 r"Executing: ", "Issuing: shutil.", print_string.rstrip("\n")
96 )
Michael Walsh37cade82020-03-26 17:32:27 -050097 gp.qprintn(re.sub(r", quiet[ ]?=.*", ")", print_string))
98 shutil.rmtree(path, ignore_errors, onerror)
99
100
Michael Walshc4d6bce2020-02-18 14:25:44 -0600101def chdir(path, quiet=None):
102 r"""
103 Call os.chdir with the caller's arguments.
104
105 This function offers this advantage over the base os.chdir function:
106 - It will print an "Issuing: os.chdir" message.
107
108 Description of argument(s):
109 path The path of the directory to change to.
110 quiet Indicates whether this function should run the print_issuing() function.
111 """
Patrick Williams20f38712022-12-08 06:18:26 -0600112 quiet = int(dft(quiet, gp.get_stack_var("quiet", 0)))
Michael Walshc4d6bce2020-02-18 14:25:44 -0600113 gp.qprint_issuing("os.chdir('" + path + "')")
114 os.chdir(path)
115
116
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600117def which(file_path):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600118 r"""
119 Find the full path of an executable file and return it.
120
121 The PATH environment variable dictates the results of this function.
122
123 Description of arguments:
Michael Walsh410b1782019-10-22 15:56:18 -0500124 file_path The relative file path (e.g. "my_file" or "lib/my_file").
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600125 """
126
Patrick Williams20f38712022-12-08 06:18:26 -0600127 shell_rc, out_buf = gc.cmd_fnc_u(
128 "which " + file_path, quiet=1, print_output=0, show_err=0
129 )
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600130 if shell_rc != 0:
Patrick Williams20f38712022-12-08 06:18:26 -0600131 error_message = (
132 'Failed to find complete path for file "' + file_path + '".\n'
133 )
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500134 error_message += gp.sprint_var(shell_rc, gp.hexa())
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600135 error_message += out_buf
136 if robot_env:
137 BuiltIn().fail(gp.sprint_error(error_message))
138 else:
139 gp.print_error_report(error_message)
140 return False
141
142 file_path = out_buf.rstrip("\n")
143
144 return file_path
145
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600146
Patrick Williams20f38712022-12-08 06:18:26 -0600147def add_path(new_path, path, position=0):
Michael Walshf7400f32018-09-26 17:13:43 -0500148 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500149 Add new_path to path, provided that path doesn't already contain new_path, and return the result.
Michael Walshf7400f32018-09-26 17:13:43 -0500150
151 Example:
152 If PATH has a value of "/bin/user:/lib/user". The following code:
153
154 PATH = add_path("/tmp/new_path", PATH)
155
156 will change PATH to "/tmp/new_path:/bin/user:/lib/user".
157
158 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500159 new_path The path to be added. This function will strip the trailing slash.
160 path The path value to which the new_path should be added.
161 position The position in path where the new_path should be added. 0 means it
162 should be added to the beginning, 1 means add it as the 2nd item, etc.
163 sys.maxsize means it should be added to the end.
Michael Walshf7400f32018-09-26 17:13:43 -0500164 """
165
166 path_list = list(filter(None, path.split(":")))
167 new_path = new_path.rstrip("/")
168 if new_path not in path_list:
169 path_list.insert(int(position), new_path)
170 return ":".join(path_list)
171
172
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600173def dft(value, default):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600174 r"""
175 Return default if value is None. Otherwise, return value.
176
177 This is really just shorthand as shown below.
178
179 dft(value, default)
180
181 vs
182
183 default if value is None else value
184
185 Description of arguments:
186 value The value to be returned.
Michael Walsh410b1782019-10-22 15:56:18 -0500187 default The default value to return if value is None.
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600188 """
189
190 return default if value is None else value
191
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600192
Patrick Williams20f38712022-12-08 06:18:26 -0600193def get_mod_global(var_name, default=None, mod_name="__main__"):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600194 r"""
195 Get module global variable value and return it.
196
197 If we are running in a robot environment, the behavior will default to
198 calling get_variable_value.
199
200 Description of arguments:
Michael Walsh410b1782019-10-22 15:56:18 -0500201 var_name The name of the variable whose value is sought.
202 default The value to return if the global does not exist.
203 mod_name The name of the module containing the global variable.
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600204 """
205
206 if robot_env:
207 return BuiltIn().get_variable_value("${" + var_name + "}", default)
208
209 try:
210 module = sys.modules[mod_name]
211 except KeyError:
Patrick Williams20f38712022-12-08 06:18:26 -0600212 gp.print_error_report(
213 "Programmer error - The mod_name passed to"
214 + " this function is invalid:\n"
215 + gp.sprint_var(mod_name)
216 )
217 raise ValueError("Programmer error.")
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600218
219 if default is None:
220 return getattr(module, var_name)
221 else:
222 return getattr(module, var_name, default)
223
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600224
Patrick Williams20f38712022-12-08 06:18:26 -0600225def global_default(var_value, default=0):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600226 r"""
227 If var_value is not None, return it. Otherwise, return the global
228 variable of the same name, if it exists. If not, return default.
229
230 This is meant for use by functions needing help assigning dynamic default
231 values to their parms. Example:
232
233 def func1(parm1=None):
234
235 parm1 = global_default(parm1, 0)
236
237 Description of arguments:
238 var_value The value being evaluated.
Michael Walsh410b1782019-10-22 15:56:18 -0500239 default The value to be returned if var_value is None AND the global variable of
240 the same name does not exist.
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600241 """
242
243 var_name = gp.get_arg_name(0, 1, stack_frame_ix=2)
244
245 return dft(var_value, get_mod_global(var_name, 0))
Michael Walsh7db77942017-01-10 11:37:06 -0600246
Michael Walsh7db77942017-01-10 11:37:06 -0600247
Patrick Williams20f38712022-12-08 06:18:26 -0600248def set_mod_global(var_value, mod_name="__main__", var_name=None):
Michael Walsh7db77942017-01-10 11:37:06 -0600249 r"""
250 Set a global variable for a given module.
251
252 Description of arguments:
253 var_value The value to set in the variable.
Michael Walsh410b1782019-10-22 15:56:18 -0500254 mod_name The name of the module whose variable is to be set.
255 var_name The name of the variable to set. This defaults to the name of the
256 variable used for var_value when calling this function.
Michael Walsh7db77942017-01-10 11:37:06 -0600257 """
258
259 try:
260 module = sys.modules[mod_name]
261 except KeyError:
Patrick Williams20f38712022-12-08 06:18:26 -0600262 gp.print_error_report(
263 "Programmer error - The mod_name passed to"
264 + " this function is invalid:\n"
265 + gp.sprint_var(mod_name)
266 )
267 raise ValueError("Programmer error.")
Michael Walsh7db77942017-01-10 11:37:06 -0600268
269 if var_name is None:
270 var_name = gp.get_arg_name(None, 1, 2)
271
272 setattr(module, var_name, var_value)
273
Michael Walsh7db77942017-01-10 11:37:06 -0600274
Michael Walshde791732016-09-06 14:25:24 -0500275def my_parm_file(prop_file_path):
Michael Walshde791732016-09-06 14:25:24 -0500276 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500277 Read a properties file, put the keys/values into a dictionary and return the dictionary.
Michael Walshde791732016-09-06 14:25:24 -0500278
279 The properties file must have the following format:
280 var_name<= or :>var_value
Michael Walsh410b1782019-10-22 15:56:18 -0500281 Comment lines (those beginning with a "#") and blank lines are allowed and will be ignored. Leading and
282 trailing single or double quotes will be stripped from the value. E.g.
Michael Walshde791732016-09-06 14:25:24 -0500283 var1="This one"
284 Quotes are stripped so the resulting value for var1 is:
285 This one
286
287 Description of arguments:
Michael Walsh410b1782019-10-22 15:56:18 -0500288 prop_file_path The caller should pass the path to the properties file.
Michael Walshde791732016-09-06 14:25:24 -0500289 """
290
Michael Walsh410b1782019-10-22 15:56:18 -0500291 # ConfigParser expects at least one section header in the file (or you get
292 # ConfigParser.MissingSectionHeaderError). Properties files don't need those so I'll write a dummy
293 # section header.
Michael Walshde791732016-09-06 14:25:24 -0500294
Michael Walshdece16c2018-08-07 15:01:05 -0500295 try:
296 string_file = StringIO.StringIO()
297 except NameError:
298 string_file = io.StringIO()
299
Michael Walshde791732016-09-06 14:25:24 -0500300 # Write the dummy section header to the string file.
Patrick Williams20f38712022-12-08 06:18:26 -0600301 string_file.write("[dummysection]\n")
Michael Walshde791732016-09-06 14:25:24 -0500302 # Write the entire contents of the properties file to the string file.
303 string_file.write(open(prop_file_path).read())
304 # Rewind the string file.
305 string_file.seek(0, os.SEEK_SET)
306
307 # Create the ConfigParser object.
Michael Walshdece16c2018-08-07 15:01:05 -0500308 try:
George Keishingb05a07a2025-03-13 22:38:50 +0530309 config_parser = ConfigParser()
Michael Walshdece16c2018-08-07 15:01:05 -0500310 except NameError:
George Keishing36efbc02018-12-12 10:18:23 -0600311 config_parser = configparser.ConfigParser(strict=False)
Michael Walshde791732016-09-06 14:25:24 -0500312 # Make the property names case-sensitive.
313 config_parser.optionxform = str
314 # Read the properties from the string file.
George Keishingb05a07a2025-03-13 22:38:50 +0530315 config_parser.read_file(string_file)
Michael Walshde791732016-09-06 14:25:24 -0500316 # Return the properties as a dictionary.
Michael Walshdece16c2018-08-07 15:01:05 -0500317 if robot_env:
Patrick Williams20f38712022-12-08 06:18:26 -0600318 return DotDict(config_parser.items("dummysection"))
Michael Walshdece16c2018-08-07 15:01:05 -0500319 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600320 return collections.OrderedDict(config_parser.items("dummysection"))
Michael Walshde791732016-09-06 14:25:24 -0500321
Michael Walsh7423c012016-10-04 10:27:21 -0500322
Patrick Williams20f38712022-12-08 06:18:26 -0600323def file_to_list(file_path, newlines=0, comments=1, trim=0):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600324 r"""
325 Return the contents of a file as a list. Each element of the resulting
326 list is one line from the file.
327
328 Description of arguments:
Michael Walsh410b1782019-10-22 15:56:18 -0500329 file_path The path to the file (relative or absolute).
330 newlines Include newlines from the file in the results.
331 comments Include comment lines and blank lines in the results. Comment lines are
332 any that begin with 0 or more spaces followed by the pound sign ("#").
333 trim Trim white space from the beginning and end of each line.
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600334 """
335
336 lines = []
337 file = open(file_path)
338 for line in file:
339 if not comments:
340 if re.match(r"[ ]*#|^$", line):
341 continue
342 if not newlines:
343 line = line.rstrip("\n")
344 if trim:
345 line = line.strip()
346 lines.append(line)
Michael Walsh1383f352018-09-27 16:25:54 -0500347 file.close()
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600348
349 return lines
350
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600351
Michael Walsh1383f352018-09-27 16:25:54 -0500352def file_to_str(*args, **kwargs):
353 r"""
354 Return the contents of a file as a string.
355
356 Description of arguments:
357 See file_to_list defined above for description of arguments.
358 """
359
Patrick Williams20f38712022-12-08 06:18:26 -0600360 return "\n".join(file_to_list(*args, **kwargs))
Michael Walsh1383f352018-09-27 16:25:54 -0500361
362
Michael Walsh111a8312019-12-05 16:48:06 -0600363def append_file(file_path, buffer):
364 r"""
365 Append the data in buffer to the file named in file_path.
366
367 Description of argument(s):
368 file_path The path to a file (e.g. "/tmp/root/file1").
369 buffer The buffer of data to be written to the file (e.g. "this and that").
370 """
371
372 with open(file_path, "a") as file:
373 file.write(buffer)
374
375
Michael Walsh7423c012016-10-04 10:27:21 -0500376def return_path_list():
Michael Walsh7423c012016-10-04 10:27:21 -0500377 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500378 This function will split the PATH environment variable into a PATH_LIST and return it. Each element in
379 the list will be normalized and have a trailing slash added.
Michael Walsh7423c012016-10-04 10:27:21 -0500380 """
381
Patrick Williams20f38712022-12-08 06:18:26 -0600382 PATH_LIST = os.environ["PATH"].split(":")
Michael Walsh7423c012016-10-04 10:27:21 -0500383 PATH_LIST = [os.path.normpath(path) + os.sep for path in PATH_LIST]
384
385 return PATH_LIST
386
Michael Walsh7db77942017-01-10 11:37:06 -0600387
Michael Walsh9fac55c2017-09-29 16:53:56 -0500388def escape_bash_quotes(buffer):
Michael Walsh9fac55c2017-09-29 16:53:56 -0500389 r"""
390 Escape quotes in string and return it.
391
392 The escape style implemented will be for use on the bash command line.
393
394 Example:
395 That's all.
396
397 Result:
398 That'\''s all.
399
400 The result may then be single quoted on a bash command. Example:
401
402 echo 'That'\''s all.'
403
404 Description of argument(s):
405 buffer The string whose quotes are to be escaped.
406 """
407
Patrick Williams20f38712022-12-08 06:18:26 -0600408 return re.sub("'", "'\\''", buffer)
Michael Walsh9fac55c2017-09-29 16:53:56 -0500409
410
Michael Walsh7db77942017-01-10 11:37:06 -0600411def quote_bash_parm(parm):
Michael Walsh7db77942017-01-10 11:37:06 -0600412 r"""
413 Return the bash command line parm with single quotes if they are needed.
414
415 Description of arguments:
416 parm The string to be quoted.
417 """
418
Michael Walsh410b1782019-10-22 15:56:18 -0500419 # If any of these characters are found in the parm string, then the string should be quoted. This list
420 # is by no means complete and should be expanded as needed by the developer of this function.
Michael Walsh9fc17c32019-01-21 14:49:10 -0600421 # Spaces
422 # Single or double quotes.
423 # Bash variables (therefore, any string with a "$" may need quoting).
424 # Glob characters: *, ?, []
425 # Extended Glob characters: +, @, !
426 # Bash brace expansion: {}
427 # Tilde expansion: ~
428 # Piped commands: |
429 # Bash re-direction: >, <
Patrick Williams20f38712022-12-08 06:18:26 -0600430 bash_special_chars = set(" '\"$*?[]+@!{}~|><")
Michael Walsh7db77942017-01-10 11:37:06 -0600431
432 if any((char in bash_special_chars) for char in parm):
Michael Walsh9fc17c32019-01-21 14:49:10 -0600433 return "'" + escape_bash_quotes(parm) + "'"
434
Patrick Williams20f38712022-12-08 06:18:26 -0600435 if parm == "":
Michael Walsh9fc17c32019-01-21 14:49:10 -0600436 parm = "''"
Michael Walsh7db77942017-01-10 11:37:06 -0600437
438 return parm
439
Michael Walsh1c85bab2017-05-04 14:29:24 -0500440
Patrick Williams20f38712022-12-08 06:18:26 -0600441def get_host_name_ip(host=None, short_name=0):
Michael Walsh1c85bab2017-05-04 14:29:24 -0500442 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500443 Get the host name and the IP address for the given host and return them as a tuple.
Michael Walsh1c85bab2017-05-04 14:29:24 -0500444
445 Description of argument(s):
Michael Walshd1b6c702017-05-30 17:54:30 -0500446 host The host name or IP address to be obtained.
Michael Walsh410b1782019-10-22 15:56:18 -0500447 short_name Include the short host name in the returned tuple, i.e. return host, ip
448 and short_host.
Michael Walsh1c85bab2017-05-04 14:29:24 -0500449 """
450
Igor Kanyukae44b3502025-10-07 17:58:55 +0100451 if not host:
452 host = socket.gethostname()
Michael Walshf74b3e42018-01-10 11:11:54 -0600453 host_name = socket.getfqdn(host)
Michael Walshd1b6c702017-05-30 17:54:30 -0500454 try:
Igor Kanyukae44b3502025-10-07 17:58:55 +0100455 host_ip = get_first_host_addr(host)
Michael Walshd1b6c702017-05-30 17:54:30 -0500456 except socket.gaierror as my_gaierror:
Patrick Williams20f38712022-12-08 06:18:26 -0600457 message = (
458 "Unable to obtain the host name for the following host:"
459 + "\n"
460 + gp.sprint_var(host)
461 )
Michael Walshd1b6c702017-05-30 17:54:30 -0500462 gp.print_error_report(message)
463 raise my_gaierror
Michael Walsh1c85bab2017-05-04 14:29:24 -0500464
Michael Walshf74b3e42018-01-10 11:11:54 -0600465 if short_name:
466 host_short_name = host_name.split(".")[0]
467 return host_name, host_ip, host_short_name
468 else:
469 return host_name, host_ip
Michael Walsh1c85bab2017-05-04 14:29:24 -0500470
Michael Walsheaa16852017-09-19 16:30:43 -0500471
Igor Kanyukae44b3502025-10-07 17:58:55 +0100472def get_first_host_addr(host):
473 # Prefer IPv4
474 addr_infos = sorted(
475 socket.getaddrinfo(host, 443, proto=socket.IPPROTO_TCP),
476 key=lambda x: x[0],
477 )
478 # socket.getaddrinfo returns a list of 5-tuples with the
479 # following structure:
480 # (family, type, proto, canonname, sockaddr)
481 # sockaddr structure is (ip, port)
482 # See: https://docs.python.org/3/library/socket.html#socket.getaddrinfo
483 # Take the first tuple for the first IP,
484 # then its 4th element - sockaddr and IP from it
485 return addr_infos[0][4][0]
486
487
Michael Walsheaa16852017-09-19 16:30:43 -0500488def pid_active(pid):
Michael Walsheaa16852017-09-19 16:30:43 -0500489 r"""
490 Return true if pid represents an active pid and false otherwise.
491
492 Description of argument(s):
493 pid The pid whose status is being sought.
494 """
495
496 try:
497 os.kill(int(pid), 0)
498 except OSError as err:
499 if err.errno == errno.ESRCH:
500 # ESRCH == No such process
501 return False
502 elif err.errno == errno.EPERM:
503 # EPERM clearly means there's a process to deny access to
504 return True
505 else:
506 # According to "man 2 kill" possible error values are
507 # (EINVAL, EPERM, ESRCH)
508 raise
509
510 return True
Michael Walsh112c3592018-06-01 14:15:58 -0500511
512
Patrick Williams20f38712022-12-08 06:18:26 -0600513def to_signed(number, bit_width=None):
Michael Walsh112c3592018-06-01 14:15:58 -0500514 r"""
515 Convert number to a signed number and return the result.
516
517 Examples:
518
519 With the following code:
520
521 var1 = 0xfffffffffffffff1
522 print_var(var1)
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500523 print_var(var1, hexa())
Michael Walsh112c3592018-06-01 14:15:58 -0500524 var1 = to_signed(var1)
525 print_var(var1)
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500526 print_var(var1, hexa())
Michael Walsh112c3592018-06-01 14:15:58 -0500527
528 The following is written to stdout:
529 var1: 18446744073709551601
530 var1: 0x00000000fffffffffffffff1
531 var1: -15
532 var1: 0xfffffffffffffff1
533
Michael Walsh410b1782019-10-22 15:56:18 -0500534 The same code but with var1 set to 0x000000000000007f produces the following:
Michael Walsh112c3592018-06-01 14:15:58 -0500535 var1: 127
536 var1: 0x000000000000007f
537 var1: 127
538 var1: 0x000000000000007f
539
540 Description of argument(s):
541 number The number to be converted.
Michael Walsh410b1782019-10-22 15:56:18 -0500542 bit_width The number of bits that defines a complete hex value. Typically, this
543 would be a multiple of 32.
Michael Walsh112c3592018-06-01 14:15:58 -0500544 """
545
Michael Walshdece16c2018-08-07 15:01:05 -0500546 if bit_width is None:
547 try:
548 bit_width = gp.bit_length(long(sys.maxsize)) + 1
549 except NameError:
550 bit_width = gp.bit_length(int(sys.maxsize)) + 1
551
Michael Walsh112c3592018-06-01 14:15:58 -0500552 if number < 0:
553 return number
Patrick Williams20f38712022-12-08 06:18:26 -0600554 neg_bit_mask = 2 ** (bit_width - 1)
Michael Walsh112c3592018-06-01 14:15:58 -0500555 if number & neg_bit_mask:
556 return ((2**bit_width) - number) * -1
557 else:
558 return number
Michael Walsh3b621fe2018-07-24 16:27:53 -0500559
560
561def get_child_pids(quiet=1):
Michael Walsh3b621fe2018-07-24 16:27:53 -0500562 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500563 Get and return a list of pids representing all first-generation processes that are the children of the
564 current process.
Michael Walsh3b621fe2018-07-24 16:27:53 -0500565
566 Example:
567
568 children = get_child_pids()
569 print_var(children)
570
571 Output:
572 children:
573 children[0]: 9123
574
575 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500576 quiet Display output to stdout detailing how this child pids are obtained.
Michael Walsh3b621fe2018-07-24 16:27:53 -0500577 """
578
579 if psutil_imported:
580 # If "import psutil" worked, find child pids using psutil.
581 current_process = psutil.Process()
582 return [x.pid for x in current_process.children(recursive=False)]
583 else:
584 # Otherwise, find child pids using shell commands.
585 print_output = not quiet
586
Patrick Williams20f38712022-12-08 06:18:26 -0600587 ps_cmd_buf = (
588 "ps --no-headers --ppid " + str(os.getpid()) + " -o pid,args"
589 )
Michael Walsh410b1782019-10-22 15:56:18 -0500590 # Route the output of ps to a temporary file for later grepping. Avoid using " | grep" in the ps
591 # command string because it creates yet another process which is of no interest to the caller.
Michael Walsh3b621fe2018-07-24 16:27:53 -0500592 temp = tempfile.NamedTemporaryFile()
593 temp_file_path = temp.name
Patrick Williams20f38712022-12-08 06:18:26 -0600594 gc.shell_cmd(
595 ps_cmd_buf + " > " + temp_file_path, print_output=print_output
596 )
Michael Walsh3b621fe2018-07-24 16:27:53 -0500597 # Sample contents of the temporary file:
598 # 30703 sleep 2
Michael Walsh410b1782019-10-22 15:56:18 -0500599 # 30795 /bin/bash -c ps --no-headers --ppid 30672 -o pid,args > /tmp/tmpqqorWY
600 # Use egrep to exclude the "ps" process itself from the results collected with the prior shell_cmd
601 # invocation. Only the other children are of interest to the caller. Use cut on the grep results to
602 # obtain only the pid column.
Patrick Williams20f38712022-12-08 06:18:26 -0600603 rc, output = gc.shell_cmd(
604 "egrep -v '"
605 + re.escape(ps_cmd_buf)
606 + "' "
607 + temp_file_path
608 + " | cut -c1-5",
609 print_output=print_output,
610 )
Michael Walsh410b1782019-10-22 15:56:18 -0500611 # Split the output buffer by line into a list. Strip each element of extra spaces and convert each
612 # element to an integer.
Michael Walsh3b621fe2018-07-24 16:27:53 -0500613 return map(int, map(str.strip, filter(None, output.split("\n"))))
Michael Walsh6aa69802018-09-21 16:38:34 -0500614
615
616def json_loads_multiple(buffer):
617 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500618 Convert the contents of the buffer to a JSON array, run json.loads() on it and return the result.
Michael Walsh6aa69802018-09-21 16:38:34 -0500619
620 The buffer is expected to contain one or more JSON objects.
621
622 Description of argument(s):
623 buffer A string containing several JSON objects.
624 """
625
Michael Walsh410b1782019-10-22 15:56:18 -0500626 # Any line consisting of just "}", which indicates the end of an object, should have a comma appended.
Michael Walsh6aa69802018-09-21 16:38:34 -0500627 regex = "([\\r\\n])[\\}]([\\r\\n])"
628 buffer = re.sub(regex, "\\1},\\2", buffer, 1)
Michael Walsh410b1782019-10-22 15:56:18 -0500629 # Remove the comma from after the final object and place the whole buffer inside square brackets.
Michael Walsh6aa69802018-09-21 16:38:34 -0500630 buffer = "[" + re.sub(",([\r\n])$", "\\1}", buffer, 1) + "]"
631 if gp.robot_env:
632 return json.loads(buffer, object_pairs_hook=DotDict)
633 else:
634 return json.loads(buffer, object_pairs_hook=collections.OrderedDict)
Michael Walshc9cb9722018-10-01 17:56:20 -0500635
636
637def file_date_time_stamp():
638 r"""
639 Return a date/time stamp in the following format: yymmdd.HHMMSS
640
Michael Walsh410b1782019-10-22 15:56:18 -0500641 This value is suitable for including in file names. Example file1.181001.171716.status
Michael Walshc9cb9722018-10-01 17:56:20 -0500642 """
643
644 return time.strftime("%y%m%d.%H%M%S", time.localtime(time.time()))
Michael Walshf5293d22019-02-01 14:23:02 -0600645
646
647def get_function_stack():
648 r"""
649 Return a list of all the function names currently in the call stack.
650
Michael Walsh410b1782019-10-22 15:56:18 -0500651 This function's name will be at offset 0. This function's caller's name will be at offset 1 and so on.
Michael Walshf5293d22019-02-01 14:23:02 -0600652 """
653
654 return [str(stack_frame[3]) for stack_frame in inspect.stack()]
Michael Walsh2ce1dba2019-02-05 19:29:28 +0000655
656
657def username():
658 r"""
659 Return the username for the current process.
660 """
661
662 username = os.environ.get("USER", "")
663 if username != "":
664 return username
665 user_num = str(os.geteuid())
666 try:
667 username = os.getlogin()
668 except OSError:
669 if user_num == "0":
670 username = "root"
671 else:
672 username = "?"
673
674 return username
Michael Walsh97081e82019-08-20 17:07:46 +0000675
676
677def version_tuple(version):
678 r"""
679 Convert the version string to a tuple and return it.
680
681 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500682 version A version string whose format is "n[.n]" (e.g. "3.6.3", "3", etc.).
Michael Walsh97081e82019-08-20 17:07:46 +0000683 """
684
685 return tuple(map(int, (version.split("."))))
686
687
Michael Walsh87353e32019-10-23 12:50:43 -0500688def get_python_version():
689 r"""
690 Get and return the python version.
691 """
692
693 sys_version = sys.version
694 # Strip out any revision code data (e.g. "3.6.3rc1" will become "3.6.3").
695 sys_version = re.sub("rc[^ ]+", "", sys_version).split(" ")[0]
696 # Remove any non-numerics, etc. (e.g. "2.7.15+" becomes ""2.7.15").
697 return re.sub("[^0-9\\.]", "", sys_version)
698
699
Patrick Williams20f38712022-12-08 06:18:26 -0600700python_version = version_tuple(get_python_version())
Michael Walsh97081e82019-08-20 17:07:46 +0000701ordered_dict_version = version_tuple("3.6")
Michael Walsh111a8312019-12-05 16:48:06 -0600702
703
704def create_temp_file_path(delim=":", suffix=""):
705 r"""
706 Create a temporary file path and return it.
707
708 This function is appropriate for users who with to create a temporary file and:
709 1) Have control over when and whether the file is deleted.
710 2) Have the name of the file indicate information such as program name, function name, line, pid, etc.
711 This can be an aid in debugging, cleanup, etc.
712
713 The dir path portion of the file path will be /tmp/<username>/. This function will create this directory
714 if it doesn't already exist.
715
716 This function will NOT create the file. The file will NOT automatically get deleted. It is the
717 responsibility of the caller to dispose of it.
718
719 Example:
720
721 pgm123.py is run by user 'joe'. It calls func1 which contains this code:
722
723 temp_file_path = create_temp_file_path(suffix='suffix1')
724 print_var(temp_file_path)
725
726 Output:
727
728 temp_file_path: /tmp/joe/pgm123.py:func1:line_55:pid_8199:831848:suffix1
729
730 Description of argument(s):
731 delim A delimiter to be used to separate the sub-components of the file name.
732 suffix A suffix to include as the last sub-component of the file name.
733 """
734
735 temp_dir_path = "/tmp/" + username() + "/"
736 try:
737 os.mkdir(temp_dir_path)
738 except FileExistsError:
739 pass
740
741 callers_stack_frame = inspect.stack()[1]
Patrick Williams20f38712022-12-08 06:18:26 -0600742 file_name_elements = [
743 gp.pgm_name,
744 callers_stack_frame.function,
745 "line_" + str(callers_stack_frame.lineno),
746 "pid_" + str(os.getpid()),
747 str(random.randint(0, 1000000)),
748 suffix,
749 ]
Michael Walsh111a8312019-12-05 16:48:06 -0600750 temp_file_name = delim.join(file_name_elements)
751
752 temp_file_path = temp_dir_path + temp_file_name
753
754 return temp_file_path
Michael Walsh05a7a6f2020-02-12 15:02:53 -0600755
756
757def pause(message="Hit enter to continue..."):
758 r"""
759 Print the message, with time stamp, and pause until the user hits enter.
760
761 Description of argument(s):
762 message The message to be printed to stdout.
763 """
764 gp.print_time(message)
765 try:
766 input()
767 except SyntaxError:
768 pass
769
770 return