Salome HOME
updated copyright message
[modules/kernel.git] / bin / PortManager.py
1 #!/usr/bin/env python3
2 #  -*- coding: iso-8859-1 -*-
3 # Copyright (C) 2007-2023  CEA, EDF, OPEN CASCADE
4 #
5 # Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
6 # CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
7 #
8 # This library is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU Lesser General Public
10 # License as published by the Free Software Foundation; either
11 # version 2.1 of the License, or (at your option) any later version.
12 #
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 # Lesser General Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public
19 # License along with this library; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
21 #
22 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
23 #
24 import os
25 import sys
26 import psutil
27
28 from socket import AF_INET, SOCK_STREAM
29
30 import pickle
31
32 __PORT_MIN_NUMBER = 2810
33 __PORT_MAX_NUMBER = 2910
34
35 import logging
36 def createLogger():
37   logger = logging.getLogger(__name__)
38   #logger.setLevel(logging.DEBUG)
39   logger.setLevel(logging.INFO)
40   ch = logging.StreamHandler()
41   ch.setLevel(logging.DEBUG)
42   formatter = logging.Formatter("%(levelname)s:%(threadName)s:%(pathname)s[%(lineno)s]%(message)s")
43   ch.setFormatter(formatter)
44   logger.addHandler(ch)
45   return logger
46 #
47 logger = createLogger()
48
49 #------------------------------------
50 # A file locker
51 def __acquire_lock(lock):
52   logger.debug("ACQUIRE LOCK")
53   if sys.platform == "win32":
54     import msvcrt
55     # lock 1 byte: file is supposed to be zero-byte long
56     msvcrt.locking(lock.fileno(), msvcrt.LK_LOCK, 1)
57   else:
58     import fcntl
59     fcntl.flock(lock, fcntl.LOCK_EX)
60   logger.debug("LOCK ACQUIRED")
61 #
62 def __release_lock(lock):
63   logger.debug("RELEASE LOCK")
64   if sys.platform == "win32":
65     import msvcrt
66     msvcrt.locking(lock.fileno(), msvcrt.LK_UNLCK, 1)
67   else:
68     import fcntl
69     fcntl.flock(lock, fcntl.LOCK_UN)
70   logger.debug("LOCK RELEASED")
71 #
72 #------------------------------------
73
74 def _getConfigurationFilename():
75   omniorbUserPath = os.getenv("OMNIORB_USER_PATH")
76
77   from salome_utils import generateFileName
78   portmanager_config = generateFileName(omniorbUserPath,
79                                         prefix="salome",
80                                         suffix="PortManager",
81                                         extension="cfg",
82                                         hidden=True)
83   import tempfile
84   temp = tempfile.NamedTemporaryFile()
85   lock_file = os.path.join(os.path.dirname(temp.name), ".salome", ".PortManager.lock")
86   try:
87     oldmask = os.umask(0)
88     os.makedirs(os.path.dirname(lock_file))
89   except IOError:
90     pass
91   finally:
92     os.umask(oldmask)
93   temp.close()
94
95   return (portmanager_config, lock_file)
96 #
97
98 def __isPortUsed(port, config):
99   busy_ports = []
100   for ports in config.values():
101     busy_ports += ports
102   return (port in busy_ports) or __isNetworkConnectionActiveOnPort(port)
103 #
104
105 def __isNetworkConnectionActiveOnPort(port):
106   # psutil realization
107   return port in [c.laddr.port for c in psutil.net_connections(kind='inet') if \
108       (c.family, c.type, c.status) == (AF_INET, SOCK_STREAM, "LISTEN")]
109   #
110
111 def getPort(preferredPort=None):
112   logger.debug("GET PORT")
113
114   config_file, lock_file = _getConfigurationFilename()
115   oldmask = os.umask(0)
116   with open(lock_file, 'wb') as lock:
117     # acquire lock
118     __acquire_lock(lock)
119
120     # read config
121     config = {}
122     logger.debug("read configuration file")
123     try:
124       with open(config_file, 'rb') as f:
125         config = pickle.load(f)
126     except Exception:
127       logger.debug("Problem loading PortManager file: %s"%config_file)
128       # In this case config dictionary is reset
129
130     logger.debug("load config: %s"%str(config))
131     appli_path = os.getenv("ABSOLUTE_APPLI_PATH", "unknown")
132     try:
133         config[appli_path]
134     except KeyError:
135         config[appli_path] = []
136
137     # append port
138     port = preferredPort
139     if not port or __isPortUsed(port, config):
140       port = __PORT_MIN_NUMBER
141       while __isPortUsed(port, config):
142         if port == __PORT_MAX_NUMBER:
143           msg  = "\n"
144           msg += "Can't find a free port to launch omniNames\n"
145           msg += "Try to kill the running servers and then launch SALOME again.\n"
146           raise RuntimeError(msg)
147         logger.debug("Port %s seems to be busy"%str(port))
148         port = port + 1
149     logger.debug("found free port: %s"%str(port))
150     config[appli_path].append(port)
151
152     # write config
153     logger.debug("write config: %s"%str(config))
154     try:
155       with open(config_file, 'wb') as f:
156         pickle.dump(config, f, protocol=0)
157     except IOError:
158       pass
159
160     # release lock
161     __release_lock(lock)
162   #
163
164   os.umask(oldmask)
165   logger.debug("get port: %s"%str(port))
166   return port
167 #
168
169 def releasePort(port):
170   port = int(port)
171   logger.debug("RELEASE PORT (%s)"%port)
172
173   config_file, lock_file = _getConfigurationFilename()
174   oldmask = os.umask(0)
175   with open(lock_file, 'wb') as lock:
176     # acquire lock
177     __acquire_lock(lock)
178
179     # read config
180     config = {}
181     logger.debug("read configuration file")
182     try:
183       with open(config_file, 'rb') as f:
184         config = pickle.load(f)
185     except IOError: # empty file
186       pass
187
188     logger.debug("load config: %s"%str(config))
189     appli_path = os.getenv("ABSOLUTE_APPLI_PATH", "unknown")
190     try:
191         config[appli_path]
192     except KeyError:
193         config[appli_path] = []
194
195     # remove port from list
196     ports_info = config[appli_path]
197     config[appli_path] = [x for x in ports_info if x != port]
198
199     # write config
200     logger.debug("write config: %s"%str(config))
201     try:
202       with open(config_file, 'wb') as f:
203         pickle.dump(config, f, protocol=0)
204     except IOError:
205       pass
206
207     # release lock
208     __release_lock(lock)
209
210     logger.debug("released port port: %s"%str(port))
211
212   os.umask(oldmask)
213 #
214
215 def getBusyPorts():
216   config_file, lock_file = _getConfigurationFilename()
217   oldmask = os.umask(0)
218   with open(lock_file, 'wb') as lock:
219     # acquire lock
220     __acquire_lock(lock)
221
222     # read config
223     config = {}
224     logger.debug("read configuration file")
225     try:
226       with open(config_file, 'rb') as f:
227         config = pickle.load(f)
228     except IOError: # empty file
229       pass
230
231     logger.debug("load config: %s"%str(config))
232     appli_path = os.getenv("ABSOLUTE_APPLI_PATH", "unknown")
233     try:
234         config[appli_path]
235     except KeyError:
236         config[appli_path] = []
237
238     # Scan all possible ports to determine which ones are owned by other applications
239     ports_info = { 'this': [], 'other': [] }
240     my_busy_ports = config[appli_path]
241     for port in range(__PORT_MIN_NUMBER, __PORT_MAX_NUMBER):
242       if __isPortUsed(port, config):
243         logger.debug("Port %s seems to be busy"%str(port))
244         if port in my_busy_ports:
245           ports_info["this"].append(port)
246         else:
247           ports_info["other"].append(port)
248
249     logger.debug("all busy_ports: %s"%str(ports_info))
250
251     sorted_ports = { 'this': sorted(ports_info['this']),
252                      'other': sorted(ports_info['other']) }
253
254     # release lock
255     __release_lock(lock)
256
257   os.umask(oldmask)
258   return sorted_ports
259 #