]> sourceware.org Git - systemtap.git/blob - remote.cxx
Close the ssh ControlMaster after uname failure
[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 public:
73 friend class remote;
74
75 virtual ~direct() {}
76
77 int start()
78 {
79 string module = s->tmpdir + "/" + s->module_name + ".ko";
80 pid_t pid = stap_spawn (s->verbose, make_run_command (*s, module));
81 if (pid <= 0)
82 return 1;
83 child = pid;
84 return 0;
85 }
86
87 int finish()
88 {
89 if (child <= 0)
90 return 1;
91
92 int ret = stap_waitpid(s->verbose, child);
93 child = 0;
94 return ret;
95 }
96 };
97
98 class ssh_remote : public remote {
99 // NB: ssh commands use a tty (-t) so signals are passed along to the remote
100 private:
101 string ssh_opts, 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 ssh_opts = " -q -o ControlPath=" + lex_cast_qstring(ssh_control) + " ";
150
151 // NB: ssh -f will stay in the foreground until authentication is
152 // complete and the control socket is created, so we know it's ready to
153 // go when stap_system returns.
154 string cmd = "ssh -f -N -M " + ssh_opts + lex_cast_qstring(host);
155 int rc = stap_system(s->verbose, cmd);
156 if (rc != 0)
157 {
158 ostringstream err;
159 err << "failed to create an ssh control master for " << host
160 << " : rc=" << rc;
161 throw runtime_error(err.str());
162 }
163
164 if (s->verbose>1)
165 clog << "Created ssh control master at "
166 << lex_cast_qstring(ssh_control) << endl;
167 }
168
169 void close_control_master()
170 {
171 if (ssh_control.empty())
172 return;
173
174 string cmd = "ssh " + ssh_opts + lex_cast_qstring(host)
175 + " -O exit 2>/dev/null >/dev/null";
176 int rc = stap_system(s->verbose, cmd);
177 if (rc != 0)
178 cerr << "failed to stop the ssh control master for " << host
179 << " : rc=" << rc << endl;
180
181 ssh_control.clear();
182 ssh_opts.clear();
183 }
184
185 void get_uname()
186 {
187 ostringstream out;
188 vector<string> uname;
189 string uname_cmd = "ssh -t " + ssh_opts + lex_cast_qstring(host) + " uname -rm";
190 int rc = stap_system_read(s->verbose, uname_cmd, out);
191 if (rc == 0)
192 tokenize(out.str(), uname, " \t\r\n");
193 if (uname.size() != 2)
194 throw runtime_error("failed to get uname from " + host
195 + " : rc=" + lex_cast(rc));
196 const string& release = uname[0];
197 const string& arch = uname[1];
198 // XXX need to deal with command-line vs. implied arch/release
199 this->s = s->clone(arch, release);
200 }
201
202 public:
203 friend class remote;
204
205 virtual ~ssh_remote()
206 {
207 close_control_master();
208 }
209
210 int start()
211 {
212 int rc;
213 string localmodule = s->tmpdir + "/" + s->module_name + ".ko";
214 string tmpmodule;
215 string qhost = lex_cast_qstring(host);
216
217 // Make a remote tempdir.
218 {
219 ostringstream out;
220 vector<string> vout;
221 string cmd = "ssh -t " + ssh_opts + qhost + " mktemp -d -t stapXXXXXX";
222 rc = stap_system_read(s->verbose, cmd, out);
223 if (rc == 0)
224 tokenize(out.str(), vout, "\r\n");
225 if (vout.size() != 1)
226 {
227 cerr << "failed to make a tempdir on " << host
228 << " : rc=" << rc << endl;
229 return -1;
230 }
231 tmpdir = vout[0];
232 tmpmodule = tmpdir + "/" + s->module_name + ".ko";
233 }
234
235 // Transfer the module. XXX and uprobes.ko, sigs, etc.
236 if (rc == 0) {
237 string cmd = "scp " + ssh_opts + localmodule + " " + qhost + ":" + tmpmodule;
238 rc = stap_system(s->verbose, cmd);
239 if (rc != 0)
240 cerr << "failed to copy the module to " << host
241 << " : rc=" << rc << endl;
242 }
243
244 // Run the module on the remote.
245 if (rc == 0) {
246 string cmd = "ssh -t " + ssh_opts + qhost + " "
247 + lex_cast_qstring(make_run_command(*s, tmpmodule));
248 pid_t pid = stap_spawn(s->verbose, cmd);
249 if (pid > 0)
250 child = pid;
251 else
252 {
253 cerr << "failed to run the module on " << host
254 << " : ret=" << pid << endl;
255 rc = -1;
256 }
257 }
258
259 return rc;
260 }
261
262 int finish()
263 {
264 int rc = 0;
265
266 if (child > 0)
267 {
268 rc = stap_waitpid(s->verbose, child);
269 child = 0;
270 }
271
272 if (!tmpdir.empty())
273 {
274 // Remove the tempdir.
275 // XXX need to make sure this runs even with e.g. CTRL-C exits
276 string qhost = lex_cast_qstring(host);
277 string cmd = "ssh -t " + ssh_opts + qhost + " rm -r " + tmpdir;
278 tmpdir.clear();
279 int rc2 = stap_system(s->verbose, cmd);
280 if (rc2 != 0)
281 cerr << "failed to delete the tempdir on " << host
282 << " : rc=" << rc2 << endl;
283 if (rc == 0)
284 rc = rc2;
285 }
286
287 close_control_master();
288
289 return rc;
290 }
291 };
292
293
294 remote*
295 remote::create(systemtap_session& s, const string& uri)
296 {
297 try
298 {
299 if (uri == "direct")
300 return new direct(s);
301 else if (uri.find(':') != string::npos)
302 {
303 const uri_decoder ud(uri);
304 if (ud.scheme == "ssh")
305 return new ssh_remote(s, ud);
306 else
307 {
308 ostringstream msg;
309 msg << "unrecognized URI scheme '" << ud.scheme
310 << "' in remote: " << uri;
311 throw runtime_error(msg.str());
312 }
313 }
314 else
315 // XXX assuming everything else is ssh for now...
316 return new ssh_remote(s, uri);
317 }
318 catch (std::runtime_error& e)
319 {
320 cerr << e.what() << endl;
321 return NULL;
322 }
323 }
324
325
326 /* vim: set sw=2 ts=8 cino=>4,n-2,{2,^-2,t0,(0,u0,w1,M1 : */
This page took 0.055095 seconds and 6 git commands to generate.