xpra icon
Bug tracker and wiki

Ticket #172: displayfd-v4.patch

File displayfd-v4.patch, 15.4 KB (added by Antoine Martin, 5 years ago)

updated patch with most of the comments addressed

  • etc/xpra/Xdummy/xpra.conf

     
    149149#
    150150# Using Xorg:
    151151xvfb=/usr/bin/Xorg -dpi 96 -noreset -nolisten tcp +extension GLX +extension RANDR +extension RENDER -logfile ${HOME}/.xpra/Xorg.${DISPLAY}.log -config /etc/xpra/xorg.conf
     152
     153# Does the xvfb command support the "-displayfd" argument?
     154#displayfd = no
  • etc/xpra/Xvfb/xpra.conf

     
    148148#
    149149# Using Xvfb:
    150150xvfb=Xvfb +extension Composite -screen 0 3840x2560x24+32 -nolisten tcp -noreset -auth $XAUTHORITY
     151
     152# Does the xvfb command support the "-displayfd" argument?
     153# (which versions do support this flag is very unclear)
     154#displayfd = no
  • etc/xpra/xpra_Xdummy/xpra.conf

     
    148148#
    149149# Here we use a wrapper script to make a non-suid copy of Xorg before launching:
    150150xvfb=xpra_Xdummy -dpi 96 -noreset -nolisten tcp +extension GLX +extension RANDR +extension RENDER -logfile ${HOME}/.xpra/Xorg.${DISPLAY}.log -config /etc/xpra/xorg.conf
     151
     152# Does the xvfb command support the "-displayfd" argument?
     153#displayfd = no
  • xpra/scripts/config.py

     
    249249                    #boolean options:
    250250                    "daemon"            : bool,
    251251                    "use-display"       : bool,
     252                    "displayfd"         : bool,
    252253                    "fake-xinerama"     : bool,
    253254                    "tray"              : bool,
    254255                    "clipboard"         : bool,
     
    341342                    "auto-refresh-delay": 0.25,
    342343                    "daemon"            : True,
    343344                    "use-display"       : False,
     345                    "displayfd"         : False,
    344346                    "fake-xinerama"     : True,
    345347                    "tray"              : True,
    346348                    "clipboard"         : True,
  • xpra/scripts/main.py

     
    110110                        "\t%prog control DISPLAY command [arg1] [arg2]..\n",
    111111                        "\t%prog version [DISPLAY]\n"
    112112                      ]
     113    defaults = make_defaults_struct()
    113114    server_modes = []
    114115    if supports_server:
    115116        server_modes.append("start")
    116117        server_modes.append("upgrade")
    117         command_options = ["\t%prog start DISPLAY\n",
    118                            "\t%prog stop [DISPLAY]\n",
    119                            "\t%prog exit [DISPLAY]\n",
    120                            "\t%prog list\n",
    121                            "\t%prog upgrade DISPLAY\n",
    122                            ] + command_options
     118        if defaults.displayfd:
     119            #display argument is optional (we can use "-displayfd")
     120            command_options = ["\t%prog start [DISPLAY]\n"]
     121        else:
     122            #display is required:
     123            command_options = ["\t%prog start DISPLAY\n"]
     124        command_options += ["\t%prog stop [DISPLAY]\n",
     125                            "\t%prog exit [DISPLAY]\n",
     126                            "\t%prog list\n",
     127                            "\t%prog upgrade DISPLAY\n",
     128                            ] + command_options
    123129    if supports_shadow:
    124130        server_modes.append("shadow")
    125131        command_options.append("\t%prog shadow [DISPLAY]\n")
     
    128134
    129135    parser = OptionParser(version="xpra v%s" % XPRA_VERSION,
    130136                          usage="\n" + "".join(command_options))
    131     defaults = make_defaults_struct()
    132     hidden_options = {"display" : defaults.display}
     137    hidden_options = {"display" : defaults.display,
     138                      "displayfd" : defaults.displayfd}
    133139    if len(server_modes):
    134140        group = OptionGroup(parser, "Server Options",
    135141                    "These options are only relevant on the server when using the %s mode." %
  • xpra/scripts/server.py

     
    1717import signal
    1818import socket
    1919import getpass
     20import select
     21import re
    2022
    2123from xpra.scripts.main import TCP_NODELAY
    2224from xpra.dotxpra import DotXpra, ServerSockInUse
     
    225227
    226228
    227229def display_name_check(display_name):
    228     if display_name.startswith(":"):
    229         n = display_name[1:]
    230         p = n.find(".")
    231         if p>0:
    232             n = n[:p]
    233         try:
    234             dno = int(n)
    235             if dno>=0 and dno<10:
    236                 sys.stderr.write("WARNING: low display number: %s\n" % dno)
    237                 sys.stderr.write("You are attempting to run the xpra server against what seems to be a default X11 display '%s'.\n" % display_name)
    238                 sys.stderr.write("This is generally not what you want.\n")
    239                 sys.stderr.write("You should probably use a higher display number just to avoid any confusion (and also this warning message).\n")
    240         except:
    241             pass
     230    #this is used to display a warning
     231    #when a low display number is specified
     232    if not display_name.startswith(":"):
     233        return
     234    n = display_name[1:].split(".")[0]    #ie: ":0.0" -> "0"
     235    try:
     236        dno = int(n)
     237        if dno>=0 and dno<10:
     238            sys.stderr.write("WARNING: low display number: %s\n" % dno)
     239            sys.stderr.write("You are attempting to run the xpra server against what seems to be a default X11 display '%s'.\n" % display_name)
     240            sys.stderr.write("This is generally not what you want.\n")
     241            sys.stderr.write("You should probably use a higher display number just to avoid any confusion (and also this warning message).\n")
     242    except:
     243        pass
    242244
    243245
    244246def get_ssh_port():
     
    363365            return
    364366    print("Uh-oh, can't close fds, please port me to your system...")
    365367
    366 def open_log_file(dotxpra, log_file, display_name):
    367     if log_file:
     368
     369def open_log_file(logpath):
     370    # rename the old log file if it exists:
     371    if os.path.exists(logpath):
     372        os.rename(logpath, logpath + ".old")
     373    return os.open(logpath, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, o0666)
     374
     375def select_log_file(dotxpra, log_file, display_name):
     376    # returns the log file path we should be using
     377    if log_file and display_name:
    368378        if os.path.isabs(log_file):
    369379            logpath = log_file
    370380        else:
    371381            logpath = os.path.join(dotxpra.sockdir(), log_file)
    372382        logpath = logpath.replace("$DISPLAY", display_name)
     383    elif display_name:
     384        logpath = dotxpra.log_path(display_name) + ".log"
    373385    else:
    374         logpath = dotxpra.log_path(display_name) + ".log"
    375     sys.stderr.write("Entering daemon mode; "
    376                      + "any further errors will be reported to:\n"
    377                      + ("  %s\n" % logpath))
    378     # Do some work up front, so any errors don't get lost.
    379     if os.path.exists(logpath):
    380         os.rename(logpath, logpath + ".old")
    381     return os.open(logpath, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, o0666)
     386        logpath = os.path.join(dotxpra.sockdir(), "tmp_%d.log" % os.getpid())
     387    return logpath
    382388
     389
    383390def daemonize(logfd):
    384391    os.chdir("/")
    385392    if os.fork():
     
    387394    os.setsid()
    388395    if os.fork():
    389396        os._exit(0)
    390     close_all_fds(exceptions=[logfd])
     397    # save current stdout/stderr to be able to print info
     398    # to user before exiting definitively
     399    old_fd_stdout = os.dup(1)
     400    old_fd_stderr = os.dup(2)
     401    close_all_fds(exceptions=[logfd,old_fd_stdout,old_fd_stderr])
    391402    fd0 = os.open("/dev/null", os.O_RDONLY)
    392403    if fd0 != 0:
    393404        os.dup2(fd0, 0)
    394405        os.close(fd0)
     406    # reopen STDIO files
     407    old_stdout = os.fdopen(old_fd_stdout, "w", 1)
     408    old_stderr = os.fdopen(old_fd_stderr, "w", 1)
     409    # replace standard stdout/stderr by the log file
    395410    os.dup2(logfd, 1)
    396411    os.dup2(logfd, 2)
    397412    os.close(logfd)
     
    398413    # Make these line-buffered:
    399414    sys.stdout = os.fdopen(1, "w", 1)
    400415    sys.stderr = os.fdopen(2, "w", 1)
     416    return (old_stdout, old_stderr)
    401417
    402418
     419
    403420def sanitize_env():
    404421    def unsetenv(*varnames):
    405422        for x in varnames:
     
    464481            sys.stderr.write("Error trying to create XAUTHORITY file %s: %s\n" % (xauthority, e))
    465482    subs = {"XAUTHORITY"    : xauthority,
    466483            "USER"          : os.environ.get("USER", "unknown-user"),
    467             "HOME"          : os.environ.get("HOME", os.getcwd()),
    468             "DISPLAY"       : display_name}
     484            "HOME"          : os.environ.get("HOME", os.getcwd()) }
     485    if display_name:
     486        subs["DISPLAY"] = display_name
    469487    for var,value in subs.items():
    470488        xvfb_str = xvfb_str.replace("$%s" % var, value)
    471489        xvfb_str = xvfb_str.replace("${%s}" % var, value)
    472     xvfb_cmd = xvfb_str.split()+[display_name]
    473     xvfb_executable = xvfb_cmd[0]
    474     xvfb_cmd[0] = "%s-for-Xpra-%s" % (xvfb_executable, display_name)
     490
    475491    def setsid():
    476492        #run in a new session
    477493        if os.name=="posix":
    478494            os.setsid()
    479     xvfb = subprocess.Popen(xvfb_cmd, executable=xvfb_executable, close_fds=True,
     495
     496    xvfb_cmd = xvfb_str.split()
     497    xvfb_executable = xvfb_cmd[0]
     498    # TODO: if Xvfb does not support -displayfd -> Error
     499    if not display_name:
     500        # allocate display automaticaly
     501        r_pipe, w_pipe = os.pipe()
     502        xvfb_cmd += ["-displayfd", str(w_pipe)]
     503        xvfb_cmd[0] = "%s-for-Xpra-%s" % (xvfb_executable, str(os.getpid()))
     504        xvfb = subprocess.Popen(xvfb_cmd, executable=xvfb_executable, close_fds=False,
    480505                                stdin=subprocess.PIPE, preexec_fn=setsid)
     506        # Read the display number from the pipe we gave to Xvfb
     507        buf = ""
     508        # Wait for 4.0 seconds each time, at most 3 times:
     509        for _ in range(3):
     510            r, _, _ = select.select([r_pipe], [], [], 4.0)
     511            if r_pipe in r:
     512                buf += os.read(r_pipe, 256)
     513                if buf[-1] == '\n':
     514                    break
     515        if len(buf) == 0:
     516            raise OSError("%s did not provide a display number using -displayfd" % xvfb_executable)
     517        if not re.match("^\d+$", buf):
     518            raise OSError("%s provided an invalid display number: %s" % (xvfb_executable, buf))
     519        display_name = ":" + buf[:-1]
     520        sys.stdout.write("Using vfb provided display: %s\n" % display_name)
     521    else:
     522        # use display specified
     523        xvfb_cmd[0] = "%s-for-Xpra-%s" % (xvfb_executable, display_name)
     524        xvfb_cmd.append(display_name)
     525        xvfb = subprocess.Popen(xvfb_cmd, executable=xvfb_executable, close_fds=True,
     526                                stdin=subprocess.PIPE, preexec_fn=setsid)
     527
    481528    from xpra.os_util import get_hex_uuid
    482529    xauth_cmd = ["xauth", "add", display_name, "MIT-MAGIC-COOKIE-1", get_hex_uuid()]
    483530    try:
     
    487534    except OSError, e:
    488535        #trying to continue anyway!
    489536        sys.stderr.write("Error running \"%s\": %s\n" % (" ".join(xauth_cmd), e))
    490     return xvfb
     537    return xvfb, display_name
    491538
    492539def check_xvfb_process(xvfb=None):
    493540    if xvfb is None:
     
    622669        from xpra.scripts.main import guess_X11_display
    623670        display_name = guess_X11_display()
    624671    else:
    625         if len(extra_args) != 1:
    626             parser.error("need exactly 1 extra argument")
    627         display_name = extra_args.pop(0)
     672        if len(extra_args) > 1:
     673            parser.error("too many extra arguments: only expected a display number")
     674        if len(extra_args) == 1:
     675            display_name = extra_args[0]
     676            display_name_check(display_name)
     677        else:
     678            if not opts.displayfd:
     679                parser.error("displayfd support is not enabled, you must specify the display to use")
     680            # We will try to find one automaticaly
     681            display_name = None
    628682
    629     if not shadowing and not proxying:
    630         display_name_check(display_name)
    631 
    632683    if not shadowing and not proxying and opts.exit_with_children and not opts.start_child:
    633684        sys.stderr.write("--exit-with-children specified without any children to spawn; exiting immediately")
    634685        return  1
     
    644695    # change if/when we daemonize:
    645696    script = xpra_runner_shell_script(xpra_file, os.getcwd(), opts.socket_dir)
    646697
     698    stdout = sys.stdout
     699    stderr = sys.stderr
     700
    647701    # Daemonize:
    648702    if opts.daemon:
    649703        #daemonize will chdir to "/", so try to use an absolute path:
    650704        if opts.password_file:
    651705            opts.password_file = os.path.abspath(opts.password_file)
    652 
    653         logfd = open_log_file(dotxpra, opts.log_file, display_name)
     706        # At this point we may not know the display name,
     707        # so log_filename0 may point to a temporary file which we will rename later
     708        log_filename0 = select_log_file(dotxpra, opts.log_file, display_name)
     709        logfd = open_log_file(log_filename0)
    654710        assert logfd > 2
    655         daemonize(logfd)
     711        stdout, stderr = daemonize(logfd)
     712        stderr.write("Entering daemon mode; "
     713                 + "any further errors will be reported to:\n"
     714                 + ("  %s\n" % log_filename0))
    656715
    657716    # Write out a shell-script so that we can start our proxy in a clean
    658717    # environment:
     
    661720    from xpra.log import Logger
    662721    log = Logger("server")
    663722
     723    # Do this after writing out the shell script:
     724    if display_name:
     725        os.environ["DISPLAY"] = display_name
     726    sanitize_env()
     727
     728    # Start the Xvfb server first to get the display_name if needed
     729    xvfb = None
     730    xvfb_pid = None
     731    if not shadowing and not proxying and not clobber:
     732        try:
     733            xvfb, display_name = start_Xvfb(opts.xvfb, display_name)
     734        except OSError, e:
     735            log.error("Error starting Xvfb: %s\n", e)
     736            return  1
     737        xvfb_pid = xvfb.pid
     738
     739    if opts.daemon:
     740        log_filename1 = select_log_file(dotxpra, opts.log_file, display_name)
     741        if log_filename0 != log_filename1:
     742            os.rename(log_filename0, log_filename1)
     743            stderr.write("Actual log file name is now: %s", log_filename1)
     744        stdout.close()
     745        stderr.close()
     746    if display_name:
     747        os.environ["DISPLAY"] = display_name
     748
    664749    try:
    665         # Initialize the sockets before the display,
    666         # That way, errors won't make us kill the Xvfb
    667         # (which may not be ours to kill at that point)
     750        # We need display_name here.
    668751        bind_tcp = parse_bind_tcp(opts.bind_tcp)
    669752
    670753        sockets = []
     
    690773        log.error("cannot start server: failed to setup sockets: %s", e)
    691774        return 1
    692775
    693     # Do this after writing out the shell script:
    694     os.environ["DISPLAY"] = display_name
    695     sanitize_env()
    696 
    697     xvfb = None
    698     xvfb_pid = None
    699     if not shadowing and not proxying and not clobber:
    700         try:
    701             xvfb = start_Xvfb(opts.xvfb, display_name)
    702         except OSError, e:
    703             log.error("Error starting Xvfb: %s\n", e)
    704             return  1
    705         xvfb_pid = xvfb.pid
    706 
    707776    if not check_xvfb_process(xvfb):
    708777        #xvfb problem: exit now
    709778        return  1