Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/server/server_util.py : 27%
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# This file is part of Xpra.
2# Copyright (C) 2017-2020 Antoine Martin <antoine@xpra.org>
3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
4# later version. See the file COPYING for details.
6import sys
7import os.path
9from xpra.util import envbool
10from xpra.os_util import OSX, POSIX, shellsub, getuid, get_util_logger, osexpand, umask_context
11from xpra.platform.dotxpra import norm_makepath
12from xpra.scripts.config import InitException
15def source_env(source=()) -> dict:
16 log = get_util_logger()
17 env = {}
18 for f in source:
19 e = env_from_sourcing(f)
20 log("source_env %s=%s", f, e)
21 env.update(e)
22 return env
25# credit: https://stackoverflow.com/a/47080959/428751
26# returns a dictionary of the environment variables resulting from sourcing a file
27def env_from_sourcing(file_to_source_path, include_unexported_variables=False):
28 import json
29 import subprocess
30 source = '%ssource %s' % ("set -a && " if include_unexported_variables else "", file_to_source_path)
31 dump = '/usr/bin/python -c "import os, json;print(json.dumps(dict(os.environ)))"'
32 pipe = subprocess.Popen(['/bin/bash', '-c', '%s && %s' % (source, dump)], stdout=subprocess.PIPE)
33 return json.loads(pipe.stdout.read())
36def sh_quotemeta(s):
37 return b"'" + s.replace(b"'", b"'\\''") + b"'"
39def xpra_runner_shell_script(xpra_file, starting_dir, socket_dir):
40 script = []
41 script.append(b"#!/bin/sh\n")
42 for var, value in os.environb.items():
43 # these aren't used by xpra, and some should not be exposed
44 # as they are either irrelevant or simply do not match
45 # the new environment used by xpra
46 # TODO: use a whitelist
47 if var in (b"XDG_SESSION_COOKIE", b"LS_COLORS", b"DISPLAY"):
48 continue
49 #XPRA_SOCKET_DIR is a special case, it is handled below
50 if var==b"XPRA_SOCKET_DIR":
51 continue
52 if var.startswith(b"BASH_FUNC"):
53 #some versions of bash will apparently generate functions
54 #that cannot be reloaded using this script
55 continue
56 # :-separated envvars that people might change while their server is
57 # going:
58 if var in (b"PATH", b"LD_LIBRARY_PATH", b"PYTHONPATH"):
59 #prevent those paths from accumulating the same values multiple times,
60 #only keep the first one:
61 pathsep = os.pathsep.encode()
62 pval = value.split(pathsep) #ie: ["/usr/bin", "/usr/local/bin", "/usr/bin"]
63 seen = set()
64 value = pathsep.join(x for x in pval if not (x in seen or seen.add(x)))
65 script.append(b"%s=%s:\"$%s\"; export %s\n"
66 % (var, sh_quotemeta(value), var, var))
67 else:
68 script.append(b"%s=%s; export %s\n"
69 % (var, sh_quotemeta(value), var))
70 #XPRA_SOCKET_DIR is a special case, we want to honour it
71 #when it is specified, but the client may override it:
72 if socket_dir:
73 script.append(b'if [ -z "${XPRA_SOCKET_DIR}" ]; then\n')
74 script.append(b' XPRA_SOCKET_DIR="%s"; export XPRA_SOCKET_DIR\n' % sh_quotemeta(os.path.expanduser(socket_dir).encode()))
75 script.append(b'fi\n')
76 # We ignore failures in cd'ing, b/c it's entirely possible that we were
77 # started from some temporary directory and all paths are absolute.
78 script.append(b"cd %s\n" % sh_quotemeta(starting_dir.encode()))
79 if OSX:
80 #OSX contortions:
81 #The executable is the python interpreter,
82 #which is execed by a shell script, which we have to find..
83 sexec = sys.executable
84 bini = sexec.rfind("Resources/bin/")
85 if bini>0:
86 sexec = os.path.join(sexec[:bini], "Resources", "MacOS", "Xpra")
87 script.append(b"_XPRA_SCRIPT=%s\n" % (sh_quotemeta(sexec.encode()),))
88 script.append(b"""
89if which "$_XPRA_SCRIPT" > /dev/null; then
90 # Happypath:
91 exec "$_XPRA_SCRIPT" "$@"
92else
93 # Hope for the best:
94 exec Xpra "$@"
95fi
96""")
97 else:
98 script.append(b"_XPRA_PYTHON=%s\n" % (sh_quotemeta(sys.executable.encode()),))
99 script.append(b"_XPRA_SCRIPT=%s\n" % (sh_quotemeta(xpra_file.encode()),))
100 script.append(b"""
101if which "$_XPRA_PYTHON" > /dev/null && [ -e "$_XPRA_SCRIPT" ]; then
102 # Happypath:
103 exec "$_XPRA_PYTHON" "$_XPRA_SCRIPT" "$@"
104else
105 cat >&2 <<END
106 Could not find one or both of '$_XPRA_PYTHON' and '$_XPRA_SCRIPT'
107 Perhaps your environment has changed since the xpra server was started?
108 I'll just try executing 'xpra' with current PATH, and hope...
109END
110 exec xpra "$@"
111fi
112""")
113 return b"".join(script)
115def write_runner_shell_scripts(contents, overwrite=True):
116 assert POSIX
117 # This used to be given a display-specific name, but now we give it a
118 # single fixed name and if multiple servers are started then the last one
119 # will clobber the rest. This isn't great, but the tradeoff is that it
120 # makes it possible to use bare 'ssh:hostname' display names and
121 # autodiscover the proper numeric display name when only one xpra server
122 # is running on the remote host. Might need to revisit this later if
123 # people run into problems or autodiscovery turns out to be less useful
124 # than expected.
125 log = get_util_logger()
126 MODE = 0o700
127 from xpra.platform.paths import get_script_bin_dirs
128 for d in get_script_bin_dirs():
129 scriptdir = osexpand(d)
130 if not os.path.exists(scriptdir):
131 try:
132 os.mkdir(scriptdir, MODE)
133 except Exception as e:
134 log("os.mkdir(%s, %s)", scriptdir, oct(MODE), exc_info=True)
135 log.warn("Warning: failed to create script directory '%s':", scriptdir)
136 log.warn(" %s", e)
137 if scriptdir.startswith("/var/run/user") or scriptdir.startswith("/run/user"):
138 log.warn(" ($XDG_RUNTIME_DIR has not been created?)")
139 continue
140 scriptpath = os.path.join(scriptdir, "run-xpra")
141 if os.path.exists(scriptpath) and not overwrite:
142 continue
143 # Write out a shell-script so that we can start our proxy in a clean
144 # environment:
145 try:
146 with umask_context(0o022):
147 h = os.open(scriptpath, os.O_WRONLY|os.O_CREAT|os.O_TRUNC, MODE)
148 try:
149 os.write(h, contents)
150 finally:
151 os.close(h)
152 except Exception as e:
153 log("writing to %s", scriptpath, exc_info=True)
154 log.error("Error: failed to write script file '%s':", scriptpath)
155 log.error(" %s\n", e)
158def find_log_dir(username="", uid=0, gid=0):
159 from xpra.platform.paths import get_default_log_dirs
160 errs = []
161 for x in get_default_log_dirs():
162 v = osexpand(x, username, uid, gid)
163 if not os.path.exists(v):
164 if getuid()==0 and uid!=0:
165 continue
166 try:
167 os.mkdir(v, 0o700)
168 except Exception as e:
169 errs.append((v, e))
170 continue
171 return v
172 for d, e in errs:
173 sys.stderr.write("Error: cannot create log directory '%s':" % d)
174 sys.stderr.write(" %s\n" % e)
175 return None
178def open_log_file(logpath):
179 """ renames the existing log file if it exists,
180 then opens it for writing.
181 """
182 if os.path.exists(logpath):
183 try:
184 os.rename(logpath, logpath + ".old")
185 except OSError:
186 pass
187 try:
188 return os.open(logpath, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o644)
189 except OSError as e:
190 raise InitException("cannot open log file '%s': %s" % (logpath, e)) from None
192def select_log_file(log_dir, log_file, display_name):
193 """ returns the log file path we should be using given the parameters,
194 this may return a temporary logpath if display_name is not available.
195 """
196 if log_file:
197 if os.path.isabs(log_file):
198 logpath = log_file
199 else:
200 logpath = os.path.join(log_dir, log_file)
201 v = shellsub(logpath, {"DISPLAY" : display_name})
202 if display_name or v==logpath:
203 #we have 'display_name', or we just don't need it:
204 return v
205 if display_name:
206 logpath = norm_makepath(log_dir, display_name) + ".log"
207 else:
208 logpath = os.path.join(log_dir, "tmp_%d.log" % os.getpid())
209 return logpath
211def redirect_std_to_log(logfd, *noclose_fds):
212 from xpra.os_util import close_all_fds
213 # save current stdout/stderr to be able to print info
214 # before exiting the non-deamon process
215 # and closing those file descriptors definitively
216 old_fd_stdout = os.dup(1)
217 old_fd_stderr = os.dup(2)
218 close_all_fds(exceptions=[logfd, old_fd_stdout, old_fd_stderr]+list(noclose_fds))
219 fd0 = os.open("/dev/null", os.O_RDONLY)
220 if fd0 != 0:
221 os.dup2(fd0, 0)
222 os.close(fd0)
223 # reopen STDIO files
224 stdout = os.fdopen(old_fd_stdout, "w", 1)
225 stderr = os.fdopen(old_fd_stderr, "w", 1)
226 # replace standard stdout/stderr by the log file
227 os.dup2(logfd, 1)
228 os.dup2(logfd, 2)
229 os.close(logfd)
230 # Make these line-buffered:
231 sys.stdout = os.fdopen(1, "w", 1)
232 sys.stderr = os.fdopen(2, "w", 1)
233 return stdout, stderr
236def daemonize():
237 os.chdir("/")
238 if os.fork():
239 os._exit(0) #pylint: disable=protected-access
240 os.setsid()
241 if os.fork():
242 os._exit(0) #pylint: disable=protected-access
245def write_pidfile(pidfile):
246 log = get_util_logger()
247 pidstr = str(os.getpid())
248 inode = 0
249 try:
250 with open(pidfile, "w") as f:
251 os.fchmod(f.fileno(), 0o600)
252 f.write("%s\n" % pidstr)
253 try:
254 inode = os.fstat(f.fileno()).st_ino
255 except OSError:
256 inode = 0
257 log.info("wrote pid %s to '%s'", pidstr, pidfile)
258 except Exception as e:
259 log.error("Error: failed to write pid %i to pidfile '%s':", os.getpid(), pidfile)
260 log.error(" %s", e)
261 return inode
263def rm_pidfile(pidfile, inode):
264 #verify this is the right file!
265 log = get_util_logger()
266 log("cleanuppidfile(%s, %s)", pidfile, inode)
267 if inode>0:
268 try:
269 i = os.stat(pidfile).st_ino
270 log("cleanuppidfile: current inode=%i", i)
271 if i!=inode:
272 return 0
273 except OSError:
274 pass
275 try:
276 os.unlink(pidfile)
277 except OSError:
278 log("rm_pidfile(%s, %s)", pidfile, inode, exc_info=True)
279 return 0
282def get_uinput_device_path(device):
283 log = get_util_logger()
284 try:
285 log("get_uinput_device_path(%s)", device)
286 fd = device._Device__uinput_fd
287 log("fd(%s)=%s", device, fd)
288 import fcntl #@UnresolvedImport
289 import ctypes
290 l = 16
291 buf = ctypes.create_string_buffer(l)
292 #this magic value was calculated using the C macros:
293 l = fcntl.ioctl(fd, 2148554028, buf)
294 if 0<l<16:
295 virt_dev_path = buf.raw[:l].rstrip(b"\0")
296 log("UI_GET_SYSNAME(%s)=%s", fd, virt_dev_path)
297 uevent_path = b"/sys/devices/virtual/input/%s" % virt_dev_path
298 event_dirs = [x for x in os.listdir(uevent_path) if x.startswith(b"event")]
299 log("event dirs(%s)=%s", uevent_path, event_dirs)
300 for d in event_dirs:
301 uevent_filename = os.path.join(uevent_path, d, b"uevent")
302 uevent_conf = open(uevent_filename, "rb").read()
303 for line in uevent_conf.splitlines():
304 if line.find(b"=")>0:
305 k,v = line.split(b"=", 1)
306 log("%s=%s", k, v)
307 if k==b"DEVNAME":
308 dev_path = b"/dev/%s" % v
309 log("found device path: %s" % dev_path)
310 return dev_path
311 except Exception as e:
312 log("get_uinput_device_path(%s)", device, exc_info=True)
313 log.error("Error: cannot query uinput device path:")
314 log.error(" %s", e)
315 return None
317def has_uinput():
318 try:
319 import uinput
320 assert uinput
321 except NameError as e:
322 log = get_util_logger()
323 log("has_uinput()", exc_info=True)
324 log.warn("Warning: the system python uinput module looks broken:")
325 log.warn(" %s", e)
326 return False
327 except ImportError as e:
328 log = get_util_logger()
329 log("has_uinput()", exc_info=True)
330 log.info("cannot access python uinput module:")
331 log.info(" %s", e)
332 return False
333 try:
334 uinput.fdopen() #@UndefinedVariable
335 except Exception as e:
336 log = get_util_logger()
337 log("has_uinput()", exc_info=True)
338 log.info("cannot use uinput for virtual devices:")
339 log.info(" %s", e)
340 return False
341 return True
343def create_uinput_device(uuid, uid, events, name):
344 log = get_util_logger()
345 import uinput
346 BUS_USB = 0x03
347 #BUS_VIRTUAL = 0x06
348 VENDOR = 0xffff
349 PRODUCT = 0x1000
350 #our xpra_udev_product_version script will use the version attribute to set
351 #the udev OWNER value
352 VERSION = uid
353 try:
354 device = uinput.Device(events, name=name, bustype=BUS_USB, vendor=VENDOR, product=PRODUCT, version=VERSION)
355 except OSError as e:
356 log("uinput.Device creation failed", exc_info=True)
357 if os.getuid()==0:
358 #running as root, this should work!
359 log.error("Error: cannot open uinput,")
360 log.error(" make sure that the kernel module is loaded")
361 log.error(" and that the /dev/uinput device exists:")
362 log.error(" %s", e)
363 else:
364 log.info("cannot access uinput: %s", e)
365 return None
366 dev_path = get_uinput_device_path(device)
367 if not dev_path:
368 device.destroy()
369 return None
370 return name, device, dev_path
372def create_uinput_pointer_device(uuid, uid):
373 if not envbool("XPRA_UINPUT_POINTER", True):
374 return None
375 from uinput import (
376 REL_X, REL_Y, REL_WHEEL, #@UnresolvedImport
377 BTN_LEFT, BTN_RIGHT, BTN_MIDDLE, BTN_SIDE, #@UnresolvedImport
378 BTN_EXTRA, BTN_FORWARD, BTN_BACK, #@UnresolvedImport
379 )
380 events = (
381 REL_X, REL_Y, REL_WHEEL,
382 BTN_LEFT, BTN_RIGHT, BTN_MIDDLE, BTN_SIDE,
383 BTN_EXTRA, BTN_FORWARD, BTN_BACK,
384 )
385 #REL_HIRES_WHEEL = 0x10
386 #uinput.REL_HWHEEL,
387 name = "Xpra Virtual Pointer %s" % uuid
388 return create_uinput_device(uuid, uid, events, name)
390def create_uinput_touchpad_device(uuid, uid):
391 if not envbool("XPRA_UINPUT_TOUCHPAD", False):
392 return None
393 from uinput import (
394 BTN_TOUCH, ABS_X, ABS_Y, ABS_PRESSURE, #@UnresolvedImport
395 )
396 events = (
397 BTN_TOUCH,
398 ABS_X + (0, 2**24-1, 0, 0),
399 ABS_Y + (0, 2**24-1, 0, 0),
400 ABS_PRESSURE + (0, 255, 0, 0),
401 #BTN_TOOL_PEN,
402 )
403 name = "Xpra Virtual Touchpad %s" % uuid
404 return create_uinput_device(uuid, uid, events, name)
407def create_uinput_devices(uinput_uuid, uid):
408 log = get_util_logger()
409 try:
410 import uinput
411 assert uinput
412 except (ImportError, NameError) as e:
413 log.error("Error: cannot access python uinput module:")
414 log.error(" %s", e)
415 return {}
416 pointer = create_uinput_pointer_device(uinput_uuid, uid)
417 touchpad = create_uinput_touchpad_device(uinput_uuid, uid)
418 if not pointer and not touchpad:
419 return {}
420 def i(device):
421 if not device:
422 return {}
423 name, uinput_pointer, dev_path = device
424 return {
425 "name" : name,
426 "uinput" : uinput_pointer,
427 "device" : dev_path,
428 }
429 return {
430 "pointer" : i(pointer),
431 "touchpad" : i(touchpad),
432 }
434def create_input_devices(uinput_uuid, uid):
435 return create_uinput_devices(uinput_uuid, uid)