Mir

Merge lp:~raof/mir/xserver-spawner into lp:mir

Proposed by Chris Halse Rogers
Status: Superseded
Proposed branch: lp:~raof/mir/xserver-spawner
Merge into: lp:mir
Diff against target: 1684 lines (+1244/-38)
32 files modified
.bzrignore (+1/-0)
.clang-format (+1/-1)
include/server/mir/default_server_configuration.h (+11/-0)
include/server/mir/process/handle.h (+45/-0)
include/server/mir/process/spawner.h (+83/-0)
include/server/mir/xserver/null_server_spawner.h (+25/-0)
include/server/mir/xserver/xserver_launcher.h (+59/-0)
include/shared/mir/pipe.h (+10/-6)
src/server/CMakeLists.txt (+5/-0)
src/server/default_server_configuration.cpp (+6/-0)
src/server/process/CMakeLists.txt (+25/-0)
src/server/process/fork_spawner.cpp (+165/-0)
src/server/process/fork_spawner.h (+44/-0)
src/server/xserver/CMakeLists.txt (+12/-0)
src/server/xserver/global_socket_listening_server_spawner.cpp (+71/-0)
src/server/xserver/global_socket_listening_server_spawner.h (+52/-0)
src/server/xserver/null_server_spawner.cpp (+34/-0)
src/shared/CMakeLists.txt (+1/-0)
src/shared/pipe/CMakeLists.txt (+19/-0)
src/shared/pipe/pipe.cpp (+40/-20)
tests/CMakeLists.txt (+1/-0)
tests/acceptance-tests/CMakeLists.txt (+3/-0)
tests/acceptance-tests/test_display_configuration.cpp (+3/-2)
tests/acceptance-tests/test_subprocess.cpp (+166/-0)
tests/acceptance-tests/test_xserver_spawner.cpp (+73/-0)
tests/integration-tests/test_display_server_main_loop_events.cpp (+3/-3)
tests/mir_test/CMakeLists.txt (+0/-1)
tests/unit-tests/CMakeLists.txt (+3/-0)
tests/unit-tests/test_asio_main_loop.cpp (+5/-5)
tests/unit-tests/test_fork_spawner.cpp (+64/-0)
tests/unit-tests/xserver/CMakeLists.txt (+8/-0)
tests/unit-tests/xserver/test_xserver_spawner.cpp (+206/-0)
To merge this branch: bzr merge lp:~raof/mir/xserver-spawner
Reviewer Review Type Date Requested Status
Daniel van Vugt Needs Fixing
Review via email: mp+203474@code.launchpad.net

This proposal has been superseded by a proposal from 2014-01-28.

Commit message

A first cut at the infrastructure required for seamless nesting of X11 clients.

In order to do this we need to be able to spawn an Xorg server, wait until it's
ready to accept connections, connect a Mir X11 client to act as a bridging WM,
and *then* offer the X11 socket to clients.

This branch accomplishes the first two requirements.

Description of the change

A first cut at the infrastructure required for seamless nesting of X11 clients.

In order to do this we need to be able to spawn an Xorg server, wait until it's
ready to accept connections, connect a Mir X11 client to act as a bridging WM,
and *then* offer the X11 socket to clients.

This branch accomplishes the first two requirements.

I'm proposing it now because it's a reasonable checkpoint and I need to go and work on something else.

To post a comment you must log in.
Revision history for this message
Daniel van Vugt (vanvugt) wrote :

Please rethink class names ending in "er". That's almost always a bad decision, and an indication that a different cleaner design is possible...

http://www.benhallbenhall.com/2013/01/naming-objects-er-object-names/

review: Needs Fixing

Unmerged revisions

1349. By Chris Halse Rogers

clang-format: We always split constructor initialisers on new lines

1348. By Chris Halse Rogers

Disable X11ClientConnects acceptance test.

This needs a bit more fiddling - it all roughly works, but in the acceptance
test framework we don't have a real graphics card, so XMir currently fails.

Needs at least an xorg.conf specifying the dummy graphics driver

1347. By Chris Halse Rogers

Get the Xserver to connect to a Mir socket

1346. By Chris Halse Rogers

Update for process::Spawner API change

1345. By Chris Halse Rogers

Merged subprocess-wrapper into xserver-spawner.

1344. By Chris Halse Rogers

X::ServerSpawner::create_server must take a shared_ptr<process::Spawner>

create_server does things asynchronously, so it has to take shared ownership
of the process::Spawner it's using

1343. By Chris Halse Rogers

Rejigger X::ServerSpawner API.

spawn_server() now returns a future<ServerHandle> that will become a present ServerHandle once
the server is ready, rather than returning a ServerHandle now that has methods that will only
work in the future.

1342. By Chris Halse Rogers

Implement -displayfd handling for GlobalSocketListeningServerSpawner.

client_connection_string now returns a string that should be associated with an Xorg server that's ready to accept connections

1341. By Chris Halse Rogers

Merge process::Spawner work required to do Xserver startup properly

1340. By Chris Halse Rogers

