Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/os_util.py : 59%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# This file is part of Xpra.
4# Copyright (C) 2013-2021 Antoine Martin <antoine@xpra.org>
5# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
6# later version. See the file COPYING for details.
8import re
9import os
10import sys
11import signal
12import uuid
13import time
14import struct
15import binascii
16import threading
18SIGNAMES = {}
19for signame in (sig for sig in dir(signal) if sig.startswith("SIG") and not sig.startswith("SIG_")):
20 SIGNAMES[getattr(signal, signame)] = signame
23WIN32 = sys.platform.startswith("win")
24OSX = sys.platform.startswith("darwin")
25LINUX = sys.platform.startswith("linux")
26NETBSD = sys.platform.startswith("netbsd")
27OPENBSD = sys.platform.startswith("openbsd")
28FREEBSD = sys.platform.startswith("freebsd")
30POSIX = os.name=="posix"
32BITS = struct.calcsize(b"P")*8
35main_thread = threading.current_thread()
36def is_main_thread():
37 return threading.current_thread()==main_thread
40def get_frame_info(ignore_threads=()):
41 info = {
42 "count" : threading.active_count() - len(ignore_threads),
43 }
44 try:
45 import traceback
46 def nn(x):
47 if x is None:
48 return ""
49 return str(x)
50 thread_ident = {}
51 for t in threading.enumerate():
52 if t not in ignore_threads:
53 thread_ident[t.ident] = t.getName()
54 else:
55 thread_ident[t.ident] = None
56 thread_ident.update({
57 threading.current_thread().ident : "info",
58 main_thread.ident : "main",
59 })
60 frames = sys._current_frames() #pylint: disable=protected-access
61 stack = None
62 for i,frame_pair in enumerate(frames.items()):
63 stack = traceback.extract_stack(frame_pair[1])
64 tident = thread_ident.get(frame_pair[0], "unknown")
65 if tident is None:
66 continue
67 #sanitize stack to prevent None values (which cause encoding errors with the bencoder)
68 sanestack = []
69 for e in stack:
70 sanestack.append(tuple([nn(x) for x in e]))
71 info[i] = {
72 "" : tident,
73 "stack" : sanestack,
74 }
75 del frames, stack
76 except Exception as e:
77 get_util_logger().error("failed to get frame info: %s", e)
78 return info
80def get_info_env():
81 filtered_env = os.environ.copy()
82 if filtered_env.get('XPRA_PASSWORD'):
83 filtered_env['XPRA_PASSWORD'] = "*****"
84 if filtered_env.get('XPRA_ENCRYPTION_KEY'):
85 filtered_env['XPRA_ENCRYPTION_KEY'] = "*****"
86 return filtered_env
88def get_sysconfig_info():
89 import sysconfig
90 sysinfo = {}
91 log = get_util_logger()
92 for attr in (
93 "platform",
94 "python-version",
95 "config-vars",
96 "paths",
97 ):
98 fn = "get_%s" % attr.replace("-", "_")
99 getter = getattr(sysconfig, fn, None)
100 if getter:
101 try:
102 sysinfo[attr] = getter() #pylint: disable=not-callable
103 except ModuleNotFoundError:
104 log("sysconfig.%s", fn, exc_info=True)
105 if attr=="config-vars" and WIN32:
106 continue
107 log.warn("Warning: failed to collect %s sysconfig information", attr)
108 except Exception:
109 log.error("Error calling sysconfig.%s", fn, exc_info=True)
110 return sysinfo
112def strtobytes(x) -> bytes:
113 if isinstance(x, bytes):
114 return x
115 return str(x).encode("latin1")
116def bytestostr(x) -> str:
117 if isinstance(x, (bytes, bytearray)):
118 return x.decode("latin1")
119 return str(x)
120def hexstr(v) -> str:
121 return bytestostr(binascii.hexlify(strtobytes(v)))
124util_logger = None
125def get_util_logger():
126 global util_logger
127 if not util_logger:
128 from xpra.log import Logger
129 util_logger = Logger("util")
130 return util_logger
132def memoryview_to_bytes(v) -> bytes:
133 if isinstance(v, bytes):
134 return v
135 if isinstance(v, memoryview):
136 return v.tobytes()
137 if isinstance(v, bytearray):
138 return bytes(v)
139 return strtobytes(v)
142def getuid() -> int:
143 if POSIX:
144 return os.getuid()
145 return 0
147def getgid() -> int:
148 if POSIX:
149 return os.getgid()
150 return 0
152def get_shell_for_uid(uid) -> str:
153 if POSIX:
154 from pwd import getpwuid
155 try:
156 return getpwuid(uid).pw_shell
157 except KeyError:
158 pass
159 return ""
161def get_username_for_uid(uid) -> str:
162 if POSIX:
163 from pwd import getpwuid
164 try:
165 return getpwuid(uid).pw_name
166 except KeyError:
167 pass
168 return ""
170def get_home_for_uid(uid) -> str:
171 if POSIX:
172 from pwd import getpwuid
173 try:
174 return getpwuid(uid).pw_dir
175 except KeyError:
176 pass
177 return ""
179def get_groups(username):
180 if POSIX:
181 import grp #@UnresolvedImport
182 return [gr.gr_name for gr in grp.getgrall() if username in gr.gr_mem]
183 return []
185def get_group_id(group) -> int:
186 try:
187 import grp #@UnresolvedImport
188 gr = grp.getgrnam(group)
189 return gr.gr_gid
190 except (ImportError, KeyError):
191 return -1
194def platform_release(release):
195 if OSX:
196 SYSTEMVERSION_PLIST = "/System/Library/CoreServices/SystemVersion.plist"
197 try:
198 import plistlib
199 with open(SYSTEMVERSION_PLIST, "rb") as f:
200 pl = plistlib.load(f) #@UndefinedVariable
201 return pl['ProductUserVisibleVersion']
202 except Exception as e:
203 get_util_logger().debug("platform_release(%s)", release, exc_info=True)
204 get_util_logger().warn("Warning: failed to get release information")
205 get_util_logger().warn(" from '%s':", SYSTEMVERSION_PLIST)
206 get_util_logger().warn(" %s", e)
207 return release
210def platform_name(sys_platform, release=None) -> str:
211 if not sys_platform:
212 return "unknown"
213 PLATFORMS = {"win32" : "Microsoft Windows",
214 "cygwin" : "Windows/Cygwin",
215 "linux.*" : "Linux",
216 "darwin" : "Mac OS X",
217 "freebsd.*": "FreeBSD",
218 "os2" : "OS/2",
219 }
220 def rel(v):
221 values = [v]
222 if isinstance(release, (tuple, list)):
223 values += list(release)
224 else:
225 values.append(release)
226 return " ".join([str(x) for x in values if x])
227 for k,v in PLATFORMS.items():
228 regexp = re.compile(k)
229 if regexp.match(sys_platform):
230 return rel(v)
231 return rel(sys_platform)
234def get_rand_chars(l=16, chars=b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") -> bytes:
235 import random
236 return b"".join(chars[random.randint(0, len(chars)-1):][:1] for _ in range(l))
238def get_hex_uuid() -> str:
239 return uuid.uuid4().hex
241def get_int_uuid() -> int:
242 return uuid.uuid4().int
244def get_machine_id() -> str:
245 """
246 Try to get uuid string which uniquely identifies this machine.
247 Warning: only works on posix!
248 (which is ok since we only used it on posix at present)
249 """
250 v = ""
251 if POSIX:
252 for filename in ("/etc/machine-id", "/var/lib/dbus/machine-id"):
253 v = load_binary_file(filename)
254 if v is not None:
255 break
256 elif WIN32:
257 v = uuid.getnode()
258 return bytestostr(v).strip("\n\r")
260def get_user_uuid() -> str:
261 """
262 Try to generate a uuid string which is unique to this user.
263 (relies on get_machine_id to uniquely identify a machine)
264 """
265 user_uuid = os.environ.get("XPRA_USER_UUID")
266 if user_uuid:
267 return user_uuid
268 import hashlib
269 u = hashlib.sha1()
270 def uupdate(ustr):
271 u.update(ustr.encode("utf-8"))
272 uupdate(get_machine_id())
273 if POSIX:
274 uupdate(u"/")
275 uupdate(str(os.getuid()))
276 uupdate(u"/")
277 uupdate(str(os.getgid()))
278 uupdate(os.path.expanduser("~/"))
279 return u.hexdigest()
282try:
283 from xpra.monotonic_time import _monotonic_time #@UnresolvedImport
284 assert _monotonic_time()>0
285 monotonic_time = _monotonic_time
286except (ImportError, AssertionError):
287 monotonic_time = time.time
290def is_X11() -> bool:
291 if OSX or WIN32:
292 return False
293 try:
294 from xpra.x11.gtk3.gdk_bindings import is_X11_Display #@UnresolvedImport
295 return is_X11_Display()
296 except ImportError:
297 get_util_logger().debug("failed to load x11 bindings", exc_info=True)
298 return True
300saved_env = os.environ.copy()
301def is_Wayland() -> bool:
302 return _is_Wayland(saved_env)
304def _is_Wayland(env : dict) -> bool:
305 backend = env.get("GDK_BACKEND", "")
306 if backend=="wayland":
307 return True
308 return backend!="x11" and (
309 bool(env.get("WAYLAND_DISPLAY")) or env.get("XDG_SESSION_TYPE")=="wayland"
310 )
313def is_distribution_variant(variant=b"Debian") -> bool:
314 if not POSIX:
315 return False
316 try:
317 v = load_os_release_file()
318 return any(l.find(variant)>=0 for l in v.splitlines() if l.startswith(b"NAME="))
319 except Exception:
320 pass
321 try:
322 if variant==b"RedHat" and get_linux_distribution()[0].startswith(variant):
323 return True
324 if get_linux_distribution()[0]==variant:
325 return True
326 except Exception:
327 pass
328 return False
330def get_distribution_version_id() -> bool:
331 if not POSIX:
332 return ""
333 try:
334 v = load_os_release_file()
335 for line in v.splitlines():
336 l = line.decode()
337 if l.startswith("VERSION_ID="):
338 return l.split("=", 1)[1].strip('"')
339 except Exception:
340 pass
341 return ""
343os_release_file_data = False
344def load_os_release_file() -> bytes:
345 global os_release_file_data
346 if os_release_file_data is False:
347 try:
348 os_release_file_data = load_binary_file("/etc/os-release")
349 except OSError: # pragma: no cover
350 os_release_file_data = None
351 return os_release_file_data
353def is_Ubuntu() -> bool:
354 return is_distribution_variant(b"Ubuntu")
356def is_Debian() -> bool:
357 return is_distribution_variant(b"Debian")
359def is_Raspbian() -> bool:
360 return is_distribution_variant(b"Raspbian")
362def is_Fedora() -> bool:
363 return is_distribution_variant(b"Fedora")
365def is_Arch() -> bool:
366 return is_distribution_variant(b"Arch")
368def is_CentOS() -> bool:
369 return is_distribution_variant(b"CentOS")
371def is_RedHat() -> bool:
372 return is_distribution_variant(b"RedHat")
375def is_arm() -> bool:
376 import platform
377 return platform.uname()[4].startswith("arm")
380_linux_distribution = None
381def get_linux_distribution():
382 global _linux_distribution
383 if LINUX and not _linux_distribution:
384 #linux_distribution is deprecated in Python 3.5 and it causes warnings,
385 #so use our own code first:
386 import subprocess
387 cmd = ["lsb_release", "-a"]
388 try:
389 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
390 out = p.communicate()[0]
391 assert p.returncode==0 and out
392 except Exception:
393 try:
394 import platform
395 _linux_distribution = platform.linux_distribution() #@UndefinedVariable, pylint: disable=deprecated-method, no-member
396 except Exception:
397 _linux_distribution = ("unknown", "unknown", "unknown")
398 else:
399 d = {}
400 for line in strtobytes(out).splitlines():
401 line = bytestostr(line)
402 parts = line.rstrip("\n\r").split(":", 1)
403 if len(parts)==2:
404 d[parts[0].lower().replace(" ", "_")] = parts[1].strip()
405 v = [d.get(x) for x in ("distributor_id", "release", "codename")]
406 if None not in v:
407 return tuple([bytestostr(x) for x in v])
408 return _linux_distribution
410def is_unity() -> bool:
411 d = os.environ.get("XDG_CURRENT_DESKTOP", "").lower()
412 return d.find("unity")>=0 or d.find("ubuntu")>=0
414def is_gnome() -> bool:
415 if os.environ.get("XDG_SESSION_DESKTOP", "").split("-", 1)[0] in ("i3", "ubuntu", ):
416 #"i3-gnome" is not really gnome... ie: the systray does work!
417 return False
418 return os.environ.get("XDG_CURRENT_DESKTOP", "").lower().find("gnome")>=0
420def is_kde() -> bool:
421 return os.environ.get("XDG_CURRENT_DESKTOP", "").lower().find("kde")>=0
424def get_loaded_kernel_modules(*modlist):
425 loaded = []
426 if LINUX and os.path.exists("/sys/module"):
427 for mod in modlist:
428 if os.path.exists("/sys/module/%s" % mod): # pragma: no cover
429 loaded.append(mod)
430 return loaded
433def is_WSL() -> bool:
434 if not POSIX:
435 return False
436 r = None
437 for f in ("/proc/sys/kernel/osrelease", "/proc/version"):
438 r = load_binary_file(f)
439 if r:
440 break
441 return r is not None and r.find(b"Microsoft")>=0
444def get_generic_os_name() -> str:
445 return do_get_generic_os_name().lower()
447def do_get_generic_os_name() -> str:
448 for k,v in {
449 "linux" : "Linux",
450 "darwin" : "MacOS",
451 "win" : "Win32",
452 "freebsd" : "FreeBSD",
453 }.items():
454 if sys.platform.startswith(k):
455 return v
456 return sys.platform # pragma: no cover
459def filedata_nocrlf(filename) -> str:
460 v = load_binary_file(filename)
461 if v is None:
462 get_util_logger().error("failed to load '%s'", filename)
463 return None
464 return v.strip(b"\n\r")
466def load_binary_file(filename) -> bytes:
467 if not os.path.exists(filename):
468 return None
469 try:
470 with open(filename, "rb") as f:
471 return f.read()
472 except Exception as e: # pragma: no cover
473 get_util_logger().warn("Warning: failed to load '%s':", filename)
474 get_util_logger().warn(" %s", e)
475 return None
477def parse_encoded_bin_data(data):
478 if not data:
479 return None
480 header = bytestostr(data).lower()[:10]
481 if header.startswith("0x"):
482 return binascii.unhexlify(data[2:])
483 import base64
484 if header.startswith("b64:"):
485 return base64.b64decode(data[4:])
486 if header.startswith("base64:"):
487 return base64.b64decode(data[7:])
488 try:
489 return binascii.unhexlify(data)
490 except (TypeError, binascii.Error):
491 try:
492 return base64.b64decode(data)
493 except Exception:
494 pass
495 return None
498#here so we can override it when needed
499def force_quit(status=1):
500 os._exit(status) #pylint: disable=protected-access
503def no_idle(fn, *args, **kwargs):
504 fn(*args, **kwargs)
505def register_SIGUSR_signals(idle_add=no_idle):
506 if not os.name=="posix":
507 return
508 from xpra.util import dump_all_frames, dump_gc_frames
509 def sigusr1(*_args):
510 log = get_util_logger().info
511 log("SIGUSR1")
512 idle_add(dump_all_frames, log)
513 def sigusr2(*_args):
514 log = get_util_logger().info
515 log("SIGUSR2")
516 idle_add(dump_gc_frames, log)
517 signal.signal(signal.SIGUSR1, sigusr1)
518 signal.signal(signal.SIGUSR2, sigusr2)
521def livefds():
522 live = set()
523 try:
524 MAXFD = os.sysconf("SC_OPEN_MAX")
525 except (ValueError, AttributeError):
526 MAXFD = 256
527 for fd in range(0, MAXFD):
528 try:
529 s = os.fstat(fd)
530 except Exception:
531 continue
532 else:
533 if s:
534 live.add(fd)
535 return live
537def get_all_fds():
538 fd_dirs = ["/dev/fd", "/proc/self/fd"]
539 fds = []
540 for fd_dir in fd_dirs:
541 if os.path.exists(fd_dir):
542 for fd_str in os.listdir(fd_dir):
543 try:
544 fd = int(fd_str)
545 fds.append(fd)
546 except OSError:
547 # This exception happens inevitably, because the fd used
548 # by listdir() is already closed.
549 pass
550 return fds
551 sys.stderr.write("Uh-oh, can't close fds, please port me to your system...\n")
552 return fds
554def close_all_fds(exceptions=()):
555 for fd in get_all_fds():
556 try:
557 if fd not in exceptions:
558 os.close(fd)
559 except OSError:
560 # This exception happens inevitably, because the fd used
561 # by listdir() is already closed.
562 pass
564def use_tty():
565 from xpra.util import envbool
566 if envbool("XPRA_NOTTY", False):
567 return False
568 from xpra.platform.gui import use_stdin
569 return use_stdin()
572def shellsub(s, subs=None):
573 """ shell style string substitution using the dictionary given """
574 if subs:
575 for var,value in subs.items():
576 try:
577 if isinstance(s, bytes):
578 s = s.replace(("$%s" % var).encode(), str(value).encode())
579 s = s.replace(("${%s}" % var).encode(), str(value).encode())
580 else:
581 s = s.replace("$%s" % var, str(value))
582 s = s.replace("${%s}" % var, str(value))
583 except (TypeError, ValueError):
584 raise Exception("failed to substitute '%s' with value '%s' (%s) in '%s'" % (
585 var, value, type(value), s)) from None
586 return s
589def osexpand(s, actual_username="", uid=0, gid=0, subs=None):
590 if not s:
591 return s
592 def expanduser(s):
593 if actual_username and s.startswith("~/"):
594 #replace "~/" with "~$actual_username/"
595 return os.path.expanduser("~%s/%s" % (actual_username, s[2:]))
596 return os.path.expanduser(s)
597 d = dict(subs or {})
598 d.update({
599 "PID" : os.getpid(),
600 "HOME" : expanduser("~/"),
601 })
602 if os.name=="posix":
603 d.update({
604 "UID" : uid or os.geteuid(),
605 "GID" : gid or os.getegid(),
606 })
607 if not OSX:
608 from xpra.platform.xposix.paths import get_runtime_dir
609 rd = get_runtime_dir()
610 if rd and "XDG_RUNTIME_DIR" not in os.environ:
611 d["XDG_RUNTIME_DIR"] = rd
612 if actual_username:
613 d["USERNAME"] = actual_username
614 d["USER"] = actual_username
615 #first, expand the substitutions themselves,
616 #as they may contain references to other variables:
617 ssub = {}
618 for k,v in d.items():
619 ssub[k] = expanduser(shellsub(str(v), d))
620 return os.path.expandvars(expanduser(shellsub(expanduser(s), ssub)))
623def path_permission_info(filename, ftype=None):
624 if not POSIX:
625 return []
626 info = []
627 try:
628 import stat
629 stat_info = os.stat(filename)
630 if not ftype:
631 ftype = "file"
632 if os.path.isdir(filename):
633 ftype = "directory"
634 info.append("permissions on %s %s: %s" % (ftype, filename, oct(stat.S_IMODE(stat_info.st_mode))))
635 import pwd
636 import grp #@UnresolvedImport
637 user = pwd.getpwuid(stat_info.st_uid)[0]
638 group = grp.getgrgid(stat_info.st_gid)[0]
639 info.append("ownership %s:%s" % (user, group))
640 except Exception as e:
641 info.append("failed to query path information for '%s': %s" % (filename, e))
642 return info
645#code to temporarily redirect stderr and restore it afterwards, adapted from:
646#http://stackoverflow.com/questions/5081657/how-do-i-prevent-a-c-shared-library-to-print-on-stdout-in-python
647#used by the sound code to get rid of the stupid gst warning below:
648#"** Message: pygobject_register_sinkfunc is deprecated (GstObject)"
649#ideally we would redirect to a buffer so we could still capture and show these messages in debug out
650class HideStdErr:
652 def __init__(self, *_args):
653 self.savedstderr = None
655 def __enter__(self):
656 if POSIX and os.getppid()==1:
657 #this interferes with server daemonizing?
658 return
659 sys.stderr.flush() # <--- important when redirecting to files
660 self.savedstderr = os.dup(2)
661 devnull = os.open(os.devnull, os.O_WRONLY)
662 os.dup2(devnull, 2)
663 os.close(devnull)
664 sys.stderr = os.fdopen(self.savedstderr, 'w')
666 def __exit__(self, *_args):
667 if self.savedstderr is not None:
668 os.dup2(self.savedstderr, 2)
670class HideSysArgv:
672 def __init__(self, *_args):
673 self.savedsysargv = None
675 def __enter__(self):
676 self.savedsysargv = sys.argv
677 sys.argv = sys.argv[:1]
679 def __exit__(self, *_args):
680 if self.savedsysargv is not None:
681 sys.argv = self.savedsysargv
684class OSEnvContext:
686 def __init__(self):
687 self.env = os.environ.copy()
688 def __enter__(self):
689 pass
690 def __exit__(self, *_args):
691 os.environ.clear()
692 os.environ.update(self.env)
693 def __repr__(self):
694 return "OSEnvContext"
697class FDChangeCaptureContext:
699 def __init__(self):
700 self.enter_fds = []
701 self.exit_fds = []
702 def __enter__(self):
703 self.enter_fds = get_all_fds()
704 def __exit__(self, *_args):
705 self.exit_fds = get_all_fds()
706 def __repr__(self):
707 return "FDChangeCaptureContext"
708 def get_new_fds(self):
709 return sorted(tuple(set(self.exit_fds)-set(self.enter_fds)))
710 def get_lost_fds(self):
711 return sorted(tuple(set(self.enter_fds)-set(self.exit_fds)))
713class DummyContextManager:
715 def __enter__(self):
716 pass
717 def __exit__(self, *_args):
718 pass
719 def __repr__(self):
720 return "DummyContextManager"
723#workaround incompatibility between paramiko and gssapi:
724class nomodule_context:
726 def __init__(self, module_name):
727 self.module_name = module_name
728 def __enter__(self):
729 self.saved_module = sys.modules.get(self.module_name)
730 sys.modules[self.module_name] = None
731 def __exit__(self, *_args):
732 if sys.modules.get(self.module_name) is None:
733 if self.saved_module is None:
734 sys.modules.pop(self.module_name, None)
735 else:
736 sys.modules[self.module_name] = self.saved_module
737 def __repr__(self):
738 return "nomodule_context(%s)" % self.module_name
740class umask_context:
742 def __init__(self, umask):
743 self.umask = umask
744 def __enter__(self):
745 self.orig_umask = os.umask(self.umask)
746 def __exit__(self, *_args):
747 os.umask(self.orig_umask)
748 def __repr__(self):
749 return "umask_context(%s)" % self.umask
752def disable_stdout_buffering():
753 import gc
754 # Appending to gc.garbage is a way to stop an object from being
755 # destroyed. If the old sys.stdout is ever collected, it will
756 # close() stdout, which is not good.
757 gc.garbage.append(sys.stdout)
758 sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
760def setbinarymode(fd):
761 if WIN32:
762 #turn on binary mode:
763 try:
764 import msvcrt
765 msvcrt.setmode(fd, os.O_BINARY) #@UndefinedVariable pylint: disable=no-member
766 except OSError:
767 get_util_logger().error("setting stdin to binary mode failed", exc_info=True)
769def find_lib_ldconfig(libname):
770 libname = re.escape(libname)
772 arch_map = {"x86_64": "libc6,x86-64"}
773 arch = arch_map.get(os.uname()[4], "libc6")
775 pattern = r'^\s+lib%s\.[^\s]+ \(%s(?:,.*?)?\) => (.*lib%s[^\s]+)' % (libname, arch, libname)
777 #try to find ldconfig first, which may not be on the $PATH
778 #(it isn't on Debian..)
779 ldconfig = "ldconfig"
780 for d in ("/sbin", "/usr/sbin"):
781 t = os.path.join(d, "ldconfig")
782 if os.path.exists(t):
783 ldconfig = t
784 break
785 import subprocess
786 p = subprocess.Popen([ldconfig, "-p"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
787 data = bytestostr(p.communicate()[0])
789 libpath = re.search(pattern, data, re.MULTILINE) #@UndefinedVariable
790 if libpath:
791 libpath = libpath.group(1)
792 return libpath
794def find_lib(libname):
795 #it would be better to rely on dlopen to find the paths
796 #but I cannot find a way of getting ctypes to tell us the path
797 #it found the library in
798 assert POSIX
799 libpaths = os.environ.get("LD_LIBRARY_PATH", "").split(":")
800 libpaths.append("/usr/lib64")
801 libpaths.append("/usr/lib")
802 for libpath in libpaths:
803 if not libpath or not os.path.exists(libpath):
804 continue
805 libname_so = os.path.join(libpath, libname)
806 if os.path.exists(libname_so):
807 return libname_so
808 return None
811def pollwait(process, timeout=5):
812 start = monotonic_time()
813 v = None
814 while monotonic_time()-start<timeout:
815 v = process.poll()
816 if v is not None:
817 break
818 time.sleep(0.1)
819 return v
821def which(command):
822 from distutils.spawn import find_executable
823 try:
824 return find_executable(command)
825 except Exception:
826 get_util_logger().debug("find_executable(%s)", command, exc_info=True)
827 return None
829def get_status_output(*args, **kwargs):
830 import subprocess
831 kwargs["stdout"] = subprocess.PIPE
832 kwargs["stderr"] = subprocess.PIPE
833 try:
834 p = subprocess.Popen(*args, **kwargs)
835 except Exception as e:
836 print("error running %s,%s: %s" % (args, kwargs, e))
837 return -1, "", ""
838 stdout, stderr = p.communicate()
839 return p.returncode, stdout.decode("utf-8"), stderr.decode("utf-8")
842def is_systemd_pid1() -> bool:
843 if not POSIX:
844 return False
845 d = load_binary_file("/proc/1/cmdline")
846 return d and d.find(b"/systemd")>=0
849def get_ssh_port() -> int:
850 #on Linux we can run "ssh -T | grep port"
851 #but this usually requires root permissions to access /etc/ssh/sshd_config
852 if WIN32:
853 return 0
854 return 22
857def setuidgid(uid, gid):
858 if not POSIX:
859 return
860 log = get_util_logger()
861 if os.getuid()!=uid or os.getgid()!=gid:
862 #find the username for the given uid:
863 from pwd import getpwuid
864 try:
865 username = getpwuid(uid).pw_name
866 except KeyError:
867 raise Exception("uid %i not found" % uid) from None
868 #set the groups:
869 if hasattr(os, "initgroups"): # python >= 2.7
870 os.initgroups(username, gid)
871 else:
872 import grp #@UnresolvedImport
873 groups = [gr.gr_gid for gr in grp.getgrall() if username in gr.gr_mem]
874 os.setgroups(groups)
875 #change uid and gid:
876 try:
877 if os.getgid()!=gid:
878 os.setgid(gid)
879 except OSError as e:
880 log.error("Error: cannot change gid to %i:", gid)
881 if os.getgid()==0:
882 #don't run as root!
883 raise
884 log.error(" %s", e)
885 log.error(" continuing with gid=%i", os.getgid())
886 try:
887 if os.getuid()!=uid:
888 os.setuid(uid)
889 except OSError as e:
890 log.error("Error: cannot change uid to %i:", uid)
891 if os.getuid()==0:
892 #don't run as root!
893 raise
894 log.error(" %s", e)
895 log.error(" continuing with uid=%i", os.getuid())
896 log("new uid=%s, gid=%s", os.getuid(), os.getgid())
898def get_peercred(sock):
899 if LINUX:
900 SO_PEERCRED = 17
901 log = get_util_logger()
902 try:
903 import socket
904 creds = sock.getsockopt(socket.SOL_SOCKET, SO_PEERCRED, struct.calcsize(b'3i'))
905 pid, uid, gid = struct.unpack(b'3i',creds)
906 log("peer: %s", (pid, uid, gid))
907 return pid, uid, gid
908 except IOError as e:
909 log("getsockopt", exc_info=True)
910 log.error("Error getting peer credentials: %s", e)
911 return None
912 elif FREEBSD:
913 log.warn("Warning: peercred is not yet implemented for FreeBSD")
914 #use getpeereid
915 #then pwd to get the gid?
916 return None