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