More basic unittests for GlobalSocketListeningServerSpawner

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file '.bzrignore'
2--- .bzrignore 1970-01-01 00:00:00 +0000
3+++ .bzrignore 2014-01-28 06:34:09 +0000
4@@ -0,0 +1,1 @@
5+build
6
7=== modified file '.clang-format'
8--- .clang-format 2014-01-10 15:48:08 +0000
9+++ .clang-format 2014-01-28 06:34:09 +0000
10@@ -14,7 +14,7 @@
11 BreakConstructorInitializersBeforeComma: false
12 BinPackParameters: true
13 ColumnLimit: 120
14-ConstructorInitializerAllOnOneLineOrOnePerLine: false
15+ConstructorInitializerAllOnOneLineOrOnePerLine: true
16 DerivePointerBinding: true
17 ExperimentalAutoDetectBinPacking: true
18 IndentCaseLabels: false
19
20=== modified file 'include/server/mir/default_server_configuration.h'
21--- include/server/mir/default_server_configuration.h 2014-01-21 15:29:52 +0000
22+++ include/server/mir/default_server_configuration.h 2014-01-28 06:34:09 +0000
23@@ -113,6 +113,11 @@
24 class Logger;
25 }
26
27+namespace X
28+{
29+class ServerSpawner;
30+}
31+
32 class DefaultServerConfiguration : public virtual ServerConfiguration, DefaultConfigurationOptions
33 {
34 public:
35@@ -232,6 +237,12 @@
36
37 virtual std::shared_ptr<time::Clock> the_clock();
38
39+ /** @name X11 server integration - customization
40+ * configurable interfaces for integration with legacy X11 apps
41+ * @{ */
42+ virtual std::shared_ptr<X::ServerSpawner> the_xserver_spawner();
43+ /** @} */
44+
45 protected:
46 using DefaultConfigurationOptions::the_options;
47 using DefaultConfigurationOptions::add_options;
48
49=== added directory 'include/server/mir/process'
50=== added file 'include/server/mir/process/handle.h'
51--- include/server/mir/process/handle.h 1970-01-01 00:00:00 +0000
52+++ include/server/mir/process/handle.h 2014-01-28 06:34:09 +0000
53@@ -0,0 +1,45 @@
54+/*
55+ * Copyright © 2014 Canonical Ltd.
56+ *
57+ * This program is free software: you can redistribute it and/or modify it
58+ * under the terms of the GNU General Public License version 3,
59+ * as published by the Free Software Foundation.
60+ *
61+ * This program is distributed in the hope that it will be useful,
62+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
63+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
64+ * GNU General Public License for more details.
65+ *
66+ * You should have received a copy of the GNU General Public License
67+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
68+ *
69+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
70+ */
71+
72+#ifndef MIR_PROCESS_HANDLE_H_
73+#define MIR_PROCESS_HANDLE_H_
74+
75+#include <sys/types.h>
76+
77+namespace mir
78+{
79+namespace process
80+{
81+class Handle
82+{
83+public:
84+ enum Status {
85+ NotStarted,
86+ Running,
87+ Stopped
88+ };
89+
90+ virtual ~Handle() = default;
91+
92+ virtual Status status() const = 0;
93+ virtual pid_t pid() const = 0;
94+};
95+
96+}
97+}
98+#endif // MIR_PROCESS_HANDLE_H_
99
100=== added file 'include/server/mir/process/spawner.h'
101--- include/server/mir/process/spawner.h 1970-01-01 00:00:00 +0000
102+++ include/server/mir/process/spawner.h 2014-01-28 06:34:09 +0000
103@@ -0,0 +1,83 @@
104+/*
105+ * Copyright © 2014 Canonical Ltd.
106+ *
107+ * This program is free software: you can redistribute it and/or modify it
108+ * under the terms of the GNU General Public License version 3,
109+ * as published by the Free Software Foundation.
110+ *
111+ * This program is distributed in the hope that it will be useful,
112+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
113+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
114+ * GNU General Public License for more details.
115+ *
116+ * You should have received a copy of the GNU General Public License
117+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
118+ *
119+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
120+ */
121+
122+#ifndef MIR_PROCESS_SPAWNER_H_
123+#define MIR_PROCESS_SPAWNER_H_
124+
125+#include <memory>
126+#include <future>
127+#include <initializer_list>
128+
129+namespace mir
130+{
131+namespace process
132+{
133+
134+class Handle;
135+
136+class Spawner
137+{
138+public:
139+ virtual ~Spawner() = default;
140+ /**
141+ * \brief Run a binary, respecting $PATH
142+ * \note All open file descriptors other than stdin, stdout, and stderr are closed before
143+ * running the binary.
144+ * \param [in] binary_name The name of the binary to run, such as “Xorg”
145+ * \return A future that will contain the handle to the spawned process.
146+ * The future will be populated after the process has been exec()d,
147+ * or the call to exec() has failed.
148+ * If spawning the binary fails the future will contain a
149+ * std::runtime_error.
150+ */
151+ virtual std::future<std::unique_ptr<Handle>> run_from_path(char const* binary_name) const = 0;
152+ /**
153+ * \brief Run a binary with arguments, respecting $PATH
154+ * \note All open file descriptors other than stdin, stdout, and stderr are closed before
155+ * running the binary.
156+ * \param [in] binary_name The name of the binary to run, such as “Xorg”
157+ * \param [in] args The arguments to be passed to the binary. No processing is done on
158+ * these strings; each one will appear verbatim as a separate char* in
159+ * the binary's argv[].
160+ * \return A future that will contain the handle to the spawned process.
161+ * The future will be populated after the process has been exec()d,
162+ * or the call to exec() has failed.
163+ * If spawning the binary fails the future will contain a
164+ * std::runtime_error.
165+ */
166+ virtual std::future<std::unique_ptr<Handle>> run_from_path(char const* binary_name, std::initializer_list<char const*> args) const = 0;
167+ /**
168+ * \brief Run a binary, respecting $PATH
169+ * \note All open file descriptors other than stdin, stdout, stderr, and the fds listed in
170+ * fds are closed before running the binary.
171+ * \param [in] binary_name The name of the binary to run, such as “Xorg”
172+ * \param [in] args The arguments to be passed to the binary. No processing is done on
173+ * these strings; each one will appear verbatim as a separate char* in
174+ * the binary's argv[].
175+ * \param [in] fds The set of fds that will remain available to the child process.
176+ * \return A future that will contain the handle to the spawned process.
177+ * The future will be populated after the process has been exec()d,
178+ * or the call to exec() has failed.
179+ * If spawning the binary fails the future will contain a
180+ * std::runtime_error.
181+ */
182+ virtual std::future<std::unique_ptr<Handle>> run_from_path(char const* binary_name, std::initializer_list<char const*> args, std::initializer_list<int> fds) const = 0;
183+};
184+}
185+}
186+#endif // MIR_PROCESS_SPAWNER_H_
187
188=== added directory 'include/server/mir/xserver'
189=== added file 'include/server/mir/xserver/null_server_spawner.h'
190--- include/server/mir/xserver/null_server_spawner.h 1970-01-01 00:00:00 +0000
191+++ include/server/mir/xserver/null_server_spawner.h 2014-01-28 06:34:09 +0000
192@@ -0,0 +1,25 @@
193+#ifndef MIR_X_NULL_SERVER_SPAWNER_H_
194+#define MIR_X_NULL_SERVER_SPAWNER_H_
195+
196+#include <mir/xserver/xserver_launcher.h>
197+
198+namespace mir
199+{
200+namespace X
201+{
202+class NullServerContext : public ServerContext
203+{
204+public:
205+ char const* client_connection_string() override;
206+};
207+
208+class NullServerSpawner : public ServerSpawner
209+{
210+public:
211+ std::future<std::unique_ptr<ServerContext>> create_server(std::shared_ptr<process::Spawner> const& spawner, std::shared_ptr<mir::frontend::Connector> const& connector) override;
212+};
213+
214+}
215+}
216+
217+#endif // MIR_X_NULL_SERVER_SPAWNER_H_
218
219=== added file 'include/server/mir/xserver/xserver_launcher.h'
220--- include/server/mir/xserver/xserver_launcher.h 1970-01-01 00:00:00 +0000
221+++ include/server/mir/xserver/xserver_launcher.h 2014-01-28 06:34:09 +0000
222@@ -0,0 +1,59 @@
223+/*
224+ * Copyright © 2014 Canonical Ltd.
225+ *
226+ * This program is free software: you can redistribute it and/or modify
227+ * it under the terms of the GNU General Public License version 3 as
228+ * published by the Free Software Foundation.
229+ *
230+ * This program is distributed in the hope that it will be useful,
231+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
232+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
233+ * GNU General Public License for more details.
234+ *
235+ * You should have received a copy of the GNU General Public License
236+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
237+ *
238+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
239+ */
240+
241+#ifndef MIR_X_XSERVER_LAUNCHER_H_
242+#define MIR_X_XSERVER_LAUNCHER_H_
243+
244+#include <future>
245+#include <memory>
246+
247+#include "mir/process/spawner.h"
248+#include "mir/frontend/connector.h"
249+
250+namespace mir
251+{
252+namespace X
253+{
254+
255+class ServerContext
256+{
257+public:
258+ virtual ~ServerContext() = default;
259+
260+ /**
261+ * \brief Get the XLib connection string to connect to this server
262+ *
263+ * This string can be passed by the client to XOpenDisplay to connect
264+ * to this server instance, or set in the DISPLAY environment variable
265+ * to be used as the default display.
266+ */
267+ virtual char const* client_connection_string() = 0;
268+};
269+
270+class ServerSpawner
271+{
272+public:
273+ virtual ~ServerSpawner() = default;
274+
275+ virtual std::future<std::unique_ptr<ServerContext>> create_server(std::shared_ptr<mir::process::Spawner> const& spawner,
276+ std::shared_ptr<mir::frontend::Connector> const& connector) = 0;
277+};
278+}
279+}
280+
281+#endif // MIR_X_XSERVER_LAUNCHER_H_
282
283=== renamed file 'include/test/mir_test/pipe.h' => 'include/shared/mir/pipe.h'
284--- include/test/mir_test/pipe.h 2013-07-03 09:54:10 +0000
285+++ include/shared/mir/pipe.h 2014-01-28 06:34:09 +0000
286@@ -1,16 +1,16 @@
287 /*
288 * Copyright © 2013 Canonical Ltd.
289 *
290- * This program is free software: you can redistribute it and/or modify it
291- * under the terms of the GNU General Public License version 3,
292- * as published by the Free Software Foundation.
293+ * This program is free software: you can redistribute it and/or modify
294+ * it under the terms of the GNU Lesser General Public License version 3 as
295+ * published by the Free Software Foundation.
296 *
297 * This program is distributed in the hope that it will be useful,
298 * but WITHOUT ANY WARRANTY; without even the implied warranty of
299 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
300- * GNU General Public License for more details.
301+ * GNU Lesser General Public License for more details.
302 *
303- * You should have received a copy of the GNU General Public License
304+ * You should have received a copy of the GNU Lesser General Public License
305 * along with this program. If not, see <http://www.gnu.org/licenses/>.
306 *
307 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
308@@ -21,18 +21,22 @@
309
310 namespace mir
311 {
312-namespace test
313+namespace pipe
314 {
315
316 class Pipe
317 {
318 public:
319 Pipe();
320+ Pipe(int flags);
321 ~Pipe();
322
323 int read_fd() const;
324 int write_fd() const;
325
326+ void close_read_fd();
327+ void close_write_fd();
328+
329 private:
330 Pipe(Pipe const&) = delete;
331 Pipe& operator=(Pipe const&) = delete;
332
333=== modified file 'src/server/CMakeLists.txt'
334--- src/server/CMakeLists.txt 2014-01-22 08:32:55 +0000
335+++ src/server/CMakeLists.txt 2014-01-28 06:34:09 +0000
336@@ -12,6 +12,8 @@
337 add_subdirectory(frontend/)
338 add_subdirectory(shell/)
339 add_subdirectory(time/)
340+add_subdirectory(process/)
341+add_subdirectory(xserver/)
342
343 set(PREFIX "${CMAKE_INSTALL_PREFIX}")
344 set(EXEC_PREFIX "${CMAKE_INSTALL_PREFIX}")
345@@ -56,6 +58,9 @@
346 mirlttng
347 mirnestedgraphics
348 miroffscreengraphics
349+ mirsharedpipe
350+ mirprocess
351+ mirxserverintegration
352 )
353
354 list(APPEND MIRSERVER_LINKS
355
356=== modified file 'src/server/default_server_configuration.cpp'
357--- src/server/default_server_configuration.cpp 2014-01-13 06:12:33 +0000
358+++ src/server/default_server_configuration.cpp 2014-01-28 06:34:09 +0000
359@@ -42,6 +42,7 @@
360 #include "mir/time/high_resolution_clock.h"
361 #include "mir/geometry/rectangles.h"
362 #include "mir/default_configuration.h"
363+#include "mir/xserver/null_server_spawner.h"
364
365 #include <map>
366
367@@ -249,3 +250,8 @@
368 return std::make_shared<mir::DefaultServerStatusListener>();
369 });
370 }
371+
372+std::shared_ptr<mir::X::ServerSpawner> mir::DefaultServerConfiguration::the_xserver_spawner()
373+{
374+ return std::make_shared<mir::X::NullServerSpawner>();
375+}
376
377=== added directory 'src/server/process'
378=== added file 'src/server/process/CMakeLists.txt'
379--- src/server/process/CMakeLists.txt 1970-01-01 00:00:00 +0000
380+++ src/server/process/CMakeLists.txt 2014-01-28 06:34:09 +0000
381@@ -0,0 +1,25 @@
382+# Copyright © 2014 Canonical Ltd.
383+#
384+# This program is free software: you can redistribute it and/or modify it
385+# under the terms of the GNU General Public License version 3,
386+# as published by the Free Software Foundation.
387+#
388+# This program is distributed in the hope that it will be useful,
389+# but WITHOUT ANY WARRANTY; without even the implied warranty of
390+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
391+# GNU General Public License for more details.
392+#
393+# You should have received a copy of the GNU General Public License
394+# along with this program. If not, see <http://www.gnu.org/licenses/>.
395+#
396+# Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
397+
398+set(
399+ PROCESS_SOURCES
400+ ${CMAKE_CURRENT_SOURCE_DIR}/fork_spawner.cpp
401+)
402+
403+add_library(
404+ mirprocess STATIC
405+ ${PROCESS_SOURCES}
406+)
407
408=== added file 'src/server/process/fork_spawner.cpp'
409--- src/server/process/fork_spawner.cpp 1970-01-01 00:00:00 +0000
410+++ src/server/process/fork_spawner.cpp 2014-01-28 06:34:09 +0000
411@@ -0,0 +1,165 @@
412+/*
413+ * Copyright © 2014 Canonical Ltd.
414+ *
415+ * This program is free software: you can redistribute it and/or modify it
416+ * under the terms of the GNU General Public License version 3,
417+ * as published by the Free Software Foundation.
418+ *
419+ * This program is distributed in the hope that it will be useful,
420+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
421+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
422+ * GNU General Public License for more details.
423+ *
424+ * You should have received a copy of the GNU General Public License
425+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
426+ *
427+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
428+ */
429+
430+#include "fork_spawner.h"
431+
432+#include "mir/process/handle.h"
433+#include "mir/pipe.h"
434+#include "mir/raii.h"
435+
436+#include <fcntl.h>
437+#include <unistd.h>
438+#include <errno.h>
439+#include <dirent.h>
440+#include <vector>
441+#include <thread>
442+#include <string>
443+#include <set>
444+#include <boost/throw_exception.hpp>
445+#include <boost/exception/errinfo_errno.hpp>
446+
447+namespace
448+{
449+class PidHandle : public mir::process::Handle
450+{
451+public:
452+ PidHandle(pid_t pid) : child_pid(pid)
453+ {
454+ }
455+
456+ Status status() const override
457+ {
458+ return Status::Running;
459+ }
460+ pid_t pid() const override
461+ {
462+ return child_pid;
463+ }
464+
465+private:
466+ pid_t child_pid;
467+};
468+
469+static std::vector<int> open_fds()
470+{
471+ std::vector<int> fds;
472+
473+ DIR* process_fds_dir;
474+ auto const dir_raii = mir::raii::paired_calls([&process_fds_dir]
475+ { process_fds_dir = opendir("/proc/self/fd"); },
476+ [&process_fds_dir]
477+ {
478+ if (process_fds_dir != nullptr)
479+ closedir(process_fds_dir);
480+ });
481+
482+ if (process_fds_dir == nullptr)
483+ BOOST_THROW_EXCEPTION(boost::enable_error_info(std::runtime_error("Failed to open process fds directory"))
484+ << boost::errinfo_errno(errno));
485+
486+ struct dirent* entry;
487+ errno = 0;
488+ while ((entry = readdir(process_fds_dir)) != nullptr)
489+ {
490+ // We don't care about directory links
491+ if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
492+ continue;
493+
494+ int fd;
495+ char* conversion_end;
496+ fd = strtol(entry->d_name, &conversion_end, 10);
497+
498+ if (*conversion_end != '\0' || conversion_end == entry->d_name)
499+ BOOST_THROW_EXCEPTION(std::runtime_error(std::string("Unexpected fd name: ") + entry->d_name));
500+
501+ if (fd != dirfd(process_fds_dir))
502+ fds.push_back(fd);
503+ }
504+ if (errno != 0)
505+ BOOST_THROW_EXCEPTION(boost::enable_error_info(std::runtime_error("Failed to read from process fds directory"))
506+ << boost::errinfo_errno(errno));
507+
508+ return fds;
509+}
510+
511+static std::unique_ptr<mir::process::Handle> run(char const* binary_name,
512+ std::initializer_list<char const*> args,
513+ std::initializer_list<int> fds)
514+{
515+ mir::pipe::Pipe error_pipe(O_CLOEXEC);
516+ pid_t child = fork();
517+
518+ if (child == 0)
519+ {
520+ std::set<int> precious_fds{fds};
521+ // Don't close stdin, stdout, or stderr
522+ precious_fds.insert({0, 1, 2});
523+ // Don't close our cross-process pipe, we need it and know it's CLOEXEC
524+ precious_fds.insert(error_pipe.write_fd());
525+
526+ error_pipe.close_read_fd();
527+
528+ for (auto fd : open_fds())
529+ {
530+ if (precious_fds.count(fd) == 0)
531+ close(fd);
532+ }
533+
534+ std::vector<char const*> argv;
535+ argv.push_back(binary_name);
536+ argv.insert(argv.end(), args);
537+ argv.push_back(nullptr);
538+
539+ execvp(binary_name, const_cast<char* const*>(argv.data()));
540+ // We can only get here by failing to exec
541+ write(error_pipe.write_fd(), &errno, sizeof errno);
542+ }
543+
544+ error_pipe.close_write_fd();
545+ error_t error = 0;
546+ if (read(error_pipe.read_fd(), &error, sizeof error) == -1)
547+ BOOST_THROW_EXCEPTION(boost::enable_error_info(std::runtime_error("Failed to read from pipe"))
548+ << boost::errinfo_errno(errno));
549+
550+ if (error != 0)
551+ BOOST_THROW_EXCEPTION(
552+ boost::enable_error_info(std::runtime_error(std::string("Failed to execute process: ") + binary_name))
553+ << boost::errinfo_errno(errno));
554+
555+ return std::unique_ptr<PidHandle>(new PidHandle(child));
556+}
557+}
558+
559+std::future<std::unique_ptr<mir::process::Handle>> mir::process::ForkSpawner::run_from_path(char const* binary_name)
560+ const
561+{
562+ return std::async(
563+ std::launch::async, run, binary_name, std::initializer_list<char const*>(), std::initializer_list<int>());
564+}
565+
566+std::future<std::unique_ptr<mir::process::Handle>> mir::process::ForkSpawner::run_from_path(
567+ char const* binary_name, std::initializer_list<char const*> args) const
568+{
569+ return std::async(std::launch::async, run, binary_name, args, std::initializer_list<int>());
570+}
571+
572+std::future<std::unique_ptr<mir::process::Handle>> mir::process::ForkSpawner::run_from_path(
573+ char const* binary_name, std::initializer_list<char const*> args, std::initializer_list<int> fds) const
574+{
575+ return std::async(std::launch::async, run, binary_name, args, fds);
576+}
577
578=== added file 'src/server/process/fork_spawner.h'
579--- src/server/process/fork_spawner.h 1970-01-01 00:00:00 +0000
580+++ src/server/process/fork_spawner.h 2014-01-28 06:34:09 +0000
581@@ -0,0 +1,44 @@
582+/*
583+ * Copyright © 2014 Canonical Ltd.
584+ *
585+ * This program is free software: you can redistribute it and/or modify it
586+ * under the terms of the GNU General Public License version 3,
587+ * as published by the Free Software Foundation.
588+ *
589+ * This program is distributed in the hope that it will be useful,
590+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
591+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
592+ * GNU General Public License for more details.
593+ *
594+ * You should have received a copy of the GNU General Public License
595+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
596+ *
597+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
598+ */
599+
600+
601+#ifndef MIR_PROCESS_FORK_SPAWNER_H_
602+#define MIR_PROCESS_FORK_SPAWNER_H_
603+
604+#include "mir/process/spawner.h"
605+
606+namespace mir
607+{
608+namespace process
609+{
610+
611+/**
612+ * \brief An implementation of mir::process::Spawner that forks new processes
613+ */
614+class ForkSpawner : public Spawner
615+{
616+public:
617+ std::future<std::unique_ptr<Handle>> run_from_path(char const* binary_name) const override;
618+ std::future<std::unique_ptr<Handle>> run_from_path(char const* binary_name, std::initializer_list<char const*> args) const override;
619+ std::future<std::unique_ptr<Handle>> run_from_path(char const* binary_name, std::initializer_list<char const*> args, std::initializer_list<int> fds) const override;
620+};
621+
622+}
623+}
624+
625+#endif // MIR_PROCESS_FORK_SPAWNER_H_
626
627=== added directory 'src/server/xserver'
628=== added file 'src/server/xserver/CMakeLists.txt'
629--- src/server/xserver/CMakeLists.txt 1970-01-01 00:00:00 +0000
630+++ src/server/xserver/CMakeLists.txt 2014-01-28 06:34:09 +0000
631@@ -0,0 +1,12 @@
632+set(
633+ MIR_XSERVER_SRCS
634+
635+ global_socket_listening_server_spawner.cpp
636+ null_server_spawner.cpp
637+)
638+
639+ADD_LIBRARY(
640+ mirxserverintegration STATIC
641+
642+ ${MIR_XSERVER_SRCS}
643+)
644
645=== added file 'src/server/xserver/global_socket_listening_server_spawner.cpp'
646--- src/server/xserver/global_socket_listening_server_spawner.cpp 1970-01-01 00:00:00 +0000
647+++ src/server/xserver/global_socket_listening_server_spawner.cpp 2014-01-28 06:34:09 +0000
648@@ -0,0 +1,71 @@
649+/*
650+ * Copyright © 2014 Canonical Ltd.
651+ *
652+ * This program is free software: you can redistribute it and/or modify
653+ * it under the terms of the GNU General Public License version 3 as
654+ * published by the Free Software Foundation.
655+ *
656+ * This program is distributed in the hope that it will be useful,
657+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
658+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
659+ * GNU General Public License for more details.
660+ *
661+ * You should have received a copy of the GNU General Public License
662+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
663+ *
664+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
665+ */
666+
667+#include <unistd.h>
668+#include <errno.h>
669+
670+#include <boost/throw_exception.hpp>
671+#include <boost/exception/errinfo_errno.hpp>
672+
673+#include "global_socket_listening_server_spawner.h"
674+#include "mir/process/handle.h"
675+
676+namespace mx = mir::X;
677+
678+mx::GlobalSocketListeningServerContext::GlobalSocketListeningServerContext(std::unique_ptr<mir::process::Handle> server_handle, std::string connection_string)
679+ : server_handle(std::move(server_handle)),
680+ connection_string(connection_string)
681+{
682+}
683+
684+char const* mx::GlobalSocketListeningServerContext::client_connection_string()
685+{
686+ return connection_string.c_str();
687+}
688+
689+std::future<std::unique_ptr<mx::ServerContext>> mx::GlobalSocketListeningServerSpawner::create_server(std::shared_ptr<mir::process::Spawner> const& spawner, std::shared_ptr<mir::frontend::Connector> const& connector)
690+{
691+ return std::async(std::launch::async, [spawner, connector]()
692+ {
693+ mir::pipe::Pipe displayfd_pipe;
694+ auto displayfd = std::to_string(displayfd_pipe.write_fd());
695+ int mir_fd = connector->client_socket_fd();
696+ auto mir_fd_arg = std::string("fd://") + std::to_string(mir_fd);
697+
698+ auto future_handle = spawner->run_from_path("Xorg",
699+ {"-displayfd", displayfd.c_str(),
700+ "-mir", "xserver",
701+ "-mirSocket", mir_fd_arg.c_str()},
702+ {displayfd_pipe.write_fd(), mir_fd});
703+
704+ char display_number[10];
705+ errno = 0;
706+ int bytes_read = read(displayfd_pipe.read_fd(), display_number, sizeof display_number);
707+
708+ while (bytes_read == -1 && errno == EINTR)
709+ bytes_read = read(displayfd_pipe.read_fd(), display_number, sizeof display_number);;
710+
711+ if (errno != 0)
712+ BOOST_THROW_EXCEPTION(boost::enable_error_info(std::runtime_error("Failed to receive display number from Xserver"))
713+ << boost::errinfo_errno(errno));
714+
715+ display_number[bytes_read] = '\0';
716+
717+ return std::unique_ptr<mx::ServerContext>(new mx::GlobalSocketListeningServerContext(future_handle.get(), std::string(":") + display_number));
718+ });
719+}
720
721=== added file 'src/server/xserver/global_socket_listening_server_spawner.h'
722--- src/server/xserver/global_socket_listening_server_spawner.h 1970-01-01 00:00:00 +0000
723+++ src/server/xserver/global_socket_listening_server_spawner.h 2014-01-28 06:34:09 +0000
724@@ -0,0 +1,52 @@
725+/*
726+ * Copyright © 2014 Canonical Ltd.
727+ *
728+ * This program is free software: you can redistribute it and/or modify
729+ * it under the terms of the GNU General Public License version 3 as
730+ * published by the Free Software Foundation.
731+ *
732+ * This program is distributed in the hope that it will be useful,
733+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
734+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
735+ * GNU General Public License for more details.
736+ *
737+ * You should have received a copy of the GNU General Public License
738+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
739+ *
740+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
741+ */
742+
743+#ifndef MIR_X_GLOBAL_SOCKET_LISTENING_SERVER_SPAWNER_H_
744+#define MIR_X_GLOBAL_SOCKET_LISTENING_SERVER_SPAWNER_H_
745+
746+#include "mir/xserver/xserver_launcher.h"
747+#include "mir/process/spawner.h"
748+#include "mir/pipe.h"
749+
750+#include <memory>
751+
752+namespace mir
753+{
754+namespace X
755+{
756+class GlobalSocketListeningServerContext : public ServerContext
757+{
758+public:
759+ GlobalSocketListeningServerContext(std::unique_ptr<mir::process::Handle> server_handle, std::string connection_string);
760+ char const* client_connection_string() override;
761+
762+private:
763+ std::unique_ptr<mir::process::Handle> server_handle;
764+ std::string connection_string;
765+};
766+
767+class GlobalSocketListeningServerSpawner : public ServerSpawner
768+{
769+public:
770+ std::future<std::unique_ptr<ServerContext>> create_server(std::shared_ptr<mir::process::Spawner> const& spawner, std::shared_ptr<mir::frontend::Connector> const& connector) override;
771+};
772+
773+}
774+}
775+
776+#endif // MIR_X_GLOBAL_SOCKET_LISTENING_SERVER_SPAWNER_H_
777
778=== added file 'src/server/xserver/null_server_spawner.cpp'
779--- src/server/xserver/null_server_spawner.cpp 1970-01-01 00:00:00 +0000
780+++ src/server/xserver/null_server_spawner.cpp 2014-01-28 06:34:09 +0000
781@@ -0,0 +1,34 @@
782+/*
783+ * Copyright © 2014 Canonical Ltd.
784+ *
785+ * This program is free software: you can redistribute it and/or modify
786+ * it under the terms of the GNU General Public License version 3 as
787+ * published by the Free Software Foundation.
788+ *
789+ * This program is distributed in the hope that it will be useful,
790+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
791+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
792+ * GNU General Public License for more details.
793+ *
794+ * You should have received a copy of the GNU General Public License
795+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
796+ *
797+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
798+ */
799+
800+#include "mir/xserver/null_server_spawner.h"
801+
802+const char* mir::X::NullServerContext::client_connection_string()
803+{
804+ return "";
805+}
806+
807+std::future<std::unique_ptr<mir::X::ServerContext>> mir::X::NullServerSpawner::create_server(
808+ std::shared_ptr<mir::process::Spawner> const& unused, std::shared_ptr<mir::frontend::Connector> const& unuseder)
809+{
810+ static_cast<void>(unused);
811+ static_cast<void>(unuseder);
812+ std::promise<std::unique_ptr<mir::X::ServerContext>> boring_promise;
813+ boring_promise.set_value(std::unique_ptr<mir::X::ServerContext>(new mir::X::NullServerContext));
814+ return boring_promise.get_future();
815+}
816
817=== modified file 'src/shared/CMakeLists.txt'
818--- src/shared/CMakeLists.txt 2014-01-13 06:12:33 +0000
819+++ src/shared/CMakeLists.txt 2014-01-28 06:34:09 +0000
820@@ -11,6 +11,7 @@
821 add_subdirectory(logging)
822 add_subdirectory(lttng)
823 add_subdirectory(env)
824+add_subdirectory(pipe)
825
826 set(
827 MIR_COMMON_PLATFORM_LIBRARIES
828
829=== added directory 'src/shared/pipe'
830=== added file 'src/shared/pipe/CMakeLists.txt'
831--- src/shared/pipe/CMakeLists.txt 1970-01-01 00:00:00 +0000
832+++ src/shared/pipe/CMakeLists.txt 2014-01-28 06:34:09 +0000
833@@ -0,0 +1,19 @@
834+# Copyright © 2014 Canonical Ltd.
835+#
836+# This program is free software: you can redistribute it and/or modify
837+# it under the terms of the GNU Lesser General Public License version 3 as
838+# published by the Free Software Foundation.
839+#
840+# This program is distributed in the hope that it will be useful,
841+# but WITHOUT ANY WARRANTY; without even the implied warranty of
842+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
843+# GNU Lesser General Public License for more details.
844+#
845+# You should have received a copy of the GNU Lesser General Public License
846+# along with this program. If not, see <http://www.gnu.org/licenses/>.
847+#
848+# Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
849+
850+add_library(mirsharedpipe STATIC
851+ pipe.cpp
852+)
853
854=== renamed file 'tests/mir_test/pipe.cpp' => 'src/shared/pipe/pipe.cpp'
855--- tests/mir_test/pipe.cpp 2013-07-02 15:44:55 +0000
856+++ src/shared/pipe/pipe.cpp 2014-01-28 06:34:09 +0000
857@@ -2,34 +2,40 @@
858 * Copyright © 2013 Canonical Ltd.
859 *
860 * This program is free software: you can redistribute it and/or modify it
861- * under the terms of the GNU General Public License version 3,
862+ * under the terms of the GNU Lesser General Public License version 3,
863 * as published by the Free Software Foundation.
864 *
865 * This program is distributed in the hope that it will be useful,
866 * but WITHOUT ANY WARRANTY; without even the implied warranty of
867 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
868- * GNU General Public License for more details.
869+ * GNU Lesser General Public License for more details.
870 *
871- * You should have received a copy of the GNU General Public License
872+ * You should have received a copy of the GNU Lesser General Public License
873 * along with this program. If not, see <http://www.gnu.org/licenses/>.
874 *
875 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
876 */
877
878-#include <mir_test/pipe.h>
879+#include "mir/pipe.h"
880
881 #include <boost/throw_exception.hpp>
882 #include <boost/exception/errinfo_errno.hpp>
883
884 #include <stdexcept>
885
886+#include <fcntl.h>
887 #include <unistd.h>
888
889-namespace mt = mir::test;
890-
891-mt::Pipe::Pipe()
892-{
893- if (pipe(pipefd))
894+namespace mp = mir::pipe;
895+
896+mp::Pipe::Pipe()
897+ : Pipe(0)
898+{
899+}
900+
901+mp::Pipe::Pipe(int flags)
902+{
903+ if (::pipe2(pipefd, flags))
904 {
905 BOOST_THROW_EXCEPTION(
906 boost::enable_error_info(std::runtime_error("Failed to create pipe"))
907@@ -37,18 +43,32 @@
908 }
909 }
910
911-mt::Pipe::~Pipe()
912+mp::Pipe::~Pipe()
913+{
914+ if (pipefd[0] != -1)
915+ close(pipefd[0]);
916+ if (pipefd[1] != -1)
917+ close(pipefd[1]);
918+}
919+
920+int mp::Pipe::read_fd() const
921+{
922+ return pipefd[0];
923+}
924+
925+int mp::Pipe::write_fd() const
926+{
927+ return pipefd[1];
928+}
929+
930+void mp::Pipe::close_read_fd()
931 {
932 close(pipefd[0]);
933+ pipefd[0] = -1;
934+}
935+
936+void mp::Pipe::close_write_fd()
937+{
938 close(pipefd[1]);
939-}
940-
941-int mt::Pipe::read_fd() const
942-{
943- return pipefd[0];
944-}
945-
946-int mt::Pipe::write_fd() const
947-{
948- return pipefd[1];
949+ pipefd[1] = -1;
950 }
951
952=== modified file 'tests/CMakeLists.txt'
953--- tests/CMakeLists.txt 2014-01-23 08:06:05 +0000
954+++ tests/CMakeLists.txt 2014-01-28 06:34:09 +0000
955@@ -36,6 +36,7 @@
956
957 if (MIR_BUILD_ACCEPTANCE_TESTS)
958 add_subdirectory(acceptance-tests/)
959+ pkg_check_modules(X11 REQUIRED x11)
960 endif (MIR_BUILD_ACCEPTANCE_TESTS)
961
962 if (MIR_BUILD_INTEGRATION_TESTS)
963
964=== modified file 'tests/acceptance-tests/CMakeLists.txt'
965--- tests/acceptance-tests/CMakeLists.txt 2014-01-24 11:15:31 +0000
966+++ tests/acceptance-tests/CMakeLists.txt 2014-01-28 06:34:09 +0000
967@@ -27,6 +27,8 @@
968 test_server_disconnect.cpp
969 test_client_library_drm.cpp
970 test_protobuf.cpp
971+ test_subprocess.cpp
972+ test_xserver_spawner.cpp
973 ${GENERATED_PROTOBUF_SRCS}
974 ${GENERATED_PROTOBUF_HDRS}
975 )
976@@ -65,6 +67,7 @@
977 ${GMOCK_LIBRARY}
978 ${GMOCK_MAIN_LIBRARY}
979 ${CMAKE_THREAD_LIBS_INIT} # Link in pthread.
980+ ${X11_LDFLAGS}
981 )
982
983 CMAKE_DEPENDENT_OPTION(
984
985=== modified file 'tests/acceptance-tests/test_display_configuration.cpp'
986--- tests/acceptance-tests/test_display_configuration.cpp 2014-01-13 06:12:33 +0000
987+++ tests/acceptance-tests/test_display_configuration.cpp 2014-01-28 06:34:09 +0000
988@@ -20,6 +20,7 @@
989 #include "mir/frontend/session_authorizer.h"
990 #include "mir/graphics/event_handler_register.h"
991 #include "src/server/scene/global_event_sender.h"
992+#include "mir/pipe.h"
993
994 #include "mir_test_framework/display_server_test_fixture.h"
995 #include "mir_test_framework/cross_process_sync.h"
996@@ -32,7 +33,6 @@
997 #include "mir_test_doubles/stub_display_configuration.h"
998 #include "mir_test_doubles/stub_buffer_allocator.h"
999 #include "mir_test/fake_shared.h"
1000-#include "mir_test/pipe.h"
1001 #include "mir_test/cross_process_action.h"
1002
1003 #include "mir_toolkit/mir_client_library.h"
1004@@ -49,6 +49,7 @@
1005 namespace mtf = mir_test_framework;
1006 namespace mtd = mir::test::doubles;
1007 namespace mt = mir::test;
1008+namespace mp = mir::pipe;
1009
1010 namespace
1011 {
1012@@ -127,7 +128,7 @@
1013 private:
1014 std::shared_ptr<mg::DisplayConfiguration> config;
1015 mtd::NullDisplayBuffer display_buffer;
1016- mt::Pipe p;
1017+ mp::Pipe p;
1018 std::atomic<bool> handler_called;
1019 };
1020
1021
1022=== added file 'tests/acceptance-tests/test_subprocess.cpp'
1023--- tests/acceptance-tests/test_subprocess.cpp 1970-01-01 00:00:00 +0000
1024+++ tests/acceptance-tests/test_subprocess.cpp 2014-01-28 06:34:09 +0000
1025@@ -0,0 +1,166 @@
1026+/*
1027+ * Copyright © 2014 Canonical Ltd.
1028+ *
1029+ * This program is free software: you can redistribute it and/or modify it
1030+ * under the terms of the GNU General Public License version 3,
1031+ * as published by the Free Software Foundation.
1032+ *
1033+ * This program is distributed in the hope that it will be useful,
1034+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1035+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1036+ * GNU General Public License for more details.
1037+ *
1038+ * You should have received a copy of the GNU General Public License
1039+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1040+ *
1041+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
1042+ */
1043+
1044+#include "src/server/process/fork_spawner.h"
1045+#include "mir/process/spawner.h"
1046+#include "mir/process/handle.h"
1047+
1048+#include <fstream>
1049+#include <sstream>
1050+#include <unistd.h>
1051+#include <thread>
1052+#include <sys/types.h>
1053+#include <sys/stat.h>
1054+#include <fcntl.h>
1055+#include <dirent.h>
1056+#include <errno.h>
1057+#include <string.h>
1058+
1059+#include <gtest/gtest.h>
1060+#include <gmock/gmock.h>
1061+
1062+TEST(ProcessTest, RunFromPathRunsCorrectBinary)
1063+{
1064+ mir::process::ForkSpawner spawner;
1065+
1066+ auto future_handle = spawner.run_from_path("true");
1067+ auto handle = future_handle.get();
1068+
1069+ std::stringstream stat_path;
1070+ stat_path << "/proc/" << handle->pid() << "/stat";
1071+
1072+ std::ifstream status{stat_path.str()};
1073+
1074+ char binary_name[PATH_MAX];
1075+
1076+ // Read up to the initial '('
1077+ while (status.good() && !status.eof() && (status.get() != '('))
1078+ ;
1079+
1080+ status.getline(binary_name, sizeof(binary_name), ')');
1081+
1082+ EXPECT_STREQ("true", binary_name);
1083+}
1084+
1085+TEST(ProcessTest, ChildHasExpectedFDs)
1086+{
1087+ mir::process::ForkSpawner spawner;
1088+
1089+ auto fd = open("/dev/null", O_RDONLY);
1090+ auto future_handle = spawner.run_from_path("sleep", {"1"});
1091+ auto handle = future_handle.get();
1092+
1093+ // TODO: Why is this racy?
1094+ std::this_thread::sleep_for(std::chrono::milliseconds{1});
1095+
1096+ std::stringstream fds_path;
1097+ fds_path << "/proc/" << handle->pid() << "/fd";
1098+
1099+ bool found_stdin = false, found_stdout = false, found_stderr = false;
1100+
1101+ DIR* process_fds_dir = opendir(fds_path.str().c_str());
1102+ ASSERT_NE(process_fds_dir, nullptr) << "Error opening " << fds_path.str() << ": " << strerror(errno) << " ("
1103+ << errno << ")" << std::endl;
1104+ struct dirent* entry;
1105+ while ((entry = readdir(process_fds_dir)) != nullptr)
1106+ {
1107+ // We don't care about directory links
1108+ if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
1109+ continue;
1110+
1111+ if (strcmp(entry->d_name, "0") == 0)
1112+ {
1113+ found_stdin = true;
1114+ }
1115+ else if (strcmp(entry->d_name, "1") == 0)
1116+ {
1117+ found_stdout = true;
1118+ }
1119+ else if (strcmp(entry->d_name, "2") == 0)
1120+ {
1121+ found_stderr = true;
1122+ }
1123+ else
1124+ {
1125+ ADD_FAILURE() << "Unexpected fd: " << entry->d_name << std::endl;
1126+ }
1127+ }
1128+ EXPECT_TRUE(found_stdin);
1129+ EXPECT_TRUE(found_stdout);
1130+ EXPECT_TRUE(found_stderr);
1131+ closedir(process_fds_dir);
1132+ close(fd);
1133+}
1134+
1135+TEST(ProcessTest, ChildReceivesExpectedCmdline)
1136+{
1137+ mir::process::ForkSpawner spawner;
1138+
1139+ auto future_handle = spawner.run_from_path("sleep", {"10"});
1140+ auto handle = future_handle.get();
1141+
1142+ std::stringstream cmdline_path;
1143+ cmdline_path << "/proc/" << handle->pid() << "/cmdline";
1144+
1145+ std::ifstream cmdline{cmdline_path.str()};
1146+
1147+ // We expect "sleep\010\0\0"
1148+ char buffer[5 + 1 + 2 + 1 + 1];
1149+ cmdline.read(buffer, sizeof(buffer));
1150+
1151+ EXPECT_STREQ("sleep", buffer);
1152+ EXPECT_STREQ("10", buffer + 6);
1153+}
1154+
1155+TEST(ProcessTest, SpawningNonExistentBinaryThrows)
1156+{
1157+ mir::process::ForkSpawner spawner;
1158+
1159+ auto future_handle = spawner.run_from_path("I'm a binary that almost certainly doesn't exist");
1160+
1161+ EXPECT_THROW(future_handle.get(), std::runtime_error);
1162+}
1163+
1164+TEST(ProcessTest, ChildRetainsSetFDs)
1165+{
1166+ mir::process::ForkSpawner spawner;
1167+
1168+ auto fd = open("/dev/null", 0);
1169+ auto future_handle = spawner.run_from_path("sleep", {"1"}, {fd});
1170+ auto handle = future_handle.get();
1171+
1172+ // TODO: Why is this racy?
1173+
1174+ std::stringstream fds_path;
1175+ fds_path << "/proc/" << handle->pid() << "/fd";
1176+
1177+ bool found_our_fd = false;
1178+
1179+ DIR* process_fds_dir = opendir(fds_path.str().c_str());
1180+ ASSERT_NE(process_fds_dir, nullptr) << "Error opening " << fds_path.str() << ": " << strerror(errno) << " ("
1181+ << errno << ")" << std::endl;
1182+ struct dirent* entry;
1183+ while ((entry = readdir(process_fds_dir)) != nullptr)
1184+ {
1185+ if (atoi(entry->d_name) == fd)
1186+ found_our_fd = true;
1187+ }
1188+ EXPECT_TRUE(found_our_fd);
1189+ closedir(process_fds_dir);
1190+ close(fd);
1191+}
1192
1193=== added file 'tests/acceptance-tests/test_xserver_spawner.cpp'
1194--- tests/acceptance-tests/test_xserver_spawner.cpp 1970-01-01 00:00:00 +0000
1195+++ tests/acceptance-tests/test_xserver_spawner.cpp 2014-01-28 06:34:09 +0000
1196@@ -0,0 +1,73 @@
1197+/*
1198+ * Copyright © 2014 Canonical Ltd.
1199+ *
1200+ * This program is free software: you can redistribute it and/or modify
1201+ * it under the terms of the GNU General Public License version 3 as
1202+ * published by the Free Software Foundation.
1203+ *
1204+ * This program is distributed in the hope that it will be useful,
1205+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1206+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1207+ * GNU General Public License for more details.
1208+ *
1209+ * You should have received a copy of the GNU General Public License
1210+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1211+ *
1212+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
1213+ */
1214+
1215+#include "src/server/xserver/global_socket_listening_server_spawner.h"
1216+#include "mir_test_framework/testing_server_configuration.h"
1217+#include "mir_test_framework/in_process_server.h"
1218+#include "mir/xserver/xserver_launcher.h"
1219+#include "src/server/process/fork_spawner.h"
1220+
1221+#include <X11/Xlib.h>
1222+#include <stdlib.h>
1223+
1224+#include <gtest/gtest.h>
1225+
1226+namespace mtf = mir_test_framework;
1227+namespace mx = mir::X;
1228+
1229+namespace
1230+{
1231+struct XserverSpawningServer : public mtf::InProcessServer
1232+{
1233+public:
1234+ std::shared_ptr<mx::ServerSpawner> the_xserver_spawner()
1235+ {
1236+ return config.the_xserver_spawner();
1237+ }
1238+
1239+ mir::DefaultServerConfiguration& server_config() override
1240+ {
1241+ return config;
1242+ }
1243+
1244+ class SocketListeningXServerConfig : public mtf::StubbedServerConfiguration
1245+ {
1246+ public:
1247+ std::shared_ptr<mx::ServerSpawner> the_xserver_spawner() override
1248+ {
1249+ return std::make_shared<mx::GlobalSocketListeningServerSpawner> ();
1250+ }
1251+ } config;
1252+};
1253+}
1254+
1255+// This requires a bit more fiddling before it will work.
1256+// Particularly, it needs an xorg.conf that specifies dummy
1257+// devices, so the real devices don't fail to load.
1258+TEST_F(XserverSpawningServer, DISABLED_X11ClientConnects)
1259+{
1260+ // Ensure the surrounding environment doesn't mess with the test
1261+ unsetenv("DISPLAY");
1262+
1263+ auto xserver = the_xserver_spawner()->create_server(std::make_shared<mir::process::ForkSpawner>(), config.the_connector());
1264+ Display* disp = XOpenDisplay(xserver.get()->client_connection_string());
1265+
1266+ ASSERT_TRUE(disp != NULL);
1267+
1268+ XCloseDisplay(disp);
1269+}
1270
1271=== modified file 'tests/integration-tests/test_display_server_main_loop_events.cpp'
1272--- tests/integration-tests/test_display_server_main_loop_events.cpp 2014-01-13 06:12:33 +0000
1273+++ tests/integration-tests/test_display_server_main_loop_events.cpp 2014-01-28 06:34:09 +0000
1274@@ -23,8 +23,8 @@
1275 #include "mir/main_loop.h"
1276 #include "mir/display_changer.h"
1277 #include "mir/server_status_listener.h"
1278+#include "mir/pipe.h"
1279
1280-#include "mir_test/pipe.h"
1281 #include "mir_test_framework/testing_server_configuration.h"
1282 #include "mir_test_doubles/mock_input_manager.h"
1283 #include "mir_test_doubles/mock_compositor.h"
1284@@ -46,7 +46,7 @@
1285 namespace mc = mir::compositor;
1286 namespace mg = mir::graphics;
1287 namespace mf = mir::frontend;
1288-namespace mt = mir::test;
1289+namespace mp = mir::pipe;
1290 namespace mtd = mir::test::doubles;
1291 namespace mtf = mir_test_framework;
1292
1293@@ -314,7 +314,7 @@
1294 std::shared_ptr<mtd::MockInputManager> mock_input_manager;
1295 std::shared_ptr<MockDisplayChanger> mock_display_changer;
1296
1297- mt::Pipe p;
1298+ mp::Pipe p;
1299 int const pause_signal;
1300 int const resume_signal;
1301 };
1302
1303=== modified file 'tests/mir_test/CMakeLists.txt'
1304--- tests/mir_test/CMakeLists.txt 2013-08-28 03:41:48 +0000
1305+++ tests/mir_test/CMakeLists.txt 2014-01-28 06:34:09 +0000
1306@@ -1,7 +1,6 @@
1307 add_library(
1308 mir-test STATIC
1309
1310- pipe.cpp
1311 display_config_matchers.cpp
1312 cross_process_action.cpp
1313 )
1314
1315=== modified file 'tests/unit-tests/CMakeLists.txt'
1316--- tests/unit-tests/CMakeLists.txt 2014-01-23 08:21:21 +0000
1317+++ tests/unit-tests/CMakeLists.txt 2014-01-28 06:34:09 +0000
1318@@ -11,6 +11,7 @@
1319 test_asio_main_loop.cpp
1320 shared_library_test.cpp
1321 test_raii.cpp
1322+ test_fork_spawner.cpp
1323 )
1324
1325 if (UMOCKDEV_REQUIRED)
1326@@ -29,6 +30,7 @@
1327 add_subdirectory(android_input/)
1328 add_subdirectory(scene/)
1329 add_subdirectory(draw/)
1330+add_subdirectory(xserver/)
1331
1332 add_executable(mir_unit_tests ${UNIT_TEST_SOURCES})
1333 uses_android_input(mir_unit_tests)
1334@@ -42,6 +44,7 @@
1335 mirdraw
1336 mirtestdraw
1337 mirlogging
1338+ mirxserverintegration
1339
1340 mir-test
1341 mir-test-doubles
1342
1343=== modified file 'tests/unit-tests/test_asio_main_loop.cpp'
1344--- tests/unit-tests/test_asio_main_loop.cpp 2014-01-13 06:12:33 +0000
1345+++ tests/unit-tests/test_asio_main_loop.cpp 2014-01-28 06:34:09 +0000
1346@@ -17,7 +17,7 @@
1347 */
1348
1349 #include "mir/asio_main_loop.h"
1350-#include "mir_test/pipe.h"
1351+#include "mir/pipe.h"
1352
1353 #include <gtest/gtest.h>
1354
1355@@ -27,7 +27,7 @@
1356 #include <sys/types.h>
1357 #include <unistd.h>
1358
1359-namespace mt = mir::test;
1360+namespace mp = mir::pipe;
1361
1362 TEST(AsioMainLoopTest, signal_handled)
1363 {
1364@@ -148,7 +148,7 @@
1365
1366 TEST(AsioMainLoopTest, fd_data_handled)
1367 {
1368- mt::Pipe p;
1369+ mp::Pipe p;
1370 char const data_to_write{'a'};
1371 int handled_fd{0};
1372 char data_read{0};
1373@@ -173,7 +173,7 @@
1374
1375 TEST(AsioMainLoopTest, multiple_fds_with_single_handler_handled)
1376 {
1377- std::vector<mt::Pipe> const pipes(2);
1378+ std::vector<mp::Pipe> const pipes(2);
1379 size_t const num_elems_to_send{10};
1380 std::vector<int> handled_fds;
1381 std::vector<size_t> elems_read;
1382@@ -223,7 +223,7 @@
1383
1384 TEST(AsioMainLoopTest, multiple_fd_handlers_are_called)
1385 {
1386- std::vector<mt::Pipe> const pipes(3);
1387+ std::vector<mp::Pipe> const pipes(3);
1388 std::vector<int> const elems_to_send{10,11,12};
1389 std::vector<int> handled_fds{0,0,0};
1390 std::vector<int> elems_read{0,0,0};
1391
1392=== added file 'tests/unit-tests/test_fork_spawner.cpp'
1393--- tests/unit-tests/test_fork_spawner.cpp 1970-01-01 00:00:00 +0000
1394+++ tests/unit-tests/test_fork_spawner.cpp 2014-01-28 06:34:09 +0000
1395@@ -0,0 +1,64 @@
1396+/*
1397+ * Copyright © 2014 Canonical Ltd.
1398+ *
1399+ * This program is free software: you can redistribute it and/or modify it
1400+ * under the terms of the GNU General Public License version 3,
1401+ * as published by the Free Software Foundation.
1402+ *
1403+ * This program is distributed in the hope that it will be useful,
1404+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1405+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1406+ * GNU General Public License for more details.
1407+ *
1408+ * You should have received a copy of the GNU General Public License
1409+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1410+ *
1411+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
1412+ */
1413+
1414+#include "src/server/process/fork_spawner.cpp"
1415+
1416+#include <set>
1417+
1418+#include <gtest/gtest.h>
1419+#include <gmock/gmock.h>
1420+
1421+using namespace testing;
1422+
1423+TEST(ForkSpawnerTest, OpenFDsListsCorrectFDs)
1424+{
1425+ // We start with stdin, stdout stderr
1426+ std::set<int> std_fds{0, 1, 2};
1427+ std::set<int> extra_fds;
1428+
1429+ extra_fds.insert(open("/dev/null", O_RDONLY));
1430+
1431+ // Ensure we have a hole in our fd numbering
1432+ int spacing_fd = open("/dev/null", O_RDONLY);
1433+
1434+ extra_fds.insert(open("/dev/null", O_RDONLY));
1435+ extra_fds.insert(open("/dev/null", O_RDONLY));
1436+
1437+ close(spacing_fd);
1438+
1439+ for (auto fd : open_fds())
1440+ {
1441+ if (std_fds.erase(fd) != 1)
1442+ {
1443+ if (extra_fds.erase(fd) != 1)
1444+ {
1445+ // We can't fail on unexpected fd as we don't control our test environment
1446+ // sufficiently - ctest leaves fds open.
1447+ close(fd);
1448+ }
1449+ }
1450+ }
1451+
1452+ EXPECT_THAT(std_fds, IsEmpty());
1453+ EXPECT_THAT(extra_fds, IsEmpty());
1454+
1455+ for (auto fd : extra_fds)
1456+ {
1457+ close(fd);
1458+ }
1459+}
1460
1461=== added directory 'tests/unit-tests/xserver'
1462=== added file 'tests/unit-tests/xserver/CMakeLists.txt'
1463--- tests/unit-tests/xserver/CMakeLists.txt 1970-01-01 00:00:00 +0000
1464+++ tests/unit-tests/xserver/CMakeLists.txt 2014-01-28 06:34:09 +0000
1465@@ -0,0 +1,8 @@
1466+list(APPEND UNIT_TEST_SOURCES
1467+ ${CMAKE_CURRENT_SOURCE_DIR}/test_xserver_spawner.cpp
1468+)
1469+
1470+set(
1471+ UNIT_TEST_SOURCES
1472+ ${UNIT_TEST_SOURCES}
1473+ PARENT_SCOPE)
1474
1475=== added file 'tests/unit-tests/xserver/test_xserver_spawner.cpp'
1476--- tests/unit-tests/xserver/test_xserver_spawner.cpp 1970-01-01 00:00:00 +0000
1477+++ tests/unit-tests/xserver/test_xserver_spawner.cpp 2014-01-28 06:34:09 +0000
1478@@ -0,0 +1,206 @@
1479+/*
1480+ * Copyright © 2014 Canonical Ltd.
1481+ *
1482+ * This program is free software: you can redistribute it and/or modify
1483+ * it under the terms of the GNU General Public License version 3 as
1484+ * published by the Free Software Foundation.
1485+ *
1486+ * This program is distributed in the hope that it will be useful,
1487+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1488+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1489+ * GNU General Public License for more details.
1490+ *
1491+ * You should have received a copy of the GNU General Public License
1492+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1493+ *
1494+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
1495+ */
1496+
1497+#include <future>
1498+#include <vector>
1499+#include <thread>
1500+#include <condition_variable>
1501+
1502+#include <gtest/gtest.h>
1503+#include <gmock/gmock.h>
1504+
1505+#include "mir/process/spawner.h"
1506+#include "mir/process/handle.h"
1507+
1508+#include "src/server/xserver/global_socket_listening_server_spawner.h"
1509+
1510+using namespace ::testing;
1511+
1512+namespace
1513+{
1514+struct MockConnector : public mir::frontend::Connector
1515+{
1516+ MOCK_METHOD0(start, void(void));
1517+ MOCK_METHOD0(stop, void(void));
1518+ MOCK_CONST_METHOD0(client_socket_fd, int(void));
1519+ MOCK_CONST_METHOD0(remove_endpoint, void(void));
1520+};
1521+
1522+struct MockProcessSpawner : public mir::process::Spawner
1523+{
1524+ MOCK_CONST_METHOD3(run, mir::process::Handle*(std::string, std::vector<char const*>, std::vector<int>));
1525+
1526+ std::future<std::unique_ptr<mir::process::Handle>> run_from_path(char const* binary) const override
1527+ {
1528+ std::promise<std::unique_ptr<mir::process::Handle>> dummy_promise;
1529+ dummy_promise.set_value(std::unique_ptr<mir::process::Handle>(
1530+ run(binary, std::initializer_list<char const*>(), std::initializer_list<int>())));
1531+ return dummy_promise.get_future();
1532+ }
1533+ std::future<std::unique_ptr<mir::process::Handle>> run_from_path(char const* binary,
1534+ std::initializer_list<char const*> args) const
1535+ override
1536+ {
1537+ std::promise<std::unique_ptr<mir::process::Handle>> dummy_promise;
1538+ dummy_promise.set_value(std::unique_ptr<mir::process::Handle>(run(binary, args, std::initializer_list<int>())));
1539+ return dummy_promise.get_future();
1540+ }
1541+
1542+ std::future<std::unique_ptr<mir::process::Handle>> run_from_path(char const* binary,
1543+ std::initializer_list<char const*> args,
1544+ std::initializer_list<int> fds) const override
1545+ {
1546+ std::promise<std::unique_ptr<mir::process::Handle>> dummy_promise;
1547+ dummy_promise.set_value(std::unique_ptr<mir::process::Handle>(run(binary, args, fds)));
1548+ return dummy_promise.get_future();
1549+ }
1550+};
1551+
1552+struct SocketListeningServerTest : public testing::Test
1553+{
1554+ SocketListeningServerTest()
1555+ : default_server_number("100"),
1556+ spawner(std::make_shared<NiceMock<MockProcessSpawner>>()),
1557+ connector(std::make_shared<NiceMock<MockConnector>>())
1558+ {
1559+ ON_CALL(*spawner, run(_, _, _))
1560+ .WillByDefault(DoAll(SaveArg<0>(&binary),
1561+ SaveArg<1>(&args),
1562+ SaveArg<2>(&fds),
1563+ InvokeWithoutArgs([this]()
1564+ { write_server_string(default_server_number); }),
1565+ Return(nullptr)));
1566+ ON_CALL(*connector, client_socket_fd())
1567+ .WillByDefault(Return(22));
1568+ }
1569+
1570+ void write_server_string(std::string server_number)
1571+ {
1572+ auto location = std::find_if(args.begin(), args.end(), [](char const* a)
1573+ { return strcmp(a, "-displayfd") == 0; });
1574+ ASSERT_NE(location, args.end());
1575+ ASSERT_NE(++location, args.end());
1576+ int server_fd = atoi(*location);
1577+ write(server_fd, server_number.data(), server_number.length());
1578+ close(server_fd);
1579+ }
1580+
1581+ std::string default_server_number;
1582+ std::string binary;
1583+ std::vector<char const*> args;
1584+ std::vector<int> fds;
1585+ std::shared_ptr<NiceMock<MockProcessSpawner>> spawner;
1586+ std::shared_ptr<NiceMock<MockConnector>> connector;
1587+};
1588+}
1589+
1590+TEST_F(SocketListeningServerTest, CreateServerAlwaysValid)
1591+{
1592+ mir::X::GlobalSocketListeningServerSpawner factory;
1593+
1594+ auto server_context = factory.create_server(spawner, connector);
1595+ ASSERT_NE(server_context.get(), nullptr);
1596+}
1597+
1598+TEST_F(SocketListeningServerTest, SpawnsCorrectExecutable)
1599+{
1600+ mir::X::GlobalSocketListeningServerSpawner factory;
1601+
1602+ auto server_context = factory.create_server(spawner, connector);
1603+ server_context.get();
1604+
1605+ EXPECT_EQ(binary, "Xorg");
1606+}
1607+
1608+namespace
1609+{
1610+MATCHER_P(ContainsSubsequence, subsequence, "")
1611+{
1612+ auto location =
1613+ std::search(arg.begin(), arg.end(), subsequence.begin(), subsequence.end(), [](char const* a, std::string b)
1614+ { return strcmp(a, b.c_str()) == 0; });
1615+ return location != arg.end();
1616+}
1617+}
1618+
1619+TEST_F(SocketListeningServerTest, SpawnsWithDisplayFDSet)
1620+{
1621+ mir::X::GlobalSocketListeningServerSpawner factory;
1622+
1623+ auto server_context = factory.create_server(spawner, connector);
1624+ server_context.get();
1625+
1626+ ASSERT_THAT(args, Not(IsEmpty()));
1627+ ASSERT_THAT(fds, Not(IsEmpty()));
1628+
1629+ Matcher<std::vector<char const*>> fd_matcher = Not(_);
1630+ for (auto fd : fds)
1631+ {
1632+ fd_matcher = AnyOf(fd_matcher, ContainsSubsequence(std::vector<std::string>{"-displayfd", std::to_string(fd)}));
1633+ }
1634+ EXPECT_THAT(args, fd_matcher);
1635+}
1636+
1637+TEST_F(SocketListeningServerTest, ReturnsCorrectDisplayString)
1638+{
1639+ mir::X::GlobalSocketListeningServerSpawner factory;
1640+
1641+ default_server_number = "20";
1642+ auto server_context = factory.create_server(spawner, connector);
1643+
1644+ EXPECT_STREQ(":20", server_context.get()->client_connection_string());
1645+}
1646+
1647+TEST_F(SocketListeningServerTest, HandlesSpawnerLifecycleCorrectly)
1648+{
1649+ mir::X::GlobalSocketListeningServerSpawner factory;
1650+
1651+ std::future<std::unique_ptr<mir::X::ServerContext>> server_context;
1652+
1653+ {
1654+ auto tmp_spawner = std::make_shared<MockProcessSpawner>();
1655+ ON_CALL(*tmp_spawner, run(_, _, _))
1656+ .WillByDefault(DoAll(SaveArg<0>(&binary),
1657+ SaveArg<1>(&args),
1658+ SaveArg<2>(&fds),
1659+ InvokeWithoutArgs([this]()
1660+ { write_server_string(default_server_number); }),
1661+ Return(nullptr)));
1662+ EXPECT_CALL(*tmp_spawner, run(_, _, _));
1663+ server_context = factory.create_server(tmp_spawner, connector);
1664+ }
1665+
1666+ ASSERT_NE(server_context.get(), nullptr);
1667+}
1668+
1669+TEST_F(SocketListeningServerTest, PassesMirSocketCorrectly)
1670+{
1671+ mir::X::GlobalSocketListeningServerSpawner factory;
1672+
1673+ int const mir_fd{32};
1674+ std::string const mir_connect_str{std::string("fd://") + std::to_string(mir_fd)};
1675+ EXPECT_CALL(*connector, client_socket_fd())
1676+ .WillOnce(Return(mir_fd));
1677+
1678+ auto server_context = factory.create_server(spawner, connector);
1679+
1680+ server_context.get();
1681+
1682+ EXPECT_THAT(args, ContainsSubsequence(std::vector<std::string>{"-mirSocket", mir_connect_str}));
1683+ EXPECT_THAT(fds, Contains(Eq(mir_fd)));
1684+}

Subscribers

People subscribed via source and target branches