softwarecontainer  0.18.0-739e8d7 2017-05-04
dbusgatewayinstance.cpp
1 /*
2  * Copyright (C) 2016-2017 Pelagicore AB
3  *
4  * Permission to use, copy, modify, and/or distribute this software for
5  * any purpose with or without fee is hereby granted, provided that the
6  * above copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
9  * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
10  * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
11  * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
12  * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
13  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
14  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
15  * SOFTWARE.
16  *
17  * For further information see LICENSE
18  */
19 
20 #include <unistd.h>
21 #include <fcntl.h>
22 
23 #include <sys/stat.h>
24 #include <sys/types.h>
25 
26 #include "dbusgateway.h"
27 #include "dbusgatewayparser.h"
28 
29 namespace softwarecontainer {
30 
31 // These lines are needed in order to define the fields, which otherwise would
32 // yield linker errors.
33 constexpr const char *DBusGatewayInstance::SESSION_CONFIG;
34 constexpr const char *DBusGatewayInstance::SYSTEM_CONFIG;
35 
37  const std::string &gatewayDir,
38  std::shared_ptr<ContainerAbstractInterface> container) :
39  Gateway(ID, container, true /*this GW is dynamic*/),
40  m_socket(""),
41  m_type(type),
42  m_pid(INVALID_PID),
43  m_proxyStdin(INVALID_FD)
44 {
45  std::string name = container->id();
46  std::string socketName = (m_type == SessionProxy ? "sess_" : "sys_") + name + ".sock";
47  m_socket = buildPath(gatewayDir, socketName);
48 
49  // Write configuration
50  m_entireConfig = json_object();
51  m_busConfig = json_array();
52 
53  const char* unusedTypeStr;
54  if (m_type == SessionProxy) {
55  typeStr = SESSION_CONFIG;
56  unusedTypeStr = SYSTEM_CONFIG;
57 
58  } else {
59  typeStr = SYSTEM_CONFIG;
60  unusedTypeStr = SESSION_CONFIG;
61  }
62  json_object_set(m_entireConfig, typeStr, m_busConfig);
63 
64  // Send an empty array as config for the irrelevant proxy type
65  json_t* emptyArray = json_array();
66  json_object_set(m_entireConfig, unusedTypeStr, emptyArray);
67 }
68 
69 DBusGatewayInstance::~DBusGatewayInstance()
70 {
71  // TODO: The "extra" teardown here is likely unnecessary. This should be removed
72  // but is pending a decision to do explicit calls to teardown or let this
73  // happen in gateway destructors always. Currently this is not a problem,
74  // it's just cruft, but we don't want to touch it until there's time
75  // to make sure we do it correctly.
76  if (isActivated()) {
77  teardown();
78  }
79 }
80 
81 bool DBusGatewayInstance::readConfigElement(const json_t *element)
82 {
83  DBusGatewayParser parser;
84 
85  // TODO: This should really be done with exceptions instead and json_t* as return type.
86  if (!parser.parseDBusConfig(element, typeStr, m_busConfig)) {
87  // This might also mean that only one of session or system bus config parsing failed
88  // so this is not a fatal error
89  log_warning() << "Failed to parse DBus configuration element";
90  return false;
91  }
92 
93  return true;
94 }
95 
97 {
98  // Setting up the environment and starting dbus-proxy should only be done the first time
99  // around. Any subsequent calls after the first should only result in the config being
100  // updated.
101  if (!m_activatedOnce) {
102  // set DBUS_{SESSION,SYSTEM}_BUS_ADDRESS env variable
103  std::string variable = std::string("DBUS_")
104  + (m_type == SessionProxy ? "SESSION" : "SYSTEM")
105  + std::string("_BUS_ADDRESS");
106  std::string value = "unix:path=" + buildPath("/gateways", socketName());
107  getContainer()->setEnvironmentVariable(variable, value);
108 
109  std::vector<std::string> commandVec = {"dbus-proxy",
110  m_socket,
111  m_type == SessionProxy ? "session" : "system"};
112 
113  // Give the dbus-proxy access to the real dbus bus address.
114  std::vector<std::string> envVec;
115 
116  bool hasEnvVar = false;
117  std::string envValue = Glib::getenv(variable, hasEnvVar);
118 
119  if (!hasEnvVar) {
120  if (SessionProxy == m_type) {
121  log_error() << "Using DBus gateway in session mode"
122  << " and no " + variable + " set in host environment, dbus-proxy won't work";
123  return false;
124  } else {
125  log_warn() << "Using DBus gateway in system mode"
126  << " and no " + variable + " set in host environment, this could be a problem";
127  }
128  } else {
129  envVec.push_back(variable + "=" + envValue);
130  }
131 
132  if (!startDBusProxy(commandVec, envVec)) {
133  return false;
134  }
135  }
136 
137  // Dump to string. Compact format is currently needed as dbus-proxy relies on
138  // the config string to be without newlines anywhere in the middle of it all
139  char *config_c = json_dumps(m_entireConfig, JSON_COMPACT);
140  std::string config = std::string(config_c);
141 
142  free(config_c);
143 
144  return testDBusConnection(config);
145 }
146 
147 bool DBusGatewayInstance::testDBusConnection(const std::string &configuration)
148 {
149  // The reading end expects there to be one newline at the end
150  std::string config{configuration + std::string("\n")};
151 
152  ssize_t count = config.length() * sizeof(char);
153  log_debug() << "Expected config byte length " << count;
154 
155  log_debug() << "Config: " << config;
156 
157  ssize_t configWrite = write(m_proxyStdin, config.c_str(), count);
158 
159  // writing didn't work at all
160  if (-1 == configWrite) {
161  log_error() << "Failed to write to STDIN of dbus-proxy: " << strerror(errno);
162  return false;
163  } else if (configWrite == (ssize_t)count) {
164  log_debug() << "Wrote " << configWrite << " bytes to dbus-proxy";
165  // dbus-proxy might take some time to create the bus socket
166  if (isSocketCreated()) {
167  log_debug() << "Found D-Bus socket: " << m_socket;
168  return true;
169  } else {
170  log_error() << "Did not find any D-Bus socket: " << m_socket;
171  return false;
172  }
173  } else {
174  // something went wrong during the write
175  log_error() << "Failed to write to STDIN of dbus-proxy!";
176  return false;
177  }
178 }
179 
180 bool DBusGatewayInstance::startDBusProxy(const std::vector<std::string> &commandVec,
181  const std::vector<std::string> &envVec)
182 {
183  // Spawn dbus-proxy with access to its stdin.
184  try {
185  Glib::spawn_async_with_pipes(".",
186  commandVec,
187  envVec,
188  Glib::SPAWN_STDOUT_TO_DEV_NULL // Redirect stdout
189  | Glib::SPAWN_STDERR_TO_DEV_NULL // Redirect stderr
190  | Glib::SPAWN_SEARCH_PATH // Search $PATH
191  | Glib::SPAWN_DO_NOT_REAP_CHILD, // Lets us do waitpid
192  sigc::slot<void>(), // child setup
193  &m_pid,
194  &m_proxyStdin);
195  } catch (const Glib::Error &ex) {
196  log_error() << "Failed to launch dbus-proxy";
197  return false;
198  }
199 
200  m_activatedOnce = true;
201  log_debug() << "Started dbus-proxy: " << m_pid;
202 
203  return true;
204 }
205 
206 bool DBusGatewayInstance::isSocketCreated() const
207 {
208  int maxCount = 1000;
209  int count = 0;
210  do {
211  if (count >= maxCount) {
212  log_error() << "Could not find dbus-proxy socket, error: " << strerror(errno);
213  return false;
214  }
215  count++;
216  usleep(1000 * 10);
217  } while (access(m_socket.c_str(), F_OK) == -1);
218  return true;
219 }
220 
222 {
223  bool success = true;
224 
225  if (nullptr != m_entireConfig) {
226  json_decref(m_entireConfig);
227  m_entireConfig = nullptr;
228  }
229 
230  if (m_pid != INVALID_PID) {
231  log_debug() << "Killing dbus-proxy with pid " << m_pid;
232 
233  // TODO: Figure out how to shut down nicely?
234  kill(m_pid, SIGKILL); // In some configurations, hangs if using SIGTERM instead
235  waitpid(m_pid, nullptr, 0); // Wait until it exits
236  Glib::spawn_close_pid(m_pid);
237  } else {
238  log_debug() << "dbus-proxy pid not set or already dead: " << m_pid;
239  }
240 
241  if (access(m_socket.c_str(), F_OK) != -1) {
242  if (unlink(m_socket.c_str()) == -1) {
243  log_error() << "Could not remove " << m_socket << ": " << strerror(errno);
244  success = false;
245  }
246  } else {
247  // TODO: Seems weird that this would ever happen. Seems like a severe error.
248  log_debug() << "Socket not accessible, has it been removed already?";
249  }
250 
251  // Close stdin to proxy as we will no longer send configs to it
252  if (close(m_proxyStdin) == -1) {
253  log_warning() << "Could not close stdin of dbus-proxy";
254  }
255 
256  return success;
257 }
258 
259 std::string DBusGatewayInstance::socketName()
260 {
261  return basename(m_socket.c_str());
262 }
263 
264 } // namespace softwarecontainer
virtual bool isActivated()
Is the gateway activated or not?
Definition: gateway.cpp:139
virtual bool readConfigElement(const json_t *element) override
Gateway specific parsing of config elements.
virtual std::string id() const
Returns the ID of the gateway.
Definition: gateway.cpp:36
virtual bool teardownGateway() override
Implements Gateway::teardownGateway.
virtual bool teardown()
Restore system to the state prior to launching of gateway.
Definition: gateway.cpp:103
Gateway base class for SoftwareContainer.
Definition: gateway.h:61
std::shared_ptr< ContainerAbstractInterface > getContainer()
Get a handle to the associated container.
Definition: gateway.cpp:128
Developers guide to adding a config item:
DBusGatewayInstance(ProxyType type, const std::string &gatewayDir, std::shared_ptr< ContainerAbstractInterface > container)
Spawn the proxy and use the supplied path for the socket.
virtual bool activateGateway()
Implements Gateway::activateGateway.