]> git.decadent.org.uk Git - videolink.git/blob - x_frame_buffer.cpp
Removed version requirement on mjpegtools.
[videolink.git] / x_frame_buffer.cpp
1 // Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
2 // See the file "COPYING" for licence details.
3
4 #include "x_frame_buffer.hpp"
5
6 #include <cassert>
7 #include <cstdio>
8 #include <cstring>
9 #include <stdexcept>
10
11 #include <sys/types.h>
12 #include <errno.h>
13 #include <fcntl.h>
14 #include <netdb.h>
15 #include <sys/socket.h>
16 #include <sys/stat.h>
17 #include <sys/utsname.h>
18 #include <unistd.h>
19 #include <wait.h>
20
21 #include "auto_fd.hpp"
22 #include "auto_handle.hpp"
23 #include "temp_file.hpp"
24
25 namespace
26 {
27     struct addrinfo_factory
28     {
29         addrinfo * operator()() const { return NULL; }
30     };
31     struct addrinfo_closer
32     {
33         void operator()(addrinfo * addr_list) const
34             {
35                 if (addr_list)
36                     freeaddrinfo(addr_list);
37             }
38     };
39     typedef auto_handle<addrinfo *, addrinfo_closer, addrinfo_factory>
40         auto_addrinfo;
41
42     int select_display_num(auto_fd & tcp4_socket, auto_fd & tcp6_socket)
43     {
44         // Minimum and maximum display numbers to use.  Xvnc and ssh's
45         // proxies start at 10, so we'll follow that convention.  We
46         // have to put a limit on iteration somewhere, and 100
47         // displays seems rather excessive so we'll stop there.
48         const int min_display_num = 10;
49         const int max_display_num = 99;
50
51         for (int display_num = min_display_num;
52              display_num <= max_display_num;
53              ++display_num)
54         {
55             // Check that there's no lock file for the local socket
56             // for this display.  We could also check for stale locks,
57             // but this will probably do.
58             char lock_file_name[20];
59             std::sprintf(lock_file_name, "/tmp/.X%d-lock", display_num);
60             if (!(access(lock_file_name, 0) == -1 && errno == ENOENT))
61                 continue;
62
63             // Attempt to create TCP socket(s) and bind them to the
64             // appropriate port number.  We won't set the X server to
65             // listen on a TCP socket but this does ensure that ssh
66             // isn't using and won't use this display number.  This is
67             // roughly based on the x11_create_display_inet function
68             // in OpenSSH.
69
70             auto_addrinfo addr_list;
71
72             {
73                 addrinfo hints = {};
74                 hints.ai_family = AF_UNSPEC;
75                 hints.ai_socktype = SOCK_STREAM;
76                 char port_str[5 + 1];
77                 std::sprintf(port_str, "%d", 6000 + display_num);
78                 addrinfo * addr_list_temp;
79                 int error = getaddrinfo(NULL, port_str, &hints,
80                                              &addr_list_temp);
81                 if (error != 0)
82                     throw std::runtime_error(
83                         std::string("getaddrinfo: ")
84                         .append(gai_strerror(error)));
85                 addr_list.reset(addr_list_temp);
86             }
87
88             const addrinfo * addr;
89
90             for (addr = addr_list.get(); addr != NULL; addr = addr->ai_next)
91             {
92                 // We're only interested in TCPv4 and TCPv6.
93                 if (addr->ai_family != AF_INET && addr->ai_family != AF_INET6)
94                     continue;
95                 auto_fd & tcp_socket =
96                     (addr->ai_family == AF_INET) ? tcp4_socket : tcp6_socket;
97
98                 tcp_socket.reset(socket(addr->ai_family,
99                                         SOCK_STREAM,
100                                         addr->ai_protocol));
101                 if (tcp_socket.get() < 0)
102                 {
103                     // If the family is unsupported, no-one can bind
104                     // to this address, so this is not a problem.
105                     if (errno == EAFNOSUPPORT
106 #                       ifdef EPFNOSUPPORT
107                         || errno == EPFNOSUPPORT
108 #                       endif
109                         )
110                         continue;
111                     throw std::runtime_error(
112                         std::string("socket: ").append(strerror(errno)));
113                 }
114
115                 // Don't let TCPv6 sockets interfere with TCPv4 sockets.
116 #               ifdef IPV6_V6ONLY
117                 if (addr->ai_family == AF_INET6)
118                 {
119                     int on = 1;
120                     if (setsockopt(tcp_socket.get(), IPPROTO_IPV6, IPV6_V6ONLY,
121                                    &on, sizeof(on)) != 0)
122                     {
123                         throw std::runtime_error(
124                             std::string("setsockopt IPV6_V6ONLY: ")
125                             .append(strerror(errno)));
126                     }
127                 }
128 #               endif
129
130                 if (bind(tcp_socket.get(), addr->ai_addr, addr->ai_addrlen)
131                     != 0)
132                     break;
133             }
134
135             // If we reached the end of the address list, we've
136             // successfully bound to all appropriate addresses for
137             // this display number, so we can use it.
138             if (addr == NULL)
139                 return display_num;
140         }
141
142         throw std::runtime_error("did not find a free X display");
143     }
144
145     void get_random_bytes(unsigned char * buf, int len)
146     {
147         assert(len > 0);
148         auto_fd random_fd(open("/dev/urandom", O_RDONLY));
149         if (random_fd.get() == -1 || read(random_fd.get(), buf, len) != len)
150             throw std::runtime_error(std::strerror(errno));
151     }
152
153     std::auto_ptr<temp_file> create_temp_auth_file(int display_num)
154     {
155         std::auto_ptr<temp_file> auth_file(new temp_file("Xvfb-auth-"));
156
157         // An xauth entry consists of the following fields.  All u16 fields
158         // are big-endian and unaligned.  Character arrays are not null-
159         // terminated.
160         // u16     address family (= 256 for local socket)
161         // u16     length of address
162         // char[]  address (= hostname)
163         // u16     length of display number
164         // char[]  display number
165         // u16     auth type name length
166         // char[]  auth type name (= "MIT-MAGIC-COOKIE-1")
167         // u16     length of auth data (= 16)
168         // char[]  auth data (= random bytes)
169         uint16_t family = htons(0x100);
170         write(auth_file->get_fd(), &family, sizeof(family));
171         utsname my_uname;
172         uname(&my_uname);
173         uint16_t len = htons(strlen(my_uname.nodename));
174         write(auth_file->get_fd(), &len, sizeof(len));
175         write(auth_file->get_fd(),
176               my_uname.nodename, strlen(my_uname.nodename));
177         char display[15];
178         std::sprintf(display, "%d", display_num);
179         len = htons(strlen(display));
180         write(auth_file->get_fd(), &len, sizeof(len));
181         write(auth_file->get_fd(), display, strlen(display));
182         static const char auth_type[] = "MIT-MAGIC-COOKIE-1";
183         len = htons(sizeof(auth_type) - 1);
184         write(auth_file->get_fd(), &len, sizeof(len));
185         write(auth_file->get_fd(), auth_type, sizeof(auth_type) - 1);
186         unsigned char auth_key[16];
187         get_random_bytes(auth_key, sizeof(auth_key));
188         len = htons(sizeof(auth_key));
189         write(auth_file->get_fd(), &len, sizeof(len));
190         write(auth_file->get_fd(), auth_key, sizeof(auth_key));
191
192         return auth_file;
193     }
194
195     // Run the X server with the specified auth file, dimensions and
196     // assigned display number.
197     auto_kill_proc spawn_x_server(int display_num,
198                                   const std::string & auth_file_name,
199                                   int width, int height, int depth)
200     {
201         char display[15];
202         std::sprintf(display, ":%d", display_num);
203         const char * auth_file_c_str = auth_file_name.c_str();
204         std::fflush(NULL);
205         auto_kill_proc server_proc(fork());
206         if (server_proc.get() == -1)
207             throw std::runtime_error(std::strerror(errno));
208
209         if (server_proc.get() == 0)
210         {
211             char dimensions[40];
212             std::sprintf(dimensions, "%dx%dx%d", width, height, depth);
213             execlp("Xvfb",
214                    "Xvfb",
215                    "-auth", auth_file_c_str,
216                    "-nolisten", "tcp",
217                    "-screen", "0", dimensions,
218                    "-terminate",
219                    display,
220                    NULL);
221             _exit(128 + errno);
222         }
223
224         // Wait for the lock file to appear or the server to exit.  We can't
225         // really wait on both of these, so poll at 1-second intervals.
226         char lock_file_name[20];
227         std::sprintf(lock_file_name, "/tmp/.X%d-lock", display_num);
228         for (;;)
229         {
230             if (access(lock_file_name, 0) == 0)
231                 break;
232             if (errno != ENOENT) // huh?
233                 throw std::runtime_error(std::strerror(errno));
234             if (waitpid(server_proc.get(), NULL, WNOHANG) == server_proc.get())
235             {
236                 server_proc.release(); // pid is now invalid
237                 // TODO: Get the exit status and decode it properly.
238                 throw std::runtime_error("X server failed to create display");
239             }
240             sleep(1);
241         }
242
243         return server_proc;
244     }
245 }
246
247 x_frame_buffer::x_frame_buffer(int width, int height, int depth)
248         : display_num_(select_display_num(tcp4_socket_, tcp6_socket_)),
249           auth_file_(create_temp_auth_file(display_num_)),
250           server_proc_(spawn_x_server(display_num_,
251                                       get_authority(),
252                                       width, height, depth))
253 {}
254
255 std::string x_frame_buffer::get_authority() const
256 {
257     return auth_file_->get_name();
258 }
259
260 std::string x_frame_buffer::get_display() const
261 {
262     char display[15];
263     std::sprintf(display, ":%d", display_num_);
264     return display;
265 }