Imported version 0.1
[videolink.git] / framebuffer.cpp
1 // Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
2 // See the file "COPYING" for licence details.
3
4 #include <cassert>
5 #include <cstdio>
6 #include <cstring>
7 #include <stdexcept>
8
9 #include <sys/types.h>
10 #include <errno.h>
11 #include <fcntl.h>
12 #include <netinet/in.h>
13 #include <sys/stat.h>
14 #include <sys/utsname.h>
15 #include <unistd.h>
16 #include <wait.h>
17
18 #include "framebuffer.hpp"
19 #include "auto_fd.hpp"
20
21 namespace
22 {
23     int select_display_num()
24     {
25         // Minimum and maximum display numbers to use.  Xvnc and ssh's
26         // proxies start at 10, so we'll follow that convention.  We
27         // have to put a limit on iteration somewhere, and 100
28         // displays seems rather excessive so we'll stop there.
29         const int min_display_num = 10;
30         const int max_display_num = 99;
31
32         // Note that we have to leave it to the X server to create the
33         // lock file for the selected display number, which leaves a
34         // race condition.  We could perhaps read its error stream to
35         // detect the case where another server grabs the display
36         // number before it.
37         char lock_file_name[20];
38         for (int display_num = min_display_num;
39              display_num <= max_display_num;
40              ++display_num)
41         {
42             // Check whether a lock file exists for this display.  Really we
43             // should also check for stale locks, but this will probably do.
44             std::sprintf(lock_file_name, "/tmp/.X%d-lock", display_num);
45             if (access(lock_file_name, 0) == -1 && errno == ENOENT)
46                 return display_num;
47         }
48
49         throw std::runtime_error("did not find a free X display");
50     }
51
52     void get_random_bytes(unsigned char * buf, std::size_t len)
53     {
54         auto_fd random_fd(open("/dev/urandom", O_RDONLY));
55         if (random_fd.get() == -1 || read(random_fd.get(), buf, len) != len)
56             throw std::runtime_error(std::strerror(errno));
57     }
58
59     auto_temp_file create_temp_auth_file(int display_num)
60     {
61         char auth_file_name[] = "/tmp/Xvfb-auth-XXXXXX";
62         auto_fd auth_file_fd(mkstemp(auth_file_name));
63         if (auth_file_fd.get() == -1)
64             throw std::runtime_error(std::strerror(errno));
65         auto_temp_file auth_file(auth_file_name);
66
67         // mkstemp may use lax permissions, so fix that before writing
68         // the auth data to it.
69         fchmod(auth_file_fd.get(), S_IREAD|S_IWRITE);
70         ftruncate(auth_file_fd.get(), 0);
71
72         // An xauth entry consists of the following fields.  All u16 fields
73         // are big-endian and unaligned.  Character arrays are not null-
74         // terminated.
75         // u16     address family (= 256 for local socket)
76         // u16     length of address
77         // char[]  address (= hostname)
78         // u16     length of display number
79         // char[]  display number
80         // u16     auth type name length
81         // char[]  auth type name (= "MIT-MAGIC-COOKIE-1")
82         // u16     length of auth data (= 16)
83         // char[]  auth data (= random bytes)
84         uint16_t family = htons(0x100);
85         write(auth_file_fd.get(), &family, sizeof(family));
86         utsname my_uname;
87         uname(&my_uname);
88         uint16_t len = htons(strlen(my_uname.nodename));
89         write(auth_file_fd.get(), &len, sizeof(len));
90         write(auth_file_fd.get(),
91               my_uname.nodename, strlen(my_uname.nodename));
92         char display[15];
93         std::sprintf(display, "%d", display_num);
94         len = htons(strlen(display));
95         write(auth_file_fd.get(), &len, sizeof(len));
96         write(auth_file_fd.get(), display, strlen(display));
97         static const char auth_type[] = "MIT-MAGIC-COOKIE-1";
98         len = htons(sizeof(auth_type) - 1);
99         write(auth_file_fd.get(), &len, sizeof(len));
100         write(auth_file_fd.get(), auth_type, sizeof(auth_type) - 1);
101         unsigned char auth_key[16];
102         get_random_bytes(auth_key, sizeof(auth_key));
103         len = htons(sizeof(auth_key));
104         write(auth_file_fd.get(), &len, sizeof(len));
105         write(auth_file_fd.get(), auth_key, sizeof(auth_key));
106
107         return auth_file;
108     }
109
110     // Run the X server with the specified auth file, dimensions and
111     // assigned display number.
112     auto_kill_proc spawn_x_server(int display_num,
113                                   const std::string & auth_file_name,
114                                   int width, int height, int depth)
115     {
116         char display[15];
117         std::sprintf(display, ":%d", display_num);
118         const char * auth_file_c_str = auth_file_name.c_str();
119         std::fflush(NULL);
120         auto_kill_proc server_proc(fork());
121         if (server_proc.get() == -1)
122             throw std::runtime_error(std::strerror(errno));
123
124         if (server_proc.get() == 0)
125         {
126             char dimensions[40];
127             std::sprintf(dimensions, "%dx%dx%d", width, height, depth);
128             execlp("Xvfb",
129                    "Xvfb",
130                    "-auth", auth_file_c_str,
131                    "-screen", "0", dimensions,
132                    display,
133                    NULL);
134             _exit(128 + errno);
135         }
136
137         // Wait for the lock file to appear or the server to exit.  We can't
138         // really wait on both of these, so poll at 1-second intervals.
139         char lock_file_name[20];
140         std::sprintf(lock_file_name, "/tmp/.X%d-lock", display_num);
141         for (;;)
142         {
143             if (access(lock_file_name, 0) == 0)
144                 break;
145             if (errno != ENOENT) // huh?
146                 throw std::runtime_error(std::strerror(errno));
147             if (waitpid(server_proc.get(), NULL, WNOHANG) == server_proc.get())
148             {
149                 server_proc.release(); // pid is now invalid
150                 // TODO: Get the exit status and decode it properly.
151                 throw std::runtime_error("X server failed to create display");
152             }
153             sleep(1);
154         }
155
156         return server_proc;
157     }
158 }
159
160 FrameBuffer::FrameBuffer(int width, int height, int depth)
161         : display_num_(select_display_num()),
162           auth_file_(create_temp_auth_file(display_num_)),
163           server_proc_(spawn_x_server(display_num_,
164                                       get_x_authority(),
165                                       width, height, depth))
166 {}
167
168 std::string FrameBuffer::get_x_authority() const
169 {
170     return auth_file_.get();
171 }
172
173 std::string FrameBuffer::get_x_display() const
174 {
175     char display[15];
176     std::sprintf(display, ":%d", display_num_);
177     return display;
178 }