Logo Search packages:      
Sourcecode: videolink version File versions  Download package

x_frame_buffer.cpp

// Copyright 2005 Ben Hutchings <ben@decadent.org.uk>.
// See the file "COPYING" for licence details.

#include "x_frame_buffer.hpp"

#include <cassert>
#include <cstdio>
#include <cstring>
#include <stdexcept>

#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <wait.h>

#include "auto_fd.hpp"
#include "auto_handle.hpp"
#include "temp_file.hpp"

namespace
{
    struct addrinfo_factory
    {
      addrinfo * operator()() const { return NULL; }
    };
    struct addrinfo_closer
    {
      void operator()(addrinfo * addr_list) const
          {
            if (addr_list)
                freeaddrinfo(addr_list);
          }
    };
    typedef auto_handle<addrinfo *, addrinfo_closer, addrinfo_factory>
        auto_addrinfo;

    int select_display_num(auto_fd & tcp4_socket, auto_fd & tcp6_socket)
    {
      // Minimum and maximum display numbers to use.  Xvnc and ssh's
      // proxies start at 10, so we'll follow that convention.  We
      // have to put a limit on iteration somewhere, and 100
      // displays seems rather excessive so we'll stop there.
      const int min_display_num = 10;
      const int max_display_num = 99;

      for (int display_num = min_display_num;
           display_num <= max_display_num;
           ++display_num)
      {
          // Check that there's no lock file for the local socket
          // for this display.  We could also check for stale locks,
          // but this will probably do.
          char lock_file_name[20];
          std::sprintf(lock_file_name, "/tmp/.X%d-lock", display_num);
          if (!(access(lock_file_name, 0) == -1 && errno == ENOENT))
            continue;

          // Attempt to create TCP socket(s) and bind them to the
          // appropriate port number.  We won't set the X server to
          // listen on a TCP socket but this does ensure that ssh
          // isn't using and won't use this display number.  This is
          // roughly based on the x11_create_display_inet function
          // in OpenSSH.

          auto_addrinfo addr_list;

          {
            addrinfo hints = {};
            hints.ai_family = AF_UNSPEC;
            hints.ai_socktype = SOCK_STREAM;
            char port_str[5 + 1];
            std::sprintf(port_str, "%d", 6000 + display_num);
            addrinfo * addr_list_temp;
            int error = getaddrinfo(NULL, port_str, &hints,
                                   &addr_list_temp);
            if (error != 0)
                throw std::runtime_error(
                  std::string("getaddrinfo: ")
                  .append(gai_strerror(error)));
            addr_list.reset(addr_list_temp);
          }

          const addrinfo * addr;

          for (addr = addr_list.get(); addr != NULL; addr = addr->ai_next)
          {
            // We're only interested in TCPv4 and TCPv6.
            if (addr->ai_family != AF_INET && addr->ai_family != AF_INET6)
                continue;
            auto_fd & tcp_socket =
                (addr->ai_family == AF_INET) ? tcp4_socket : tcp6_socket;

            tcp_socket.reset(socket(addr->ai_family,
                              SOCK_STREAM,
                              addr->ai_protocol));
            if (tcp_socket.get() < 0)
            {
                // If the family is unsupported, no-one can bind
                // to this address, so this is not a problem.
                if (errno == EAFNOSUPPORT
#                   ifdef EPFNOSUPPORT
                    || errno == EPFNOSUPPORT
#                   endif
                  )
                  continue;
                throw std::runtime_error(
                  std::string("socket: ").append(strerror(errno)));
            }

            // Don't let TCPv6 sockets interfere with TCPv4 sockets.
#           ifdef IPV6_V6ONLY
            if (addr->ai_family == AF_INET6)
            {
                int on = 1;
                if (setsockopt(tcp_socket.get(), IPPROTO_IPV6, IPV6_V6ONLY,
                           &on, sizeof(on)) != 0)
                {
                  throw std::runtime_error(
                      std::string("setsockopt IPV6_V6ONLY: ")
                      .append(strerror(errno)));
                }
            }
#           endif

            if (bind(tcp_socket.get(), addr->ai_addr, addr->ai_addrlen)
                != 0)
                break;
          }

          // If we reached the end of the address list, we've
          // successfully bound to all appropriate addresses for
          // this display number, so we can use it.
          if (addr == NULL)
            return display_num;
      }

      throw std::runtime_error("did not find a free X display");
    }

