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