]> sourceware.org Git - systemtap.git/blob - remote.cxx
Move multi-target pass-5 into the remote class
[systemtap.git] / remote.cxx
1 // systemtap remote execution
2 // Copyright (C) 2010 Red Hat Inc.
3 //
4 // This file is part of systemtap, and is free software. You can
5 // redistribute it and/or modify it under the terms of the GNU General
6 // Public License (GPL); either version 2, or (at your option) any
7 // later version.
8
9 extern "C" {
10 #include <sys/utsname.h>
11 }
12
13 #include <stdexcept>
14 #include <sstream>
15 #include <string>
16 #include <vector>
17
18 #include "buildrun.h"
19 #include "remote.h"
20 #include "util.h"
21
22 using namespace std;
23
24 // Decode URIs as per RFC 3986, though not bothering to be strict
25 class uri_decoder {
26 public:
27 const string uri;
28 string scheme, authority, path, query, fragment;
29 bool has_authority, has_query, has_fragment;
30
31 uri_decoder(const string& uri):
32 uri(uri), has_authority(false), has_query(false), has_fragment(false)
33 {
34 const string re =
35 "^([^:]+):(//[^/?#]*)?([^?#]*)(\\?[^#]*)?(#.*)?$";
36
37 vector<string> matches;
38 if (regexp_match(uri, re, matches) != 0)
39 throw runtime_error("string doesn't appear to be a URI: " + uri);
40
41 scheme = matches[1];
42
43 if (!matches[2].empty())
44 {
45 has_authority = true;
46 authority = matches[2].substr(2);
47 }
48
49 path = matches[3];
50
51 if (!matches[4].empty())
52 {
53 has_query = true;
54 query = matches[4].substr(1);
55 }
56
57 if (!matches[5].empty())
58 {
59 has_fragment = true;
60 fragment = matches[5].substr(1);
61 }
62 }
63 };
64
65
66 // loopback target for running locally
67 class direct : public remote {
68 private:
69 pid_t child;
70 direct(systemtap_session& s): remote(s), child(0) {}
71
72 int start()
73 {
74 pid_t pid = stap_spawn (s->verbose, make_run_command (*s));
75 if (pid <= 0)
76 return 1;
77 child = pid;
78 return 0;
79 }
80
81 int finish()
82 {
83 if (child <= 0)
84 return 1;
85
86 int ret = stap_waitpid(s->verbose, child);
87 child = 0;
88 return ret;
89 }
90
91 public:
92 friend class remote;
93
94 virtual ~direct() {}
95 };
96
97 class ssh_remote : public remote {
98 // NB: ssh commands use a tty (-t) so signals are passed along to the remote
99 private:
100 vector<string> ssh_args, scp_args;
101 string ssh_control;
102 string host, tmpdir;
103 pid_t child;
104
105 ssh_remote(systemtap_session& s, const string& host)
106 : remote(s), host(host), child(0)
107 {
108 init();
109 }
110
111 ssh_remote(systemtap_session& s, const uri_decoder& ud)
112 : remote(s), child(0)
113 {
114 if (!ud.has_authority || ud.authority.empty())
115 throw runtime_error("ssh target requires a hostname");
116 if (!ud.path.empty() && ud.path != "/")
117 throw runtime_error("ssh target URI doesn't support a /path");
118 if (ud.has_query)
119 throw runtime_error("ssh target URI doesn't support a ?query");
120 if (ud.has_fragment)
121 throw runtime_error("ssh target URI doesn't support a #fragment");
122
123 host = ud.authority;
124 init();
125 }
126
127 void init()
128 {
129 open_control_master();
130 try
131 {
132 get_uname();
133 }
134 catch (runtime_error&)
135 {
136 close_control_master();
137 throw;
138 }
139 }
140
141 void open_control_master()
142 {
143 static unsigned index = 0;
144
145 if (s->tmpdir.empty()) // sanity check, shouldn't happen
146 throw runtime_error("No tmpdir available for ssh control master");
147
148 ssh_control = s->tmpdir + "/ssh_remote_control_" + lex_cast(++index);
149
150 scp_args.clear();
151 scp_args.push_back("scp");
152 scp_args.push_back("-q");
153 scp_args.push_back("-o");
154 scp_args.push_back("ControlPath=" + ssh_control);
155
156 ssh_args = scp_args;
157 ssh_args[0] = "ssh";
158 ssh_args.push_back(host);
159
160 // NB: ssh -f will stay in the foreground until authentication is
161 // complete and the control socket is created, so we know it's ready to
162 // go when stap_system returns.
163 vector<string> cmd = ssh_args;
164 cmd.push_back("-f");
165 cmd.push_back("-N");
166 cmd.push_back("-M");
167 int rc = stap_system(s->verbose, cmd);
168 if (rc != 0)
169 {
170 ostringstream err;
171 err << "failed to create an ssh control master for " << host
172 << " : rc=" << rc;
173 throw runtime_error(err.str());
174 }
175
176 if (s->verbose>1)
177 clog << "Created ssh control master at "
178 << lex_cast_qstring(ssh_control) << endl;
179 }
180
181 void close_control_master()
182 {
183 if (ssh_control.empty())
184 return;
185
186 vector<string> cmd = ssh_args;
187 cmd.push_back("-O");
188 cmd.push_back("exit");
189 int rc = stap_system(s->verbose, cmd, true, true);
190 if (rc != 0)
191 cerr << "failed to stop the ssh control master for " << host
192 << " : rc=" << rc << endl;
193
194 ssh_control.clear();
195 scp_args.clear();
196 ssh_args.clear();
197 }
198
199 void get_uname()
200 {
201 ostringstream out;
202 vector<string> uname;
203 vector<string> cmd = ssh_args;
204 cmd.push_back("-t");
205 cmd.push_back("uname -rm");
206 int rc = stap_system_read(s->verbose, cmd, out);
207 if (rc == 0)
208 tokenize(out.str(), uname, " \t\r\n");
209 if (uname.size() != 2)
210 throw runtime_error("failed to get uname from " + host
211 + " : rc=" + lex_cast(rc));
212 const string& release = uname[0];
213 const string& arch = uname[1];
214 // XXX need to deal with command-line vs. implied arch/release
215 this->s = s->clone(arch, release);
216 }
217
218 int start()
219 {
220 int rc;
221 string localmodule = s->tmpdir + "/" + s->module_name + ".ko";
222 string tmpmodule;
223
224 // Make a remote tempdir.
225 {
226 ostringstream out;
227 vector<string> vout;
228 vector<string> cmd = ssh_args;
229 cmd.push_back("-t");
230 cmd.push_back("mktemp -d -t stapXXXXXX");
231 rc = stap_system_read(s->verbose, cmd, out);
232 if (rc == 0)
233 tokenize(out.str(), vout, "\r\n");
234 if (vout.size() != 1)
235 {
236 cerr << "failed to make a tempdir on " << host
237 << " : rc=" << rc << endl;
238 return -1;
239 }
240 tmpdir = vout[0];
241 tmpmodule = tmpdir + "/" + s->module_name + ".ko";
242 }
243
244 // Transfer the module. XXX and uprobes.ko, sigs, etc.
245 if (rc == 0) {
246 vector<string> cmd = scp_args;
247 cmd.push_back(localmodule);
248 cmd.push_back(host + ":" + tmpmodule);
249 rc = stap_system(s->verbose, cmd);
250 if (rc != 0)
251 cerr << "failed to copy the module to " << host
252 << " : rc=" << rc << endl;
253 }
254
255 // Run the module on the remote.
256 if (rc == 0) {
257 vector<string> cmd = ssh_args;
258 cmd.push_back("-t");
259 cmd.push_back(cmdstr_join(make_run_command(*s, tmpmodule)));
260 pid_t pid = stap_spawn(s->verbose, cmd);
261 if (pid > 0)
262 child = pid;
263 else
264 {
265 cerr << "failed to run the module on " << host
266 << " : ret=" << pid << endl;
267 rc = -1;
268 }
269 }
270
271 return rc;
272 }
273
274 int finish()
275 {
276 int rc = 0;
277
278 if (child > 0)
279 {
280 rc = stap_waitpid(s->verbose, child);
281 child = 0;
282 }
283
284 if (!tmpdir.empty())
285 {
286 // Remove the tempdir.
287 // XXX need to make sure this runs even with e.g. CTRL-C exits
288 vector<string> cmd = ssh_args;
289 cmd.push_back("-t");
290 cmd.push_back("rm -r " + cmdstr_quoted(tmpdir));
291 int rc2 = stap_system(s->verbose, cmd);
292 if (rc2 != 0)
293 cerr << "failed to delete the tempdir on " << host
294 << " : rc=" << rc2 << endl;
295 if (rc == 0)
296 rc = rc2;
297 tmpdir.clear();
298 }
299
300 close_control_master();
301
302 return rc;
303 }
304
305 public:
306 friend class remote;
307
308 virtual ~ssh_remote()
309 {
310 close_control_master();
311 }
312 };
313
314
315 remote*
316 remote::create(systemtap_session& s, const string& uri)
317 {
318 try
319 {
320 if (uri == "direct")
321 return new direct(s);
322 else if (uri.find(':') != string::npos)
323 {
324 const uri_decoder ud(uri);
325 if (ud.scheme == "ssh")
326 return new ssh_remote(s, ud);
327 else
328 {
329 ostringstream msg;
330 msg << "unrecognized URI scheme '" << ud.scheme
331 << "' in remote: " << uri;
332 throw runtime_error(msg.str());
333 }
334 }
335 else
336 // XXX assuming everything else is ssh for now...
337 return new ssh_remote(s, uri);
338 }
339 catch (std::runtime_error& e)
340 {
341 cerr << e.what() << endl;
342 return NULL;
343 }
344 }
345
346 int
347 remote::run(const vector<remote*>& remotes)
348 {
349 // NB: the first failure "wins"
350 int ret = 0, rc = 0;
351
352 for (unsigned i = 0; i < remotes.size() && !pending_interrupts; ++i)
353 {
354 rc = remotes[i]->start();
355 if (!ret)
356 ret = rc;
357 }
358
359 for (unsigned i = 0; i < remotes.size(); ++i)
360 {
361 rc = remotes[i]->finish();
362 if (!ret)
363 ret = rc;
364 }
365
366 return ret;
367 }
368
369
370 /* vim: set sw=2 ts=8 cino=>4,n-2,{2,^-2,t0,(0,u0,w1,M1 : */
This page took 0.053513 seconds and 6 git commands to generate.