    void get_random_bytes(unsigned char * buf, int len)
    {
      assert(len > 0);
      auto_fd random_fd(open("/dev/urandom", O_RDONLY));
      if (random_fd.get() == -1 || read(random_fd.get(), buf, len) != len)
          throw std::runtime_error(std::strerror(errno));
    }

    std::auto_ptr<temp_file> create_temp_auth_file(int display_num)
    {
      std::auto_ptr<temp_file> auth_file(new temp_file("Xvfb-auth-"));

      // An xauth entry consists of the following fields.  All u16 fields
      // are big-endian and unaligned.  Character arrays are not null-
      // terminated.
      // u16     address family (= 256 for local socket)
      // u16     length of address
      // char[]  address (= hostname)
      // u16     length of display number
      // char[]  display number
      // u16     auth type name length
      // char[]  auth type name (= "MIT-MAGIC-COOKIE-1")
      // u16     length of auth data (= 16)
      // char[]  auth data (= random bytes)
      uint16_t family = htons(0x100);
      write(auth_file->get_fd(), &family, sizeof(family));
      utsname my_uname;
      uname(&my_uname);
      uint16_t len = htons(strlen(my_uname.nodename));
      write(auth_file->get_fd(), &len, sizeof(len));
      write(auth_file->get_fd(),
            my_uname.nodename, strlen(my_uname.nodename));
      char display[15];
      std::sprintf(display, "%d", display_num);
      len = htons(strlen(display));
      write(auth_file->get_fd(), &len, sizeof(len));
      write(auth_file->get_fd(), display, strlen(display));
      static const char auth_type[] = "MIT-MAGIC-COOKIE-1";
      len = htons(sizeof(auth_type) - 1);
      write(auth_file->get_fd(), &len, sizeof(len));
      write(auth_file->get_fd(), auth_type, sizeof(auth_type) - 1);
      unsigned char auth_key[16];
      get_random_bytes(auth_key, sizeof(auth_key));
      len = htons(sizeof(auth_key));
      write(auth_file->get_fd(), &len, sizeof(len));
      write(auth_file->get_fd(), auth_key, sizeof(auth_key));

      return auth_file;
    }

    // Run the X server with the specified auth file, dimensions and
    // assigned display number.
    auto_kill_proc spawn_x_server(int display_num,
                          const std::string & auth_file_name,
                          int width, int height, int depth)
    {
      char display[15];
      std::sprintf(display, ":%d", display_num);
      const char * auth_file_c_str = auth_file_name.c_str();
      std::fflush(NULL);
      auto_kill_proc server_proc(fork());
      if (server_proc.get() == -1)
          throw std::runtime_error(std::strerror(errno));

      if (server_proc.get() == 0)
      {
          char dimensions[40];
          std::sprintf(dimensions, "%dx%dx%d", width, height, depth);
          execlp("Xvfb",
               "Xvfb",
               "-auth", auth_file_c_str,
               "-nolisten", "tcp",
               "-screen", "0", dimensions,
               "-terminate",
               display,
               NULL);
          _exit(128 + errno);
      }

      // Wait for the lock file to appear or the server to exit.  We can't
      // really wait on both of these, so poll at 1-second intervals.
      char lock_file_name[20];
      std::sprintf(lock_file_name, "/tmp/.X%d-lock", display_num);
      for (;;)
      {
          if (access(lock_file_name, 0) == 0)
            break;
          if (errno != ENOENT) // huh?
            throw std::runtime_error(std::strerror(errno));
          if (waitpid(server_proc.get(), NULL, WNOHANG) == server_proc.get())
          {
            server_proc.release(); // pid is now invalid
            // TODO: Get the exit status and decode it properly.
            throw std::runtime_error("X server failed to create display");
          }
          sleep(1);
      }

      return server_proc;
    }
}

x_frame_buffer::x_frame_buffer(int width, int height, int depth)
      : display_num_(select_display_num(tcp4_socket_, tcp6_socket_)),
        auth_file_(create_temp_auth_file(display_num_)),
        server_proc_(spawn_x_server(display_num_,
                              get_authority(),
                              width, height, depth))
{}

std::string x_frame_buffer::get_authority() const
{
    return auth_file_->get_name();
}

std::string x_frame_buffer::get_display() const
{
    char display[15];
    std::sprintf(display, ":%d", display_num_);
    return display;
}

Generated by  Doxygen 1.6.0   Back to index