1 // Copyright 2005 Ben Hutchings <ben@decadent.org.uk>.
2 // See the file "COPYING" for licence details.
4 #include "x_frame_buffer.hpp"
11 #include <sys/types.h>
15 #include <sys/socket.h>
17 #include <sys/utsname.h>
21 #include "auto_fd.hpp"
22 #include "auto_handle.hpp"
23 #include "temp_file.hpp"
27 struct addrinfo_factory
29 addrinfo * operator()() const { return NULL; }
31 struct addrinfo_closer
33 void operator()(addrinfo * addr_list) const
36 freeaddrinfo(addr_list);
39 typedef auto_handle<addrinfo *, addrinfo_closer, addrinfo_factory>
42 int select_display_num(auto_fd & tcp4_socket, auto_fd & tcp6_socket)
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;
51 for (int display_num = min_display_num;
52 display_num <= max_display_num;
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))
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
70 auto_addrinfo addr_list;
74 hints.ai_family = AF_UNSPEC;
75 hints.ai_socktype = SOCK_STREAM;
77 std::sprintf(port_str, "%d", 6000 + display_num);
78 addrinfo * addr_list_temp;
79 int error = getaddrinfo(NULL, port_str, &hints,
82 throw std::runtime_error(
83 std::string("getaddrinfo: ")
84 .append(gai_strerror(error)));
85 addr_list.reset(addr_list_temp);
88 const addrinfo * addr;
90 for (addr = addr_list.get(); addr != NULL; addr = addr->ai_next)
92 // We're only interested in TCPv4 and TCPv6.
93 if (addr->ai_family != AF_INET && addr->ai_family != AF_INET6)
95 auto_fd & tcp_socket =
96 (addr->ai_family == AF_INET) ? tcp4_socket : tcp6_socket;
98 tcp_socket.reset(socket(addr->ai_family,
101 if (tcp_socket.get() < 0)
103 // If the family is unsupported, no-one can bind
104 // to this address, so this is not a problem.
105 if (errno == EAFNOSUPPORT
107 || errno == EPFNOSUPPORT
111 throw std::runtime_error(
112 std::string("socket: ").append(strerror(errno)));
115 // Don't let TCPv6 sockets interfere with TCPv4 sockets.
117 if (addr->ai_family == AF_INET6)
120 if (setsockopt(tcp_socket.get(), IPPROTO_IPV6, IPV6_V6ONLY,
121 &on, sizeof(on)) != 0)
123 throw std::runtime_error(
124 std::string("setsockopt IPV6_V6ONLY: ")
125 .append(strerror(errno)));
130 if (bind(tcp_socket.get(), addr->ai_addr, addr->ai_addrlen)
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.
142 throw std::runtime_error("did not find a free X display");
145 void get_random_bytes(unsigned char * buf, int len)
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));
153 std::auto_ptr<temp_file> create_temp_auth_file(int display_num)
155 std::auto_ptr<temp_file> auth_file(new temp_file("Xvfb-auth-"));
157 // An xauth entry consists of the following fields. All u16 fields
158 // are big-endian and unaligned. Character arrays are not null-
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));
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));
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));
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)
202 std::sprintf(display, ":%d", display_num);
203 const char * auth_file_c_str = auth_file_name.c_str();
205 auto_kill_proc server_proc(fork());
206 if (server_proc.get() == -1)
207 throw std::runtime_error(std::strerror(errno));
209 if (server_proc.get() == 0)
212 std::sprintf(dimensions, "%dx%dx%d", width, height, depth);
215 "-auth", auth_file_c_str,
217 "-screen", "0", dimensions,
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);
230 if (access(lock_file_name, 0) == 0)
232 if (errno != ENOENT) // huh?
233 throw std::runtime_error(std::strerror(errno));
234 if (waitpid(server_proc.get(), NULL, WNOHANG) == server_proc.get())
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");
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_,
252 width, height, depth))
255 std::string x_frame_buffer::get_authority() const
257 return auth_file_->get_name();
260 std::string x_frame_buffer::get_display() const
263 std::sprintf(display, ":%d", display_num_);