]>
Commit | Line | Data |
---|---|---|
daa75206 JS |
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 | ||
e04a4b41 JS |
9 | #include "config.h" |
10 | ||
daa75206 | 11 | extern "C" { |
e96f2257 | 12 | #include <fcntl.h> |
e04a4b41 | 13 | #include <poll.h> |
e96f2257 JS |
14 | #include <sys/types.h> |
15 | #include <sys/stat.h> | |
16 | #include <unistd.h> | |
daa75206 JS |
17 | } |
18 | ||
e96f2257 | 19 | #include <cstdio> |
bf9c0b28 | 20 | #include <iomanip> |
8d1a21b3 | 21 | #include <memory> |
daa75206 JS |
22 | #include <stdexcept> |
23 | #include <sstream> | |
24 | #include <string> | |
25 | #include <vector> | |
26 | ||
27 | #include "buildrun.h" | |
28 | #include "remote.h" | |
29 | #include "util.h" | |
30 | ||
31 | using namespace std; | |
32 | ||
91fa953d JS |
33 | // Decode URIs as per RFC 3986, though not bothering to be strict |
34 | class uri_decoder { | |
35 | public: | |
36 | const string uri; | |
37 | string scheme, authority, path, query, fragment; | |
38 | bool has_authority, has_query, has_fragment; | |
39 | ||
40 | uri_decoder(const string& uri): | |
41 | uri(uri), has_authority(false), has_query(false), has_fragment(false) | |
42 | { | |
43 | const string re = | |
44 | "^([^:]+):(//[^/?#]*)?([^?#]*)(\\?[^#]*)?(#.*)?$"; | |
45 | ||
46 | vector<string> matches; | |
47 | if (regexp_match(uri, re, matches) != 0) | |
58a834b1 | 48 | throw runtime_error(_F("string doesn't appear to be a URI: %s", uri.c_str())); |
91fa953d JS |
49 | |
50 | scheme = matches[1]; | |
51 | ||
52 | if (!matches[2].empty()) | |
53 | { | |
54 | has_authority = true; | |
55 | authority = matches[2].substr(2); | |
56 | } | |
57 | ||
58 | path = matches[3]; | |
59 | ||
60 | if (!matches[4].empty()) | |
61 | { | |
62 | has_query = true; | |
63 | query = matches[4].substr(1); | |
64 | } | |
65 | ||
66 | if (!matches[5].empty()) | |
67 | { | |
68 | has_fragment = true; | |
69 | fragment = matches[5].substr(1); | |
70 | } | |
71 | } | |
72 | }; | |
73 | ||
74 | ||
daa75206 JS |
75 | // loopback target for running locally |
76 | class direct : public remote { | |
ebff2ed0 JS |
77 | private: |
78 | pid_t child; | |
79 | direct(systemtap_session& s): remote(s), child(0) {} | |
daa75206 | 80 | |
ebff2ed0 JS |
81 | int start() |
82 | { | |
5eea6ed1 | 83 | pid_t pid = stap_spawn (s->verbose, make_run_command (*s)); |
4c156a0e JS |
84 | if (pid <= 0) |
85 | return 1; | |
86 | child = pid; | |
87 | return 0; | |
ebff2ed0 JS |
88 | } |
89 | ||
90 | int finish() | |
91 | { | |
4c156a0e JS |
92 | if (child <= 0) |
93 | return 1; | |
ebff2ed0 | 94 | |
4c156a0e JS |
95 | int ret = stap_waitpid(s->verbose, child); |
96 | child = 0; | |
97 | return ret; | |
ebff2ed0 | 98 | } |
4f244435 JS |
99 | |
100 | public: | |
101 | friend class remote; | |
102 | ||
103 | virtual ~direct() {} | |
daa75206 JS |
104 | }; |
105 | ||
e96f2257 JS |
106 | |
107 | class stapsh : public remote { | |
108 | private: | |
109 | int interrupts_sent; | |
110 | int fdin, fdout; | |
111 | FILE *IN, *OUT; | |
112 | ||
113 | virtual void prepare_poll(vector<pollfd>& fds) | |
114 | { | |
115 | if (fdout >= 0 && OUT) | |
116 | { | |
117 | pollfd p = { fdout, POLLIN }; | |
118 | fds.push_back(p); | |
119 | } | |
120 | ||
121 | // need to send a signal? | |
122 | if (fdin >= 0 && IN && interrupts_sent < pending_interrupts) | |
123 | { | |
124 | pollfd p = { fdin, POLLOUT }; | |
125 | fds.push_back(p); | |
126 | } | |
127 | } | |
128 | ||
129 | virtual void handle_poll(vector<pollfd>& fds) | |
130 | { | |
131 | for (unsigned i=0; i < fds.size(); ++i) | |
132 | if (fds[i].revents) | |
133 | { | |
134 | if (fdout >= 0 && OUT && fds[i].fd == fdout) | |
135 | { | |
136 | if (fds[i].revents & POLLIN) | |
137 | { | |
e96f2257 | 138 | char buf[4096]; |
756cfd49 | 139 | if (!prefix.empty()) |
e96f2257 | 140 | { |
756cfd49 JS |
141 | // If we have a line prefix, then read lines one at a |
142 | // time and copy out with the prefix. | |
143 | errno = 0; | |
144 | while (fgets(buf, sizeof(buf), OUT)) | |
145 | cout << prefix << buf; | |
146 | if (errno == EAGAIN) | |
147 | continue; | |
148 | } | |
149 | else | |
150 | { | |
151 | // Otherwise read an entire block of data at once. | |
152 | size_t rc = fread(buf, 1, sizeof(buf), OUT); | |
153 | if (rc > 0) | |
154 | { | |
155 | // NB: The buf could contain binary data, | |
156 | // including \0, so write as a block instead of | |
157 | // the usual <<string. | |
158 | cout.write(buf, rc); | |
159 | continue; | |
160 | } | |
e96f2257 JS |
161 | } |
162 | } | |
163 | close(); | |
164 | } | |
165 | ||
166 | // need to send a signal? | |
167 | if (fdin >= 0 && IN && fds[i].fd == fdin && | |
168 | interrupts_sent < pending_interrupts) | |
169 | { | |
170 | if (fds[i].revents & POLLOUT) | |
171 | { | |
7185074b | 172 | if (send_command("quit\n") == 0) |
e96f2257 JS |
173 | { |
174 | ++interrupts_sent; | |
175 | continue; | |
176 | } | |
177 | } | |
178 | close(); | |
179 | } | |
180 | } | |
181 | } | |
182 | ||
35df6b43 JS |
183 | string get_reply() |
184 | { | |
185 | char reply[4096]; | |
186 | if (!fgets(reply, sizeof(reply), OUT)) | |
187 | reply[0] = '\0'; | |
188 | return reply; | |
189 | } | |
190 | ||
e96f2257 JS |
191 | int send_command(const string& cmd) |
192 | { | |
193 | if (!IN) | |
194 | return 2; | |
195 | if (fputs(cmd.c_str(), IN) < 0 || | |
196 | fflush(IN) != 0) | |
197 | return 1; | |
198 | return 0; | |
199 | } | |
200 | ||
201 | int send_file(const string& filename, const string& dest) | |
202 | { | |
203 | int rc = 0; | |
204 | FILE* f = fopen(filename.c_str(), "r"); | |
205 | if (!f) | |
206 | return 1; | |
207 | ||
208 | struct stat fs; | |
209 | rc = fstat(fileno(f), &fs); | |
210 | if (!rc) | |
211 | { | |
212 | ostringstream cmd; | |
213 | cmd << "file " << fs.st_size << " " << dest << "\n"; | |
214 | rc = send_command(cmd.str()); | |
215 | } | |
216 | ||
217 | off_t i = 0; | |
218 | while (!rc && i < fs.st_size) | |
219 | { | |
220 | char buf[4096]; | |
221 | size_t r = sizeof(buf); | |
222 | if (fs.st_size - i < (off_t)r) | |
223 | r = fs.st_size - i; | |
224 | r = fread(buf, 1, r, f); | |
225 | if (r == 0) | |
226 | rc = 1; | |
227 | else | |
228 | { | |
229 | size_t w = fwrite(buf, 1, r, IN); | |
230 | if (w != r) | |
231 | rc = 1; | |
232 | else | |
233 | i += w; | |
234 | } | |
235 | } | |
236 | if (!rc) | |
237 | rc = fflush(IN); | |
238 | ||
239 | fclose(f); | |
35df6b43 JS |
240 | |
241 | if (!rc) | |
242 | { | |
243 | string reply = get_reply(); | |
244 | if (reply != "OK\n") | |
245 | { | |
246 | rc = 1; | |
247 | if (s->verbose > 1) | |
248 | { | |
249 | if (reply.empty()) | |
f3f6443c | 250 | clog << _("stapsh file ERROR: no reply") << endl; |
35df6b43 | 251 | else |
f3f6443c | 252 | clog << _F("stapsh file replied %s", reply.c_str()); |
35df6b43 JS |
253 | } |
254 | } | |
255 | } | |
256 | ||
e96f2257 JS |
257 | return rc; |
258 | } | |
259 | ||
bf9c0b28 | 260 | static string qpencode(const string& str) |
e96f2257 | 261 | { |
bf9c0b28 JS |
262 | ostringstream o; |
263 | o << setfill('0') << hex; | |
264 | for (const char* s = str.c_str(); *s; ++s) | |
265 | if (*s >= 33 && *s <= 126 && *s != 61) | |
266 | o << *s; | |
267 | else | |
268 | o << '=' << setw(2) << (unsigned)(unsigned char) *s; | |
269 | return o.str(); | |
e96f2257 JS |
270 | } |
271 | ||
272 | protected: | |
273 | stapsh(systemtap_session& s) | |
274 | : remote(s), interrupts_sent(0), | |
275 | fdin(-1), fdout(-1), IN(0), OUT(0) | |
276 | {} | |
277 | ||
3fe776ae | 278 | virtual int prepare() |
e96f2257 JS |
279 | { |
280 | int rc = 0; | |
281 | ||
282 | string localmodule = s->tmpdir + "/" + s->module_name + ".ko"; | |
283 | string remotemodule = s->module_name + ".ko"; | |
284 | if ((rc = send_file(localmodule, remotemodule))) | |
285 | return rc; | |
286 | ||
599c80df JS |
287 | if (file_exists(localmodule + ".sgn") && |
288 | (rc = send_file(localmodule + ".sgn", remotemodule + ".sgn"))) | |
289 | return rc; | |
290 | ||
291 | if (!s->uprobes_path.empty()) | |
292 | { | |
293 | string remoteuprobes = basename(s->uprobes_path.c_str()); | |
294 | if ((rc = send_file(s->uprobes_path, remoteuprobes))) | |
295 | return rc; | |
296 | ||
297 | if (file_exists(s->uprobes_path + ".sgn") && | |
298 | (rc = send_file(s->uprobes_path + ".sgn", remoteuprobes + ".sgn"))) | |
299 | return rc; | |
300 | } | |
e96f2257 | 301 | |
3fe776ae JS |
302 | return rc; |
303 | } | |
304 | ||
305 | virtual int start() | |
306 | { | |
e96f2257 JS |
307 | // Send the staprun args |
308 | // NB: The remote is left to decide its own staprun path | |
bf9c0b28 | 309 | ostringstream run("run", ios::out | ios::ate); |
3fe776ae | 310 | vector<string> cmd = make_run_command(*s, s->module_name + ".ko"); |
e96f2257 | 311 | for (unsigned i = 1; i < cmd.size(); ++i) |
bf9c0b28 JS |
312 | run << ' ' << qpencode(cmd[i]); |
313 | run << '\n'; | |
e96f2257 | 314 | |
3fe776ae | 315 | int rc = send_command(run.str()); |
e96f2257 | 316 | |
35df6b43 JS |
317 | if (!rc) |
318 | { | |
319 | string reply = get_reply(); | |
320 | if (reply != "OK\n") | |
321 | { | |
322 | rc = 1; | |
323 | if (s->verbose > 1) | |
324 | { | |
325 | if (reply.empty()) | |
f3f6443c | 326 | clog << _("stapsh run ERROR: no reply") << endl; |
35df6b43 | 327 | else |
f3f6443c | 328 | clog << _F("stapsh run replied %s", reply.c_str()); |
35df6b43 JS |
329 | } |
330 | } | |
331 | } | |
332 | ||
e96f2257 JS |
333 | if (!rc) |
334 | { | |
335 | long flags = fcntl(fdout, F_GETFL) | O_NONBLOCK; | |
336 | fcntl(fdout, F_SETFL, flags); | |
337 | } | |
338 | ||
339 | return rc; | |
340 | } | |
341 | ||
342 | void close() | |
343 | { | |
344 | if (IN) fclose(IN); | |
345 | if (OUT) fclose(OUT); | |
346 | IN = OUT = NULL; | |
347 | fdin = fdout = -1; | |
348 | } | |
349 | ||
350 | virtual int finish() | |
351 | { | |
352 | close(); | |
353 | return 0; | |
354 | } | |
355 | ||
356 | void set_child_fds(int in, int out) | |
357 | { | |
358 | if (fdin >= 0 || fdout >= 0 || IN || OUT) | |
8d1a21b3 | 359 | throw runtime_error(_("stapsh file descriptors already set")); |
e96f2257 JS |
360 | |
361 | fdin = in; | |
362 | fdout = out; | |
363 | IN = fdopen(fdin, "w"); | |
364 | OUT = fdopen(fdout, "r"); | |
365 | if (!IN || !OUT) | |
8d1a21b3 | 366 | throw runtime_error(_("invalid file descriptors for stapsh")); |
e96f2257 JS |
367 | |
368 | if (send_command("stap " VERSION "\n")) | |
8d1a21b3 | 369 | throw runtime_error(_("error sending hello to stapsh")); |
e96f2257 | 370 | |
35df6b43 JS |
371 | string reply = get_reply(); |
372 | if (reply.empty()) | |
8d1a21b3 | 373 | throw runtime_error(_("error receiving hello from stapsh")); |
e96f2257 JS |
374 | |
375 | // stapsh VERSION MACHINE RELEASE | |
376 | vector<string> uname; | |
377 | tokenize(reply, uname, " \t\r\n"); | |
378 | if (uname.size() != 4 || uname[0] != "stapsh") | |
f3f6443c | 379 | throw runtime_error(_("failed to get uname from stapsh")); |
e96f2257 JS |
380 | // XXX check VERSION compatibility |
381 | ||
382 | this->s = s->clone(uname[2], uname[3]); | |
383 | } | |
384 | ||
385 | public: | |
386 | virtual ~stapsh() { close(); } | |
387 | }; | |
388 | ||
389 | ||
88e08f53 JS |
390 | // direct_stapsh is meant only for testing, as a way to exercise the stapsh |
391 | // mechanisms without requiring test machines to have actual remote access. | |
e96f2257 JS |
392 | class direct_stapsh : public stapsh { |
393 | private: | |
394 | pid_t child; | |
395 | ||
396 | direct_stapsh(systemtap_session& s) | |
397 | : stapsh(s), child(0) | |
398 | { | |
8d1a21b3 JS |
399 | int in, out; |
400 | vector<string> cmd; | |
401 | cmd.push_back(BINDIR "/stapsh"); | |
402 | if (s.perpass_verbose[4] > 1) | |
403 | cmd.push_back("-v"); | |
404 | if (s.perpass_verbose[4] > 2) | |
405 | cmd.push_back("-v"); | |
406 | ||
88e08f53 JS |
407 | // mask signals while we spawn, so we can simulate manual signals to |
408 | // the "remote" target, as we must for the real ssh_remote case. | |
409 | sigset_t mask, oldmask; | |
410 | sigemptyset (&mask); | |
411 | sigaddset (&mask, SIGHUP); | |
412 | sigaddset (&mask, SIGPIPE); | |
413 | sigaddset (&mask, SIGINT); | |
414 | sigaddset (&mask, SIGTERM); | |
415 | sigprocmask (SIG_BLOCK, &mask, &oldmask); | |
416 | ||
e96f2257 | 417 | child = stap_spawn_piped(s.verbose, cmd, &in, &out); |
8d1a21b3 JS |
418 | |
419 | // back to normal signals | |
420 | sigprocmask (SIG_SETMASK, &oldmask, NULL); | |
421 | ||
e96f2257 | 422 | if (child <= 0) |
8d1a21b3 | 423 | throw runtime_error(_("error launching stapsh")); |
e96f2257 JS |
424 | |
425 | try | |
426 | { | |
427 | set_child_fds(in, out); | |
428 | } | |
429 | catch (runtime_error&) | |
430 | { | |
431 | finish(); | |
432 | throw; | |
433 | } | |
434 | } | |
435 | ||
436 | virtual int finish() | |
437 | { | |
438 | int rc = stapsh::finish(); | |
439 | if (child <= 0) | |
440 | return rc; | |
441 | ||
442 | int rc2 = stap_waitpid(s->verbose, child); | |
443 | child = 0; | |
444 | return rc ?: rc2; | |
445 | } | |
446 | ||
447 | public: | |
448 | friend class remote; | |
449 | ||
450 | virtual ~direct_stapsh() {} | |
451 | }; | |
452 | ||
453 | ||
8d1a21b3 | 454 | // stapsh-based ssh_remote |
e96f2257 JS |
455 | class ssh_remote : public stapsh { |
456 | private: | |
457 | pid_t child; | |
458 | ||
8d1a21b3 | 459 | ssh_remote(systemtap_session& s): stapsh(s), child(0) {} |
e96f2257 | 460 | |
8d1a21b3 | 461 | int connect(const string& host) |
e96f2257 | 462 | { |
8d1a21b3 JS |
463 | int rc = 0; |
464 | int in, out; | |
465 | vector<string> cmd; | |
466 | cmd.push_back("ssh"); | |
467 | cmd.push_back("-q"); | |
468 | cmd.push_back(host); | |
469 | ||
470 | // This is crafted so that we get a silent failure with status 127 if | |
471 | // the command is not found. The combination of -P and $cmd ensures | |
472 | // that we pull the command out of the PATH, not aliases or such. | |
473 | cmd.push_back("cmd=`type -P stapsh || exit 127` && exec \"$cmd\""); | |
474 | if (s->perpass_verbose[4] > 1) | |
475 | cmd.back().append(" -v"); | |
476 | if (s->perpass_verbose[4] > 2) | |
477 | cmd.back().append(" -v"); | |
e96f2257 | 478 | |
e96f2257 JS |
479 | // mask signals while we spawn, so we can manually send even tty |
480 | // signals *through* ssh rather than to ssh itself | |
481 | sigset_t mask, oldmask; | |
482 | sigemptyset (&mask); | |
483 | sigaddset (&mask, SIGHUP); | |
484 | sigaddset (&mask, SIGPIPE); | |
485 | sigaddset (&mask, SIGINT); | |
486 | sigaddset (&mask, SIGTERM); | |
487 | sigprocmask (SIG_BLOCK, &mask, &oldmask); | |
488 | ||
e96f2257 | 489 | child = stap_spawn_piped(s->verbose, cmd, &in, &out); |
8d1a21b3 JS |
490 | |
491 | // back to normal signals | |
492 | sigprocmask (SIG_SETMASK, &oldmask, NULL); | |
493 | ||
e96f2257 | 494 | if (child <= 0) |
8d1a21b3 | 495 | throw runtime_error(_("error launching stapsh")); |
e96f2257 JS |
496 | |
497 | try | |
498 | { | |
499 | set_child_fds(in, out); | |
500 | } | |
501 | catch (runtime_error&) | |
502 | { | |
8d1a21b3 JS |
503 | // If rc == 127, that's command-not-found, so we let ::create() |
504 | // try again in legacy mode. But only do this if there's a single | |
505 | // remote, as the old code didn't handle ttys well with multiple | |
506 | // remotes. Otherwise, throw up again. *barf* | |
507 | rc = finish(); | |
508 | if (rc != 127 || s->remote_uris.size() > 1) | |
509 | throw; | |
e96f2257 | 510 | } |
8d1a21b3 JS |
511 | |
512 | return rc; | |
e96f2257 JS |
513 | } |
514 | ||
515 | int finish() | |
516 | { | |
517 | int rc = stapsh::finish(); | |
518 | if (child <= 0) | |
519 | return rc; | |
520 | ||
521 | int rc2 = stap_waitpid(s->verbose, child); | |
522 | child = 0; | |
523 | return rc ?: rc2; | |
524 | } | |
525 | ||
526 | public: | |
8d1a21b3 JS |
527 | |
528 | static remote* create(systemtap_session& s, const string& host); | |
529 | static remote* create(systemtap_session& s, const uri_decoder& ud); | |
e96f2257 JS |
530 | |
531 | virtual ~ssh_remote() { } | |
532 | }; | |
8d1a21b3 JS |
533 | |
534 | ||
535 | // ssh connection without stapsh, for legacy stap installations | |
536 | // NB: ssh commands use a tty (-t) so signals are passed along to the remote. | |
537 | // It does this by putting the local tty in raw mode, so it only works for tty | |
538 | // signals, and only for a single remote at a time. | |
539 | class ssh_legacy_remote : public remote { | |
daa75206 | 540 | private: |
20f90026 JS |
541 | vector<string> ssh_args, scp_args; |
542 | string ssh_control; | |
91fa953d | 543 | string host, tmpdir; |
ebff2ed0 JS |
544 | pid_t child; |
545 | ||
8d1a21b3 | 546 | ssh_legacy_remote(systemtap_session& s, const string& host) |
91fa953d | 547 | : remote(s), host(host), child(0) |
1544bdf9 | 548 | { |
4c156a0e | 549 | open_control_master(); |
1544bdf9 JS |
550 | try |
551 | { | |
552 | get_uname(); | |
553 | } | |
554 | catch (runtime_error&) | |
555 | { | |
556 | close_control_master(); | |
557 | throw; | |
558 | } | |
91fa953d JS |
559 | } |
560 | ||
4c156a0e JS |
561 | void open_control_master() |
562 | { | |
563 | static unsigned index = 0; | |
564 | ||
565 | if (s->tmpdir.empty()) // sanity check, shouldn't happen | |
58a834b1 | 566 | throw runtime_error(_("No tmpdir available for ssh control master")); |
4c156a0e JS |
567 | |
568 | ssh_control = s->tmpdir + "/ssh_remote_control_" + lex_cast(++index); | |
20f90026 JS |
569 | |
570 | scp_args.clear(); | |
571 | scp_args.push_back("scp"); | |
572 | scp_args.push_back("-q"); | |
573 | scp_args.push_back("-o"); | |
574 | scp_args.push_back("ControlPath=" + ssh_control); | |
575 | ||
576 | ssh_args = scp_args; | |
577 | ssh_args[0] = "ssh"; | |
578 | ssh_args.push_back(host); | |
4c156a0e JS |
579 | |
580 | // NB: ssh -f will stay in the foreground until authentication is | |
581 | // complete and the control socket is created, so we know it's ready to | |
582 | // go when stap_system returns. | |
20f90026 JS |
583 | vector<string> cmd = ssh_args; |
584 | cmd.push_back("-f"); | |
585 | cmd.push_back("-N"); | |
586 | cmd.push_back("-M"); | |
4c156a0e JS |
587 | int rc = stap_system(s->verbose, cmd); |
588 | if (rc != 0) | |
8d1a21b3 JS |
589 | throw runtime_error(_F("failed to create an ssh control master for %s : rc= %d", |
590 | host.c_str(), rc)); | |
4c156a0e JS |
591 | |
592 | if (s->verbose>1) | |
58a834b1 LB |
593 | clog << _F("Created ssh control master at %s", |
594 | lex_cast_qstring(ssh_control).c_str()) << endl; | |
4c156a0e JS |
595 | } |
596 | ||
597 | void close_control_master() | |
598 | { | |
599 | if (ssh_control.empty()) | |
600 | return; | |
601 | ||
20f90026 JS |
602 | vector<string> cmd = ssh_args; |
603 | cmd.push_back("-O"); | |
604 | cmd.push_back("exit"); | |
2323da5e | 605 | int rc = stap_system(s->verbose, cmd, true, true); |
4c156a0e | 606 | if (rc != 0) |
58a834b1 LB |
607 | cerr << _F("failed to stop the ssh control master for %s : rc=%d", |
608 | host.c_str(), rc) << endl; | |
4c156a0e JS |
609 | |
610 | ssh_control.clear(); | |
20f90026 JS |
611 | scp_args.clear(); |
612 | ssh_args.clear(); | |
4c156a0e JS |
613 | } |
614 | ||
91fa953d | 615 | void get_uname() |
daa75206 JS |
616 | { |
617 | ostringstream out; | |
618 | vector<string> uname; | |
20f90026 JS |
619 | vector<string> cmd = ssh_args; |
620 | cmd.push_back("-t"); | |
621 | cmd.push_back("uname -rm"); | |
622 | int rc = stap_system_read(s->verbose, cmd, out); | |
daa75206 JS |
623 | if (rc == 0) |
624 | tokenize(out.str(), uname, " \t\r\n"); | |
625 | if (uname.size() != 2) | |
58a834b1 | 626 | throw runtime_error(_F("failed to get uname from %s : rc= %d", host.c_str(), rc)); |
4c156a0e JS |
627 | const string& release = uname[0]; |
628 | const string& arch = uname[1]; | |
629 | // XXX need to deal with command-line vs. implied arch/release | |
630 | this->s = s->clone(arch, release); | |
daa75206 JS |
631 | } |
632 | ||
ebff2ed0 | 633 | int start() |
daa75206 JS |
634 | { |
635 | int rc; | |
ebff2ed0 JS |
636 | string localmodule = s->tmpdir + "/" + s->module_name + ".ko"; |
637 | string tmpmodule; | |
daa75206 JS |
638 | |
639 | // Make a remote tempdir. | |
640 | { | |
641 | ostringstream out; | |
642 | vector<string> vout; | |
20f90026 JS |
643 | vector<string> cmd = ssh_args; |
644 | cmd.push_back("-t"); | |
645 | cmd.push_back("mktemp -d -t stapXXXXXX"); | |
ebff2ed0 | 646 | rc = stap_system_read(s->verbose, cmd, out); |
daa75206 | 647 | if (rc == 0) |
b2cbfd40 | 648 | tokenize(out.str(), vout, "\r\n"); |
daa75206 JS |
649 | if (vout.size() != 1) |
650 | { | |
58a834b1 LB |
651 | cerr << _F("failed to make a tempdir on %s : rc=%d", |
652 | host.c_str(), rc) << endl; | |
daa75206 JS |
653 | return -1; |
654 | } | |
655 | tmpdir = vout[0]; | |
ebff2ed0 | 656 | tmpmodule = tmpdir + "/" + s->module_name + ".ko"; |
daa75206 JS |
657 | } |
658 | ||
659 | // Transfer the module. XXX and uprobes.ko, sigs, etc. | |
660 | if (rc == 0) { | |
20f90026 JS |
661 | vector<string> cmd = scp_args; |
662 | cmd.push_back(localmodule); | |
663 | cmd.push_back(host + ":" + tmpmodule); | |
ebff2ed0 | 664 | rc = stap_system(s->verbose, cmd); |
daa75206 | 665 | if (rc != 0) |
58a834b1 LB |
666 | cerr << _F("failed to copy the module to %s : rc=%d", |
667 | host.c_str(), rc) << endl; | |
daa75206 JS |
668 | } |
669 | ||
670 | // Run the module on the remote. | |
671 | if (rc == 0) { | |
20f90026 JS |
672 | vector<string> cmd = ssh_args; |
673 | cmd.push_back("-t"); | |
5eea6ed1 | 674 | cmd.push_back(cmdstr_join(make_run_command(*s, tmpmodule))); |
ebff2ed0 JS |
675 | pid_t pid = stap_spawn(s->verbose, cmd); |
676 | if (pid > 0) | |
4c156a0e JS |
677 | child = pid; |
678 | else | |
679 | { | |
58a834b1 LB |
680 | cerr << _F("failed to run the module on %s : ret=%d", |
681 | host.c_str(), pid) << endl; | |
4c156a0e JS |
682 | rc = -1; |
683 | } | |
daa75206 JS |
684 | } |
685 | ||
4c156a0e | 686 | return rc; |
ebff2ed0 | 687 | } |
daa75206 | 688 | |
ebff2ed0 JS |
689 | int finish() |
690 | { | |
4c156a0e | 691 | int rc = 0; |
ebff2ed0 | 692 | |
4c156a0e JS |
693 | if (child > 0) |
694 | { | |
695 | rc = stap_waitpid(s->verbose, child); | |
696 | child = 0; | |
697 | } | |
ebff2ed0 JS |
698 | |
699 | if (!tmpdir.empty()) | |
4c156a0e JS |
700 | { |
701 | // Remove the tempdir. | |
702 | // XXX need to make sure this runs even with e.g. CTRL-C exits | |
20f90026 JS |
703 | vector<string> cmd = ssh_args; |
704 | cmd.push_back("-t"); | |
705 | cmd.push_back("rm -r " + cmdstr_quoted(tmpdir)); | |
4c156a0e JS |
706 | int rc2 = stap_system(s->verbose, cmd); |
707 | if (rc2 != 0) | |
58a834b1 LB |
708 | cerr << _F("failed to delete the tempdir on %s : rc=%d", |
709 | host.c_str(), rc2) << endl; | |
4c156a0e JS |
710 | if (rc == 0) |
711 | rc = rc2; | |
20f90026 | 712 | tmpdir.clear(); |
4c156a0e JS |
713 | } |
714 | ||
715 | close_control_master(); | |
716 | ||
717 | return rc; | |
daa75206 | 718 | } |
4f244435 JS |
719 | |
720 | public: | |
8d1a21b3 | 721 | friend class ssh_remote; |
4f244435 | 722 | |
8d1a21b3 | 723 | virtual ~ssh_legacy_remote() |
4f244435 JS |
724 | { |
725 | close_control_master(); | |
726 | } | |
daa75206 | 727 | }; |
8d1a21b3 JS |
728 | |
729 | ||
730 | // Try to establish a stapsh connection to the remote, but fallback | |
731 | // to the older mechanism if the command is not found. | |
732 | remote* | |
733 | ssh_remote::create(systemtap_session& s, const string& host) | |
734 | { | |
735 | auto_ptr<ssh_remote> p (new ssh_remote(s)); | |
736 | int rc = p->connect(host); | |
737 | if (rc == 0) | |
738 | return p.release(); | |
739 | else if (rc == 127) // stapsh command not found | |
740 | return new ssh_legacy_remote(s, host); // try legacy instead | |
741 | return NULL; | |
742 | } | |
743 | ||
744 | remote* | |
745 | ssh_remote::create(systemtap_session& s, const uri_decoder& ud) | |
746 | { | |
747 | if (!ud.has_authority || ud.authority.empty()) | |
748 | throw runtime_error(_("ssh target requires a hostname")); | |
749 | if (!ud.path.empty() && ud.path != "/") | |
750 | throw runtime_error(_("ssh target URI doesn't support a /path")); | |
751 | if (ud.has_query) | |
752 | throw runtime_error(_("ssh target URI doesn't support a ?query")); | |
753 | if (ud.has_fragment) | |
754 | throw runtime_error(_("ssh target URI doesn't support a #fragment")); | |
755 | ||
756 | return create(s, ud.authority); | |
757 | } | |
daa75206 JS |
758 | |
759 | ||
760 | remote* | |
761 | remote::create(systemtap_session& s, const string& uri) | |
762 | { | |
763 | try | |
764 | { | |
765 | if (uri == "direct") | |
766 | return new direct(s); | |
e96f2257 JS |
767 | else if (uri == "stapsh") |
768 | return new direct_stapsh(s); | |
91fa953d JS |
769 | else if (uri.find(':') != string::npos) |
770 | { | |
771 | const uri_decoder ud(uri); | |
772 | if (ud.scheme == "ssh") | |
8d1a21b3 | 773 | return ssh_remote::create(s, ud); |
91fa953d | 774 | else |
8d1a21b3 JS |
775 | throw runtime_error(_F("unrecognized URI scheme '%s' in remote: %s", |
776 | ud.scheme.c_str(), uri.c_str())); | |
91fa953d | 777 | } |
daa75206 JS |
778 | else |
779 | // XXX assuming everything else is ssh for now... | |
8d1a21b3 | 780 | return ssh_remote::create(s, uri); |
daa75206 JS |
781 | } |
782 | catch (std::runtime_error& e) | |
783 | { | |
8d1a21b3 | 784 | cerr << e.what() << " on remote '" << uri << "'" << endl; |
daa75206 JS |
785 | return NULL; |
786 | } | |
787 | } | |
788 | ||
e04a4b41 JS |
789 | #ifndef HAVE_PPOLL |
790 | // This is a poor-man's ppoll, only used by remote::run below. It does not | |
791 | // provide the same guarantee of atomicity as on systems with a true ppoll. | |
792 | // | |
793 | // In our use, this would cause trouble if a signal came in any time from the | |
794 | // moment we mask signals to prepare pollfds, to the moment we call poll in | |
795 | // emulation here. If there's no data on any of the pollfds, we will be stuck | |
796 | // waiting indefinitely. | |
797 | // | |
798 | // Since this is mainly about responsiveness of CTRL-C cleanup, we'll just | |
799 | // throw in a one-second forced timeout to ensure we have a chance to notice | |
800 | // there was an interrupt without too much delay. | |
801 | static int | |
802 | ppoll(struct pollfd *fds, nfds_t nfds, | |
803 | const struct timespec *timeout_ts, | |
804 | const sigset_t *sigmask) | |
805 | { | |
806 | sigset_t origmask; | |
807 | int timeout = (timeout_ts == NULL) ? 1000 // don't block forever... | |
808 | : (timeout_ts->tv_sec * 1000 + timeout_ts->tv_nsec / 1000000); | |
809 | sigprocmask(SIG_SETMASK, sigmask, &origmask); | |
810 | int rc = poll(fds, nfds, timeout); | |
811 | sigprocmask(SIG_SETMASK, &origmask, NULL); | |
812 | return rc; | |
813 | } | |
814 | #endif | |
815 | ||
4f244435 JS |
816 | int |
817 | remote::run(const vector<remote*>& remotes) | |
818 | { | |
819 | // NB: the first failure "wins" | |
820 | int ret = 0, rc = 0; | |
821 | ||
822 | for (unsigned i = 0; i < remotes.size() && !pending_interrupts; ++i) | |
3fe776ae | 823 | { |
756cfd49 JS |
824 | remote *r = remotes[i]; |
825 | r->s->verbose = r->s->perpass_verbose[4]; | |
826 | if (r->s->use_remote_prefix) | |
827 | r->prefix = lex_cast(i) + ": "; | |
828 | rc = r->prepare(); | |
3fe776ae JS |
829 | if (rc) |
830 | return rc; | |
831 | } | |
832 | ||
833 | for (unsigned i = 0; i < remotes.size() && !pending_interrupts; ++i) | |
4f244435 JS |
834 | { |
835 | rc = remotes[i]->start(); | |
836 | if (!ret) | |
837 | ret = rc; | |
838 | } | |
839 | ||
e96f2257 JS |
840 | // mask signals while we're preparing to poll |
841 | sigset_t mask, oldmask; | |
842 | sigemptyset (&mask); | |
843 | sigaddset (&mask, SIGHUP); | |
844 | sigaddset (&mask, SIGPIPE); | |
845 | sigaddset (&mask, SIGINT); | |
846 | sigaddset (&mask, SIGTERM); | |
847 | sigprocmask (SIG_BLOCK, &mask, &oldmask); | |
848 | ||
849 | for (;;) // polling loop for remotes that have fds to watch | |
850 | { | |
851 | vector<pollfd> fds; | |
852 | for (unsigned i = 0; i < remotes.size(); ++i) | |
853 | remotes[i]->prepare_poll (fds); | |
854 | if (fds.empty()) | |
855 | break; | |
856 | ||
857 | rc = ppoll (&fds[0], fds.size(), NULL, &oldmask); | |
858 | if (rc < 0 && errno != EINTR) | |
859 | break; | |
860 | ||
861 | for (unsigned i = 0; i < remotes.size(); ++i) | |
862 | remotes[i]->handle_poll (fds); | |
863 | } | |
864 | ||
865 | sigprocmask (SIG_SETMASK, &oldmask, NULL); | |
866 | ||
4f244435 JS |
867 | for (unsigned i = 0; i < remotes.size(); ++i) |
868 | { | |
869 | rc = remotes[i]->finish(); | |
870 | if (!ret) | |
871 | ret = rc; | |
872 | } | |
873 | ||
874 | return ret; | |
875 | } | |
876 | ||
daa75206 JS |
877 | |
878 | /* vim: set sw=2 ts=8 cino=>4,n-2,{2,^-2,t0,(0,u0,w1,M1 : */ |