Branch data Line data Source code
1 : : // systemtap remote execution
2 : : // Copyright (C) 2010-2011 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 : : #include "config.h"
10 : :
11 : : extern "C" {
12 : : #include <fcntl.h>
13 : : #include <poll.h>
14 : : #include <sys/types.h>
15 : : #include <sys/stat.h>
16 : : #include <unistd.h>
17 : : #include <sys/socket.h>
18 : : #include <sys/un.h>
19 : : }
20 : :
21 : : #include <cstdio>
22 : : #include <iomanip>
23 : : #include <memory>
24 : : #include <stdexcept>
25 : : #include <sstream>
26 : : #include <string>
27 : : #include <vector>
28 : :
29 : : #include "buildrun.h"
30 : : #include "remote.h"
31 : : #include "util.h"
32 : :
33 : : using namespace std;
34 : :
35 : : // Decode URIs as per RFC 3986, though not bothering to be strict
36 [ + - ][ + - ]: 2214 : class uri_decoder {
[ + - ][ + - ]
[ + - ]
37 : : public:
38 : : const string uri;
39 : : string scheme, authority, path, query, fragment;
40 : : bool has_authority, has_query, has_fragment;
41 : :
42 : 2214 : uri_decoder(const string& uri):
43 [ + - ][ + - ]: 2214 : uri(uri), has_authority(false), has_query(false), has_fragment(false)
[ + - ][ + - ]
[ + - ]
44 : : {
45 : : const string re =
46 [ + - ]: 2214 : "^([^:]+):(//[^/?#]*)?([^?#]*)(\\?[^#]*)?(#.*)?$";
47 : :
48 [ + - ]: 2214 : vector<string> matches;
49 [ + - ][ - + ]: 2214 : if (regexp_match(uri, re, matches) != 0)
50 [ # # ][ # # ]: 0 : throw runtime_error(_F("string doesn't appear to be a URI: %s", uri.c_str()));
[ # # ]
51 : :
52 [ + - ]: 2214 : scheme = matches[1];
53 : :
54 [ + - ][ - + ]: 2214 : if (!matches[2].empty())
55 : : {
56 : 0 : has_authority = true;
57 [ # # ][ # # ]: 0 : authority = matches[2].substr(2);
[ # # ]
58 : : }
59 : :
60 [ + - ]: 2214 : path = matches[3];
61 : :
62 [ + - ][ - + ]: 2214 : if (!matches[4].empty())
63 : : {
64 : 0 : has_query = true;
65 [ # # ][ # # ]: 0 : query = matches[4].substr(1);
[ # # ]
66 : : }
67 : :
68 [ + - ][ - + ]: 2214 : if (!matches[5].empty())
69 : : {
70 : 0 : has_fragment = true;
71 [ # # ][ # # ]: 0 : fragment = matches[5].substr(1);
[ # # ]
72 [ + - ][ + - ]: 2214 : }
73 : 2214 : }
74 : : };
75 : :
76 : :
77 : : // loopback target for running locally
78 : : class direct : public remote {
79 : : private:
80 : : pid_t child;
81 : : vector<string> args;
82 [ + - ]: 2209 : direct(systemtap_session& s): remote(s), child(0) {}
83 : :
84 : 473 : int start()
85 : : {
86 [ + - ][ + - ]: 473 : args = make_run_command(*s);
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ]
87 [ + + ]: 473 : if (! staprun_r_arg.empty()) // PR13354
88 : : {
89 [ + - ][ + - ]: 2 : args.push_back ("-r");
[ + - ]
90 : 2 : args.push_back (staprun_r_arg);
91 : : }
92 : 473 : pid_t pid = stap_spawn (s->verbose, args);
93 [ - + ]: 473 : if (pid <= 0)
94 : 0 : return 1;
95 : 473 : child = pid;
96 : 473 : return 0;
97 : : }
98 : :
99 : 2673 : int finish()
100 : : {
101 [ + + ]: 2673 : if (child <= 0)
102 : 2200 : return 1;
103 : :
104 : 473 : int ret = stap_waitpid(s->verbose, child);
105 [ - + ]: 473 : if (ret > 128)
106 : 0 : s->print_warning(_F("%s exited with signal: %d (%s)",
107 : : args.front().c_str(), ret - 128,
108 [ # # ]: 0 : strsignal(ret - 128)));
109 [ + + ]: 473 : else if (ret > 0)
110 : 101 : s->print_warning(_F("%s exited with status: %d",
111 [ + - ]: 101 : args.front().c_str(), ret));
112 : 473 : child = 0;
113 : 2673 : return ret;
114 : : }
115 : :
116 : : public:
117 : : friend class remote;
118 : :
119 [ + - ][ + - ]: 4400 : virtual ~direct() { finish(); }
[ - + ]
120 : : };
121 : :
122 : :
123 : : class stapsh : public remote {
124 : : private:
125 : : int interrupts_sent;
126 : : int fdin, fdout;
127 : : FILE *IN, *OUT;
128 : : string remote_version;
129 : :
130 : 19 : virtual void prepare_poll(vector<pollfd>& fds)
131 : : {
132 [ + + ][ + - ]: 19 : if (fdout >= 0 && OUT)
133 : : {
134 : 16 : pollfd p = { fdout, POLLIN, 0 };
135 [ + - ]: 16 : fds.push_back(p);
136 : : }
137 : :
138 : : // need to send a signal?
139 [ + + ][ + - ]: 19 : if (fdin >= 0 && IN && interrupts_sent < pending_interrupts)
[ + + ]
140 : : {
141 : 3 : pollfd p = { fdin, POLLOUT, 0 };
142 [ + - ]: 3 : fds.push_back(p);
143 : : }
144 : 19 : }
145 : :
146 : 16 : virtual void handle_poll(vector<pollfd>& fds)
147 : : {
148 [ + + ]: 35 : for (unsigned i=0; i < fds.size(); ++i)
149 [ + + ][ + - ]: 19 : if (fds[i].fd == fdin || fds[i].fd == fdout)
[ + - ]
150 : : {
151 : 19 : bool err = false;
152 : :
153 : : // need to send a signal?
154 [ + + ][ + - ]: 19 : if (fds[i].revents & POLLOUT && IN &&
[ + - ][ + + ]
155 : : interrupts_sent < pending_interrupts)
156 : : {
157 [ + - ][ + - ]: 3 : if (send_command("quit\n") == 0)
[ + - ][ + - ]
158 : 3 : ++interrupts_sent;
159 : : else
160 : 0 : err = true;
161 : : }
162 : :
163 : : // have data to read?
164 [ + + ][ + - ]: 19 : if (fds[i].revents & POLLIN && OUT)
[ + + ]
165 : : {
166 : : char buf[4096];
167 [ + - ][ - + ]: 7 : if (!prefix.empty())
168 : : {
169 : : // If we have a line prefix, then read lines one at a
170 : : // time and copy out with the prefix.
171 : 0 : errno = 0;
172 [ # # ][ # # ]: 0 : while (fgets(buf, sizeof(buf), OUT))
173 [ # # ][ # # ]: 0 : cout << prefix << buf;
174 [ # # ]: 0 : if (errno != EAGAIN)
175 : 0 : err = true;
176 : : }
177 : : else
178 : : {
179 : : // Otherwise read an entire block of data at once.
180 [ + - ]: 7 : size_t rc = fread(buf, 1, sizeof(buf), OUT);
181 [ + - ]: 7 : if (rc > 0)
182 : : {
183 : : // NB: The buf could contain binary data,
184 : : // including \0, so write as a block instead of
185 : : // the usual <<string.
186 [ + - ]: 7 : cout.write(buf, rc);
187 : : }
188 : : else
189 : 7 : err = true;
190 : : }
191 : : }
192 : :
193 : : // any errors?
194 [ + - ][ + + ]: 19 : if (err || fds[i].revents & ~(POLLIN|POLLOUT))
[ + + ]
195 : 3 : close();
196 : : }
197 : 16 : }
198 : :
199 : 10 : string get_reply()
200 : : {
201 : : // Some schemes like unix may have stdout and stderr mushed together.
202 : : // There shouldn't be anything except dbug messages on stderr before we
203 : : // actually start running, and there's no get_reply after that. So
204 : : // we'll just loop and skip those that start with "stapsh:".
205 : : char reply[4096];
206 [ + - ][ + - ]: 10 : while (fgets(reply, sizeof(reply), OUT))
207 : : {
208 [ + - ][ + - ]: 10 : if (!startswith(reply, "stapsh:"))
[ + - ][ + - ]
209 [ + - ]: 10 : return reply;
210 : :
211 : : // Why not clog here? Well, once things get running we won't be
212 : : // able to distinguish stdout/err, so trying to fake it here would
213 : : // be less consistent than just keeping it merged.
214 [ # # ]: 0 : cout << reply;
215 : : }
216 : :
217 : : // Reached EOF, nothing to reply...
218 [ # # ]: 10 : return "";
219 : : }
220 : :
221 : 13 : int send_command(const string& cmd)
222 : : {
223 [ - + ]: 13 : if (!IN)
224 : 0 : return 2;
225 [ + - - + ]: 26 : if (fputs(cmd.c_str(), IN) < 0 ||
[ - + ]
226 : 13 : fflush(IN) != 0)
227 : 0 : return 1;
228 : 13 : return 0;
229 : : }
230 : :
231 : 3 : int send_file(const string& filename, const string& dest)
232 : : {
233 : 3 : int rc = 0;
234 [ + - ][ + - ]: 3 : FILE* f = fopen(filename.c_str(), "r");
235 [ - + ]: 3 : if (!f)
236 : 0 : return 1;
237 : :
238 : : struct stat fs;
239 : 3 : rc = fstat(fileno(f), &fs);
240 [ + - ]: 3 : if (!rc)
241 : : {
242 [ + - ]: 3 : ostringstream cmd;
243 [ + - ][ + - ]: 3 : cmd << "file " << fs.st_size << " " << dest << "\n";
[ + - ][ + - ]
[ + - ]
244 [ + - ][ + - ]: 3 : rc = send_command(cmd.str());
[ + - ][ + - ]
245 : : }
246 : :
247 : 3 : off_t i = 0;
248 [ + - ][ + + ]: 216 : while (!rc && i < fs.st_size)
[ + + ]
249 : : {
250 : : char buf[4096];
251 : 213 : size_t r = sizeof(buf);
252 [ + + ]: 213 : if (fs.st_size - i < (off_t)r)
253 : 3 : r = fs.st_size - i;
254 [ + - ]: 213 : r = fread(buf, 1, r, f);
255 [ - + ]: 213 : if (r == 0)
256 : 0 : rc = 1;
257 : : else
258 : : {
259 [ + - ]: 213 : size_t w = fwrite(buf, 1, r, IN);
260 [ - + ]: 213 : if (w != r)
261 : 0 : rc = 1;
262 : : else
263 : 213 : i += w;
264 : : }
265 : : }
266 [ + - ]: 3 : if (!rc)
267 [ + - ]: 3 : rc = fflush(IN);
268 : :
269 [ + - ]: 3 : fclose(f);
270 : :
271 [ + - ]: 3 : if (!rc)
272 : : {
273 [ + - ]: 3 : string reply = get_reply();
274 [ + - ][ - + ]: 3 : if (reply != "OK\n")
275 : : {
276 : 0 : rc = 1;
277 [ # # ]: 0 : if (s->verbose > 0)
278 : : {
279 [ # # ][ # # ]: 0 : if (reply.empty())
280 [ # # ][ # # ]: 0 : clog << _("stapsh file ERROR: no reply") << endl;
281 : : else
282 [ # # ][ # # ]: 0 : clog << _F("stapsh file replied %s", reply.c_str());
[ # # ][ # # ]
283 : : }
284 [ + - ]: 3 : }
285 : : }
286 : :
287 : 3 : return rc;
288 : : }
289 : :
290 : 12 : static string qpencode(const string& str)
291 : : {
292 [ + - ]: 12 : ostringstream o;
293 [ + - ][ + - ]: 12 : o << setfill('0') << hex;
294 [ + - ][ + + ]: 192 : for (const char* s = str.c_str(); *s; ++s)
295 [ + - ][ + - ]: 180 : if (*s >= 33 && *s <= 126 && *s != 61)
[ + - ]
296 [ + - ]: 180 : o << *s;
297 : : else
298 [ # # ][ # # ]: 0 : o << '=' << setw(2) << (unsigned)(unsigned char) *s;
[ # # ]
299 [ + - ][ + - ]: 12 : return o.str();
300 : : }
301 : :
302 : : protected:
303 : 5 : stapsh(systemtap_session& s)
304 : : : remote(s), interrupts_sent(0),
305 [ + - ]: 5 : fdin(-1), fdout(-1), IN(0), OUT(0)
306 : 5 : {}
307 : :
308 : 3 : virtual int prepare()
309 : : {
310 : 3 : int rc = 0;
311 : :
312 [ + - ][ + - ]: 3 : string localmodule = s->tmpdir + "/" + s->module_name + ".ko";
[ + - ][ + - ]
[ + - ]
313 [ + - ]: 3 : string remotemodule = s->module_name + ".ko";
314 [ + - ][ - + ]: 3 : if ((rc = send_file(localmodule, remotemodule)))
315 : 0 : return rc;
316 : :
317 [ + - ][ + - ]: 6 : if (file_exists(localmodule + ".sgn") &&
[ - + ][ # # ]
[ # # ][ + - ]
[ + - ]
[ - + # # ]
318 [ # # ][ # # ]: 3 : (rc = send_file(localmodule + ".sgn", remotemodule + ".sgn")))
[ - + ][ # # ]
[ - + ][ # # ]
[ # # # # ]
319 : 0 : return rc;
320 : :
321 [ + - ][ - + ]: 3 : if (!s->uprobes_path.empty())
322 : : {
323 [ # # ][ # # ]: 0 : string remoteuprobes = basename(s->uprobes_path.c_str());
324 [ # # ][ # # ]: 0 : if ((rc = send_file(s->uprobes_path, remoteuprobes)))
325 : 0 : return rc;
326 : :
327 [ # # ][ # # ]: 0 : if (file_exists(s->uprobes_path + ".sgn") &&
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ]
[ # # # # ]
328 [ # # ][ # # ]: 0 : (rc = send_file(s->uprobes_path + ".sgn", remoteuprobes + ".sgn")))
[ # # ][ # # ]
[ # # ][ # # ]
[ # # # # ]
329 [ # # ][ # # ]: 0 : return rc;
330 : : }
331 : :
332 [ + - ][ + - ]: 3 : return rc;
333 : : }
334 : :
335 : 3 : virtual int start()
336 : : {
337 : : // Send the staprun args
338 : : // NB: The remote is left to decide its own staprun path
339 [ + - ][ + - ]: 3 : ostringstream run("run", ios::out | ios::ate);
[ + - ]
340 [ + - ][ + - ]: 3 : vector<string> cmd = make_run_command(*s, ".", remote_version);
[ + - ]
341 : :
342 : : // PR13354: identify our remote index/url
343 [ + - ][ + - ]: 6 : if (strverscmp("1.7", remote_version.c_str()) <= 0 && // -r supported?
[ + - ][ + - ]
344 [ + - ]: 3 : ! staprun_r_arg.empty())
345 : : {
346 [ + - ][ + - ]: 3 : cmd.push_back ("-r");
[ + - ]
347 [ + - ]: 3 : cmd.push_back (staprun_r_arg);
348 : : }
349 : :
350 [ + + ]: 15 : for (unsigned i = 1; i < cmd.size(); ++i)
351 [ + - ][ + - ]: 12 : run << ' ' << qpencode(cmd[i]);
[ + - ][ + - ]
352 [ + - ]: 3 : run << '\n';
353 : :
354 [ + - ][ + - ]: 3 : int rc = send_command(run.str());
[ + - ]
355 : :
356 [ + - ]: 3 : if (!rc)
357 : : {
358 [ + - ]: 3 : string reply = get_reply();
359 [ + - ][ - + ]: 3 : if (reply != "OK\n")
360 : : {
361 : 0 : rc = 1;
362 [ # # ]: 0 : if (s->verbose > 0)
363 : : {
364 [ # # ][ # # ]: 0 : if (reply.empty())
365 [ # # ][ # # ]: 0 : clog << _("stapsh run ERROR: no reply") << endl;
366 : : else
367 [ # # ][ # # ]: 0 : clog << _F("stapsh run replied %s", reply.c_str());
[ # # ][ # # ]
368 : : }
369 [ + - ]: 3 : }
370 : : }
371 : :
372 [ + - ]: 3 : if (!rc)
373 : : {
374 [ + - ]: 3 : long flags = fcntl(fdout, F_GETFL) | O_NONBLOCK;
375 [ + - ]: 3 : fcntl(fdout, F_SETFL, flags);
376 : : }
377 : : else
378 : : // If run failed for any reason, then this
379 : : // connection is effectively dead to us.
380 [ # # ]: 0 : close();
381 : :
382 [ + - ][ + - ]: 3 : return rc;
383 : : }
384 : :
385 : 15 : void close()
386 : : {
387 [ + + ]: 15 : if (IN) fclose(IN);
388 [ + + ]: 15 : if (OUT) fclose(OUT);
389 : 15 : IN = OUT = NULL;
390 : 15 : fdin = fdout = -1;
391 : 15 : }
392 : :
393 : 7 : virtual int finish()
394 : : {
395 : 7 : close();
396 : 7 : return 0;
397 : : }
398 : :
399 : 4 : void set_child_fds(int in, int out)
400 : : {
401 [ + - ][ + - ]: 4 : if (fdin >= 0 || fdout >= 0 || IN || OUT)
[ + - ][ - + ]
402 [ # # ][ # # ]: 0 : throw runtime_error(_("stapsh file descriptors already set"));
403 : :
404 : 4 : fdin = in;
405 : 4 : fdout = out;
406 : 4 : IN = fdopen(fdin, "w");
407 : 4 : OUT = fdopen(fdout, "r");
408 [ + - ][ - + ]: 4 : if (!IN || !OUT)
409 [ # # ][ # # ]: 0 : throw runtime_error(_("invalid file descriptors for stapsh"));
410 : :
411 [ + - ][ + - ]: 4 : if (send_command("stap " VERSION "\n"))
[ + - ][ - + ]
412 [ # # ][ # # ]: 0 : throw runtime_error(_("error sending hello to stapsh"));
413 : :
414 [ + - ]: 4 : string reply = get_reply();
415 [ + - ][ - + ]: 4 : if (reply.empty())
416 [ # # ][ # # ]: 0 : throw runtime_error(_("error receiving hello from stapsh"));
417 : :
418 : : // stapsh VERSION MACHINE RELEASE
419 [ + - ]: 4 : vector<string> uname;
420 [ + - ][ + - ]: 4 : tokenize(reply, uname, " \t\r\n");
[ + - ]
421 [ + - ][ + - ]: 4 : if (uname.size() != 4 || uname[0] != "stapsh")
[ - + ][ - + ]
422 [ # # ][ # # ]: 0 : throw runtime_error(_("failed to get uname from stapsh"));
423 : :
424 : : // We assume that later versions will know how to talk to us.
425 : : // Looking backward, we use this for make_run_command().
426 [ + - ]: 4 : this->remote_version = uname[1];
427 : :
428 [ + - ][ + - ]: 4 : this->s = s->clone(uname[2], uname[3]);
[ + - ]
429 : 4 : }
430 : :
431 : : public:
432 [ + - ][ + - ]: 5 : virtual ~stapsh() { close(); }
[ - + ]
433 : : };
434 : :
435 : :
436 : : // direct_stapsh is meant only for testing, as a way to exercise the stapsh
437 : : // mechanisms without requiring test machines to have actual remote access.
438 : : class direct_stapsh : public stapsh {
439 : : private:
440 : : pid_t child;
441 : :
442 : 4 : direct_stapsh(systemtap_session& s)
443 : 4 : : stapsh(s), child(0)
444 : : {
445 : : int in, out;
446 [ + - ]: 4 : vector<string> cmd;
447 [ + - ][ + - ]: 4 : cmd.push_back(BINDIR "/stapsh");
[ + - ]
448 [ - + ]: 4 : if (s.perpass_verbose[4] > 1)
449 [ # # ][ # # ]: 0 : cmd.push_back("-v");
[ # # ]
450 [ - + ]: 4 : if (s.perpass_verbose[4] > 2)
451 [ # # ][ # # ]: 0 : cmd.push_back("-v");
[ # # ]
452 : :
453 : : // mask signals while we spawn, so we can simulate manual signals to
454 : : // the "remote" target, as we must for the real ssh_remote case.
455 : : {
456 : 4 : stap_sigmasker masked;
457 [ + - ]: 4 : child = stap_spawn_piped(s.verbose, cmd, &in, &out);
458 : : }
459 : :
460 [ - + ]: 4 : if (child <= 0)
461 [ # # ][ # # ]: 0 : throw runtime_error(_("error launching stapsh"));
462 : :
463 : : try
464 : : {
465 [ + - ]: 4 : set_child_fds(in, out);
466 : : }
467 [ # # ]: : catch (runtime_error&)
468 : : {
469 [ # # ]: : finish();
470 : : throw;
471 [ + - ]: 4 : }
472 : 4 : }
473 : :
474 : 7 : virtual int finish()
475 : : {
476 : 7 : int rc = stapsh::finish();
477 [ + + ]: 7 : if (child <= 0)
478 : 3 : return rc;
479 : :
480 : 4 : int rc2 = stap_waitpid(s->verbose, child);
481 : 4 : child = 0;
482 [ - + ]: 7 : return rc ?: rc2;
483 : : }
484 : :
485 : : public:
486 : : friend class remote;
487 : :
488 [ + - ][ - + ]: 8 : virtual ~direct_stapsh() { finish(); }
489 : : };
490 : :
491 : :
492 : : // Connect to an existing stapsh on a unix socket.
493 : : class unix_stapsh : public stapsh {
494 : : private:
495 : :
496 : 1 : unix_stapsh(systemtap_session& s, const uri_decoder& ud)
497 : 1 : : stapsh(s)
498 : : {
499 : : sockaddr_un server;
500 : 1 : server.sun_family = AF_UNIX;
501 [ + - ][ - + ]: 1 : if (ud.path.empty())
502 [ # # ][ # # ]: 0 : throw runtime_error(_("unix target requires a /path"));
503 [ + - ][ - + ]: 1 : if (ud.path.size() > sizeof(server.sun_path) - 1)
504 [ # # ][ # # ]: 0 : throw runtime_error(_("unix target /path is too long"));
505 [ + - ]: 1 : strcpy(server.sun_path, ud.path.c_str());
506 : :
507 [ - + ]: 1 : if (ud.has_authority)
508 [ # # ][ # # ]: 0 : throw runtime_error(_("unix target doesn't support a hostname"));
509 [ - + ]: 1 : if (ud.has_query)
510 [ # # ][ # # ]: 0 : throw runtime_error(_("unix target URI doesn't support a ?query"));
511 [ - + ]: 1 : if (ud.has_fragment)
512 [ # # ][ # # ]: 0 : throw runtime_error(_("unix target URI doesn't support a #fragment"));
513 : :
514 : 1 : int fd = socket(AF_UNIX, SOCK_STREAM, 0);
515 [ - + ]: 1 : if (fd <= 0)
516 [ # # ][ # # ]: 0 : throw runtime_error(_("error opening a socket"));
517 : :
518 [ + - ][ + - ]: 1 : if (connect(fd, (struct sockaddr *)&server, SUN_LEN(&server)) < 0)
519 : : {
520 : 1 : const char *msg = strerror(errno);
521 [ + - ]: 1 : ::close(fd);
522 [ + - ][ + - ]: 1 : throw runtime_error(_F("error connecting to socket: %s", msg));
523 : : }
524 : :
525 : : // Try to dup it, so class stapsh can have truly separate fds for its
526 : : // fdopen handles. If it fails for some reason, it can still work with
527 : : // just one though.
528 : 0 : int fd2 = dup(fd);
529 [ # # ]: 0 : if (fd2 < 0)
530 : 0 : fd2 = fd;
531 : :
532 : : try
533 : : {
534 [ # # ]: 0 : set_child_fds(fd, fd2);
535 : : }
536 [ # # ]: : catch (runtime_error&)
537 : : {
538 [ # # ]: : finish();
539 [ # # ]: : ::close(fd);
540 [ # # ]: : ::close(fd2);
541 : 1 : throw;
542 : : }
543 : 1 : }
544 : :
545 : : public:
546 : : friend class remote;
547 : :
548 [ # # ][ # # ]: 0 : virtual ~unix_stapsh() { finish(); }
549 : : };
550 : :
551 : :
552 : : // stapsh-based ssh_remote
553 : : class ssh_remote : public stapsh {
554 : : private:
555 : : pid_t child;
556 : :
557 : 0 : ssh_remote(systemtap_session& s): stapsh(s), child(0) {}
558 : :
559 : 0 : int connect(const string& host, const string& port)
560 : : {
561 : 0 : int rc = 0;
562 : : int in, out;
563 [ # # ]: 0 : vector<string> cmd;
564 [ # # ][ # # ]: 0 : cmd.push_back("ssh");
[ # # ]
565 [ # # ][ # # ]: 0 : cmd.push_back("-q");
[ # # ]
566 [ # # ]: 0 : cmd.push_back(host);
567 [ # # ][ # # ]: 0 : if (!port.empty())
568 : : {
569 [ # # ][ # # ]: 0 : cmd.push_back("-p");
[ # # ]
570 [ # # ]: 0 : cmd.push_back(port);
571 : : }
572 : :
573 : : // This is crafted so that we get a silent failure with status 127 if
574 : : // the command is not found. The combination of -P and $cmd ensures
575 : : // that we pull the command out of the PATH, not aliases or such.
576 [ # # ]: 0 : string stapsh_cmd = "cmd=`type -P stapsh || exit 127` && exec \"$cmd\"";
577 [ # # ]: 0 : if (s->perpass_verbose[4] > 1)
578 [ # # ]: 0 : stapsh_cmd.append(" -v");
579 [ # # ]: 0 : if (s->perpass_verbose[4] > 2)
580 [ # # ]: 0 : stapsh_cmd.append(" -v");
581 : : // NB: We need to explicitly choose bash, as $SHELL may be weird...
582 [ # # ][ # # ]: 0 : cmd.push_back("/bin/bash -c '" + stapsh_cmd + "'");
[ # # ][ # # ]
[ # # ]
583 : :
584 : : // mask signals while we spawn, so we can manually send even tty
585 : : // signals *through* ssh rather than to ssh itself
586 : : {
587 : 0 : stap_sigmasker masked;
588 [ # # ]: 0 : child = stap_spawn_piped(s->verbose, cmd, &in, &out);
589 : : }
590 : :
591 [ # # ]: 0 : if (child <= 0)
592 [ # # ][ # # ]: 0 : throw runtime_error(_("error launching stapsh"));
593 : :
594 : : try
595 : : {
596 [ # # ]: 0 : set_child_fds(in, out);
597 : : }
598 [ # # ]: : catch (runtime_error&)
599 : : {
600 [ # # ]: : rc = finish();
601 : :
602 : : // ssh itself signals errors with 255
603 [ # # ]: : if (rc == 255)
604 [ # # # # ]: : throw runtime_error(_("error establishing ssh connection"));
605 : :
606 : : // If rc == 127, that's command-not-found, so we let ::create()
607 : : // try again in legacy mode. But only do this if there's a single
608 : : // remote, as the old code didn't handle ttys well with multiple
609 : : // remotes. Otherwise, throw up again. *barf*
610 [ # # # # : : if (rc != 127 || s->remote_uris.size() > 1)
# # ]
611 : : throw;
612 : : }
613 : :
614 [ # # ][ # # ]: 0 : return rc;
615 : : }
616 : :
617 : 0 : int finish()
618 : : {
619 : 0 : int rc = stapsh::finish();
620 [ # # ]: 0 : if (child <= 0)
621 : 0 : return rc;
622 : :
623 : 0 : int rc2 = stap_waitpid(s->verbose, child);
624 : 0 : child = 0;
625 [ # # ]: 0 : return rc ?: rc2;
626 : : }
627 : :
628 : : public:
629 : :
630 : : static remote* create(systemtap_session& s, const string& host);
631 : : static remote* create(systemtap_session& s, const uri_decoder& ud);
632 : :
633 [ # # ][ # # ]: 0 : virtual ~ssh_remote() { finish(); }
634 : : };
635 : :
636 : :
637 : : // ssh connection without stapsh, for legacy stap installations
638 : : // NB: ssh commands use a tty (-t) so signals are passed along to the remote.
639 : : // It does this by putting the local tty in raw mode, so it only works for tty
640 : : // signals, and only for a single remote at a time.
641 : : class ssh_legacy_remote : public remote {
642 : : private:
643 : : vector<string> ssh_args, scp_args;
644 : : string ssh_control;
645 : : string host, port, tmpdir;
646 : : pid_t child;
647 : :
648 : 0 : ssh_legacy_remote(systemtap_session& s, const string& host, const string& port)
649 [ # # ][ # # ]: 0 : : remote(s), host(host), port(port), child(0)
[ # # ][ # # ]
[ # # ][ # # ]
650 : : {
651 [ # # ]: 0 : open_control_master();
652 : : try
653 : : {
654 [ # # ]: 0 : get_uname();
655 : : }
656 [ # # ]: : catch (runtime_error&)
657 : : {
658 [ # # ]: : close_control_master();
659 : : throw;
660 : : }
661 : 0 : }
662 : :
663 : 0 : void open_control_master()
664 : : {
665 : : static unsigned index = 0;
666 : :
667 [ # # ][ # # ]: 0 : if (s->tmpdir.empty()) // sanity check, shouldn't happen
668 [ # # ][ # # ]: 0 : throw runtime_error(_("No tmpdir available for ssh control master"));
669 : :
670 [ # # ][ # # ]: 0 : ssh_control = s->tmpdir + "/ssh_remote_control_" + lex_cast(++index);
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ]
671 : :
672 [ # # ]: 0 : scp_args.clear();
673 [ # # ][ # # ]: 0 : scp_args.push_back("scp");
[ # # ]
674 [ # # ][ # # ]: 0 : scp_args.push_back("-q");
[ # # ]
675 [ # # ][ # # ]: 0 : scp_args.push_back("-o");
[ # # ]
676 [ # # ][ # # ]: 0 : scp_args.push_back("ControlPath=" + ssh_control);
[ # # ]
677 : :
678 [ # # ]: 0 : ssh_args = scp_args;
679 [ # # ]: 0 : ssh_args[0] = "ssh";
680 [ # # ]: 0 : ssh_args.push_back(host);
681 : :
682 [ # # ][ # # ]: 0 : if (!port.empty())
683 : : {
684 [ # # ][ # # ]: 0 : scp_args.push_back("-P");
[ # # ]
685 [ # # ]: 0 : scp_args.push_back(port);
686 [ # # ][ # # ]: 0 : ssh_args.push_back("-p");
[ # # ]
687 [ # # ]: 0 : ssh_args.push_back(port);
688 : : }
689 : :
690 : : // NB: ssh -f will stay in the foreground until authentication is
691 : : // complete and the control socket is created, so we know it's ready to
692 : : // go when stap_system returns.
693 [ # # ]: 0 : vector<string> cmd = ssh_args;
694 [ # # ][ # # ]: 0 : cmd.push_back("-f");
[ # # ]
695 [ # # ][ # # ]: 0 : cmd.push_back("-N");
[ # # ]
696 [ # # ][ # # ]: 0 : cmd.push_back("-M");
[ # # ]
697 [ # # ]: 0 : int rc = stap_system(s->verbose, cmd);
698 [ # # ]: 0 : if (rc != 0)
699 : 0 : throw runtime_error(_F("failed to create an ssh control master for %s : rc= %d",
700 [ # # ][ # # ]: 0 : host.c_str(), rc));
[ # # ]
701 : :
702 [ # # ]: 0 : if (s->verbose>1)
703 [ # # ][ # # ]: 0 : clog << _F("Created ssh control master at %s",
[ # # ][ # # ]
[ # # ][ # # ]
704 [ # # ][ # # ]: 0 : lex_cast_qstring(ssh_control).c_str()) << endl;
705 : 0 : }
706 : :
707 : 0 : void close_control_master()
708 : : {
709 [ # # ][ # # ]: 0 : if (ssh_control.empty())
710 : 0 : return;
711 : :
712 [ # # ]: 0 : vector<string> cmd = ssh_args;
713 [ # # ][ # # ]: 0 : cmd.push_back("-O");
[ # # ]
714 [ # # ][ # # ]: 0 : cmd.push_back("exit");
[ # # ]
715 [ # # ]: 0 : int rc = stap_system(s->verbose, cmd, true, true);
716 [ # # ]: 0 : if (rc != 0)
717 [ # # ][ # # ]: 0 : cerr << _F("failed to stop the ssh control master for %s : rc=%d",
[ # # ][ # # ]
718 [ # # ]: 0 : host.c_str(), rc) << endl;
719 : :
720 [ # # ]: 0 : ssh_control.clear();
721 [ # # ]: 0 : scp_args.clear();
722 [ # # ][ # # ]: 0 : ssh_args.clear();
723 : : }
724 : :
725 : 0 : void get_uname()
726 : : {
727 [ # # ]: 0 : ostringstream out;
728 [ # # ]: 0 : vector<string> uname;
729 [ # # ]: 0 : vector<string> cmd = ssh_args;
730 [ # # ][ # # ]: 0 : cmd.push_back("-t");
[ # # ]
731 [ # # ][ # # ]: 0 : cmd.push_back("uname -rm");
[ # # ]
732 [ # # ]: 0 : int rc = stap_system_read(s->verbose, cmd, out);
733 [ # # ]: 0 : if (rc == 0)
734 [ # # ][ # # ]: 0 : tokenize(out.str(), uname, " \t\r\n");
[ # # ][ # # ]
[ # # ]
735 [ # # ]: 0 : if (uname.size() != 2)
736 [ # # ][ # # ]: 0 : throw runtime_error(_F("failed to get uname from %s : rc= %d", host.c_str(), rc));
[ # # ]
737 : 0 : const string& release = uname[0];
738 : 0 : const string& arch = uname[1];
739 : : // XXX need to deal with command-line vs. implied arch/release
740 [ # # ][ # # ]: 0 : this->s = s->clone(arch, release);
[ # # ][ # # ]
741 : 0 : }
742 : :
743 : 0 : int start()
744 : : {
745 : : int rc;
746 [ # # ][ # # ]: 0 : string localmodule = s->tmpdir + "/" + s->module_name + ".ko";
[ # # ][ # # ]
[ # # ]
747 [ # # ]: 0 : string tmpmodule;
748 : :
749 : : // Make a remote tempdir.
750 : : {
751 [ # # ]: 0 : ostringstream out;
752 [ # # ]: 0 : vector<string> vout;
753 [ # # ]: 0 : vector<string> cmd = ssh_args;
754 [ # # ][ # # ]: 0 : cmd.push_back("-t");
[ # # ]
755 [ # # ][ # # ]: 0 : cmd.push_back("mktemp -d -t stapXXXXXX");
[ # # ]
756 [ # # ]: 0 : rc = stap_system_read(s->verbose, cmd, out);
757 [ # # ]: 0 : if (rc == 0)
758 [ # # ][ # # ]: 0 : tokenize(out.str(), vout, "\r\n");
[ # # ][ # # ]
[ # # ]
759 [ # # ]: 0 : if (vout.size() != 1)
760 : : {
761 [ # # ][ # # ]: 0 : cerr << _F("failed to make a tempdir on %s : rc=%d",
[ # # ][ # # ]
762 [ # # ]: 0 : host.c_str(), rc) << endl;
763 : 0 : return -1;
764 : : }
765 [ # # ]: 0 : tmpdir = vout[0];
766 [ # # ][ # # ]: 0 : tmpmodule = tmpdir + "/" + s->module_name + ".ko";
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ]
767 : : }
768 : :
769 : : // Transfer the module.
770 [ # # ]: 0 : if (rc == 0)
771 : : {
772 [ # # ]: 0 : vector<string> cmd = scp_args;
773 [ # # ]: 0 : cmd.push_back(localmodule);
774 [ # # ][ # # ]: 0 : cmd.push_back(host + ":" + tmpmodule);
[ # # ][ # # ]
[ # # ]
775 [ # # ]: 0 : rc = stap_system(s->verbose, cmd);
776 [ # # ]: 0 : if (rc != 0)
777 [ # # ][ # # ]: 0 : cerr << _F("failed to copy the module to %s : rc=%d",
[ # # ][ # # ]
778 [ # # ][ # # ]: 0 : host.c_str(), rc) << endl;
779 : : }
780 : :
781 : : // Transfer the module signature.
782 [ # # ][ # # ]: 0 : if (rc == 0 && file_exists(localmodule + ".sgn"))
[ # # ][ # # ]
[ # # ][ # # ]
[ # # # # ]
783 : : {
784 [ # # ]: 0 : vector<string> cmd = scp_args;
785 [ # # ][ # # ]: 0 : cmd.push_back(localmodule + ".sgn");
[ # # ]
786 [ # # ][ # # ]: 0 : cmd.push_back(host + ":" + tmpmodule + ".sgn");
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ]
787 [ # # ]: 0 : rc = stap_system(s->verbose, cmd);
788 [ # # ]: 0 : if (rc != 0)
789 [ # # ][ # # ]: 0 : cerr << _F("failed to copy the module signature to %s : rc=%d",
[ # # ][ # # ]
790 [ # # ][ # # ]: 0 : host.c_str(), rc) << endl;
791 : : }
792 : :
793 : : // What about transfering uprobes.ko? In this ssh "legacy" mode, we
794 : : // don't the remote systemtap version, but -uPATH wasn't added until
795 : : // 1.4. Rather than risking a getopt error, we'll just assume that
796 : : // this isn't supported. The remote will just have to provide its own
797 : : // uprobes.ko in SYSTEMTAP_RUNTIME or already loaded.
798 : :
799 : : // Run the module on the remote.
800 [ # # ]: 0 : if (rc == 0) {
801 [ # # ]: 0 : vector<string> cmd = ssh_args;
802 [ # # ][ # # ]: 0 : cmd.push_back("-t");
[ # # ]
803 : : // We don't know the actual version, but all <=1.3 are approx equal.
804 [ # # ][ # # ]: 0 : vector<string> staprun_cmd = make_run_command(*s, tmpdir, "1.3");
[ # # ]
805 [ # # ]: 0 : staprun_cmd[0] = "staprun"; // NB: The remote decides its own path
806 : : // NB: PR13354: we assume legacy installations don't have
807 : : // staprun -r support, so we ignore staprun_r_arg.
808 [ # # ][ # # ]: 0 : cmd.push_back(cmdstr_join(staprun_cmd));
[ # # ]
809 [ # # ]: 0 : pid_t pid = stap_spawn(s->verbose, cmd);
810 [ # # ]: 0 : if (pid > 0)
811 : 0 : child = pid;
812 : : else
813 : : {
814 [ # # ][ # # ]: 0 : cerr << _F("failed to run the module on %s : ret=%d",
[ # # ][ # # ]
815 [ # # ]: 0 : host.c_str(), pid) << endl;
816 : 0 : rc = -1;
817 [ # # ][ # # ]: 0 : }
818 : : }
819 : :
820 [ # # ][ # # ]: 0 : return rc;
821 : : }
822 : :
823 : 0 : int finish()
824 : : {
825 : 0 : int rc = 0;
826 : :
827 [ # # ]: 0 : if (child > 0)
828 : : {
829 : 0 : rc = stap_waitpid(s->verbose, child);
830 : 0 : child = 0;
831 : : }
832 : :
833 [ # # ]: 0 : if (!tmpdir.empty())
834 : : {
835 : : // Remove the tempdir.
836 : : // XXX need to make sure this runs even with e.g. CTRL-C exits
837 [ # # ]: 0 : vector<string> cmd = ssh_args;
838 [ # # ][ # # ]: 0 : cmd.push_back("-t");
[ # # ]
839 [ # # ][ # # ]: 0 : cmd.push_back("rm -r " + cmdstr_quoted(tmpdir));
[ # # ][ # # ]
[ # # ]
840 [ # # ]: 0 : int rc2 = stap_system(s->verbose, cmd);
841 [ # # ]: 0 : if (rc2 != 0)
842 [ # # ][ # # ]: 0 : cerr << _F("failed to delete the tempdir on %s : rc=%d",
[ # # ][ # # ]
843 [ # # ]: 0 : host.c_str(), rc2) << endl;
844 [ # # ]: 0 : if (rc == 0)
845 : 0 : rc = rc2;
846 [ # # ][ # # ]: 0 : tmpdir.clear();
847 : : }
848 : :
849 : 0 : close_control_master();
850 : :
851 : 0 : return rc;
852 : : }
853 : :
854 : : public:
855 : : friend class ssh_remote;
856 : :
857 : 0 : virtual ~ssh_legacy_remote()
858 : 0 : {
859 [ # # ]: 0 : close_control_master();
860 [ # # ][ # # ]: 0 : }
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ]
861 : : };
862 : :
863 : :
864 : : // Try to establish a stapsh connection to the remote, but fallback
865 : : // to the older mechanism if the command is not found.
866 : : remote*
867 : 0 : ssh_remote::create(systemtap_session& s, const string& target)
868 : : {
869 [ # # ][ # # ]: 0 : string port, host = target;
870 [ # # ]: 0 : size_t i = host.find(':');
871 [ # # ]: 0 : if (i != string::npos)
872 : : {
873 [ # # ][ # # ]: 0 : port = host.substr(i + 1);
[ # # ]
874 [ # # ]: 0 : host.erase(i);
875 : : }
876 : :
877 [ # # ][ # # ]: 0 : auto_ptr<ssh_remote> p (new ssh_remote(s));
878 [ # # ]: 0 : int rc = p->connect(host, port);
879 [ # # ]: 0 : if (rc == 0)
880 : 0 : return p.release();
881 [ # # ]: 0 : else if (rc == 127) // stapsh command not found
882 [ # # ][ # # ]: 0 : return new ssh_legacy_remote(s, host, port); // try legacy instead
883 [ # # ][ # # ]: 0 : return NULL;
[ # # ]
884 : : }
885 : :
886 : : remote*
887 : 0 : ssh_remote::create(systemtap_session& s, const uri_decoder& ud)
888 : : {
889 [ # # ][ # # ]: 0 : if (!ud.has_authority || ud.authority.empty())
[ # # ]
890 [ # # ][ # # ]: 0 : throw runtime_error(_("ssh target requires a hostname"));
891 [ # # ][ # # ]: 0 : if (!ud.path.empty() && ud.path != "/")
[ # # ]
892 [ # # ][ # # ]: 0 : throw runtime_error(_("ssh target URI doesn't support a /path"));
893 [ # # ]: 0 : if (ud.has_query)
894 [ # # ][ # # ]: 0 : throw runtime_error(_("ssh target URI doesn't support a ?query"));
895 [ # # ]: 0 : if (ud.has_fragment)
896 [ # # ][ # # ]: 0 : throw runtime_error(_("ssh target URI doesn't support a #fragment"));
897 : :
898 : 0 : return create(s, ud.authority);
899 : : }
900 : :
901 : :
902 : : remote*
903 : 2214 : remote::create(systemtap_session& s, const string& uri, int idx)
904 : : {
905 : 2214 : remote *it = 0;
906 : : try
907 : : {
908 [ + - ][ + - ]: 2214 : if (uri.find(':') != string::npos)
909 : : {
910 [ + - ]: 2214 : const uri_decoder ud(uri);
911 : :
912 : : // An ssh "host:port" is ambiguous with a URI "scheme:path".
913 : : // So if it looks like a number, just assume ssh.
914 [ + - ][ + - ]: 4429 : if (!ud.has_authority && !ud.has_query &&
[ + - ][ + + ]
[ - + ][ - + ]
915 [ + - ]: 2214 : !ud.has_fragment && !ud.path.empty() &&
916 [ + - ]: 1 : ud.path.find_first_not_of("1234567890") == string::npos)
917 [ # # ]: 0 : it = ssh_remote::create(s, uri);
918 [ + - ][ + + ]: 2214 : else if (ud.scheme == "direct")
919 [ + - ][ + - ]: 2209 : it = new direct(s);
920 [ + - ][ + + ]: 5 : else if (ud.scheme == "stapsh")
921 [ + - ][ + - ]: 4 : it = new direct_stapsh(s);
922 [ + - ][ + - ]: 1 : else if (ud.scheme == "unix")
923 [ + - ][ - + ]: 1 : it = new unix_stapsh(s, ud);
924 [ # # ][ # # ]: 0 : else if (ud.scheme == "ssh")
925 [ # # ]: 0 : it = ssh_remote::create(s, ud);
926 : : else
927 : 0 : throw runtime_error(_F("unrecognized URI scheme '%s' in remote: %s",
928 [ # # ][ # # ]: 2214 : ud.scheme.c_str(), uri.c_str()));
[ # # ][ # # ]
[ + - ]
929 : : }
930 : : else
931 : : // XXX assuming everything else is ssh for now...
932 [ # # ]: 0 : it = ssh_remote::create(s, uri);
933 : : }
934 [ - + ]: 2 : catch (std::runtime_error& e)
935 : : {
936 [ - + ][ - + ]: 1 : cerr << e.what() << " on remote '" << uri << "'" << endl;
[ - + ][ - + ]
[ - + ]
937 : 1 : it = 0;
938 : : }
939 : :
940 [ + + ][ + + ]: 2214 : if (it && idx >= 0) // PR13354: remote metadata for staprun -r IDX:URI
941 : : {
942 [ + - ]: 7 : stringstream r_arg;
943 [ + - ][ + - ]: 7 : r_arg << idx << ":" << uri;
[ + - ]
944 [ + - ][ + - ]: 7 : it->staprun_r_arg = r_arg.str();
[ + - ][ + - ]
945 : : }
946 : :
947 : 2214 : return it;
948 : : }
949 : :
950 : : int
951 : 475 : remote::run(const vector<remote*>& remotes)
952 : : {
953 : : // NB: the first failure "wins"
954 : 475 : int ret = 0, rc = 0;
955 : :
956 [ + + ][ + - ]: 951 : for (unsigned i = 0; i < remotes.size() && !pending_interrupts; ++i)
[ + + ]
957 : : {
958 : 476 : remote *r = remotes[i];
959 : 476 : r->s->verbose = r->s->perpass_verbose[4];
960 [ - + ]: 476 : if (r->s->use_remote_prefix)
961 [ # # ][ # # ]: 0 : r->prefix = lex_cast(i) + ": ";
[ # # ][ # # ]
[ # # ]
962 [ + - ]: 476 : rc = r->prepare();
963 [ - + ]: 476 : if (rc)
964 : 0 : return rc;
965 : : }
966 : :
967 [ + + ][ + - ]: 951 : for (unsigned i = 0; i < remotes.size() && !pending_interrupts; ++i)
[ + + ]
968 : : {
969 : 476 : rc = remotes[i]->start();
970 [ + - ]: 476 : if (!ret)
971 : 476 : ret = rc;
972 : : }
973 : :
974 : : // mask signals while we're preparing to poll
975 : : {
976 : 475 : stap_sigmasker masked;
977 : :
978 : : // polling loop for remotes that have fds to watch
979 : 16 : for (;;)
980 : : {
981 [ + - ]: 491 : vector<pollfd> fds;
982 [ + + ]: 988 : for (unsigned i = 0; i < remotes.size(); ++i)
983 [ + - ]: 497 : remotes[i]->prepare_poll (fds);
984 [ + - ][ + + ]: 491 : if (fds.empty())
985 : : break;
986 : :
987 [ + - ]: 16 : rc = ppoll (&fds[0], fds.size(), NULL, &masked.old);
988 [ + + ][ - + ]: 16 : if (rc < 0 && errno != EINTR)
989 : : break;
990 : :
991 [ + + ]: 37 : for (unsigned i = 0; i < remotes.size(); ++i)
992 [ + - ]: 21 : remotes[i]->handle_poll (fds);
993 [ + - ][ + + ]: 491 : }
994 : : }
995 : :
996 [ + + ]: 951 : for (unsigned i = 0; i < remotes.size(); ++i)
997 : : {
998 : 476 : rc = remotes[i]->finish();
999 [ + - ]: 476 : if (!ret)
1000 : 476 : ret = rc;
1001 : : }
1002 : :
1003 : 475 : return ret;
1004 [ + - ][ + - ]: 7242 : }
1005 : :
1006 : :
1007 : : /* vim: set sw=2 ts=8 cino=>4,n-2,{2,^-2,t0,(0,u0,w1,M1 : */
|