[Spice-devel] [PATCH spice-streaming-agent 4/5] Implement a daemon/agent separation

Lukáš Hrázký lhrazky at redhat.com
Tue Feb 6 12:36:28 UTC 2018


On Mon, 2018-02-05 at 18:05 +0100, Christophe de Dinechin wrote:
> > On 2 Feb 2018, at 18:34, Lukáš Hrázký <lhrazky at redhat.com> wrote:
> > 
> > On Thu, 2018-02-01 at 16:40 +0000, Frediano Ziglio wrote:
> > > This allows to manage properly multiple servers (currently only Xorg).
> > > The executable will run as a service forking the proper agent.
> > > The agent will then manage the active server.
> > > The server receive just minimal information for the various
> > > graphic terminals and use to fork the agent.
> > > 
> > > Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
> > > ---
> > > Makefile.am                           |   8 +
> > > configure.ac                          |   4 +
> > > data/90-spice-guest-streaming.rules   |   3 +-
> > > data/spice-streaming-agent.service.in |  11 +
> > > data/spice-streaming-agent.socket     |  10 +
> > > spice-streaming-agent.spec.in         |   5 +-
> > > src/Makefile.am                       |   2 +
> > > src/daemon.cpp                        | 542 ++++++++++++++++++++++++++++++++++
> > > src/daemon.h                          |  19 ++
> > > src/spice-streaming-agent.cpp         |  17 +-
> > > 10 files changed, 616 insertions(+), 5 deletions(-)
> > > create mode 100644 data/spice-streaming-agent.service.in
> > > create mode 100644 data/spice-streaming-agent.socket
> > > create mode 100644 src/daemon.cpp
> > > create mode 100644 src/daemon.h
> > > 
> > > diff --git a/Makefile.am b/Makefile.am
> > > index aaf1638..11ef9a1 100644
> > > --- a/Makefile.am
> > > +++ b/Makefile.am
> > > @@ -18,9 +18,17 @@ pkgconfig_DATA = spice-streaming-agent.pc
> > > udevrulesdir = /usr/lib/udev/rules.d
> > > udevrules_DATA = $(srcdir)/data/90-spice-guest-streaming.rules
> > > 
> > > +systemdunitdir = $(SYSTEMDSYSTEMUNITDIR)
> > > +systemdunit_DATA = \
> > > +	$(srcdir)/data/spice-streaming-agent.service \
> > > +	$(srcdir)/data/spice-streaming-agent.socket \
> > > +	$(NULL)
> > > +
> > 
> > What is this $(NULL) thing... I've already noticed it earlier all over
> > the Makefile.am files, I see no meaning in it...
> > 
> > > EXTRA_DIST = \
> > > 	spice-streaming-agent.spec \
> > > 	spice-streaming-agent.pc \
> > > 	data/90-spice-guest-streaming.rules \
> > > 	data/spice-streaming.desktop \
> > > +	data/spice-streaming-agent.socket \
> > > +	data/spice-streaming-agent.service \
> > > 	$(NULL)
> > > diff --git a/configure.ac b/configure.ac
> > > index 8795dae..fe0afd9 100644
> > > --- a/configure.ac
> > > +++ b/configure.ac
> > > @@ -49,6 +49,9 @@ AC_CHECK_LIB(jpeg, jpeg_destroy_decompress,
> > >     AC_MSG_ERROR([libjpeg not found]))
> > > AC_SUBST(JPEG_LIBS)
> > > 
> > > +SYSTEMDSYSTEMUNITDIR=`${PKG_CONFIG} systemd --variable=systemdsystemunitdir`
> > > +AC_SUBST(SYSTEMDSYSTEMUNITDIR)
> > > +
> > > dnl ===========================================================================
> > > dnl check compiler flags
> > > 
> > > @@ -83,6 +86,7 @@ AC_DEFINE_DIR([BINDIR], [bindir], [Where data are placed to.])
> > > AC_OUTPUT([
> > > spice-streaming-agent.spec
> > > data/spice-streaming.desktop
> > > +data/spice-streaming-agent.service
> > > Makefile
> > > src/Makefile
> > > src/unittests/Makefile
> > > diff --git a/data/90-spice-guest-streaming.rules b/data/90-spice-guest-streaming.rules
> > > index 6cedd5c..7980ab9 100644
> > > --- a/data/90-spice-guest-streaming.rules
> > > +++ b/data/90-spice-guest-streaming.rules
> > > @@ -1,2 +1 @@
> > > -ACTION=="add", SUBSYSTEM=="virtio-ports", ENV{DEVLINKS}=="/dev/virtio-ports/com.redhat.stream.0", MODE="0666"
> > > -
> > > +ACTION=="add", SUBSYSTEM=="virtio-ports", ENV{DEVLINKS}=="/dev/virtio-ports/com.redhat.stream.0", ENV{SYSTEMD_WANTS}="spice-streaming-agent.socket"
> > > diff --git a/data/spice-streaming-agent.service.in b/data/spice-streaming-agent.service.in
> > > new file mode 100644
> > > index 0000000..df47230
> > > --- /dev/null
> > > +++ b/data/spice-streaming-agent.service.in
> > > @@ -0,0 +1,11 @@
> > > +[Unit]
> > > +Description=Agent daemon for SPICE streaming agent
> > > +Requires=spice-streaming-agent.socket
> > > +
> > > +[Service]
> > > +Type=simple
> > > +EnvironmentFile=-/etc/sysconfig/spice-streaming-agent
> > > +ExecStart=@BINDIR@/spice-streaming-agent $SPICE_STREAMING_EXTRA_ARGS
> > > +
> > > +[Install]
> > > +Also=spice-streaming-agent.socket
> > > diff --git a/data/spice-streaming-agent.socket b/data/spice-streaming-agent.socket
> > > new file mode 100644
> > > index 0000000..c59d0bd
> > > --- /dev/null
> > > +++ b/data/spice-streaming-agent.socket
> > > @@ -0,0 +1,10 @@
> > > +[Unit]
> > > +Description=Activation socket for SPICE streaming agent daemon
> > > +# only start the socket if the virtio port device exists
> > > +Requisite=dev-virtio\x2dports-com.redhat.stream.0.device
> > > +
> > > +[Socket]
> > > +ListenStream=@spice-streaming-agent-daemon
> > > +
> > > +[Install]
> > > +WantedBy=sockets.target
> > > diff --git a/spice-streaming-agent.spec.in b/spice-streaming-agent.spec.in
> > > index 57872d1..eef8f90 100644
> > > --- a/spice-streaming-agent.spec.in
> > > +++ b/spice-streaming-agent.spec.in
> > > @@ -9,6 +9,7 @@ Source0:        %{name}-%{version}.tar.xz
> > > BuildRequires:  spice-protocol >= @SPICE_PROTOCOL_MIN_VER@
> > > BuildRequires:  libX11-devel libXfixes-devel
> > > BuildRequires:  libjpeg-turbo-devel
> > > +BuildRequires:  systemd-devel
> > > # we need /usr/sbin/semanage program which is available on different
> > > # packages depending on distribution
> > > Requires(post): /usr/sbin/semanage
> > > @@ -51,10 +52,12 @@ fi
> > > 
> > > %files
> > > %doc COPYING ChangeLog README
> > > -/lib/udev/rules.d/90-spice-guest-streaming.rules
> > > +%{_udevrulesdir}/90-spice-guest-streaming.rules
> > > %{_bindir}/spice-streaming-agent
> > > %{_sysconfdir}/xdg/autostart/spice-streaming.desktop
> > > %{_datadir}/gdm/greeter/autostart/spice-streaming.desktop
> > > +/usr/lib/systemd/system/spice-streaming-agent.socket
> > > +/usr/lib/systemd/system/spice-streaming-agent.service
> > > 
> > > %files devel
> > > %defattr(-,root,root,-)
> > > diff --git a/src/Makefile.am b/src/Makefile.am
> > > index bbfaa35..d2f8a47 100644
> > > --- a/src/Makefile.am
> > > +++ b/src/Makefile.am
> > > @@ -58,4 +58,6 @@ spice_streaming_agent_SOURCES = \
> > > 	mjpeg-fallback.cpp \
> > > 	jpeg.cpp \
> > > 	jpeg.hpp \
> > > +	daemon.cpp \
> > > +	daemon.h \
> > > 	$(NULL)
> > > diff --git a/src/daemon.cpp b/src/daemon.cpp
> > > new file mode 100644
> > > index 0000000..87201a1
> > > --- /dev/null
> > > +++ b/src/daemon.cpp
> > > @@ -0,0 +1,542 @@
> > > +/* An implementation of a SPICE streaming agent
> > > + *
> > > + * \copyright
> > > + * Copyright 2016-2017 Red Hat Inc. All rights reserved.
> > > + */
> > > +
> > > +/* This file implement the daemon part required to
> > > + * avoid permission issues.
> > > + * The daemon will listen to a socket for clients.
> > > + * Clients simply will send informations about new display.
> > > + * Daemon will monitor current terminal and launch a proper
> > > + * agent with information passed.
> > > + */
> > > +
> > > +#ifdef HAVE_CONFIG_H
> > > +#include <config.h>
> > > +#endif
> > > +
> > > +#include <stdio.h>
> > > +#include <stdlib.h>
> > > +#include <stdint.h>
> > > +#include <string.h>
> > > +#include <getopt.h>
> > > +#include <unistd.h>
> > > +#include <signal.h>
> > > +#include <syslog.h>
> > > +#include <poll.h>
> > > +#include <err.h>
> > > +#include <errno.h>
> > > +#include <sys/socket.h>
> > > +#include <sys/un.h>
> > > +#include <sys/types.h>
> > > +#include <sys/stat.h>
> > > +#include <sys/wait.h>
> > > +#include <fcntl.h>
> > > +#include <pwd.h>
> > > +#include <grp.h>
> > > +#include <systemd/sd-daemon.h>
> > > +
> > > +#include "daemon.h"
> > > +
> > > +/*
> > > + * There are 3 "rules" for the agent:
> > 
> > did you mean: "roles"? "rules" certainly doesn't make sense here.
> > 
> > > + * - main agent;
> > > + * - daemon
> > > + * - helper
> > 
> > This nomenclature is really blurry here. Especially between 'agent' and
> > 'daemon'. The streaming agent is already supposed to run as a daemon.
> > You also say 'main agent' here, but later on actually call the main
> > process 'daemon' and the subprocess 'agent'. Totally confusing.
> > 
> > I suggest calling the main process 'streaming agent' and the
> > subprocesses 'streaming worker'. Not my best name to date but sounds
> > servicable :D I'll keep thinking about it :)
> > 
> > I still haven't made up my mind on helpers.
> > 
> > > + * The rule of the agent is to handle a given graphical session
> > > + * capturing and sending video stream to SPICE server.
> > > + * The rule of the daemon is to listen to information from helpers
> > > + * collecting Unix session information from the helpers and from
> > > + * system and managing agents.
> > > + * The helper just send session information to the daemon.
> > 
> > I think this could be described more in detail, as in:
> > 
> > - What session information (don't make a list of what exactly, but
> > describe what is the nature of the information, i.e. 'session
> > information for the X server connection')
> > 
> > - How does it send the information.
> > 
> > - How is the information further used.
> > 
> > > + * The agent are usually child (forked) of the daemon.
> > 
> > 'usually'? Describe when, I am still confused about this...
> > 
> > > + * This schema is used for different reasons:
> > > + * - the daemon can be run as root having access to the streaming
> > > + *   device file;
> > > + * - the daemon can control the live of the agent making possible to
> > 
> > 'life', 'making it possible'
> > 
> > > + *   switch between sessions;
> > > + * - running agents directly launched from a graphical session cause
> > > + *   some issue with SELinux, launching outside a Unix session allows
> > 
> > 'causes some issues' :)
> > 
> > > + *   the process to have less restrictions.
> > > + */
> > > +
> > > +/*
> > > + * The protocol between helper and daemon is pretty simple.
> > > + * Helper connects to daemon and send all information needed to connect
> > > + * to the graphical interface.
> > > + * The helper send a single message which is pretty small (should be at
> > > + * most 1K) through a Unix socket.
> > > + * This:
> > > + * - allows to pass credentials;
> > > + * - a single small message prevent the helper to consume memory on the
> > > + *   daemon side;
> > > + * - allows dynamic activation using SystemD;
> > > + * - writing the client is really easy and can be written in a script
> > > + *   language.
> > > + *
> > > + * Message is:
> > > + * - 1 byte. Version. Has to be 1;
> > > + * - a set of strings, each:
> > > + *   - 1 byte type field, currently:
> > > + *     1) DISPLAY environment;
> > > + *     2) XAUTHORITY environment;
> > > + *   - 1 byte for length
> > > + *   - data
> > > + * - DISPLAY content. The DISPLAY should be local like ":1";
> > > + * - XAUTHORITY environment content (a filename).
> > 
> > I am still not entirely sure about this in general, but I would really
> > like if we started using some extablished serialization protocol for
> > these arbitrary communication channels. I know, this is just a really
> > simple thing. It may not always be so. Seems to me we have quite a
> > number of communication channels, with a lot of code around them. Of
> > course it is mainly the SPICE protocol, which would be a huge task to
> > replace, if it would ever happen. One can dream, though :)
> > 
> > It's more of a food for thought, this little thing probably doesn't
> > warrant it. But we could have more places for it, I'm still not that
> > familiar with SPICE.
> > 
> > As for concrete examples of what we could use, Cap'n Proto [1] came to
> > my mind. There are others, some research would have to be done. These
> > days nobody really implements their own serialization protocol, right?
> > :D
> > 
> > [1] https://capnproto.org/
> > 
> > > + *
> > > + * Note: Linux allows to read the peer credentials (user/group) and
> > > + * PID which we need. If we would need to extent to other systems
> > > + * (like *BSD/Mac OS X) these allows to pass these informations using
> > > + * an ancilliary message and SCM_CREDS (Linux also has a similar
> > > + * SCM_CREDENTIALS).
> > > + */
> > > +
> > > +const unsigned max_clients = 63;
> > > +enum {
> > > +    LISTEN_FD,
> > > +    VT_FD,
> > > +    FIXED_FDS
> > > +};
> > > +static struct pollfd fds[max_clients+FIXED_FDS];
> > > +static int listen_fd = -1;
> > > +static struct pollfd * const client_fds = fds+FIXED_FDS;
> > > +static unsigned num_clients = 0;
> > > +static volatile pid_t child_pid = -1;
> > 
> > Okay... Why do you put C code into a .cpp file? :)
> > 
> > As I said, the static variables are a bad practice... Why not create a
> > class called Daemon (or, taking into account my suggestions for naming,
> > StreamingWorker) and put them into the class. The functions below can
> > become the methods of the class and you have a nice context for your
> > data.
> > 
> > > +#if CLIENT_DATA
> > > +struct ClientData
> > > +{
> > > +    char display[256];
> > > +    char xauthority[256];
> > > +};
> > 
> > std::string
> > 
> > > +static ClientData client_data[max_clients];
> > 
> > std::vector (and then you don't need max_clients)
> > 
> > > +#endif
> > > +
> > > +struct TerminalInfo
> > > +{
> > > +    bool valid;
> > > +    
> > > +    char display[256];
> > > +    char xauthority[256];
> > 
> > std::string
> > 
> > > +    uid_t uid;
> > > +};
> > > +static TerminalInfo terminal_info[64];
> > > +
> > > +static bool fd_is_agent(int fd)
> > > +{
> > > +    // must be a socket
> > > +    struct stat st;
> > > +    if (fstat(fd, &st) != 0)
> > > +        return false;
> > 
> > So.. what about the rule of 'always brackets for ifs'? :)
> > 
> > > +    if ((st.st_mode & S_IFMT) != S_IFSOCK)
> > > +        return false;
> > > +
> > > +    // must have our address
> > > +    struct sockaddr_un address;
> > > +    socklen_t len = sizeof(address);
> > > +    if (getsockname(fd, (sockaddr *) &address, &len) != 0)
> > > +        return false;
> > > +    if (address.sun_family != AF_UNIX)
> > > +        return false;
> > > +    if (address.sun_path[0] != 0)
> > > +        return false;
> > > +    address.sun_path[0] = '@';
> > > +    if (len != SUN_LEN(&address) || strcmp(address.sun_path, "@spice-streaming-agent-daemon") != 0)
> > 
> > You could put the constant into a std::string (it's used again few
> > lines below) and use ==, just sayin :)
> > 
> > > +        return false;
> > > +    // TODO must be in listening
> > > +
> > > +    return true;
> > > +}
> > > +static void init()
> > > +{
> > > +    int ret;
> > > +
> > > +    int fd;
> > > +    if (fd_is_agent(0)) {
> > > +        fd = 0;
> > > +    } else if (fd_is_agent(SD_LISTEN_FDS_START)) {
> > > +        fd = SD_LISTEN_FDS_START;
> > > +    } else {
> > > +        // open socket
> > > +        fd = socket(PF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0);
> > > +        if (fd < 0)
> > > +            err(1, "socket");
> > > +
> > > +        struct sockaddr_un address;
> > > +        memset(&address, 0, sizeof(address));
> > > +        address.sun_family = AF_UNIX;
> > > +        strcpy(address.sun_path, "@spice-streaming-agent-daemon");
> > > +        int len = SUN_LEN(&address);
> > > +        address.sun_path[0] = 0;
> > > +        ret = bind(fd, (struct sockaddr *)&address, len);
> > > +        if (ret < 0)
> > > +            err(1, "bind");
> > 
> > Throw exceptions, use descriptive error messages?
> > 
> > > +
> > > +        // listen to socket
> > > +        ret = listen(fd, 5);
> > > +        if (ret < 0)
> > > +            err(1, "listen");
> > > +    }
> > > +
> > > +    listen_fd = fd;
> > > +    fds[LISTEN_FD].fd = fd;
> > > +
> > > +    // detect TTY changes
> > > +    fd = open("/sys/class/tty/tty0/active", O_RDONLY|O_CLOEXEC);
> > > +    if (fd < 0)
> > > +        err(1, "open");
> > > +    fds[VT_FD].fd = fd;
> > > +    fds[VT_FD].events = POLLPRI;
> > > +}
> > > +
> > > +static bool check_xauthority(const char *fn, uid_t uid)
> > > +{
> > > +    // TODO timeout on check, could have passed a weird NFS
> > > +    // impersonate uid
> > > +    // file must be present
> > > +    // file must be small
> > > +    // read file
> > > +    // check for keys (memmem)
> > > +    //   MIT-MAGIC-COOKIE-1
> > > +    //   XDM-AUTHORIZATION-1
> > > +    //   MIT-KERBEROS-5
> > > +    //   SUN-DES-1
> > > +    return true;
> > > +}
> > > +
> > > +static void remove_client(unsigned n)
> > > +{
> > > +    close(client_fds[n].fd);
> > > +    client_fds[n].fd = -1;
> > > +    if (n != num_clients-1) {
> > > +        client_fds[n] = client_fds[num_clients-1];
> > > +#if CLIENT_DATA
> > > +        client_data[n] = client_data[num_clients-1];
> > > +#endif
> > > +    }
> > > +    --num_clients;
> > > +}
> > > +
> > > +/**
> > > + * Get terminal number from PID.
> > > + * @returns terminal or -1 if error
> > > + */
> > > +static int get_terminal(pid_t pid)
> > > +{
> > > +    char fn[128];
> > > +    sprintf(fn, "/proc/%u/stat", pid);
> > > +    FILE *f = fopen(fn, "re");
> > > +    if (!f)
> > > +        return -1;
> > > +
> > > +    char line[1024*2];
> > > +    if (fgets(line, sizeof(line), f) == NULL) {
> > > +        fclose(f);
> > > +        return -1;
> > > +    }
> > > +    fclose(f);
> > 
> > This could also be done using std::ifstream (though streams in C++ are
> > kind of borked...).
> > 
> > Again, no error handling.
> > 
> > > +    int terminal = -1, tty = -1;
> > > +    const char *end_exename = strstr(line, ") ");
> > > +    if (end_exename && sscanf(end_exename+2, "%*c %*d %*d %*d %d", &tty) > 0) {
> > > +        // check tty is a physical one (just looks at major/minor)
> > > +        int major = tty >> 8;
> > > +        int minor = tty & 0xff;
> > > +        if (major == 4 && minor > 0 && minor < 64)
> > > +            terminal = minor;
> > > +    }
> > > +    return terminal;
> > > +}
> > > +
> > > +/**
> > > + * Parse a message from a client
> > > + * @returns true if message is valid.
> > > + */
> > > +static bool parse_message(TerminalInfo& info, const uint8_t *msg, size_t msg_len)
> > > +{
> > > +    memset(&info, 0, sizeof(info));
> > 
> > Oldschool!
> > 
> > Just create info on the stack and return it at the end. In case of
> > error, throw an exception.
> > 
> > > +    if (msg_len < 1 || msg[0] != 1)
> > > +        return false;
> > > +
> > > +    auto msg_end = msg + msg_len;
> > > +    ++msg;
> > > +    while (msg_end - msg >= 2) {
> > > +        uint8_t type = *msg++;
> > > +        uint8_t len = *msg++;
> > > +        if (msg_end - msg < len)
> > > +            return false;
> > > +
> > > +        char *out;
> > > +        switch (type) {
> > > +        case 1:
> > > +            out = info.display;
> > > +            break;
> > > +        case 2:
> > > +            out = info.xauthority;
> > > +            break;
> > > +        default:
> > > +            return false;
> > > +        }
> > > +
> > > +        memcpy(out, msg, len);
> > > +        out[len] = 0;
> > > +        msg += len;
> > > +    }
> > > +    if (msg != msg_end)
> > > +        return false;
> > > +    info.valid = true;
> > > +    return true;
> > > +}
> > > +
> > > +// check message, should contain a DISPLAY and XAUTHORITY
> > > +// callback when data are received
> > > +static void data_from_client(unsigned n)
> > > +{
> > > +    const int fd = client_fds[n].fd;
> > > +
> > > +    // get message, the protocol specify a single message
> > > +    char msg_buffer[1024*4];
> > > +    iovec iov[1];
> > > +    iov[0].iov_base = msg_buffer;
> > > +    iov[0].iov_len = sizeof(msg_buffer);
> > > +    msghdr msg;
> > > +    memset(&msg, 0, sizeof(msg));
> > > +    msg.msg_iov = iov;
> > > +    msg.msg_iovlen = 1;
> > > +    ssize_t msg_len = recvmsg(fd, &msg, MSG_CMSG_CLOEXEC|MSG_DONTWAIT);
> > > +    if (msg_len < 0 && errno == EAGAIN)
> > > +        return;
> > > +    if (msg_len < 0 && errno == EWOULDBLOCK)
> > > +        return;
> > > +    if (msg_len <= 0) {
> > > +        remove_client(n);
> > > +        return;
> > > +    }
> > > +
> > > +    // get credentials (uid+pid)
> > > +    ucred cred;
> > > +    socklen_t cred_length = sizeof(cred);
> > > +    if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &cred_length) < 0) {
> > > +        remove_client(n);
> > > +        return;
> > > +    }
> > > +
> > > +    // get tty terminal from process
> > > +    int num_terminal = get_terminal(cred.pid);
> > > +
> > > +    // send an ack to the client, we got all the information
> > > +    remove_client(n);
> > > +
> > > +    if (num_terminal < 0)
> > > +        return;
> > > +
> > > +    // parse message, should contain data and credentials
> > > +    TerminalInfo info;
> > > +    if (!parse_message(info, (const uint8_t *) msg_buffer, msg_len))
> > > +        return;
> > > +
> > > +    // check Xauthority using the uid passed
> > > +    if (!check_xauthority(info.xauthority, cred.uid))
> > > +        return;
> > > +
> > > +    // set final informations
> > > +    info.uid = cred.uid;
> > > +    terminal_info[num_terminal] = info;
> > > +}
> > > +
> > > +static void handle_new_fd(int fd)
> > > +{
> > > +    // limit number of clients
> > > +    if (num_clients >= max_clients) {
> > > +        close(fd);
> > > +        return;
> > > +    }
> > > +
> > > +    // append to loop handlers
> > > +    client_fds[num_clients].fd = fd;
> > > +    client_fds[num_clients].events = POLLIN;
> > > +    client_fds[num_clients].revents = 0;
> > > +#if CLIENT_DATA
> > > +    memset(&client_data[num_clients], 0, sizeof(client_data[num_clients]));
> > > +#endif
> > > +    ++num_clients;
> > > +
> > > +    // TODO timeout for data
> > > +}
> > > +
> > > +static void handle_fd_events(unsigned n, unsigned events)
> > > +{
> > > +    if (events == POLLIN) {
> > > +        data_from_client(n);
> > > +        return;
> > > +    }
> > > +    // delete client if other events
> > > +    if (events)
> > > +        remove_client(n);
> > > +}
> > > +
> > > +static int current_tty = -1;
> > > +static int working_tty = -1;
> > > +
> > > +static void handle_vt_change(int fd)
> > > +{
> > > +    char vt_name[128];
> > > +    for (;;) {
> > > +        auto len = read(fd, vt_name, sizeof(vt_name));
> > > +        if (len < 0 && errno == EINTR)
> > > +            continue;
> > > +        if (len < 0)
> > > +            // TODO error
> > > +            return;
> > > +
> > > +        unsigned tty_num;
> > > +        if (sscanf(vt_name, "tty%u", &tty_num) == 1 && tty_num < 64) {
> > > +            current_tty = tty_num;
> > > +        }
> > > +        lseek(fd, 0, SEEK_SET);
> > > +        break;
> > > +    }
> > > +}
> > > +
> > > +static bool check_agent()
> > > +{
> > > +    syslog(LOG_WARNING, "tty working %d current %d", working_tty, current_tty);
> > > +
> > > +    // TODO execute this part also on main loop
> > > +    // when should we try again ?
> > > +    if (working_tty == current_tty && child_pid != -1)
> > > +        return false;
> > > +
> > > +    syslog(LOG_WARNING, "trace %d", __LINE__);
> > > +    // can we handle a new TTY ?
> > > +    if (!terminal_info[current_tty].valid)
> > > +        return false;
> > > +
> > > +    syslog(LOG_WARNING, "trace %d child pid %d", __LINE__, (int) child_pid);
> > > +    // TODO who clear TTY when reset ?
> > > +    // TODO switch to text ?
> > > +
> > > +    if (child_pid != -1)
> > > +        return false;
> > > +
> > > +    syslog(LOG_WARNING, "trace %d", __LINE__);
> > > +    // save pid, manage only one agent
> > > +    child_pid = fork();
> > 
> > Is the whole code structure upside down now? The daemon here is the
> > parent process which should be forking children, but main() is in
> > spice-streaming-agent.cpp. Does this even work after the first fork?
> > 
> > This is really ugly and some serious code refactoring should have
> > happened before introducing this... I've already been thinking about
> > it. The streaming code should be separated from the main .cpp file and
> > encapsulated and only be called from there.
> 
> I agree the code seems to have trouble deciding if it’s C or C++.
> 
> Frediano, do you prefer to go “all the way” to C++, or rather stick with C? If we go for C++, can we stick to a C++ way of doing things? But I’m not sure what C++ brings us for that kind of code…

Although it seems to me you've picked a wrong remark of mine to reply
to (I was asking if the code structure around fork could be improved,
should apply to C as well as C++), good question. :)

