X-Git-Url: https://git.decadent.org.uk/gitweb/?a=blobdiff_plain;f=x_frame_buffer.cpp;fp=x_frame_buffer.cpp;h=75d95957a29d86bd6a264c7c7a1f0bee62d0f49d;hb=0acb5f1329d294faf42e247f8c2daf68d82150f6;hp=0000000000000000000000000000000000000000;hpb=1b6026c7baa122b99011f760857b80b7f253dfbb;p=videolink.git diff --git a/x_frame_buffer.cpp b/x_frame_buffer.cpp new file mode 100644 index 0000000..75d9595 --- /dev/null +++ b/x_frame_buffer.cpp @@ -0,0 +1,265 @@ +// Copyright 2005 Ben Hutchings . +// See the file "COPYING" for licence details. + +#include "x_frame_buffer.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + 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 create_temp_auth_file(int display_num) + { + std::auto_ptr 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; +}