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
=== added file '.bzrignore'
--- .bzrignore 1970-01-01 00:00:00 +0000
+++ .bzrignore 2014-01-28 06:34:09 +0000
@@ -0,0 +1,1 @@
1build
02
=== modified file '.clang-format'
--- .clang-format 2014-01-10 15:48:08 +0000
+++ .clang-format 2014-01-28 06:34:09 +0000
@@ -14,7 +14,7 @@
14BreakConstructorInitializersBeforeComma: false14BreakConstructorInitializersBeforeComma: false
15BinPackParameters: true15BinPackParameters: true
16ColumnLimit: 12016ColumnLimit: 120
17ConstructorInitializerAllOnOneLineOrOnePerLine: false17ConstructorInitializerAllOnOneLineOrOnePerLine: true
18DerivePointerBinding: true18DerivePointerBinding: true
19ExperimentalAutoDetectBinPacking: true19ExperimentalAutoDetectBinPacking: true
20IndentCaseLabels: false20IndentCaseLabels: false
2121
=== modified file 'include/server/mir/default_server_configuration.h'
--- include/server/mir/default_server_configuration.h 2014-01-21 15:29:52 +0000
+++ include/server/mir/default_server_configuration.h 2014-01-28 06:34:09 +0000
@@ -113,6 +113,11 @@
113class Logger;113class Logger;
114}114}
115115
116namespace X
117{
118class ServerSpawner;
119}
120
116class DefaultServerConfiguration : public virtual ServerConfiguration, DefaultConfigurationOptions121class DefaultServerConfiguration : public virtual ServerConfiguration, DefaultConfigurationOptions
117{122{
118public:123public:
@@ -232,6 +237,12 @@
232237
233 virtual std::shared_ptr<time::Clock> the_clock();238 virtual std::shared_ptr<time::Clock> the_clock();
234239
240 /** @name X11 server integration - customization
241 * configurable interfaces for integration with legacy X11 apps
242 * @{ */
243 virtual std::shared_ptr<X::ServerSpawner> the_xserver_spawner();
244 /** @} */
245
235protected:246protected:
236 using DefaultConfigurationOptions::the_options;247 using DefaultConfigurationOptions::the_options;
237 using DefaultConfigurationOptions::add_options;248 using DefaultConfigurationOptions::add_options;
238249
=== added directory 'include/server/mir/process'
=== added file 'include/server/mir/process/handle.h'
--- include/server/mir/process/handle.h 1970-01-01 00:00:00 +0000
+++ include/server/mir/process/handle.h 2014-01-28 06:34:09 +0000
@@ -0,0 +1,45 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
17 */
18
19#ifndef MIR_PROCESS_HANDLE_H_
20#define MIR_PROCESS_HANDLE_H_
21
22#include <sys/types.h>
23
24namespace mir
25{
26namespace process
27{
28class Handle
29{
30public:
31 enum Status {
32 NotStarted,
33 Running,
34 Stopped
35 };
36
37 virtual ~Handle() = default;
38
39 virtual Status status() const = 0;
40 virtual pid_t pid() const = 0;
41};
42
43}
44}
45#endif // MIR_PROCESS_HANDLE_H_
046
=== added file 'include/server/mir/process/spawner.h'
--- include/server/mir/process/spawner.h 1970-01-01 00:00:00 +0000
+++ include/server/mir/process/spawner.h 2014-01-28 06:34:09 +0000
@@ -0,0 +1,83 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
17 */
18
19#ifndef MIR_PROCESS_SPAWNER_H_
20#define MIR_PROCESS_SPAWNER_H_
21
22#include <memory>
23#include <future>
24#include <initializer_list>
25
26namespace mir
27{
28namespace process
29{
30
31class Handle;
32
33class Spawner
34{
35public:
36 virtual ~Spawner() = default;
37 /**
38 * \brief Run a binary, respecting $PATH
39 * \note All open file descriptors other than stdin, stdout, and stderr are closed before
40 * running the binary.
41 * \param [in] binary_name The name of the binary to run, such as “Xorg”
42 * \return A future that will contain the handle to the spawned process.
43 * The future will be populated after the process has been exec()d,
44 * or the call to exec() has failed.
45 * If spawning the binary fails the future will contain a
46 * std::runtime_error.
47 */
48 virtual std::future<std::unique_ptr<Handle>> run_from_path(char const* binary_name) const = 0;
49 /**
50 * \brief Run a binary with arguments, respecting $PATH
51 * \note All open file descriptors other than stdin, stdout, and stderr are closed before
52 * running the binary.
53 * \param [in] binary_name The name of the binary to run, such as “Xorg”
54 * \param [in] args The arguments to be passed to the binary. No processing is done on
55 * these strings; each one will appear verbatim as a separate char* in
56 * the binary's argv[].
57 * \return A future that will contain the handle to the spawned process.
58 * The future will be populated after the process has been exec()d,
59 * or the call to exec() has failed.
60 * If spawning the binary fails the future will contain a
61 * std::runtime_error.
62 */
63 virtual std::future<std::unique_ptr<Handle>> run_from_path(char const* binary_name, std::initializer_list<char const*> args) const = 0;
64 /**
65 * \brief Run a binary, respecting $PATH
66 * \note All open file descriptors other than stdin, stdout, stderr, and the fds listed in
67 * fds are closed before running the binary.
68 * \param [in] binary_name The name of the binary to run, such as “Xorg”
69 * \param [in] args The arguments to be passed to the binary. No processing is done on
70 * these strings; each one will appear verbatim as a separate char* in
71 * the binary's argv[].
72 * \param [in] fds The set of fds that will remain available to the child process.
73 * \return A future that will contain the handle to the spawned process.
74 * The future will be populated after the process has been exec()d,
75 * or the call to exec() has failed.
76 * If spawning the binary fails the future will contain a
77 * std::runtime_error.
78 */
79 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;
80};
81}
82}
83#endif // MIR_PROCESS_SPAWNER_H_
084
=== added directory 'include/server/mir/xserver'
=== added file 'include/server/mir/xserver/null_server_spawner.h'
--- include/server/mir/xserver/null_server_spawner.h 1970-01-01 00:00:00 +0000
+++ include/server/mir/xserver/null_server_spawner.h 2014-01-28 06:34:09 +0000
@@ -0,0 +1,25 @@
1#ifndef MIR_X_NULL_SERVER_SPAWNER_H_
2#define MIR_X_NULL_SERVER_SPAWNER_H_
3
4#include <mir/xserver/xserver_launcher.h>
5
6namespace mir
7{
8namespace X
9{
10class NullServerContext : public ServerContext
11{
12public:
13 char const* client_connection_string() override;
14};
15
16class NullServerSpawner : public ServerSpawner
17{
18public:
19 std::future<std::unique_ptr<ServerContext>> create_server(std::shared_ptr<process::Spawner> const& spawner, std::shared_ptr<mir::frontend::Connector> const& connector) override;
20};
21
22}
23}
24
25#endif // MIR_X_NULL_SERVER_SPAWNER_H_
026
=== added file 'include/server/mir/xserver/xserver_launcher.h'
--- include/server/mir/xserver/xserver_launcher.h 1970-01-01 00:00:00 +0000
+++ include/server/mir/xserver/xserver_launcher.h 2014-01-28 06:34:09 +0000
@@ -0,0 +1,59 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
17 */
18
19#ifndef MIR_X_XSERVER_LAUNCHER_H_
20#define MIR_X_XSERVER_LAUNCHER_H_
21
22#include <future>
23#include <memory>
24
25#include "mir/process/spawner.h"
26#include "mir/frontend/connector.h"
27
28namespace mir
29{
30namespace X
31{
32
33class ServerContext
34{
35public:
36 virtual ~ServerContext() = default;
37
38 /**
39 * \brief Get the XLib connection string to connect to this server
40 *
41 * This string can be passed by the client to XOpenDisplay to connect
42 * to this server instance, or set in the DISPLAY environment variable
43 * to be used as the default display.
44 */
45 virtual char const* client_connection_string() = 0;
46};
47
48class ServerSpawner
49{
50public:
51 virtual ~ServerSpawner() = default;
52
53 virtual std::future<std::unique_ptr<ServerContext>> create_server(std::shared_ptr<mir::process::Spawner> const& spawner,
54 std::shared_ptr<mir::frontend::Connector> const& connector) = 0;
55};
56}
57}
58
59#endif // MIR_X_XSERVER_LAUNCHER_H_
060
=== renamed file 'include/test/mir_test/pipe.h' => 'include/shared/mir/pipe.h'
--- include/test/mir_test/pipe.h 2013-07-03 09:54:10 +0000
+++ include/shared/mir/pipe.h 2014-01-28 06:34:09 +0000
@@ -1,16 +1,16 @@
1/*1/*
2 * Copyright © 2013 Canonical Ltd.2 * Copyright © 2013 Canonical Ltd.
3 *3 *
4 * This program is free software: you can redistribute it and/or modify it4 * This program is free software: you can redistribute it and/or modify
5 * under the terms of the GNU General Public License version 3,5 * it under the terms of the GNU Lesser General Public License version 3 as
6 * as published by the Free Software Foundation.6 * published by the Free Software Foundation.
7 *7 *
8 * This program is distributed in the hope that it will be useful,8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.11 * GNU Lesser General Public License for more details.
12 *12 *
13 * You should have received a copy of the GNU General Public License13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *15 *
16 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>16 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
@@ -21,18 +21,22 @@
2121
22namespace mir22namespace mir
23{23{
24namespace test24namespace pipe
25{25{
2626
27class Pipe27class Pipe
28{28{
29public:29public:
30 Pipe();30 Pipe();
31 Pipe(int flags);
31 ~Pipe();32 ~Pipe();
3233
33 int read_fd() const;34 int read_fd() const;
34 int write_fd() const;35 int write_fd() const;
3536
37 void close_read_fd();
38 void close_write_fd();
39
36private:40private:
37 Pipe(Pipe const&) = delete;41 Pipe(Pipe const&) = delete;
38 Pipe& operator=(Pipe const&) = delete;42 Pipe& operator=(Pipe const&) = delete;
3943
=== modified file 'src/server/CMakeLists.txt'
--- src/server/CMakeLists.txt 2014-01-22 08:32:55 +0000
+++ src/server/CMakeLists.txt 2014-01-28 06:34:09 +0000
@@ -12,6 +12,8 @@
12add_subdirectory(frontend/)12add_subdirectory(frontend/)
13add_subdirectory(shell/)13add_subdirectory(shell/)
14add_subdirectory(time/)14add_subdirectory(time/)
15add_subdirectory(process/)
16add_subdirectory(xserver/)
1517
16set(PREFIX "${CMAKE_INSTALL_PREFIX}")18set(PREFIX "${CMAKE_INSTALL_PREFIX}")
17set(EXEC_PREFIX "${CMAKE_INSTALL_PREFIX}")19set(EXEC_PREFIX "${CMAKE_INSTALL_PREFIX}")
@@ -56,6 +58,9 @@
56 mirlttng58 mirlttng
57 mirnestedgraphics59 mirnestedgraphics
58 miroffscreengraphics60 miroffscreengraphics
61 mirsharedpipe
62 mirprocess
63 mirxserverintegration
59)64)
6065
61list(APPEND MIRSERVER_LINKS66list(APPEND MIRSERVER_LINKS
6267
=== modified file 'src/server/default_server_configuration.cpp'
--- src/server/default_server_configuration.cpp 2014-01-13 06:12:33 +0000
+++ src/server/default_server_configuration.cpp 2014-01-28 06:34:09 +0000
@@ -42,6 +42,7 @@
42#include "mir/time/high_resolution_clock.h"42#include "mir/time/high_resolution_clock.h"
43#include "mir/geometry/rectangles.h"43#include "mir/geometry/rectangles.h"
44#include "mir/default_configuration.h"44#include "mir/default_configuration.h"
45#include "mir/xserver/null_server_spawner.h"
4546
46#include <map>47#include <map>
4748
@@ -249,3 +250,8 @@
249 return std::make_shared<mir::DefaultServerStatusListener>();250 return std::make_shared<mir::DefaultServerStatusListener>();
250 });251 });
251}252}
253
254std::shared_ptr<mir::X::ServerSpawner> mir::DefaultServerConfiguration::the_xserver_spawner()
255{
256 return std::make_shared<mir::X::NullServerSpawner>();
257}
252258
=== added directory 'src/server/process'
=== added file 'src/server/process/CMakeLists.txt'
--- src/server/process/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ src/server/process/CMakeLists.txt 2014-01-28 06:34:09 +0000
@@ -0,0 +1,25 @@
1# Copyright © 2014 Canonical Ltd.
2#
3# This program is free software: you can redistribute it and/or modify it
4# under the terms of the GNU General Public License version 3,
5# as published by the Free Software Foundation.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14#
15# Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
16
17set(
18 PROCESS_SOURCES
19 ${CMAKE_CURRENT_SOURCE_DIR}/fork_spawner.cpp
20)
21
22add_library(
23 mirprocess STATIC
24 ${PROCESS_SOURCES}
25)
026
=== added file 'src/server/process/fork_spawner.cpp'
--- src/server/process/fork_spawner.cpp 1970-01-01 00:00:00 +0000
+++ src/server/process/fork_spawner.cpp 2014-01-28 06:34:09 +0000
@@ -0,0 +1,165 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
17 */
18
19#include "fork_spawner.h"
20
21#include "mir/process/handle.h"
22#include "mir/pipe.h"
23#include "mir/raii.h"
24
25#include <fcntl.h>
26#include <unistd.h>
27#include <errno.h>
28#include <dirent.h>
29#include <vector>
30#include <thread>
31#include <string>
32#include <set>
33#include <boost/throw_exception.hpp>
34#include <boost/exception/errinfo_errno.hpp>
35
36namespace
37{
38class PidHandle : public mir::process::Handle
39{
40public:
41 PidHandle(pid_t pid) : child_pid(pid)
42 {
43 }
44
45 Status status() const override
46 {
47 return Status::Running;
48 }
49 pid_t pid() const override
50 {
51 return child_pid;
52 }
53
54private:
55 pid_t child_pid;
56};
57
58static std::vector<int> open_fds()
59{
60 std::vector<int> fds;
61
62 DIR* process_fds_dir;
63 auto const dir_raii = mir::raii::paired_calls([&process_fds_dir]
64 { process_fds_dir = opendir("/proc/self/fd"); },
65 [&process_fds_dir]
66 {
67 if (process_fds_dir != nullptr)
68 closedir(process_fds_dir);
69 });
70
71 if (process_fds_dir == nullptr)
72 BOOST_THROW_EXCEPTION(boost::enable_error_info(std::runtime_error("Failed to open process fds directory"))
73 << boost::errinfo_errno(errno));
74
75 struct dirent* entry;
76 errno = 0;
77 while ((entry = readdir(process_fds_dir)) != nullptr)
78 {
79 // We don't care about directory links
80 if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
81 continue;
82
83 int fd;
84 char* conversion_end;
85 fd = strtol(entry->d_name, &conversion_end, 10);
86
87 if (*conversion_end != '\0' || conversion_end == entry->d_name)
88 BOOST_THROW_EXCEPTION(std::runtime_error(std::string("Unexpected fd name: ") + entry->d_name));
89
90 if (fd != dirfd(process_fds_dir))
91 fds.push_back(fd);
92 }
93 if (errno != 0)
94 BOOST_THROW_EXCEPTION(boost::enable_error_info(std::runtime_error("Failed to read from process fds directory"))
95 << boost::errinfo_errno(errno));
96
97 return fds;
98}
99
100static std::unique_ptr<mir::process::Handle> run(char const* binary_name,
101 std::initializer_list<char const*> args,
102 std::initializer_list<int> fds)
103{
104 mir::pipe::Pipe error_pipe(O_CLOEXEC);
105 pid_t child = fork();
106
107 if (child == 0)
108 {
109 std::set<int> precious_fds{fds};
110 // Don't close stdin, stdout, or stderr
111 precious_fds.insert({0, 1, 2});
112 // Don't close our cross-process pipe, we need it and know it's CLOEXEC
113 precious_fds.insert(error_pipe.write_fd());
114
115 error_pipe.close_read_fd();
116
117 for (auto fd : open_fds())
118 {
119 if (precious_fds.count(fd) == 0)
120 close(fd);
121 }
122
123 std::vector<char const*> argv;
124 argv.push_back(binary_name);
125 argv.insert(argv.end(), args);
126 argv.push_back(nullptr);
127
128 execvp(binary_name, const_cast<char* const*>(argv.data()));
129 // We can only get here by failing to exec
130 write(error_pipe.write_fd(), &errno, sizeof errno);
131 }
132
133 error_pipe.close_write_fd();
134 error_t error = 0;
135 if (read(error_pipe.read_fd(), &error, sizeof error) == -1)
136 BOOST_THROW_EXCEPTION(boost::enable_error_info(std::runtime_error("Failed to read from pipe"))
137 << boost::errinfo_errno(errno));
138
139 if (error != 0)
140 BOOST_THROW_EXCEPTION(
141 boost::enable_error_info(std::runtime_error(std::string("Failed to execute process: ") + binary_name))
142 << boost::errinfo_errno(errno));
143
144 return std::unique_ptr<PidHandle>(new PidHandle(child));
145}
146}
147
148std::future<std::unique_ptr<mir::process::Handle>> mir::process::ForkSpawner::run_from_path(char const* binary_name)
149 const
150{
151 return std::async(
152 std::launch::async, run, binary_name, std::initializer_list<char const*>(), std::initializer_list<int>());
153}
154
155std::future<std::unique_ptr<mir::process::Handle>> mir::process::ForkSpawner::run_from_path(
156 char const* binary_name, std::initializer_list<char const*> args) const
157{
158 return std::async(std::launch::async, run, binary_name, args, std::initializer_list<int>());
159}
160
161std::future<std::unique_ptr<mir::process::Handle>> mir::process::ForkSpawner::run_from_path(
162 char const* binary_name, std::initializer_list<char const*> args, std::initializer_list<int> fds) const
163{
164 return std::async(std::launch::async, run, binary_name, args, fds);
165}
0166
=== added file 'src/server/process/fork_spawner.h'
--- src/server/process/fork_spawner.h 1970-01-01 00:00:00 +0000
+++ src/server/process/fork_spawner.h 2014-01-28 06:34:09 +0000
@@ -0,0 +1,44 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
17 */
18
19
20#ifndef MIR_PROCESS_FORK_SPAWNER_H_
21#define MIR_PROCESS_FORK_SPAWNER_H_
22
23#include "mir/process/spawner.h"
24
25namespace mir
26{
27namespace process
28{
29
30/**
31 * \brief An implementation of mir::process::Spawner that forks new processes
32 */
33class ForkSpawner : public Spawner
34{
35public:
36 std::future<std::unique_ptr<Handle>> run_from_path(char const* binary_name) const override;
37 std::future<std::unique_ptr<Handle>> run_from_path(char const* binary_name, std::initializer_list<char const*> args) const override;
38 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;
39};
40
41}
42}
43
44#endif // MIR_PROCESS_FORK_SPAWNER_H_
045
=== added directory 'src/server/xserver'
=== added file 'src/server/xserver/CMakeLists.txt'
--- src/server/xserver/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ src/server/xserver/CMakeLists.txt 2014-01-28 06:34:09 +0000
@@ -0,0 +1,12 @@
1set(
2 MIR_XSERVER_SRCS
3
4 global_socket_listening_server_spawner.cpp
5 null_server_spawner.cpp
6)
7
8ADD_LIBRARY(
9 mirxserverintegration STATIC
10
11 ${MIR_XSERVER_SRCS}
12)
013
=== added file 'src/server/xserver/global_socket_listening_server_spawner.cpp'
--- src/server/xserver/global_socket_listening_server_spawner.cpp 1970-01-01 00:00:00 +0000
+++ src/server/xserver/global_socket_listening_server_spawner.cpp 2014-01-28 06:34:09 +0000
@@ -0,0 +1,71 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
17 */
18
19#include <unistd.h>
20#include <errno.h>
21
22#include <boost/throw_exception.hpp>
23#include <boost/exception/errinfo_errno.hpp>
24
25#include "global_socket_listening_server_spawner.h"
26#include "mir/process/handle.h"
27
28namespace mx = mir::X;
29
30mx::GlobalSocketListeningServerContext::GlobalSocketListeningServerContext(std::unique_ptr<mir::process::Handle> server_handle, std::string connection_string)
31 : server_handle(std::move(server_handle)),
32 connection_string(connection_string)
33{
34}
35
36char const* mx::GlobalSocketListeningServerContext::client_connection_string()
37{
38 return connection_string.c_str();
39}
40
41std::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)
42{
43 return std::async(std::launch::async, [spawner, connector]()
44 {
45 mir::pipe::Pipe displayfd_pipe;
46 auto displayfd = std::to_string(displayfd_pipe.write_fd());
47 int mir_fd = connector->client_socket_fd();
48 auto mir_fd_arg = std::string("fd://") + std::to_string(mir_fd);
49
50 auto future_handle = spawner->run_from_path("Xorg",
51 {"-displayfd", displayfd.c_str(),
52 "-mir", "xserver",
53 "-mirSocket", mir_fd_arg.c_str()},
54 {displayfd_pipe.write_fd(), mir_fd});
55
56 char display_number[10];
57 errno = 0;
58 int bytes_read = read(displayfd_pipe.read_fd(), display_number, sizeof display_number);
59
60 while (bytes_read == -1 && errno == EINTR)
61 bytes_read = read(displayfd_pipe.read_fd(), display_number, sizeof display_number);;
62
63 if (errno != 0)
64 BOOST_THROW_EXCEPTION(boost::enable_error_info(std::runtime_error("Failed to receive display number from Xserver"))
65 << boost::errinfo_errno(errno));
66
67 display_number[bytes_read] = '\0';
68
69 return std::unique_ptr<mx::ServerContext>(new mx::GlobalSocketListeningServerContext(future_handle.get(), std::string(":") + display_number));
70 });
71}
072
=== added file 'src/server/xserver/global_socket_listening_server_spawner.h'
--- src/server/xserver/global_socket_listening_server_spawner.h 1970-01-01 00:00:00 +0000
+++ src/server/xserver/global_socket_listening_server_spawner.h 2014-01-28 06:34:09 +0000
@@ -0,0 +1,52 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
17 */
18
19#ifndef MIR_X_GLOBAL_SOCKET_LISTENING_SERVER_SPAWNER_H_
20#define MIR_X_GLOBAL_SOCKET_LISTENING_SERVER_SPAWNER_H_
21
22#include "mir/xserver/xserver_launcher.h"
23#include "mir/process/spawner.h"
24#include "mir/pipe.h"
25
26#include <memory>
27
28namespace mir
29{
30namespace X
31{
32class GlobalSocketListeningServerContext : public ServerContext
33{
34public:
35 GlobalSocketListeningServerContext(std::unique_ptr<mir::process::Handle> server_handle, std::string connection_string);
36 char const* client_connection_string() override;
37
38private:
39 std::unique_ptr<mir::process::Handle> server_handle;
40 std::string connection_string;
41};
42
43class GlobalSocketListeningServerSpawner : public ServerSpawner
44{
45public:
46 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;
47};
48
49}
50}
51
52#endif // MIR_X_GLOBAL_SOCKET_LISTENING_SERVER_SPAWNER_H_
053
=== added file 'src/server/xserver/null_server_spawner.cpp'
--- src/server/xserver/null_server_spawner.cpp 1970-01-01 00:00:00 +0000
+++ src/server/xserver/null_server_spawner.cpp 2014-01-28 06:34:09 +0000
@@ -0,0 +1,34 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
17 */
18
19#include "mir/xserver/null_server_spawner.h"
20
21const char* mir::X::NullServerContext::client_connection_string()
22{
23 return "";
24}
25
26std::future<std::unique_ptr<mir::X::ServerContext>> mir::X::NullServerSpawner::create_server(
27 std::shared_ptr<mir::process::Spawner> const& unused, std::shared_ptr<mir::frontend::Connector> const& unuseder)
28{
29 static_cast<void>(unused);
30 static_cast<void>(unuseder);
31 std::promise<std::unique_ptr<mir::X::ServerContext>> boring_promise;
32 boring_promise.set_value(std::unique_ptr<mir::X::ServerContext>(new mir::X::NullServerContext));
33 return boring_promise.get_future();
34}
035
=== modified file 'src/shared/CMakeLists.txt'
--- src/shared/CMakeLists.txt 2014-01-13 06:12:33 +0000
+++ src/shared/CMakeLists.txt 2014-01-28 06:34:09 +0000
@@ -11,6 +11,7 @@
11add_subdirectory(logging)11add_subdirectory(logging)
12add_subdirectory(lttng)12add_subdirectory(lttng)
13add_subdirectory(env)13add_subdirectory(env)
14add_subdirectory(pipe)
1415
15set(16set(
16 MIR_COMMON_PLATFORM_LIBRARIES17 MIR_COMMON_PLATFORM_LIBRARIES
1718
=== added directory 'src/shared/pipe'
=== added file 'src/shared/pipe/CMakeLists.txt'
--- src/shared/pipe/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ src/shared/pipe/CMakeLists.txt 2014-01-28 06:34:09 +0000
@@ -0,0 +1,19 @@
1# Copyright © 2014 Canonical Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU Lesser General Public License version 3 as
5# published by the Free Software Foundation.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU Lesser General Public License for more details.
11#
12# You should have received a copy of the GNU Lesser General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14#
15# Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
16
17add_library(mirsharedpipe STATIC
18 pipe.cpp
19)
020
=== renamed file 'tests/mir_test/pipe.cpp' => 'src/shared/pipe/pipe.cpp'
--- tests/mir_test/pipe.cpp 2013-07-02 15:44:55 +0000
+++ src/shared/pipe/pipe.cpp 2014-01-28 06:34:09 +0000
@@ -2,34 +2,40 @@
2 * Copyright © 2013 Canonical Ltd.2 * Copyright © 2013 Canonical Ltd.
3 *3 *
4 * This program is free software: you can redistribute it and/or modify it4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3,5 * under the terms of the GNU Lesser General Public License version 3,
6 * as published by the Free Software Foundation.6 * as published by the Free Software Foundation.
7 *7 *
8 * This program is distributed in the hope that it will be useful,8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.11 * GNU Lesser General Public License for more details.
12 *12 *
13 * You should have received a copy of the GNU General Public License13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *15 *
16 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>16 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
17 */17 */
1818
19#include <mir_test/pipe.h>19#include "mir/pipe.h"
2020
21#include <boost/throw_exception.hpp>21#include <boost/throw_exception.hpp>
22#include <boost/exception/errinfo_errno.hpp>22#include <boost/exception/errinfo_errno.hpp>
2323
24#include <stdexcept>24#include <stdexcept>
2525
26#include <fcntl.h>
26#include <unistd.h>27#include <unistd.h>
2728
28namespace mt = mir::test;29namespace mp = mir::pipe;
2930
30mt::Pipe::Pipe()31mp::Pipe::Pipe()
31{32 : Pipe(0)
32 if (pipe(pipefd))33{
34}
35
36mp::Pipe::Pipe(int flags)
37{
38 if (::pipe2(pipefd, flags))
33 {39 {
34 BOOST_THROW_EXCEPTION(40 BOOST_THROW_EXCEPTION(
35 boost::enable_error_info(std::runtime_error("Failed to create pipe"))41 boost::enable_error_info(std::runtime_error("Failed to create pipe"))
@@ -37,18 +43,32 @@
37 }43 }
38}44}
3945
40mt::Pipe::~Pipe()46mp::Pipe::~Pipe()
47{
48 if (pipefd[0] != -1)
49 close(pipefd[0]);
50 if (pipefd[1] != -1)
51 close(pipefd[1]);
52}
53
54int mp::Pipe::read_fd() const
55{
56 return pipefd[0];
57}
58
59int mp::Pipe::write_fd() const
60{
61 return pipefd[1];
62}
63
64void mp::Pipe::close_read_fd()
41{65{
42 close(pipefd[0]);66 close(pipefd[0]);
67 pipefd[0] = -1;
68}
69
70void mp::Pipe::close_write_fd()
71{
43 close(pipefd[1]);72 close(pipefd[1]);
44}73 pipefd[1] = -1;
45
46int mt::Pipe::read_fd() const
47{
48 return pipefd[0];
49}
50
51int mt::Pipe::write_fd() const
52{
53 return pipefd[1];
54}74}
5575
=== modified file 'tests/CMakeLists.txt'
--- tests/CMakeLists.txt 2014-01-23 08:06:05 +0000
+++ tests/CMakeLists.txt 2014-01-28 06:34:09 +0000
@@ -36,6 +36,7 @@
3636
37if (MIR_BUILD_ACCEPTANCE_TESTS)37if (MIR_BUILD_ACCEPTANCE_TESTS)
38 add_subdirectory(acceptance-tests/)38 add_subdirectory(acceptance-tests/)
39 pkg_check_modules(X11 REQUIRED x11)
39endif (MIR_BUILD_ACCEPTANCE_TESTS)40endif (MIR_BUILD_ACCEPTANCE_TESTS)
4041
41if (MIR_BUILD_INTEGRATION_TESTS)42if (MIR_BUILD_INTEGRATION_TESTS)
4243
=== modified file 'tests/acceptance-tests/CMakeLists.txt'
--- tests/acceptance-tests/CMakeLists.txt 2014-01-24 11:15:31 +0000
+++ tests/acceptance-tests/CMakeLists.txt 2014-01-28 06:34:09 +0000
@@ -27,6 +27,8 @@
27 test_server_disconnect.cpp27 test_server_disconnect.cpp
28 test_client_library_drm.cpp28 test_client_library_drm.cpp
29 test_protobuf.cpp29 test_protobuf.cpp
30 test_subprocess.cpp
31 test_xserver_spawner.cpp
30 ${GENERATED_PROTOBUF_SRCS}32 ${GENERATED_PROTOBUF_SRCS}
31 ${GENERATED_PROTOBUF_HDRS}33 ${GENERATED_PROTOBUF_HDRS}
32)34)
@@ -65,6 +67,7 @@
65 ${GMOCK_LIBRARY}67 ${GMOCK_LIBRARY}
66 ${GMOCK_MAIN_LIBRARY}68 ${GMOCK_MAIN_LIBRARY}
67 ${CMAKE_THREAD_LIBS_INIT} # Link in pthread.69 ${CMAKE_THREAD_LIBS_INIT} # Link in pthread.
70 ${X11_LDFLAGS}
68)71)
6972
70CMAKE_DEPENDENT_OPTION(73CMAKE_DEPENDENT_OPTION(
7174
=== modified file 'tests/acceptance-tests/test_display_configuration.cpp'
--- tests/acceptance-tests/test_display_configuration.cpp 2014-01-13 06:12:33 +0000
+++ tests/acceptance-tests/test_display_configuration.cpp 2014-01-28 06:34:09 +0000
@@ -20,6 +20,7 @@
20#include "mir/frontend/session_authorizer.h"20#include "mir/frontend/session_authorizer.h"
21#include "mir/graphics/event_handler_register.h"21#include "mir/graphics/event_handler_register.h"
22#include "src/server/scene/global_event_sender.h"22#include "src/server/scene/global_event_sender.h"
23#include "mir/pipe.h"
2324
24#include "mir_test_framework/display_server_test_fixture.h"25#include "mir_test_framework/display_server_test_fixture.h"
25#include "mir_test_framework/cross_process_sync.h"26#include "mir_test_framework/cross_process_sync.h"
@@ -32,7 +33,6 @@
32#include "mir_test_doubles/stub_display_configuration.h"33#include "mir_test_doubles/stub_display_configuration.h"
33#include "mir_test_doubles/stub_buffer_allocator.h"34#include "mir_test_doubles/stub_buffer_allocator.h"
34#include "mir_test/fake_shared.h"35#include "mir_test/fake_shared.h"
35#include "mir_test/pipe.h"
36#include "mir_test/cross_process_action.h"36#include "mir_test/cross_process_action.h"
3737
38#include "mir_toolkit/mir_client_library.h"38#include "mir_toolkit/mir_client_library.h"
@@ -49,6 +49,7 @@
49namespace mtf = mir_test_framework;49namespace mtf = mir_test_framework;
50namespace mtd = mir::test::doubles;50namespace mtd = mir::test::doubles;
51namespace mt = mir::test;51namespace mt = mir::test;
52namespace mp = mir::pipe;
5253
53namespace54namespace
54{55{
@@ -127,7 +128,7 @@
127private:128private:
128 std::shared_ptr<mg::DisplayConfiguration> config;129 std::shared_ptr<mg::DisplayConfiguration> config;
129 mtd::NullDisplayBuffer display_buffer;130 mtd::NullDisplayBuffer display_buffer;
130 mt::Pipe p;131 mp::Pipe p;
131 std::atomic<bool> handler_called;132 std::atomic<bool> handler_called;
132};133};
133134
134135
=== added file 'tests/acceptance-tests/test_subprocess.cpp'
--- tests/acceptance-tests/test_subprocess.cpp 1970-01-01 00:00:00 +0000
+++ tests/acceptance-tests/test_subprocess.cpp 2014-01-28 06:34:09 +0000
@@ -0,0 +1,166 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
17 */
18
19#include "src/server/process/fork_spawner.h"
20#include "mir/process/spawner.h"
21#include "mir/process/handle.h"
22
23#include <fstream>
24#include <sstream>
25#include <unistd.h>
26#include <thread>
27#include <sys/types.h>
28#include <sys/stat.h>
29#include <fcntl.h>
30#include <dirent.h>
31#include <errno.h>
32#include <string.h>
33
34#include <gtest/gtest.h>
35#include <gmock/gmock.h>
36
37TEST(ProcessTest, RunFromPathRunsCorrectBinary)
38{
39 mir::process::ForkSpawner spawner;
40
41 auto future_handle = spawner.run_from_path("true");
42 auto handle = future_handle.get();
43
44 std::stringstream stat_path;
45 stat_path << "/proc/" << handle->pid() << "/stat";
46
47 std::ifstream status{stat_path.str()};
48
49 char binary_name[PATH_MAX];
50
51 // Read up to the initial '('
52 while (status.good() && !status.eof() && (status.get() != '('))
53 ;
54
55 status.getline(binary_name, sizeof(binary_name), ')');
56
57 EXPECT_STREQ("true", binary_name);
58}
59
60TEST(ProcessTest, ChildHasExpectedFDs)
61{
62 mir::process::ForkSpawner spawner;
63
64 auto fd = open("/dev/null", O_RDONLY);
65 auto future_handle = spawner.run_from_path("sleep", {"1"});
66 auto handle = future_handle.get();
67
68 // TODO: Why is this racy?
69 std::this_thread::sleep_for(std::chrono::milliseconds{1});
70
71 std::stringstream fds_path;
72 fds_path << "/proc/" << handle->pid() << "/fd";
73
74 bool found_stdin = false, found_stdout = false, found_stderr = false;
75
76 DIR* process_fds_dir = opendir(fds_path.str().c_str());
77 ASSERT_NE(process_fds_dir, nullptr) << "Error opening " << fds_path.str() << ": " << strerror(errno) << " ("
78 << errno << ")" << std::endl;
79 struct dirent* entry;
80 while ((entry = readdir(process_fds_dir)) != nullptr)
81 {
82 // We don't care about directory links
83 if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
84 continue;
85
86 if (strcmp(entry->d_name, "0") == 0)
87 {
88 found_stdin = true;
89 }
90 else if (strcmp(entry->d_name, "1") == 0)
91 {
92 found_stdout = true;
93 }
94 else if (strcmp(entry->d_name, "2") == 0)
95 {
96 found_stderr = true;
97 }
98 else
99 {
100 ADD_FAILURE() << "Unexpected fd: " << entry->d_name << std::endl;
101 }
102 }
103 EXPECT_TRUE(found_stdin);
104 EXPECT_TRUE(found_stdout);
105 EXPECT_TRUE(found_stderr);
106 closedir(process_fds_dir);
107 close(fd);
108}
109
110TEST(ProcessTest, ChildReceivesExpectedCmdline)
111{
112 mir::process::ForkSpawner spawner;
113
114 auto future_handle = spawner.run_from_path("sleep", {"10"});
115 auto handle = future_handle.get();
116
117 std::stringstream cmdline_path;
118 cmdline_path << "/proc/" << handle->pid() << "/cmdline";
119
120 std::ifstream cmdline{cmdline_path.str()};
121
122 // We expect "sleep\010\0\0"
123 char buffer[5 + 1 + 2 + 1 + 1];
124 cmdline.read(buffer, sizeof(buffer));
125
126 EXPECT_STREQ("sleep", buffer);
127 EXPECT_STREQ("10", buffer + 6);
128}
129
130TEST(ProcessTest, SpawningNonExistentBinaryThrows)
131{
132 mir::process::ForkSpawner spawner;
133
134 auto future_handle = spawner.run_from_path("I'm a binary that almost certainly doesn't exist");
135
136 EXPECT_THROW(future_handle.get(), std::runtime_error);
137}
138
139TEST(ProcessTest, ChildRetainsSetFDs)
140{
141 mir::process::ForkSpawner spawner;
142
143 auto fd = open("/dev/null", 0);
144 auto future_handle = spawner.run_from_path("sleep", {"1"}, {fd});
145 auto handle = future_handle.get();
146
147 // TODO: Why is this racy?
148
149 std::stringstream fds_path;
150 fds_path << "/proc/" << handle->pid() << "/fd";
151
152 bool found_our_fd = false;
153
154 DIR* process_fds_dir = opendir(fds_path.str().c_str());
155 ASSERT_NE(process_fds_dir, nullptr) << "Error opening " << fds_path.str() << ": " << strerror(errno) << " ("
156 << errno << ")" << std::endl;
157 struct dirent* entry;
158 while ((entry = readdir(process_fds_dir)) != nullptr)
159 {
160 if (atoi(entry->d_name) == fd)
161 found_our_fd = true;
162 }
163 EXPECT_TRUE(found_our_fd);
164 closedir(process_fds_dir);
165 close(fd);
166}
0167
=== added file 'tests/acceptance-tests/test_xserver_spawner.cpp'
--- tests/acceptance-tests/test_xserver_spawner.cpp 1970-01-01 00:00:00 +0000
+++ tests/acceptance-tests/test_xserver_spawner.cpp 2014-01-28 06:34:09 +0000
@@ -0,0 +1,73 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
17 */
18
19#include "src/server/xserver/global_socket_listening_server_spawner.h"
20#include "mir_test_framework/testing_server_configuration.h"
21#include "mir_test_framework/in_process_server.h"
22#include "mir/xserver/xserver_launcher.h"
23#include "src/server/process/fork_spawner.h"
24
25#include <X11/Xlib.h>
26#include <stdlib.h>
27
28#include <gtest/gtest.h>
29
30namespace mtf = mir_test_framework;
31namespace mx = mir::X;
32
33namespace
34{
35struct XserverSpawningServer : public mtf::InProcessServer
36{
37public:
38 std::shared_ptr<mx::ServerSpawner> the_xserver_spawner()
39 {
40 return config.the_xserver_spawner();
41 }
42
43 mir::DefaultServerConfiguration& server_config() override
44 {
45 return config;
46 }
47
48 class SocketListeningXServerConfig : public mtf::StubbedServerConfiguration
49 {
50 public:
51 std::shared_ptr<mx::ServerSpawner> the_xserver_spawner() override
52 {
53 return std::make_shared<mx::GlobalSocketListeningServerSpawner> ();
54 }
55 } config;
56};
57}
58
59// This requires a bit more fiddling before it will work.
60// Particularly, it needs an xorg.conf that specifies dummy
61// devices, so the real devices don't fail to load.
62TEST_F(XserverSpawningServer, DISABLED_X11ClientConnects)
63{
64 // Ensure the surrounding environment doesn't mess with the test
65 unsetenv("DISPLAY");
66
67 auto xserver = the_xserver_spawner()->create_server(std::make_shared<mir::process::ForkSpawner>(), config.the_connector());
68 Display* disp = XOpenDisplay(xserver.get()->client_connection_string());
69
70 ASSERT_TRUE(disp != NULL);
71
72 XCloseDisplay(disp);
73}
074
=== modified file 'tests/integration-tests/test_display_server_main_loop_events.cpp'
--- tests/integration-tests/test_display_server_main_loop_events.cpp 2014-01-13 06:12:33 +0000
+++ tests/integration-tests/test_display_server_main_loop_events.cpp 2014-01-28 06:34:09 +0000
@@ -23,8 +23,8 @@
23#include "mir/main_loop.h"23#include "mir/main_loop.h"
24#include "mir/display_changer.h"24#include "mir/display_changer.h"
25#include "mir/server_status_listener.h"25#include "mir/server_status_listener.h"
26#include "mir/pipe.h"
2627
27#include "mir_test/pipe.h"
28#include "mir_test_framework/testing_server_configuration.h"28#include "mir_test_framework/testing_server_configuration.h"
29#include "mir_test_doubles/mock_input_manager.h"29#include "mir_test_doubles/mock_input_manager.h"
30#include "mir_test_doubles/mock_compositor.h"30#include "mir_test_doubles/mock_compositor.h"
@@ -46,7 +46,7 @@
46namespace mc = mir::compositor;46namespace mc = mir::compositor;
47namespace mg = mir::graphics;47namespace mg = mir::graphics;
48namespace mf = mir::frontend;48namespace mf = mir::frontend;
49namespace mt = mir::test;49namespace mp = mir::pipe;
50namespace mtd = mir::test::doubles;50namespace mtd = mir::test::doubles;
51namespace mtf = mir_test_framework;51namespace mtf = mir_test_framework;
5252
@@ -314,7 +314,7 @@
314 std::shared_ptr<mtd::MockInputManager> mock_input_manager;314 std::shared_ptr<mtd::MockInputManager> mock_input_manager;
315 std::shared_ptr<MockDisplayChanger> mock_display_changer;315 std::shared_ptr<MockDisplayChanger> mock_display_changer;
316316
317 mt::Pipe p;317 mp::Pipe p;
318 int const pause_signal;318 int const pause_signal;
319 int const resume_signal;319 int const resume_signal;
320};320};
321321
=== modified file 'tests/mir_test/CMakeLists.txt'
--- tests/mir_test/CMakeLists.txt 2013-08-28 03:41:48 +0000
+++ tests/mir_test/CMakeLists.txt 2014-01-28 06:34:09 +0000
@@ -1,7 +1,6 @@
1add_library(1add_library(
2 mir-test STATIC2 mir-test STATIC
33
4 pipe.cpp
5 display_config_matchers.cpp4 display_config_matchers.cpp
6 cross_process_action.cpp5 cross_process_action.cpp
7)6)
87
=== modified file 'tests/unit-tests/CMakeLists.txt'
--- tests/unit-tests/CMakeLists.txt 2014-01-23 08:21:21 +0000
+++ tests/unit-tests/CMakeLists.txt 2014-01-28 06:34:09 +0000
@@ -11,6 +11,7 @@
11 test_asio_main_loop.cpp11 test_asio_main_loop.cpp
12 shared_library_test.cpp12 shared_library_test.cpp
13 test_raii.cpp13 test_raii.cpp
14 test_fork_spawner.cpp
14)15)
1516
16if (UMOCKDEV_REQUIRED)17if (UMOCKDEV_REQUIRED)
@@ -29,6 +30,7 @@
29add_subdirectory(android_input/)30add_subdirectory(android_input/)
30add_subdirectory(scene/)31add_subdirectory(scene/)
31add_subdirectory(draw/)32add_subdirectory(draw/)
33add_subdirectory(xserver/)
3234
33add_executable(mir_unit_tests ${UNIT_TEST_SOURCES})35add_executable(mir_unit_tests ${UNIT_TEST_SOURCES})
34uses_android_input(mir_unit_tests)36uses_android_input(mir_unit_tests)
@@ -42,6 +44,7 @@
42 mirdraw44 mirdraw
43 mirtestdraw45 mirtestdraw
44 mirlogging46 mirlogging
47 mirxserverintegration
4548
46 mir-test49 mir-test
47 mir-test-doubles50 mir-test-doubles
4851
=== modified file 'tests/unit-tests/test_asio_main_loop.cpp'
--- tests/unit-tests/test_asio_main_loop.cpp 2014-01-13 06:12:33 +0000
+++ tests/unit-tests/test_asio_main_loop.cpp 2014-01-28 06:34:09 +0000
@@ -17,7 +17,7 @@
17 */17 */
1818
19#include "mir/asio_main_loop.h"19#include "mir/asio_main_loop.h"
20#include "mir_test/pipe.h"20#include "mir/pipe.h"
2121
22#include <gtest/gtest.h>22#include <gtest/gtest.h>
2323
@@ -27,7 +27,7 @@
27#include <sys/types.h>27#include <sys/types.h>
28#include <unistd.h>28#include <unistd.h>
2929
30namespace mt = mir::test;30namespace mp = mir::pipe;
3131
32TEST(AsioMainLoopTest, signal_handled)32TEST(AsioMainLoopTest, signal_handled)
33{33{
@@ -148,7 +148,7 @@
148148
149TEST(AsioMainLoopTest, fd_data_handled)149TEST(AsioMainLoopTest, fd_data_handled)
150{150{
151 mt::Pipe p;151 mp::Pipe p;
152 char const data_to_write{'a'};152 char const data_to_write{'a'};
153 int handled_fd{0};153 int handled_fd{0};
154 char data_read{0};154 char data_read{0};
@@ -173,7 +173,7 @@
173173
174TEST(AsioMainLoopTest, multiple_fds_with_single_handler_handled)174TEST(AsioMainLoopTest, multiple_fds_with_single_handler_handled)
175{175{
176 std::vector<mt::Pipe> const pipes(2);176 std::vector<mp::Pipe> const pipes(2);
177 size_t const num_elems_to_send{10};177 size_t const num_elems_to_send{10};
178 std::vector<int> handled_fds;178 std::vector<int> handled_fds;
179 std::vector<size_t> elems_read;179 std::vector<size_t> elems_read;
@@ -223,7 +223,7 @@
223223
224TEST(AsioMainLoopTest, multiple_fd_handlers_are_called)224TEST(AsioMainLoopTest, multiple_fd_handlers_are_called)
225{225{
226 std::vector<mt::Pipe> const pipes(3);226 std::vector<mp::Pipe> const pipes(3);
227 std::vector<int> const elems_to_send{10,11,12};227 std::vector<int> const elems_to_send{10,11,12};
228 std::vector<int> handled_fds{0,0,0};228 std::vector<int> handled_fds{0,0,0};
229 std::vector<int> elems_read{0,0,0};229 std::vector<int> elems_read{0,0,0};
230230
=== added file 'tests/unit-tests/test_fork_spawner.cpp'
--- tests/unit-tests/test_fork_spawner.cpp 1970-01-01 00:00:00 +0000
+++ tests/unit-tests/test_fork_spawner.cpp 2014-01-28 06:34:09 +0000
@@ -0,0 +1,64 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
17 */
18
19#include "src/server/process/fork_spawner.cpp"
20
21#include <set>
22
23#include <gtest/gtest.h>
24#include <gmock/gmock.h>
25
26using namespace testing;
27
28TEST(ForkSpawnerTest, OpenFDsListsCorrectFDs)
29{
30 // We start with stdin, stdout stderr
31 std::set<int> std_fds{0, 1, 2};
32 std::set<int> extra_fds;
33
34 extra_fds.insert(open("/dev/null", O_RDONLY));
35
36 // Ensure we have a hole in our fd numbering
37 int spacing_fd = open("/dev/null", O_RDONLY);
38
39 extra_fds.insert(open("/dev/null", O_RDONLY));
40 extra_fds.insert(open("/dev/null", O_RDONLY));
41
42 close(spacing_fd);
43
44 for (auto fd : open_fds())
45 {
46 if (std_fds.erase(fd) != 1)
47 {
48 if (extra_fds.erase(fd) != 1)
49 {
50 // We can't fail on unexpected fd as we don't control our test environment
51 // sufficiently - ctest leaves fds open.
52 close(fd);
53 }
54 }
55 }
56
57 EXPECT_THAT(std_fds, IsEmpty());
58 EXPECT_THAT(extra_fds, IsEmpty());
59
60 for (auto fd : extra_fds)
61 {
62 close(fd);
63 }
64}
065
=== added directory 'tests/unit-tests/xserver'
=== added file 'tests/unit-tests/xserver/CMakeLists.txt'
--- tests/unit-tests/xserver/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ tests/unit-tests/xserver/CMakeLists.txt 2014-01-28 06:34:09 +0000
@@ -0,0 +1,8 @@
1list(APPEND UNIT_TEST_SOURCES
2 ${CMAKE_CURRENT_SOURCE_DIR}/test_xserver_spawner.cpp
3)
4
5set(
6 UNIT_TEST_SOURCES
7 ${UNIT_TEST_SOURCES}
8 PARENT_SCOPE)
09
=== added file 'tests/unit-tests/xserver/test_xserver_spawner.cpp'
--- tests/unit-tests/xserver/test_xserver_spawner.cpp 1970-01-01 00:00:00 +0000
+++ tests/unit-tests/xserver/test_xserver_spawner.cpp 2014-01-28 06:34:09 +0000
@@ -0,0 +1,206 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
17 */
18
19#include <future>
20#include <vector>
21#include <thread>
22#include <condition_variable>
23
24#include <gtest/gtest.h>
25#include <gmock/gmock.h>
26
27#include "mir/process/spawner.h"
28#include "mir/process/handle.h"
29
30#include "src/server/xserver/global_socket_listening_server_spawner.h"
31
32using namespace ::testing;
33
34namespace
35{
36struct MockConnector : public mir::frontend::Connector
37{
38 MOCK_METHOD0(start, void(void));
39 MOCK_METHOD0(stop, void(void));
40 MOCK_CONST_METHOD0(client_socket_fd, int(void));
41 MOCK_CONST_METHOD0(remove_endpoint, void(void));
42};
43
44struct MockProcessSpawner : public mir::process::Spawner
45{
46 MOCK_CONST_METHOD3(run, mir::process::Handle*(std::string, std::vector<char const*>, std::vector<int>));
47
48 std::future<std::unique_ptr<mir::process::Handle>> run_from_path(char const* binary) const override
49 {
50 std::promise<std::unique_ptr<mir::process::Handle>> dummy_promise;
51 dummy_promise.set_value(std::unique_ptr<mir::process::Handle>(
52 run(binary, std::initializer_list<char const*>(), std::initializer_list<int>())));
53 return dummy_promise.get_future();
54 }
55 std::future<std::unique_ptr<mir::process::Handle>> run_from_path(char const* binary,
56 std::initializer_list<char const*> args) const
57 override
58 {
59 std::promise<std::unique_ptr<mir::process::Handle>> dummy_promise;
60 dummy_promise.set_value(std::unique_ptr<mir::process::Handle>(run(binary, args, std::initializer_list<int>())));
61 return dummy_promise.get_future();
62 }
63
64 std::future<std::unique_ptr<mir::process::Handle>> run_from_path(char const* binary,
65 std::initializer_list<char const*> args,
66 std::initializer_list<int> fds) const override
67 {
68 std::promise<std::unique_ptr<mir::process::Handle>> dummy_promise;
69 dummy_promise.set_value(std::unique_ptr<mir::process::Handle>(run(binary, args, fds)));
70 return dummy_promise.get_future();
71 }
72};
73
74struct SocketListeningServerTest : public testing::Test
75{
76 SocketListeningServerTest()
77 : default_server_number("100"),
78 spawner(std::make_shared<NiceMock<MockProcessSpawner>>()),
79 connector(std::make_shared<NiceMock<MockConnector>>())
80 {
81 ON_CALL(*spawner, run(_, _, _))
82 .WillByDefault(DoAll(SaveArg<0>(&binary),
83 SaveArg<1>(&args),
84 SaveArg<2>(&fds),
85 InvokeWithoutArgs([this]()
86 { write_server_string(default_server_number); }),
87 Return(nullptr)));
88 ON_CALL(*connector, client_socket_fd())
89 .WillByDefault(Return(22));
90 }
91
92 void write_server_string(std::string server_number)
93 {
94 auto location = std::find_if(args.begin(), args.end(), [](char const* a)
95 { return strcmp(a, "-displayfd") == 0; });
96 ASSERT_NE(location, args.end());
97 ASSERT_NE(++location, args.end());
98 int server_fd = atoi(*location);
99 write(server_fd, server_number.data(), server_number.length());
100 close(server_fd);
101 }
102
103 std::string default_server_number;
104 std::string binary;
105 std::vector<char const*> args;
106 std::vector<int> fds;
107 std::shared_ptr<NiceMock<MockProcessSpawner>> spawner;
108 std::shared_ptr<NiceMock<MockConnector>> connector;
109};
110}
111
112TEST_F(SocketListeningServerTest, CreateServerAlwaysValid)
113{
114 mir::X::GlobalSocketListeningServerSpawner factory;
115
116 auto server_context = factory.create_server(spawner, connector);
117 ASSERT_NE(server_context.get(), nullptr);
118}
119
120TEST_F(SocketListeningServerTest, SpawnsCorrectExecutable)
121{
122 mir::X::GlobalSocketListeningServerSpawner factory;
123
124 auto server_context = factory.create_server(spawner, connector);
125 server_context.get();
126
127 EXPECT_EQ(binary, "Xorg");
128}
129
130namespace
131{
132MATCHER_P(ContainsSubsequence, subsequence, "")
133{
134 auto location =
135 std::search(arg.begin(), arg.end(), subsequence.begin(), subsequence.end(), [](char const* a, std::string b)
136 { return strcmp(a, b.c_str()) == 0; });
137 return location != arg.end();
138}
139}
140
141TEST_F(SocketListeningServerTest, SpawnsWithDisplayFDSet)
142{
143 mir::X::GlobalSocketListeningServerSpawner factory;
144
145 auto server_context = factory.create_server(spawner, connector);
146 server_context.get();
147
148 ASSERT_THAT(args, Not(IsEmpty()));
149 ASSERT_THAT(fds, Not(IsEmpty()));
150
151 Matcher<std::vector<char const*>> fd_matcher = Not(_);
152 for (auto fd : fds)
153 {
154 fd_matcher = AnyOf(fd_matcher, ContainsSubsequence(std::vector<std::string>{"-displayfd", std::to_string(fd)}));
155 }
156 EXPECT_THAT(args, fd_matcher);
157}
158
159TEST_F(SocketListeningServerTest, ReturnsCorrectDisplayString)
160{
161 mir::X::GlobalSocketListeningServerSpawner factory;
162
163 default_server_number = "20";
164 auto server_context = factory.create_server(spawner, connector);
165
166 EXPECT_STREQ(":20", server_context.get()->client_connection_string());
167}
168
169TEST_F(SocketListeningServerTest, HandlesSpawnerLifecycleCorrectly)
170{
171 mir::X::GlobalSocketListeningServerSpawner factory;
172
173 std::future<std::unique_ptr<mir::X::ServerContext>> server_context;
174
175 {
176 auto tmp_spawner = std::make_shared<MockProcessSpawner>();
177 ON_CALL(*tmp_spawner, run(_, _, _))
178 .WillByDefault(DoAll(SaveArg<0>(&binary),
179 SaveArg<1>(&args),
180 SaveArg<2>(&fds),
181 InvokeWithoutArgs([this]()
182 { write_server_string(default_server_number); }),
183 Return(nullptr)));
184 EXPECT_CALL(*tmp_spawner, run(_, _, _));
185 server_context = factory.create_server(tmp_spawner, connector);
186 }
187
188 ASSERT_NE(server_context.get(), nullptr);
189}
190
191TEST_F(SocketListeningServerTest, PassesMirSocketCorrectly)
192{
193 mir::X::GlobalSocketListeningServerSpawner factory;
194
195 int const mir_fd{32};
196 std::string const mir_connect_str{std::string("fd://") + std::to_string(mir_fd)};
197 EXPECT_CALL(*connector, client_socket_fd())
198 .WillOnce(Return(mir_fd));
199
200 auto server_context = factory.create_server(spawner, connector);
201
202 server_context.get();
203
204 EXPECT_THAT(args, ContainsSubsequence(std::vector<std::string>{"-mirSocket", mir_connect_str}));
205 EXPECT_THAT(fds, Contains(Eq(mir_fd)));
206}

Subscribers

People subscribed via source and target branches