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 from StringIO 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