Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/scripts/server.py : 54%
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# -*- coding: utf-8 -*-
2# This file is part of Xpra.
3# Copyright (C) 2010-2020 Antoine Martin <antoine@xpra.org>
4# Copyright (C) 2008 Nathaniel Smith <njs@pobox.com>
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.
8# DO NOT IMPORT GTK HERE: see
9# http://lists.partiwm.org/pipermail/parti-discuss/2008-September/000041.html
10# http://lists.partiwm.org/pipermail/parti-discuss/2008-September/000042.html
11# (also do not import anything that imports gtk)
12import sys
13import os.path
14import atexit
15import traceback
17from xpra.scripts.main import info, warn, no_gtk, validate_encryption, parse_env, configure_env
18from xpra.scripts.config import InitException, FALSE_OPTIONS, parse_bool
19from xpra.common import CLOBBER_USE_DISPLAY, CLOBBER_UPGRADE
20from xpra.exit_codes import EXIT_VFB_ERROR, EXIT_OK
21from xpra.os_util import (
22 SIGNAMES, POSIX, WIN32, OSX,
23 FDChangeCaptureContext,
24 force_quit,
25 get_username_for_uid, get_home_for_uid, get_shell_for_uid, getuid, setuidgid,
26 get_hex_uuid, get_status_output, strtobytes, bytestostr, get_util_logger, osexpand,
27 )
28from xpra.util import envbool, unsetenv, noerr
29from xpra.platform.dotxpra import DotXpra
32_cleanups = []
33def run_cleanups():
34 global _cleanups
35 cleanups = _cleanups
36 _cleanups = []
37 for c in cleanups:
38 try:
39 c()
40 except Exception:
41 print("error running cleanup %s" % c)
42 traceback.print_exception(*sys.exc_info())
44_when_ready = []
46def add_when_ready(f):
47 _when_ready.append(f)
49def add_cleanup(f):
50 _cleanups.append(f)
53def deadly_signal(signum):
54 info("got deadly signal %s, exiting\n" % SIGNAMES.get(signum, signum))
55 run_cleanups()
56 # This works fine in tests, but for some reason if I use it here, then I
57 # get bizarre behavior where the signal handler runs, and then I get a
58 # KeyboardException (?!?), and the KeyboardException is handled normally
59 # and exits the program (causing the cleanup handlers to be run again):
60 #signal.signal(signum, signal.SIG_DFL)
61 #kill(os.getpid(), signum)
62 force_quit(128 + signum)
65def _root_prop_set(prop_name, ptype="u32", value=0):
66 from xpra.gtk_common.gtk_util import get_default_root_window
67 from xpra.x11.gtk_x11.prop import prop_set
68 prop_set(get_default_root_window(), prop_name, ptype, value)
69def _root_prop_get(prop_name, ptype="u32"):
70 from xpra.gtk_common.gtk_util import get_default_root_window
71 from xpra.x11.gtk_x11.prop import prop_get
72 try:
73 return prop_get(get_default_root_window(), prop_name, ptype)
74 except Exception:
75 return None
77def _save_int(prop_name, pid):
78 _root_prop_set(prop_name, "u32", pid)
80def _get_int(prop_name):
81 return _root_prop_get(prop_name, "u32")
83def _save_str(prop_name, s):
84 _root_prop_set(prop_name, "latin1", s)
86def _get_str(prop_name):
87 v = _root_prop_get(prop_name, "latin1")
88 if v is not None:
89 return v.encode("latin1")
90 return v
93def save_xvfb_pid(pid):
94 _save_int("_XPRA_SERVER_PID", pid)
96def get_xvfb_pid():
97 return _get_int("_XPRA_SERVER_PID")
100def save_uinput_id(uuid):
101 _save_str("_XPRA_UINPUT_ID", (uuid or b"").decode())
103#def get_uinput_id():
104# return _get_str("_XPRA_UINPUT_ID")
107def validate_pixel_depth(pixel_depth, starting_desktop=False):
108 try:
109 pixel_depth = int(pixel_depth)
110 except ValueError:
111 raise InitException("invalid value '%s' for pixel depth, must be a number" % pixel_depth) from None
112 if pixel_depth==0:
113 pixel_depth = 24
114 if pixel_depth not in (8, 16, 24, 30):
115 raise InitException("invalid pixel depth: %s" % pixel_depth)
116 if not starting_desktop and pixel_depth==8:
117 raise InitException("pixel depth 8 is only supported in 'start-desktop' mode")
118 return pixel_depth
121def display_name_check(display_name):
122 """ displays a warning
123 when a low display number is specified """
124 if not display_name.startswith(":"):
125 return
126 n = display_name[1:].split(".")[0] #ie: ":0.0" -> "0"
127 try:
128 dno = int(n)
129 if 0<=dno<10:
130 warn("WARNING: low display number: %s" % dno)
131 warn(" You are attempting to run the xpra server")
132 warn(" against a low X11 display number: '%s'." % (display_name,))
133 warn(" This is generally not what you want.")
134 warn(" You should probably use a higher display number")
135 warn(" just to avoid any confusion and this warning message.")
136 except IOError:
137 pass
140def print_DE_warnings():
141 de = os.environ.get("XDG_SESSION_DESKTOP") or os.environ.get("SESSION_DESKTOP")
142 if not de:
143 return
144 log = get_util_logger()
145 log.warn("Warning: xpra start from an existing '%s' desktop session", de)
146 log.warn(" without using dbus-launch,")
147 log.warn(" notifications forwarding may not work")
148 log.warn(" try using a clean environment, a dedicated user,")
149 log.warn(" or disable xpra's notifications option")
152def sanitize_env():
153 #we don't want client apps to think these mean anything:
154 #(if set, they belong to the desktop the server was started from)
155 #TODO: simply whitelisting the env would be safer/better
156 unsetenv("DESKTOP_SESSION",
157 "GDMSESSION",
158 "GNOME_DESKTOP_SESSION_ID",
159 "SESSION_MANAGER",
160 "XDG_VTNR",
161 #we must keep this value on Debian / Ubuntu
162 #to avoid breaking menu loading:
163 #"XDG_MENU_PREFIX",
164 "XDG_CURRENT_DESKTOP",
165 "XDG_SESSION_DESKTOP",
166 "XDG_SESSION_TYPE",
167 "XDG_SESSION_ID",
168 "XDG_SEAT",
169 "XDG_VTNR",
170 "QT_GRAPHICSSYSTEM_CHECKED",
171 "CKCON_TTY",
172 "CKCON_X11_DISPLAY",
173 "CKCON_X11_DISPLAY_DEVICE",
174 "WAYLAND_DISPLAY",
175 )
177def configure_imsettings_env(input_method):
178 im = (input_method or "").lower()
179 if im in ("none", "no"):
180 #the default: set DISABLE_IMSETTINGS=1, fallback to xim
181 #that's because the 'ibus' 'immodule' breaks keyboard handling
182 #unless its daemon is also running - and we don't know if it is..
183 imsettings_env(True, "xim", "xim", "xim", "none", "@im=none")
184 elif im=="keep":
185 #do nothing and keep whatever is already set, hoping for the best
186 pass
187 elif im in ("xim", "ibus", "scim", "uim"):
188 #ie: (False, "ibus", "ibus", "IBus", "@im=ibus")
189 imsettings_env(True, im.lower(), im.lower(), im.lower(), im, "@im=%s" % im.lower())
190 else:
191 v = imsettings_env(True, im.lower(), im.lower(), im.lower(), im, "@im=%s" % im.lower())
192 warn("using input method settings: %s" % str(v))
193 warn("unknown input method specified: %s" % input_method)
194 warn(" if it is correct, you may want to file a bug to get it recognized")
196def imsettings_env(disabled, gtk_im_module, qt_im_module, clutter_im_module, imsettings_module, xmodifiers):
197 #for more information, see imsettings:
198 #https://code.google.com/p/imsettings/source/browse/trunk/README
199 if disabled is True:
200 os.environ["DISABLE_IMSETTINGS"] = "1" #this should override any XSETTINGS too
201 elif disabled is False and ("DISABLE_IMSETTINGS" in os.environ):
202 del os.environ["DISABLE_IMSETTINGS"]
203 v = {
204 "GTK_IM_MODULE" : gtk_im_module, #or "gtk-im-context-simple"?
205 "QT_IM_MODULE" : qt_im_module, #or "simple"?
206 "QT4_IM_MODULE" : qt_im_module,
207 "CLUTTER_IM_MODULE" : clutter_im_module,
208 "IMSETTINGS_MODULE" : imsettings_module, #or "xim"?
209 "XMODIFIERS" : xmodifiers,
210 #not really sure what to do with those:
211 #"IMSETTINGS_DISABLE_DESKTOP_CHECK" : "true", #
212 #"IMSETTINGS_INTEGRATE_DESKTOP" : "no"} #we're not a real desktop
213 }
214 os.environ.update(v)
215 return v
217def create_runtime_dir(xrd, uid, gid):
218 if not POSIX or OSX or getuid()!=0 or (uid==0 and gid==0):
219 return
220 #workarounds:
221 #* some distros don't set a correct value,
222 #* or they don't create the directory for us,
223 #* or pam_open is going to create the directory but needs time to do so..
224 if xrd and xrd.endswith("/user/0"):
225 #don't keep root's directory, as this would not work:
226 xrd = None
227 if not xrd:
228 #find the "/run/user" directory:
229 run_user = "/run/user"
230 if not os.path.exists(run_user):
231 run_user = "/var/run/user"
232 if os.path.exists(run_user):
233 xrd = os.path.join(run_user, str(uid))
234 if not xrd:
235 return None
236 if not os.path.exists(xrd):
237 os.mkdir(xrd, 0o700)
238 os.lchown(xrd, uid, gid)
239 xpra_dir = os.path.join(xrd, "xpra")
240 if not os.path.exists(xpra_dir):
241 os.mkdir(xpra_dir, 0o700)
242 os.lchown(xpra_dir, uid, gid)
243 return xrd
246def guess_xpra_display(socket_dir, socket_dirs):
247 dotxpra = DotXpra(socket_dir, socket_dirs)
248 results = dotxpra.sockets()
249 live = [display for state, display in results if state==DotXpra.LIVE]
250 if not live:
251 raise InitException("no existing xpra servers found")
252 if len(live)>1:
253 raise InitException("too many existing xpra servers found, cannot guess which one to use")
254 return live[0]
257def show_encoding_help(opts):
258 #avoid errors and warnings:
259 opts.encoding = ""
260 opts.clipboard = False
261 opts.notifications = False
262 print("xpra server supports the following encodings:")
263 print("(please wait, encoder initialization may take a few seconds)")
264 #disable info logging which would be confusing here
265 from xpra.log import get_all_loggers, set_default_level
266 import logging
267 set_default_level(logging.WARN)
268 logging.root.setLevel(logging.WARN)
269 for x in get_all_loggers():
270 if x.logger.getEffectiveLevel()==logging.INFO:
271 x.logger.setLevel(logging.WARN)
272 from xpra.server.server_base import ServerBase
273 sb = ServerBase()
274 sb.init(opts)
275 from xpra.codecs.codec_constants import PREFERRED_ENCODING_ORDER, HELP_ORDER
276 if "help" in opts.encodings:
277 sb.allowed_encodings = PREFERRED_ENCODING_ORDER
278 from xpra.server.mixins.encoding_server import EncodingServer
279 assert isinstance(sb, EncodingServer)
280 EncodingServer.threaded_setup(sb)
281 from xpra.codecs.loader import encoding_help
282 for e in (x for x in HELP_ORDER if x in sb.encodings):
283 print(" * %s" % encoding_help(e))
284 return 0
287def set_server_features(opts):
288 def b(v):
289 return str(v).lower() not in FALSE_OPTIONS
290 #turn off some server mixins:
291 from xpra.server import server_features
292 impwarned = []
293 def impcheck(*modules):
294 for mod in modules:
295 try:
296 __import__("xpra.%s" % mod, {}, {}, [])
297 except ImportError:
298 if mod not in impwarned:
299 impwarned.append(mod)
300 log = get_util_logger()
301 log.warn("Warning: missing %s module", mod)
302 return False
303 return True
304 server_features.notifications = opts.notifications and impcheck("notifications")
305 server_features.webcam = b(opts.webcam) and impcheck("codecs")
306 server_features.clipboard = b(opts.clipboard) and impcheck("clipboard")
307 server_features.audio = (b(opts.speaker) or b(opts.microphone)) and impcheck("sound")
308 server_features.av_sync = server_features.audio and b(opts.av_sync)
309 server_features.fileprint = b(opts.printing) or b(opts.file_transfer)
310 server_features.mmap = b(opts.mmap)
311 server_features.input_devices = not opts.readonly and impcheck("keyboard")
312 server_features.commands = impcheck("server.control_command")
313 server_features.dbus = opts.dbus_proxy and impcheck("dbus", "server.dbus")
314 server_features.encoding = impcheck("codecs")
315 server_features.logging = b(opts.remote_logging)
316 #server_features.network_state = ??
317 server_features.shell = envbool("XPRA_SHELL", True)
318 server_features.display = opts.windows
319 server_features.windows = opts.windows and impcheck("codecs")
320 server_features.rfb = b(opts.rfb_upgrade) and impcheck("server.rfb")
323def make_desktop_server(clobber):
324 from xpra.x11.desktop_server import XpraDesktopServer
325 return XpraDesktopServer(clobber)
327def make_server(clobber):
328 from xpra.x11.server import XpraServer
329 return XpraServer(clobber)
331def make_shadow_server():
332 from xpra.platform.shadow_server import ShadowServer
333 return ShadowServer()
335def make_proxy_server():
336 from xpra.platform.proxy_server import ProxyServer
337 return ProxyServer()
340def verify_display(xvfb=None, display_name=None, shadowing=False, log_errors=True, timeout=None):
341 #check that we can access the X11 display:
342 from xpra.x11.vfb_util import verify_display_ready, VFB_WAIT
343 if timeout is None:
344 timeout = VFB_WAIT
345 if not verify_display_ready(xvfb, display_name, shadowing, log_errors, timeout):
346 return 1
347 from xpra.log import Logger
348 log = Logger("screen", "x11")
349 log("X11 display is ready")
350 no_gtk()
351 from xpra.x11.gtk_x11.gdk_display_source import verify_gdk_display
352 display = verify_gdk_display(display_name)
353 if not display:
354 return 1
355 log("GDK can access the display")
356 return 0
358def do_run_server(error_cb, opts, mode, xpra_file, extra_args, desktop_display=None, progress_cb=None):
359 assert mode in (
360 "start", "start-desktop",
361 "upgrade", "upgrade-desktop",
362 "shadow", "proxy",
363 )
365 def _progress(i, msg):
366 if progress_cb:
367 progress_cb(i, msg)
368 progress = _progress
370 progress(10, "initializing environment")
371 try:
372 cwd = os.getcwd()
373 except OSError:
374 cwd = os.path.expanduser("~")
375 warn("current working directory does not exist, using '%s'\n" % cwd)
376 validate_encryption(opts)
377 if opts.encoding=="help" or "help" in opts.encodings:
378 return show_encoding_help(opts)
380 #remove anything pointing to dbus from the current env
381 #(so we only detect a dbus instance started by pam,
382 # and override everything else)
383 for k in tuple(os.environ.keys()):
384 if k.startswith("DBUS_"):
385 del os.environ[k]
387 use_display = parse_bool("use-display", opts.use_display)
388 starting = mode == "start"
389 starting_desktop = mode == "start-desktop"
390 upgrading = mode == "upgrade"
391 upgrading_desktop = mode == "upgrade-desktop"
392 shadowing = mode == "shadow"
393 proxying = mode == "proxy"
395 if not proxying and POSIX and not OSX:
396 #we don't support wayland servers,
397 #so make sure GDK will use the X11 backend:
398 from xpra.os_util import saved_env
399 saved_env["GDK_BACKEND"] = "x11"
400 os.environ["GDK_BACKEND"] = "x11"
402 has_child_arg = (
403 opts.start_child or
404 opts.start_child_on_connect or
405 opts.start_child_after_connect or
406 opts.start_child_on_last_client_exit
407 )
408 if proxying or upgrading or upgrading_desktop:
409 #when proxying or upgrading, don't exec any plain start commands:
410 opts.start = opts.start_child = []
411 elif opts.exit_with_children:
412 assert has_child_arg, "exit-with-children was specified but start-child* is missing!"
413 elif opts.start_child:
414 warn("Warning: the 'start-child' option is used,")
415 warn(" but 'exit-with-children' is not enabled,")
416 warn(" use 'start' instead")
418 if opts.bind_rfb and (proxying or starting):
419 get_util_logger().warn("Warning: bind-rfb sockets cannot be used with '%s' mode" % mode)
420 opts.bind_rfb = []
422 if not shadowing and not starting_desktop:
423 opts.rfb_upgrade = 0
425 if upgrading or upgrading_desktop or shadowing:
426 #there should already be one running
427 #so change None ('auto') to False
428 if opts.pulseaudio is None:
429 opts.pulseaudio = False
431 #get the display name:
432 if shadowing and not extra_args:
433 if WIN32 or OSX:
434 #just a virtual name for the only display available:
435 display_name = "Main"
436 else:
437 from xpra.scripts.main import guess_X11_display
438 dotxpra = DotXpra(opts.socket_dir, opts.socket_dirs)
439 display_name = guess_X11_display(dotxpra, desktop_display)
440 elif (upgrading or upgrading_desktop) and not extra_args:
441 display_name = guess_xpra_display(opts.socket_dir, opts.socket_dirs)
442 else:
443 if len(extra_args) > 1:
444 error_cb("too many extra arguments (%i): only expected a display number" % len(extra_args))
445 if len(extra_args) == 1:
446 display_name = extra_args[0]
447 if not shadowing and not upgrading and not use_display:
448 display_name_check(display_name)
449 else:
450 if proxying:
451 #find a free display number:
452 dotxpra = DotXpra(opts.socket_dir, opts.socket_dirs)
453 all_displays = dotxpra.sockets()
454 #ie: [("LIVE", ":100"), ("LIVE", ":200"), ...]
455 displays = [v[1] for v in all_displays]
456 display_name = None
457 for x in range(1000, 20000):
458 v = ":%s" % x
459 if v not in displays:
460 display_name = v
461 break
462 if not display_name:
463 error_cb("you must specify a free virtual display name to use with the proxy server")
464 elif use_display:
465 #only use automatic guess for xpra displays and not X11 displays:
466 display_name = guess_xpra_display(opts.socket_dir, opts.socket_dirs)
467 else:
468 # We will try to find one automaticaly
469 # Use the temporary magic value 'S' as marker:
470 display_name = 'S' + str(os.getpid())
472 if not (shadowing or proxying or upgrading or upgrading_desktop) and \
473 opts.exit_with_children and not has_child_arg:
474 error_cb("--exit-with-children specified without any children to spawn; exiting immediately")
476 atexit.register(run_cleanups)
478 # Generate the script text now, because os.getcwd() will
479 # change if/when we daemonize:
480 from xpra.server.server_util import (
481 xpra_runner_shell_script,
482 write_runner_shell_scripts,
483 find_log_dir,
484 create_input_devices,
485 source_env,
486 )
487 script = None
488 if POSIX and getuid()!=0:
489 script = xpra_runner_shell_script(xpra_file, cwd, opts.socket_dir)
491 uid = int(opts.uid)
492 gid = int(opts.gid)
493 username = get_username_for_uid(uid)
494 home = get_home_for_uid(uid)
495 ROOT = POSIX and getuid()==0
497 protected_fds = []
498 protected_env = {}
499 stdout = sys.stdout
500 stderr = sys.stderr
501 # Daemonize:
502 if POSIX and opts.daemon:
503 #daemonize will chdir to "/", so try to use an absolute path:
504 if opts.password_file:
505 opts.password_file = tuple(os.path.abspath(x) for x in opts.password_file)
506 from xpra.server.server_util import daemonize
507 daemonize()
509 displayfd = 0
510 if POSIX and opts.displayfd:
511 try:
512 displayfd = int(opts.displayfd)
513 if displayfd>0:
514 protected_fds.append(displayfd)
515 except ValueError as e:
516 stderr.write("Error: invalid displayfd '%s':\n" % opts.displayfd)
517 stderr.write(" %s\n" % e)
518 del e
520 clobber = int(upgrading or upgrading_desktop)*CLOBBER_UPGRADE | int(use_display or 0)*CLOBBER_USE_DISPLAY
521 start_vfb = not (shadowing or proxying or clobber)
522 xauth_data = None
523 if start_vfb:
524 xauth_data = get_hex_uuid()
526 # if pam is present, try to create a new session:
527 pam = None
528 PAM_OPEN = POSIX and envbool("XPRA_PAM_OPEN", ROOT and uid!=0)
529 if PAM_OPEN:
530 try:
531 from xpra.server.pam import pam_session #@UnresolvedImport
532 except ImportError as e:
533 stderr.write("Error: failed to import pam module\n")
534 stderr.write(" %s" % e)
535 del e
536 PAM_OPEN = False
537 if PAM_OPEN:
538 fdc = FDChangeCaptureContext()
539 with fdc:
540 pam = pam_session(username)
541 env = {
542 #"XDG_SEAT" : "seat1",
543 #"XDG_VTNR" : "0",
544 "XDG_SESSION_TYPE" : "x11",
545 #"XDG_SESSION_CLASS" : "user",
546 "XDG_SESSION_DESKTOP" : "xpra",
547 }
548 #maybe we should just bail out instead?
549 if pam.start():
550 pam.set_env(env)
551 items = {}
552 if display_name.startswith(":"):
553 items["XDISPLAY"] = display_name
554 if xauth_data:
555 items["XAUTHDATA"] = xauth_data
556 pam.set_items(items)
557 if pam.open():
558 #we can't close it, because we're not going to be root any more,
559 #but since we're the process leader for the session,
560 #terminating will also close the session
561 #add_cleanup(pam.close)
562 protected_env = pam.get_envlist()
563 os.environ.update(protected_env)
564 #closing the pam fd causes the session to be closed,
565 #and we don't want that!
566 protected_fds += fdc.get_new_fds()
568 #get XDG_RUNTIME_DIR from env options,
569 #which may not be have updated os.environ yet when running as root with "--uid="
570 xrd = os.path.abspath(parse_env(opts.env).get("XDG_RUNTIME_DIR", ""))
571 if ROOT and (uid>0 or gid>0):
572 #we're going to chown the directory if we create it,
573 #ensure this cannot be abused, only use "safe" paths:
574 if not any(x for x in ("/run/user/%i" % uid, "/tmp", "/var/tmp") if xrd.startswith(x)):
575 xrd = ""
576 #these paths could cause problems if we were to create and chown them:
577 if xrd.startswith("/tmp/.X11-unix") or xrd.startswith("/tmp/.XIM-unix"):
578 xrd = ""
579 if not xrd:
580 xrd = os.environ.get("XDG_RUNTIME_DIR")
581 xrd = create_runtime_dir(xrd, uid, gid)
582 if xrd:
583 #this may override the value we get from pam
584 #with the value supplied by the user:
585 protected_env["XDG_RUNTIME_DIR"] = xrd
587 if script:
588 # Write out a shell-script so that we can start our proxy in a clean
589 # environment:
590 write_runner_shell_scripts(script)
592 import datetime
593 extra_expand = {"TIMESTAMP" : datetime.datetime.now().strftime("%Y%m%d-%H%M%S")}
594 log_to_file = opts.daemon or os.environ.get("XPRA_LOG_TO_FILE", "")=="1"
595 if start_vfb or log_to_file:
596 #we will probably need a log dir
597 #either for the vfb, or for our own log file
598 log_dir = opts.log_dir or ""
599 if not log_dir or log_dir.lower()=="auto":
600 log_dir = find_log_dir(username, uid=uid, gid=gid)
601 if not log_dir:
602 raise InitException("cannot find or create a logging directory")
603 #expose the log-dir as "XPRA_LOG_DIR",
604 #this is used by Xdummy for the Xorg log file
605 if "XPRA_LOG_DIR" not in os.environ:
606 os.environ["XPRA_LOG_DIR"] = log_dir
608 if log_to_file:
609 from xpra.server.server_util import select_log_file, open_log_file, redirect_std_to_log
610 log_filename0 = osexpand(select_log_file(log_dir, opts.log_file, display_name),
611 username, uid, gid, extra_expand)
612 if os.path.exists(log_filename0) and not display_name.startswith("S"):
613 #don't overwrite the log file just yet,
614 #as we may still fail to start
615 log_filename0 += ".new"
616 logfd = open_log_file(log_filename0)
617 if POSIX and ROOT and (uid>0 or gid>0):
618 try:
619 os.fchown(logfd, uid, gid)
620 except OSError as e:
621 noerr(stderr.write, "failed to chown the log file '%s'\n" % log_filename0)
622 noerr(stderr.flush)
623 stdout, stderr = redirect_std_to_log(logfd, *protected_fds)
624 noerr(stderr.write, "Entering daemon mode; "
625 + "any further errors will be reported to:\n"
626 + (" %s\n" % log_filename0))
627 noerr(stderr.flush)
628 os.environ["XPRA_SERVER_LOG"] = log_filename0
629 else:
630 #server log does not exist:
631 os.environ.pop("XPRA_SERVER_LOG", None)
633 #warn early about this:
634 if (starting or starting_desktop) and desktop_display and opts.notifications and not opts.dbus_launch:
635 print_DE_warnings()
637 if start_vfb and opts.xvfb.find("Xephyr")>=0 and opts.sync_xvfb<=0:
638 warn("Warning: using Xephyr as vfb")
639 warn(" you should also enable the sync-xvfb option")
640 warn(" to keep the Xephyr window updated")
642 progress(10, "creating sockets")
643 from xpra.net.socket_util import get_network_logger, setup_local_sockets, create_sockets
644 sockets = create_sockets(opts, error_cb)
646 sanitize_env()
647 os.environ.update(source_env(opts.source))
648 if POSIX:
649 if xrd:
650 os.environ["XDG_RUNTIME_DIR"] = xrd
651 if not OSX:
652 os.environ["XDG_SESSION_TYPE"] = "x11"
653 if not starting_desktop:
654 os.environ["XDG_CURRENT_DESKTOP"] = opts.wm_name
655 configure_imsettings_env(opts.input_method)
656 if display_name[0] != 'S':
657 os.environ["DISPLAY"] = display_name
658 if POSIX:
659 os.environ["CKCON_X11_DISPLAY"] = display_name
660 elif not start_vfb or opts.xvfb.find("Xephyr")<0:
661 os.environ.pop("DISPLAY", None)
662 os.environ.update(protected_env)
663 from xpra.log import Logger
664 log = Logger("server")
665 log("env=%s", os.environ)
667 UINPUT_UUID_LEN = 12
668 UINPUT_UUID_MIN_LEN = 12
669 UINPUT_UUID_MAX_LEN = 32
670 # Start the Xvfb server first to get the display_name if needed
671 odisplay_name = display_name
672 xvfb = None
673 xvfb_pid = None
674 uinput_uuid = None
675 if start_vfb and use_display is None:
676 #use-display='auto' so we have to figure out
677 #if we have to start the vfb or not:
678 if not display_name:
679 use_display = False
680 else:
681 progress(20, "connecting to the display")
682 start_vfb = verify_display(None, display_name, log_errors=False, timeout=1)!=0
683 if start_vfb:
684 progress(20, "starting a virtual display")
685 assert not proxying and xauth_data
686 pixel_depth = validate_pixel_depth(opts.pixel_depth, starting_desktop)
687 from xpra.x11.vfb_util import start_Xvfb, check_xvfb_process, parse_resolution
688 from xpra.server.server_util import has_uinput
689 uinput_uuid = None
690 if has_uinput() and opts.input_devices.lower() in ("uinput", "auto") and not shadowing:
691 from xpra.os_util import get_rand_chars
692 uinput_uuid = get_rand_chars(UINPUT_UUID_LEN)
693 vfb_geom = ""
694 try:
695 vfb_geom = parse_resolution(opts.resize_display)
696 except Exception:
697 pass
698 xvfb, display_name, cleanups = start_Xvfb(opts.xvfb, vfb_geom, pixel_depth, display_name, cwd,
699 uid, gid, username, xauth_data, uinput_uuid)
700 for f in cleanups:
701 add_cleanup(f)
702 xvfb_pid = xvfb.pid
703 #always update as we may now have the "real" display name:
704 os.environ["DISPLAY"] = display_name
705 os.environ["CKCON_X11_DISPLAY"] = display_name
706 os.environ.update(protected_env)
707 if display_name!=odisplay_name and pam:
708 pam.set_items({"XDISPLAY" : display_name})
710 def check_xvfb(timeout=0):
711 return check_xvfb_process(xvfb, timeout=timeout, command=opts.xvfb)
712 else:
713 if POSIX and clobber:
714 #if we're meant to be using a private XAUTHORITY file,
715 #make sure to point to it:
716 from xpra.x11.vfb_util import get_xauthority_path
717 xauthority = get_xauthority_path(display_name, username, uid, gid)
718 if os.path.exists(xauthority):
719 log("found XAUTHORITY=%s", xauthority)
720 os.environ["XAUTHORITY"] = xauthority
721 def check_xvfb(timeout=0):
722 return True
724 if POSIX and not OSX and displayfd>0:
725 from xpra.platform.displayfd import write_displayfd
726 try:
727 display_no = display_name[1:]
728 #ensure it is a string containing the number:
729 display_no = str(int(display_no))
730 log("writing display_no='%s' to displayfd=%i", display_no, displayfd)
731 assert write_displayfd(displayfd, display_no), "timeout"
732 except Exception as e:
733 log.error("write_displayfd failed", exc_info=True)
734 log.error("Error: failed to write '%s' to fd=%s", display_name, displayfd)
735 log.error(" %s", str(e) or type(e))
736 del e
738 if not check_xvfb(1):
739 noerr(stderr.write, "vfb failed to start, exiting\n")
740 return EXIT_VFB_ERROR
742 if WIN32 and os.environ.get("XPRA_LOG_FILENAME"):
743 os.environ["XPRA_SERVER_LOG"] = os.environ["XPRA_LOG_FILENAME"]
744 if opts.daemon:
745 log_filename1 = osexpand(select_log_file(log_dir, opts.log_file, display_name),
746 username, uid, gid, extra_expand)
747 if log_filename0 != log_filename1:
748 # we now have the correct log filename, so use it:
749 try:
750 os.rename(log_filename0, log_filename1)
751 except (OSError, IOError):
752 pass
753 else:
754 os.environ["XPRA_SERVER_LOG"] = log_filename1
755 if odisplay_name!=display_name:
756 #this may be used by scripts, let's try not to change it:
757 noerr(stderr.write, "Actual display used: %s\n" % display_name)
758 noerr(stderr.write, "Actual log file name is now: %s\n" % log_filename1)
759 noerr(stderr.flush)
760 noerr(stdout.close)
761 noerr(stderr.close)
762 #we should not be using stdout or stderr from this point:
763 del stdout
764 del stderr
766 if not check_xvfb():
767 noerr(stderr.write, "vfb failed to start, exiting\n")
768 return EXIT_VFB_ERROR
770 #create devices for vfb if needed:
771 devices = {}
772 if not start_vfb and not proxying and not shadowing and envbool("XPRA_UINPUT", True):
773 #try to find the existing uinput uuid:
774 #use a subprocess to avoid polluting our current process
775 #with X11 connections before we get a chance to change uid
776 prop = "_XPRA_UINPUT_ID"
777 cmd = ["xprop", "-display", display_name, "-root", prop]
778 log("looking for '%s' on display '%s' with XAUTHORITY='%s'", prop, display_name, os.environ.get("XAUTHORITY"))
779 try:
780 code, out, err = get_status_output(cmd)
781 except Exception as e:
782 log("failed to get existing uinput id: %s", e)
783 del e
784 else:
785 log("Popen(%s)=%s", cmd, (code, out, err))
786 if code==0 and out.find("=")>0:
787 uinput_uuid = out.split("=", 1)[1]
788 log("raw uinput uuid=%s", uinput_uuid)
789 uinput_uuid = strtobytes(uinput_uuid.strip('\n\r"\\ '))
790 if uinput_uuid:
791 if len(uinput_uuid)>UINPUT_UUID_MAX_LEN or len(uinput_uuid)<UINPUT_UUID_MIN_LEN:
792 log.warn("Warning: ignoring invalid uinput id:")
793 log.warn(" '%s'", uinput_uuid)
794 uinput_uuid = None
795 else:
796 log.info("retrieved existing uinput id: %s", bytestostr(uinput_uuid))
797 if uinput_uuid:
798 devices = create_input_devices(uinput_uuid, uid)
800 if ROOT and (uid!=0 or gid!=0):
801 log("root: switching to uid=%i, gid=%i", uid, gid)
802 setuidgid(uid, gid)
803 os.environ.update({
804 "HOME" : home,
805 "USER" : username,
806 "LOGNAME" : username,
807 })
808 shell = get_shell_for_uid(uid)
809 if shell:
810 os.environ["SHELL"] = shell
811 #now we've changed uid, it is safe to honour all the env updates:
812 configure_env(opts.env)
813 os.environ.update(protected_env)
815 if opts.chdir:
816 log("chdir(%s)", opts.chdir)
817 os.chdir(opts.chdir)
819 dbus_pid, dbus_env = 0, {}
820 if not shadowing and POSIX and not OSX and not clobber:
821 no_gtk()
822 assert starting or starting_desktop or proxying
823 try:
824 from xpra.server.dbus.dbus_start import start_dbus
825 except ImportError as e:
826 log("dbus components are not installed: %s", e)
827 else:
828 dbus_pid, dbus_env = start_dbus(opts.dbus_launch)
829 if dbus_env:
830 os.environ.update(dbus_env)
832 if not proxying:
833 if POSIX and not OSX:
834 no_gtk()
835 if starting or starting_desktop or shadowing:
836 r = verify_display(xvfb, display_name, shadowing)
837 if r:
838 return r
839 #on win32, this ensures that we get the correct screen size to shadow:
840 from xpra.platform.gui import init as gui_init
841 log("gui_init()")
842 gui_init()
844 progress(50, "creating local sockets")
845 #setup unix domain socket:
846 netlog = get_network_logger()
847 local_sockets = setup_local_sockets(opts.bind,
848 opts.socket_dir, opts.socket_dirs,
849 display_name, clobber,
850 opts.mmap_group, opts.socket_permissions,
851 username, uid, gid)
852 netlog("setting up local sockets: %s", local_sockets)
853 sockets.update(local_sockets)
854 if POSIX and (starting or upgrading or starting_desktop or upgrading_desktop):
855 #all unix domain sockets:
856 ud_paths = [sockpath for stype, _, sockpath, _ in local_sockets if stype=="unix-domain"]
857 if ud_paths:
858 #choose one so our xdg-open override script can use to talk back to us:
859 if opts.forward_xdg_open:
860 for x in ("/usr/libexec/xpra", "/usr/lib/xpra"):
861 xdg_override = os.path.join(x, "xdg-open")
862 if os.path.exists(xdg_override):
863 os.environ["PATH"] = x+os.pathsep+os.environ.get("PATH", "")
864 os.environ["XPRA_SERVER_SOCKET"] = ud_paths[0]
865 break
866 else:
867 log.warn("Warning: no local server sockets,")
868 if opts.forward_xdg_open:
869 log.warn(" forward-xdg-open cannot be enabled")
870 log.warn(" non-embedded ssh connections will not be available")
872 set_server_features(opts)
874 if not proxying and POSIX and not OSX:
875 if not check_xvfb():
876 return 1
877 from xpra.x11.gtk_x11.gdk_display_source import init_gdk_display_source
878 if os.environ.get("NO_AT_BRIDGE") is None:
879 os.environ["NO_AT_BRIDGE"] = "1"
880 init_gdk_display_source()
881 #(now we can access the X11 server)
882 if uinput_uuid:
883 save_uinput_id(uinput_uuid)
885 progress(60, "initializing server")
886 if shadowing:
887 app = make_shadow_server()
888 elif proxying:
889 app = make_proxy_server()
890 else:
891 if starting or upgrading:
892 app = make_server(clobber)
893 else:
894 assert starting_desktop or upgrading_desktop
895 app = make_desktop_server(clobber)
896 app.init_virtual_devices(devices)
898 try:
899 app.exec_cwd = opts.chdir or cwd
900 app.display_name = display_name
901 app.init(opts)
902 progress(70, "initializing sockets")
903 app.init_sockets(sockets)
904 app.init_dbus(dbus_pid, dbus_env)
905 if not shadowing and not proxying:
906 app.init_display_pid(xvfb_pid)
907 app.original_desktop_display = desktop_display
908 del opts
909 if not app.server_ready():
910 return 1
911 progress(80, "finalizing")
912 app.server_init()
913 app.setup()
914 app.init_when_ready(_when_ready)
915 except InitException as e:
916 log.error("xpra server initialization error:")
917 log.error(" %s", e)
918 app.cleanup()
919 return 1
920 except Exception as e:
921 log.error("Error: cannot start the %s server", app.session_type, exc_info=True)
922 log.error(str(e))
923 log.info("")
924 if upgrading or upgrading_desktop:
925 #something abnormal occurred,
926 #don't kill the vfb on exit:
927 from xpra.server import EXITING_CODE
928 app._upgrading = EXITING_CODE
929 app.cleanup()
930 return 1
932 try:
933 progress(100, "running")
934 log("running %s", app.run)
935 r = app.run()
936 log("%s()=%s", app.run, r)
937 except KeyboardInterrupt:
938 log.info("stopping on KeyboardInterrupt")
939 app.cleanup()
940 return EXIT_OK
941 except Exception:
942 log.error("server error", exc_info=True)
943 app.cleanup()
944 return -128
945 else:
946 if r>0:
947 r = 0
948 return r