]> git.decadent.org.uk Git - videolink.git/blobdiff - framebuffer.cpp
Brought documentation up-to-date.
[videolink.git] / framebuffer.cpp
index eab879a34eeb3d940bf9f717cddb091087c507c5..2ae42cde48addaf3f758e77af7d7d652b2c883cd 100644 (file)
@@ -1,6 +1,8 @@
 // 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 <sys/types.h>
 #include <errno.h>
 #include <fcntl.h>
-#include <netinet/in.h>
+#include <netdb.h>
+#include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/utsname.h>
 #include <unistd.h>
 #include <wait.h>
 
-#include "framebuffer.hpp"
 #include "auto_fd.hpp"
+#include "auto_handle.hpp"
+#include "temp_file.hpp"
 
 namespace
 {
-    int select_display_num()
+    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
@@ -29,45 +48,111 @@ namespace
        const int min_display_num = 10;
        const int max_display_num = 99;
 
-       // Note that we have to leave it to the X server to create the
-       // lock file for the selected display number, which leaves a
-       // race condition.  We could perhaps read its error stream to
-       // detect the case where another server grabs the display
-       // number before it.
-       char lock_file_name[20];
        for (int display_num = min_display_num;
             display_num <= max_display_num;
             ++display_num)
        {
-           // Check whether a lock file exists for this display.  Really we
-           // should also check for stale locks, but this will probably do.
+           // 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)
+           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, std::size_t len)
+    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));
     }
 
-    auto_temp_file create_temp_auth_file(int display_num)
+    std::auto_ptr<temp_file> create_temp_auth_file(int display_num)
     {
-       char auth_file_name[] = "/tmp/Xvfb-auth-XXXXXX";
-       auto_fd auth_file_fd(mkstemp(auth_file_name));
-       if (auth_file_fd.get() == -1)
-           throw std::runtime_error(std::strerror(errno));
-       auto_temp_file auth_file(auth_file_name);
-
-       // mkstemp may use lax permissions, so fix that before writing
-       // the auth data to it.
-       fchmod(auth_file_fd.get(), S_IREAD|S_IWRITE);
-       ftruncate(auth_file_fd.get(), 0);
+       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-
@@ -82,27 +167,27 @@ namespace
        // u16     length of auth data (= 16)
        // char[]  auth data (= random bytes)
        uint16_t family = htons(0x100);
-       write(auth_file_fd.get(), &family, sizeof(family));
+       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_fd.get(), &len, sizeof(len));
-       write(auth_file_fd.get(),
+       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_fd.get(), &len, sizeof(len));
-       write(auth_file_fd.get(), display, 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_fd.get(), &len, sizeof(len));
-       write(auth_file_fd.get(), auth_type, 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_fd.get(), &len, sizeof(len));
-       write(auth_file_fd.get(), auth_key, sizeof(auth_key));
+       write(auth_file->get_fd(), &len, sizeof(len));
+       write(auth_file->get_fd(), auth_key, sizeof(auth_key));
 
        return auth_file;
     }
@@ -128,7 +213,9 @@ namespace
            execlp("Xvfb",
                   "Xvfb",
                   "-auth", auth_file_c_str,
+                  "-nolisten", "tcp",
                   "-screen", "0", dimensions,
+                  "-terminate",
                   display,
                   NULL);
            _exit(128 + errno);
@@ -158,7 +245,7 @@ namespace
 }
 
 FrameBuffer::FrameBuffer(int width, int height, int depth)
-       : display_num_(select_display_num()),
+       : 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(),
@@ -167,7 +254,7 @@ FrameBuffer::FrameBuffer(int width, int height, int depth)
 
 std::string FrameBuffer::get_x_authority() const
 {
-    return auth_file_.get();
+    return auth_file_->get_name();
 }
 
 std::string FrameBuffer::get_x_display() const