I think C++ still brings benefits, although smaller than they would be
in higher-level code.

But better to answer this so that I know I should stop trying :)

Lukas

> > 
> > > +    switch (child_pid) {
> > > +    case -1:
> > > +        // TODO
> > > +        return false;
> > > +    case 0:
> > > +        // child
> > > +        child_pid = -1;
> > > +        break;
> > > +    default:
> > > +        // parent
> > > +        // TODO close streamfd opened to lock it
> > > +        return false;
> > > +    }
> > > +
> > > +    // we are the child here, we return to do the stream work
> > > +    uid_t uid = terminal_info[current_tty].uid;
> > > +    syslog(LOG_WARNING, "trace %d uid %d", __LINE__, (int) uid);
> > > +    passwd *pw = getpwuid(uid);
> > > +    if (!pw) {
> > > +        exit(1);
> > > +    }
> > > +
> > > +    working_tty = current_tty;
> > > +    return true;
> > > +}
> > > +
> > > +static void loop()
> > > +{
> > > +    // poll for new events
> > > +    while (true) {
> > > +        // check if we need to execute the agent
> > > +        // this should be done here so if poll get a EINTR
> > > +        // for a closed child we check again
> > > +        if (check_agent())
> > > +            break;
> > > +
> > > +        // limit clients
> > > +        if (num_clients >= max_clients) {
> > > +            fds[0].events = 0;
> > > +        } else {
> > > +            fds[0].events = POLLIN;
> > > +        }
> > > +        if (poll(fds, num_clients+FIXED_FDS, -1) < 0)
> > > +            // TODO errors
> > > +            continue;
> > > +        if ((fds[LISTEN_FD].revents & POLLIN) != 0) {
> > > +            // accept
> > > +            int new_fd = accept(listen_fd, NULL, NULL);
> > > +            if (new_fd < 0)
> > > +                continue;
> > > +            handle_new_fd(new_fd);
> > > +        }
> > > +        if ((fds[VT_FD].revents & POLLPRI) != 0) {
> > > +            handle_vt_change(fds[VT_FD].fd);
> > > +        }
> > > +        for (unsigned n = num_clients; n-- > 0; )
> > > +            if (client_fds[n].revents)
> > > +                handle_fd_events(n, client_fds[n].revents);
> > > +    }
> > > +}
> > > +
> > > +static void handle_sigchild(int)
> > > +{
> > > +    pid_t pid;
> > > +    int status;
> > > +    while ((pid=waitpid(-1, &status, WNOHANG)) != -1) {
> > > +        if (pid == child_pid) {
> > > +            child_pid = -1;
> > > +            // TODO try to open streamfd if not already opened
> > > +        }
> > > +    }
> > > +}
> > > +
> > > +void daemonize(const char *streamport, int *streamfd)
> > > +{
> > 
> > This function really does not daemonize anything, so the name is wrong?
> > 
> > > +    // ignore pipe, prevent signal handling data from file descriptors
> > > +    signal(SIGPIPE, SIG_IGN);
> > > +    signal(SIGCHLD, handle_sigchild);
> > > +
> > > +    init();
> > > +
> > > +    loop();
> > > +
> > > +    signal(SIGCHLD, SIG_DFL);
> > > +    signal(SIGPIPE, SIG_DFL);
> > > +
> > > +    // open device before switching user
> > > +    *streamfd = open(streamport, O_RDWR|O_CLOEXEC);
> > > +    if (*streamfd < 0)
> > > +        exit(1);
> > > +
> > > +    uid_t uid = terminal_info[current_tty].uid;
> > > +    passwd *pw = getpwuid(uid);
> > > +    if (!pw) {
> > > +        exit(1);
> > > +    }
> > > +    if (setgid(pw->pw_gid) != 0) {
> > > +        exit(1);
> > > +    }
> > > +    if (initgroups(pw->pw_name, pw->pw_gid) != 0) {
> > > +        exit(1);
> > > +    }
> > > +    if (setuid(uid) != 0) {
> > > +        exit(1);
> > > +    }
> > 
> > Error handling once again.
> > 
> > > +    for (unsigned n = 0; n < num_clients+FIXED_FDS; ++n) {
> > > +        close(fds[n].fd);
> > > +        fds[n].fd = -1;
> > > +    }
> > > +
> > > +    setenv("DISPLAY", terminal_info[current_tty].display, 1);
> > > +    setenv("XAUTHORITY", terminal_info[current_tty].xauthority, 1);
> > > +}
> > > diff --git a/src/daemon.h b/src/daemon.h
> > > new file mode 100644
> > > index 0000000..007ba7f
> > > --- /dev/null
> > > +++ b/src/daemon.h
> > > @@ -0,0 +1,19 @@
> > > +/* Declaration for daemon code
> > > + *
> > > + * \copyright
> > > + * Copyright 2017 Red Hat Inc. All rights reserved.
> > > + */
> > > +#ifndef SPICE_STREAMING_AGENT_DAEMON_H_
> > > +#define SPICE_STREAMING_AGENT_DAEMON_H_
> > > +
> > > +#ifdef __cplusplus
> > > +extern "C" {
> > > +#endif
> > > +
> > > +void daemonize(const char *streamport, int *streamfd);
> > > +
> > > +#ifdef __cplusplus
> > > +}
> > > +#endif
> > 
> > Why all this? C file suffix (.h) and the extern "C" for this function?
> > 
> > > +
> > > +#endif
> > > diff --git a/src/spice-streaming-agent.cpp b/src/spice-streaming-agent.cpp
> > > index 2c32070..91dfead 100644
> > > --- a/src/spice-streaming-agent.cpp
> > > +++ b/src/spice-streaming-agent.cpp
> > > @@ -37,6 +37,7 @@
> > > #include "hexdump.h"
> > > #include "xorg-utils.hpp"
> > > #include "concrete-agent.hpp"
> > > +#include "daemon.h"
> > > 
> > > using namespace std;
> > > using namespace SpiceStreamingAgent;
> > > @@ -166,6 +167,8 @@ static int read_command_from_device(void)
> > > static void start_capture()
> > > {
> > >     for (int attempts = 0; ; ) {
> > > +        if (streamfd >= 0)
> > > +            break;
> > >         streamfd = open(streamport.c_str(), O_RDWR);
> > >         if (streamfd >= 0)
> > >             break;
> > > @@ -206,7 +209,7 @@ static int read_command(bool blocking)
> > >             if (vt_active) {
> > >                 start_capture();
> > >             } else {
> > > -                stop_capture();
> > > +                exit(0);
> > >             }
> > >         } else {
> > >             n = read_command_from_stdin();
> > > @@ -380,7 +383,13 @@ static void cursor_changes(Display *display, int event_base)
> > >             if (vt_property == None || event.xproperty.atom != vt_property)
> > >                 continue;
> > >             // update vt property, activate screen read if needed
> > > -            vt_active.store(get_win_prop_int(display, rootwindow, vt_property) != 0, std::memory_order_relaxed);
> > > +            bool vt_active = get_win_prop_int(display, rootwindow, vt_property) != 0;
> > > +            if (!vt_active) {
> > > +                printf("exiting...\n");
> > > +                // this is necessary as to avoid a clean exit that will hangs :(
> > > +                _Exit(0);
> > > +            }
> > > +            ::vt_active.store(vt_active, std::memory_order_relaxed);
> > >             std::atomic_thread_fence(std::memory_order_acquire);
> > >             uint64_t n = 1;
> > >             write(update_fd, &n, sizeof(n));
> > > @@ -533,6 +542,10 @@ int main(int argc, char* argv[])
> > >         }
> > >     }
> > > 
> > > +    daemonize(streamport.c_str(), &streamfd);
> > > +
> > > +    // this should be done after the fork to avoid duplicating
> > > +    // resources
> > >     agent.LoadPlugins(PLUGINSDIR);
> > > 
> > >     update_fd = eventfd(0, EFD_CLOEXEC|EFD_NONBLOCK);
> > 
> > _______________________________________________
> > Spice-devel mailing list
> > Spice-devel at lists.freedesktop.org
> > https://lists.freedesktop.org/mailman/listinfo/spice-devel
> 
> 


More information about the Spice-devel mailing list