Salome HOME
End of moving Odyssee in SSL mode
[modules/kernel.git] / bin / salome_utils.py
1 #  -*- coding: iso-8859-1 -*-
2 # Copyright (C) 2007-2021  CEA/DEN, EDF R&D, OPEN CASCADE
3 #
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2.1 of the License, or (at your option) any later version.
8 #
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 # Lesser General Public License for more details.
13 #
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
17 #
18 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
19 #
20
21 ## @package salome_utils
22 #  @brief Set of utility functions used by SALOME python scripts.
23
24 """
25 Various utilities for SALOME.
26 """
27
28 # pragma pylint: disable=invalid-name
29
30 import os
31 import os.path as osp
32 import re
33 import shutil
34 import socket
35 import sys
36 import tempfile
37 from contextlib import suppress
38
39 import psutil
40
41 def _try_bool(arg):
42     """
43     Convert given `arg` to a boolean value.
44     String values like 'True', 'TRUE', 'YES', 'Yes', 'y', 'NO', 'false', 'n', etc.
45     are supported.
46     If `arg` does not represent a boolean, an exception is raised.
47     :param arg : value being converted
48     :return result of conversion: `True` or `False`
49     """
50     if isinstance(arg, bool):
51         return arg
52     if isinstance(arg, bytes):
53         arg = arg.decode('utf-8', errors='ignore')
54     if isinstance(arg, str):
55         if arg.lower() in ('yes', 'y', 'true', 't'):
56             return True
57         if arg.lower() in ('no', 'n', 'false', 'f'):
58             return False
59     raise ValueError('Not a boolean value')
60
61 # ---
62
63 def getORBcfgInfo():
64     """
65     Get current omniORB configuration.
66
67     The information is retrieved from the omniORB configuration file defined
68     by the OMNIORB_CONFIG environment variable.
69     If omniORB configuration file can not be accessed, a tuple of three empty
70     strings is returned.
71
72     :return tuple of three strings: (orb_version, host_name, port_number)
73     """
74     orb_version = ''
75     hostname = ''
76     port_number = ''
77     with suppress(Exception), open(os.getenv('OMNIORB_CONFIG')) as forb:
78         regvar = re.compile(r'(ORB)?InitRef.*corbaname::(.*):(\d+)\s*$')
79         for line in forb.readlines():
80             match = regvar.match(line)
81             if match:
82                 orb_version = '4' if match.group(1) is None else '3'
83                 hostname = match.group(2)
84                 port_number = match.group(3)
85                 break
86     return orb_version, hostname, port_number
87
88 # ---
89
90 def getHostFromORBcfg():
91     """
92     Get current omniORB host name.
93     :return host name
94     """
95     return getORBcfgInfo()[1]
96
97 # ---
98
99 def getPortFromORBcfg():
100     """
101     Get current omniORB port.
102     :return port number
103     """
104     return getORBcfgInfo()[2]
105
106 # ---
107
108 def getUserName():
109     """
110     Get user name.
111
112     The following procedure is perfomed to deduce user name:
113     1. try USER (USERNAME on Windows) environment variable.
114     2. if (1) fails, try LOGNAME (un*x only).
115     3. if (2) fails, return 'unknown' as default user name
116
117     :return user name
118     """
119     return os.getenv('USERNAME', 'unknown') if sys.platform == 'win32' \
120         else os.getenv('USER', os.getenv('LOGNAME', 'unknown'))
121
122 # ---
123
124 def getHostName():
125     """
126     Get host name.
127
128     The following procedure is perfomed to deduce host name:
129     1. try socket python module, gethostname() function
130     2. if (1) fails, try HOSTNAME environment variable
131     3. if (2) fails, try HOST environment variable
132     4. if (3) fails, tries 'unknown' as default host name
133     5. finally, checks that IP is configured for hostname; if not, returns 'localhost'
134
135     :return host name
136     """
137     host = None
138     with suppress(Exception):
139         host = socket.gethostname()
140     if not host:
141         host = os.getenv('HOSTNAME', os.getenv('HOST', 'unknown'))
142     try:
143         # the following line just checks that IP is configured for hostname
144         socket.gethostbyname(host)
145     except (TypeError, OSError):
146         host = 'localhost'
147     return host
148
149 # ---
150
151 def getShortHostName():
152     """
153     Get short host name (with domain stripped).
154     See `getHostName()` for more details.
155     :return short host name
156     """
157     with suppress(AttributeError, IndexError):
158         return getHostName().split('.')[0]
159     return 'unknown' # default host name
160
161 # ---
162
163 def getAppName():
164     """
165     Get application name.
166     The following procedure is perfomed to deduce application name:
167     1. try APPNAME environment variable
168     2. if (1) fails, return 'SALOME' as default application name
169     :return application name
170     """
171     return os.getenv('APPNAME', 'SALOME') # 'SALOME' is default user name
172
173 def getPid():
174     return os.getpid()
175
176 # ---
177
178 def getPortNumber(use_default=True):
179     """
180     Get currently used omniORB port.
181     The following procedure is perfomed to deduce port number:
182     1. try NSPORT environment variable
183     2. if (1) fails, try to parse config file defined by OMNIORB_CONFIG environment variable
184     3. if (2) fails, return 2809 as default port number (if use_default is `True`) or `None`
185        (if use_default is `False`)
186     :return port number
187     """
188     with suppress(TypeError, ValueError):
189         return int(os.getenv('NSPORT'))
190     with suppress(TypeError, ValueError):
191         port = int(getPortFromORBcfg())
192         if port:
193             return port
194     return 2809 if use_default else None
195
196 # ---
197
198 def getHomeDir():
199     """
200     Get home directory.
201     :return home directory path
202     """
203     return osp.realpath(osp.expanduser('~'))
204
205 # ---
206
207 def getLogDir():
208     """
209     Get directory that stores log files.
210     :return path to the log directory
211     """
212     return osp.join(getTmpDir(), 'logs', getUserName())
213
214 # ---
215
216 def getTmpDir():
217     """
218     Get directory to store temporary files.
219     :return temporary directory path
220     """
221     with tempfile.NamedTemporaryFile() as tmp:
222         return osp.dirname(tmp.name)
223     return None
224
225 # ---
226
227 # pragma pylint: disable=too-many-arguments
228 def generateFileName(path, prefix=None, suffix=None, extension=None,
229                      unique=False, separator='_', hidden=False, **kwargs):
230     """
231     Generate file name.
232
233     :param path      : directory path
234     :param prefix    : file name prefix (none by default)
235     :param suffix    : file name suffix (none by default)
236     :param extension : file extension (none by default)
237     :param unique    : if `True`, function generates unique file name -
238                        in this case, if file with the generated name already
239                        exists in `path` directory, an integer suffix is appended
240                        to the file name (`False` by default)
241     :param separator : words separator ('_' by default)
242     :param hidden    : if `True`, file name is prepended with dot symbol
243                        (`False` by default)
244     :param kwargs    : additional keywrods arguments (see below)
245     :return generated file name
246
247     Additionally supported keyword parameters:
248     - with_username : use user name:
249     - with_hostname : use host name:
250     - with_port : use port number:
251     - with_app      : use application name:
252     - with_pid      : use current pid
253
254     Any of these keyword arguments can accept either explicit string value,
255     or `True` to automatically deduce value from current configuration.
256     """
257     filename = []
258
259     def _with_str(_str):
260         _str = '' if _str is None else str(_str)
261         if _str:
262             filename.append(_str)
263
264     def _with_kwarg(_kwarg, _func):
265         _value = kwargs.get(_kwarg, False)
266         try:
267             if _try_bool(_value):
268                 filename.append(str(_func()))
269         except ValueError:
270             _with_str(_value)
271
272     _with_str(prefix)
273     _with_kwarg('with_username', getUserName)
274     _with_kwarg('with_hostname', getShortHostName)
275     _with_kwarg('with_port', getPortNumber)
276     _with_kwarg('with_app', getAppName)
277     _with_kwarg('with_pid', getPid)
278     _with_str(suffix)
279
280     # raise an exception if file name is empty
281     if not filename:
282         raise ValueError('Empty file name')
283
284     # extension
285     extension = '' if extension is None else str(extension)
286     if extension.startswith('.'):
287         extension = extension[1:]
288
289     # separator
290     separator = '' if separator is None else str(separator)
291
292     def _generate(_index=None):
293         # join all components together, add index if necessary
294         if _index is not None:
295             _name = separator.join(filename+[str(_index)])
296         else:
297             _name = separator.join(filename)
298         # prepend with dot if necessary
299         if hidden:
300             _name = '.' + _name
301         # append extension if ncessary
302         if extension:
303             _name = _name + '.' + extension
304         # now get full path
305         return osp.join(path, _name)
306
307     name = _generate()
308     if unique:
309         index = 0
310         while osp.exists(name):
311             index = index + 1
312             name = _generate(index)
313     return osp.normpath(name)
314
315 # ---
316
317 def cleanDir(path):
318     """
319     Clear contents of directory.
320     :param path directory path
321     """
322     if osp.exists(path):
323         for filename in os.listdir(path):
324             file_path = osp.join(path, filename)
325             with suppress(OSError):
326                 if osp.isdir(file_path):
327                     shutil.rmtree(file_path)
328                 else:
329                     os.unlink(file_path)
330
331 # ---
332
333 def makeDir(path, mode=0o777):
334     """
335     Make directory with the specified path.
336     :param path : directory path
337     :param mode : access mode
338     """
339     try:
340         oldmask = os.umask(0)
341         os.makedirs(path, mode=mode, exist_ok=True)
342     except IOError:
343         pass
344     finally:
345         os.umask(oldmask)
346
347 # ---
348
349 def makeTmpDir(path, mode=0o777):
350     """
351     Make temporary directory with the specified path.
352     If the directory exists, clear all its contents.
353     :param path : directory path
354     :param mode : access mode
355     """
356     makeDir(path, mode)
357     cleanDir(path)
358
359 # ---
360
361 def uniteFiles(src_file, dest_file):
362     """
363     Join contents of `src_file` and `dest_file` and put result to `dest_file`.
364     File `dest_file` may not exist.
365     :param src_file  : source file path
366     :param dest_file : destination file path
367     """
368     if not osp.exists(src_file):
369         return
370
371     if osp.exists(dest_file):
372         with suppress(OSError), open(src_file, 'rb') as src, open(dest_file, 'ab') as dest:
373             dest.write(b'\n')
374             dest.write(src.read())
375     else:
376         with suppress(OSError):
377             shutil.copy(src_file, dest_file)
378
379 # --
380
381 def verbose():
382     """
383     Get current verbosity level.
384
385     Default verbosity level is specified via the environment variable SALOME_VERBOSE,
386     e.g. in bash:
387
388         $ export SALOME_VERBOSE=1
389
390     The function `setVerbose()` can be used to explicitly set verbosity level.
391
392     :return current verbosity level
393     """
394     if not hasattr(verbose, 'verbosity_level'):
395         verbose.verbosity_level = 0 # default value
396         with suppress(TypeError, ValueError):
397             # from SALOME_VERBOSE environment variable
398             verbose.verbosity_level = int(os.getenv('SALOME_VERBOSE', '0'))
399     return verbose.verbosity_level
400 # --
401
402 def setVerbose(level):
403     """
404     Change verbosity level.
405     The function `verbose()` can be used to get current verbosity level.
406     :param level : verbosity level
407     """
408     with suppress(TypeError, ValueError):
409         verbose.verbosity_level = int(level)
410 # --
411
412 def killPid(pid, sig=9):
413     """
414     Send signal `sig` to the process with given `pid`.
415
416     :param pid : PID of the process
417     :param sig : signal to send; some of possible values:
418        - 9 : kill process
419        - 0 : do nothing, just check process existence (see below)
420        NOTE: other values are not processed on Windows
421     :return result of execution:
422        -  1 : success
423        -  0 : fail, no such process
424        - -1 : fail, another reason
425     """
426     if not pid:
427         return -1
428
429     with suppress(ValueError):
430         pid = int(pid)
431
432     if sig == 0:
433         ret = 1 if psutil.pid_exists(pid) else 0
434     else:
435         if verbose():
436             print("######## killPid pid = ", pid)
437         try:
438             process = psutil.Process(pid)
439             process.terminate()
440             _, alive = psutil.wait_procs([process], timeout=5)
441             for proc in alive:
442                 proc.kill()
443             ret = 1
444         except psutil.NoSuchProcess:
445             ret = 0
446         except OSError:
447             ret = -1
448     return ret
449 # --
450
451 def getOmniNamesPid(port):
452     """
453     Get PID of omniNames process running on given `port`.
454     :param port : port number
455     :return omniNames process's PID
456     """
457     processes = {p.info['pid']: p.info['name'] for p in psutil.process_iter(['pid', 'name'])}
458     return next((c.pid for c in psutil.net_connections(kind='inet') \
459                      if str(c.laddr.port) == str(port) and processes.get(c.pid).startswith('omniNames')), None)
460 # --
461
462 def killOmniNames(port):
463     """
464     Kill omniNames process running on given `port`.
465     :param port : port number
466     """
467     with suppress(Exception):
468         killPid(getOmniNamesPid(port))
469 # --