]> git.decadent.org.uk Git - videolink.git/blobdiff - x_frame_buffer.cpp
Renamed various types to fit lower_case_with_underscores convention.
[videolink.git] / x_frame_buffer.cpp
diff --git a/x_frame_buffer.cpp b/x_frame_buffer.cpp
new file mode 100644 (file)
index 0000000..75d9595
--- /dev/null
@@ -0,0 +1,265 @@
+// Copyright 2005 Ben Hutchings <ben@decadentplace.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;
+}