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