+++ /dev/null
-// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
-// See the file "COPYING" for licence details.
-
-#include "framebuffer.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;
- }
-}
-
-FrameBuffer::FrameBuffer(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_x_authority(),
- width, height, depth))
-{}
-
-std::string FrameBuffer::get_x_authority() const
-{
- return auth_file_->get_name();
-}
-
-std::string FrameBuffer::get_x_display() const
-{
- char display[15];
- std::sprintf(display, ":%d", display_num_);
- return display;
-}