]>
Commit | Line | Data |
---|---|---|
daa75206 | 1 | // systemtap remote execution |
1b6156ad | 2 | // Copyright (C) 2010-2015 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> | |
be7e131b | 28 | #include <fstream> |
daa75206 JS |
29 | |
30 | #include "buildrun.h" | |
31 | #include "remote.h" | |
32 | #include "util.h" | |
33 | ||
34 | using namespace std; | |
35 | ||
91fa953d JS |
36 | // Decode URIs as per RFC 3986, though not bothering to be strict |
37 | class uri_decoder { | |
38 | public: | |
39 | const string uri; | |
40 | string scheme, authority, path, query, fragment; | |
41 | bool has_authority, has_query, has_fragment; | |
42 | ||
43 | uri_decoder(const string& uri): | |
44 | uri(uri), has_authority(false), has_query(false), has_fragment(false) | |
45 | { | |
46 | const string re = | |
47 | "^([^:]+):(//[^/?#]*)?([^?#]*)(\\?[^#]*)?(#.*)?$"; | |
48 | ||
49 | vector<string> matches; | |
50 | if (regexp_match(uri, re, matches) != 0) | |
58a834b1 | 51 | throw runtime_error(_F("string doesn't appear to be a URI: %s", uri.c_str())); |
91fa953d JS |
52 | |
53 | scheme = matches[1]; | |
54 | ||
55 | if (!matches[2].empty()) | |
56 | { | |
57 | has_authority = true; | |
58 | authority = matches[2].substr(2); | |
59 | } | |
60 | ||
61 | path = matches[3]; | |
62 | ||
63 | if (!matches[4].empty()) | |
64 | { | |
65 | has_query = true; | |
66 | query = matches[4].substr(1); | |
67 | } | |
68 | ||
69 | if (!matches[5].empty()) | |
70 | { | |
71 | has_fragment = true; | |
72 | fragment = matches[5].substr(1); | |
73 | } | |
74 | } | |
75 | }; | |
76 | ||
77 | ||
daa75206 JS |
78 | // loopback target for running locally |
79 | class direct : public remote { | |
ebff2ed0 JS |
80 | private: |
81 | pid_t child; | |
5def5d2a | 82 | vector<string> args; |
ebff2ed0 | 83 | direct(systemtap_session& s): remote(s), child(0) {} |
daa75206 | 84 | |
be7e131b MC |
85 | // Save the staprun commandline to a file, where the privileged |
86 | // parent process can find it and spawn staprun. | |
87 | void put_args() | |
88 | { | |
89 | ofstream f(s->tmpdir + "/staprun_args_direct"); | |
90 | if(f.fail()) | |
91 | cerr << "ERROR: Failed writing staprun_args_direct." << endl; | |
92 | for (vector<string>::iterator t=args.begin(); t!=args.end(); ++t) | |
93 | f << *t << endl; | |
94 | f.close(); | |
95 | } | |
96 | ||
97 | // Retreive saved staprun commandline from the file. | |
98 | // Feed the args vector. | |
99 | void get_args() | |
100 | { | |
101 | ifstream f(s->tmpdir + "/staprun_args_direct"); | |
102 | if(f.fail()) | |
103 | cerr << "ERROR: Failed reading staprun_args_direct" << endl; | |
104 | string l; | |
105 | while(getline(f, l)) | |
106 | args.insert(args.end(), l); | |
107 | f.close(); | |
108 | } | |
109 | ||
110 | // Define prepare() and move the initial part of pass_5 (i.e. the | |
111 | // preparation of the staprun commandline) into it. The staprun | |
112 | // invocation stays in start() below. | |
113 | int prepare() | |
ebff2ed0 | 114 | { |
5def5d2a | 115 | args = make_run_command(*s); |
5137a7a9 | 116 | if (! staprun_r_arg.empty()) // PR13354 |
15700f9b CS |
117 | { |
118 | if (s->runtime_mode == systemtap_session::dyninst_runtime) | |
119 | args.insert(args.end(), {"_stp_dyninst_remote=" + staprun_r_arg}); | |
f78ee21b | 120 | else if (s->runtime_mode == systemtap_session::kernel_runtime) |
15700f9b | 121 | args.insert(args.end(), { "-r", staprun_r_arg }); |
f78ee21b | 122 | // leave args empty for bpf_runtime |
15700f9b | 123 | } |
f78ee21b | 124 | |
be7e131b MC |
125 | // Dump args to a file, where parent can read it. |
126 | put_args(); | |
127 | return 0; | |
128 | } | |
129 | ||
130 | int start() | |
131 | { | |
132 | // If args is an empty vector, read it from file. | |
133 | if (args.size() == 0) | |
134 | get_args(); | |
135 | ||
5def5d2a | 136 | pid_t pid = stap_spawn (s->verbose, args); |
4c156a0e JS |
137 | if (pid <= 0) |
138 | return 1; | |
139 | child = pid; | |
140 | return 0; | |
ebff2ed0 JS |
141 | } |
142 | ||
143 | int finish() | |
144 | { | |
4c156a0e JS |
145 | if (child <= 0) |
146 | return 1; | |
ebff2ed0 | 147 | |
4c156a0e | 148 | int ret = stap_waitpid(s->verbose, child); |
df324c9f JS |
149 | if (ret > 128) |
150 | s->print_warning(_F("%s exited with signal: %d (%s)", | |
151 | args.front().c_str(), ret - 128, | |
152 | strsignal(ret - 128))); | |
153 | else if (ret > 0) | |
154 | s->print_warning(_F("%s exited with status: %d", | |
155 | args.front().c_str(), ret)); | |
4c156a0e JS |
156 | child = 0; |
157 | return ret; | |
ebff2ed0 | 158 | } |
4f244435 JS |
159 | |
160 | public: | |
161 | friend class remote; | |
162 | ||
10a5bce5 | 163 | virtual ~direct() { finish(); } |
daa75206 JS |
164 | }; |
165 | ||
e96f2257 JS |
166 | |
167 | class stapsh : public remote { | |
168 | private: | |
169 | int interrupts_sent; | |
170 | int fdin, fdout; | |
171 | FILE *IN, *OUT; | |
4112f219 | 172 | string remote_version; |
004f2b9c JL |
173 | size_t data_size; |
174 | string target_stream; | |
be7e131b | 175 | vector<string> args; |
004f2b9c JL |
176 | |
177 | enum { | |
178 | STAPSH_READY, // ready to receive next command | |
179 | STAPSH_DATA // currently printing data from a 'data' command | |
180 | } stream_state; | |
e96f2257 | 181 | |
be7e131b MC |
182 | // Save the staprun commandline to a file, where the privileged |
183 | // parent process can find it and spawn staprun. | |
184 | void put_args() | |
185 | { | |
186 | ofstream f(s->tmpdir + "/staprun_args_stapsh"); | |
187 | if(f.fail()) | |
188 | cerr << "ERROR: Failed writing staprun_args_stapsh." << endl; | |
189 | for (vector<string>::iterator t=args.begin(); t!=args.end(); ++t) | |
190 | f << *t << endl; | |
191 | f.close(); | |
192 | } | |
193 | ||
194 | // Retreive saved staprun commandline from the file. | |
195 | // Feed the args vector. | |
196 | void get_args() | |
197 | { | |
198 | ifstream f(s->tmpdir + "/staprun_args_stapsh"); | |
199 | if(f.fail()) | |
200 | cerr << "ERROR: Failed reading staprun_args_stapsh" << endl; | |
201 | string l; | |
202 | while(getline(f, l)) | |
203 | args.insert(args.end(), l); | |
204 | f.close(); | |
205 | } | |
206 | ||
e96f2257 JS |
207 | virtual void prepare_poll(vector<pollfd>& fds) |
208 | { | |
209 | if (fdout >= 0 && OUT) | |
210 | { | |
822a6a3d | 211 | pollfd p = { fdout, POLLIN, 0 }; |
e96f2257 JS |
212 | fds.push_back(p); |
213 | } | |
214 | ||
215 | // need to send a signal? | |
216 | if (fdin >= 0 && IN && interrupts_sent < pending_interrupts) | |
217 | { | |
822a6a3d | 218 | pollfd p = { fdin, POLLOUT, 0 }; |
e96f2257 JS |
219 | fds.push_back(p); |
220 | } | |
221 | } | |
222 | ||
223 | virtual void handle_poll(vector<pollfd>& fds) | |
224 | { | |
225 | for (unsigned i=0; i < fds.size(); ++i) | |
e7e8737f | 226 | if (fds[i].fd == fdin || fds[i].fd == fdout) |
e96f2257 | 227 | { |
e7e8737f | 228 | bool err = false; |
e96f2257 JS |
229 | |
230 | // need to send a signal? | |
e7e8737f | 231 | if (fds[i].revents & POLLOUT && IN && |
e96f2257 JS |
232 | interrupts_sent < pending_interrupts) |
233 | { | |
e7e8737f JS |
234 | if (send_command("quit\n") == 0) |
235 | ++interrupts_sent; | |
236 | else | |
237 | err = true; | |
238 | } | |
239 | ||
240 | // have data to read? | |
241 | if (fds[i].revents & POLLIN && OUT) | |
242 | { | |
004f2b9c JL |
243 | // if stapsh doesn't support commands, then just dump everything |
244 | if (!vector_has(options, string("data"))) | |
e7e8737f | 245 | { |
004f2b9c JL |
246 | // NB: we could splice here, but we don't know how much |
247 | // data is available for reading. One way could be to | |
248 | // splice in small chunks and poll() fdout to check if | |
249 | // there's more. | |
250 | char buf[4096]; | |
251 | size_t bytes_read; | |
252 | while ((bytes_read = fread(buf, 1, sizeof(buf), OUT)) > 0) | |
253 | printout(buf, bytes_read); | |
e7e8737f JS |
254 | if (errno != EAGAIN) |
255 | err = true; | |
256 | } | |
004f2b9c | 257 | else // we expect commands (just data for now) |
e96f2257 | 258 | { |
004f2b9c JL |
259 | // When the 'data' option is turned on, all outputs from |
260 | // staprun are first prepended with a line formatted as | |
261 | // "data <stream> <size>\n", where <stream> is either | |
262 | // "stdout" or "stderr", and <size> is the size of the | |
263 | // data. Also, the line "quit\n" is sent from stapsh right | |
264 | // before it exits. | |
265 | ||
266 | if (stream_state == STAPSH_READY) | |
e96f2257 | 267 | { |
004f2b9c JL |
268 | char cmdbuf[1024]; |
269 | char *rc; | |
270 | while ((rc = fgets(cmdbuf, sizeof(cmdbuf), OUT)) != NULL) | |
271 | { | |
272 | // check if it's a debug output from stapsh itself | |
273 | string line = string(cmdbuf); | |
274 | if (startswith(line, "stapsh:")) | |
275 | clog << line; | |
276 | else if (startswith(line, "quit\n")) | |
277 | // && vector_has(options, string("data"))) | |
278 | // uncomment above if another command becomes | |
279 | // possible | |
280 | { | |
281 | err = true; // close connection | |
282 | break; | |
283 | } | |
284 | else if (startswith(line, "data")) | |
285 | // && vector_has(options, string("data"))) | |
286 | // uncomment above if another command becomes | |
287 | // possible | |
288 | { | |
289 | vector<string> cmd; | |
290 | tokenize(line, cmd, " \t\r\n"); | |
291 | if (!is_valid_data_cmd(cmd)) | |
292 | { | |
293 | clog << _("invalid data command from stapsh") << endl; | |
294 | clog << _("received: ") << line; | |
295 | err = true; | |
296 | } | |
297 | else | |
298 | { | |
299 | target_stream = cmd[1]; | |
300 | data_size = lex_cast<size_t>(cmd[2]); | |
301 | stream_state = STAPSH_DATA; | |
302 | } | |
303 | break; | |
304 | } | |
305 | } | |
306 | if (rc == NULL && errno != EAGAIN) | |
307 | err = true; | |
308 | } | |
309 | ||
310 | if (stream_state == STAPSH_DATA) | |
311 | { | |
312 | if (data_size != 0) | |
313 | { | |
314 | // keep reading from OUT until either dry or data_size bytes done | |
315 | char buf[4096]; | |
316 | size_t max_read = min<size_t>(sizeof(buf), data_size); | |
317 | size_t bytes_read = 0; | |
318 | while (max_read > 0 | |
319 | && (bytes_read = fread(buf, 1, max_read, OUT)) > 0) | |
320 | { | |
321 | printout(buf, bytes_read); | |
322 | data_size -= bytes_read; | |
323 | max_read = min<size_t>(sizeof(buf), data_size); | |
324 | } | |
325 | if (bytes_read == 0 && errno != EAGAIN) | |
326 | err = true; | |
327 | } | |
328 | if (data_size == 0) | |
329 | stream_state = STAPSH_READY; | |
e96f2257 JS |
330 | } |
331 | } | |
e96f2257 | 332 | } |
e7e8737f JS |
333 | |
334 | // any errors? | |
335 | if (err || fds[i].revents & ~(POLLIN|POLLOUT)) | |
336 | close(); | |
e96f2257 JS |
337 | } |
338 | } | |
339 | ||
35df6b43 JS |
340 | string get_reply() |
341 | { | |
e7e8737f JS |
342 | // Some schemes like unix may have stdout and stderr mushed together. |
343 | // There shouldn't be anything except dbug messages on stderr before we | |
344 | // actually start running, and there's no get_reply after that. So | |
345 | // we'll just loop and skip those that start with "stapsh:". | |
35df6b43 | 346 | char reply[4096]; |
e7e8737f JS |
347 | while (fgets(reply, sizeof(reply), OUT)) |
348 | { | |
349 | if (!startswith(reply, "stapsh:")) | |
350 | return reply; | |
351 | ||
004f2b9c JL |
352 | // if data is not prefixed, we will have no way to distinguish |
353 | // between stdout and stderr during staprun runtime, so we might as | |
354 | // well print to stdout now as well to be more consistent | |
355 | if (vector_has(options, string("data"))) | |
356 | clog << reply; // must be stderr since only replies go to stdout | |
357 | else | |
358 | cout << reply; // output to stdout to be more consistent with later | |
e7e8737f JS |
359 | } |
360 | ||
361 | // Reached EOF, nothing to reply... | |
362 | return ""; | |
35df6b43 JS |
363 | } |
364 | ||
e96f2257 JS |
365 | int send_command(const string& cmd) |
366 | { | |
367 | if (!IN) | |
368 | return 2; | |
369 | if (fputs(cmd.c_str(), IN) < 0 || | |
370 | fflush(IN) != 0) | |
371 | return 1; | |
372 | return 0; | |
373 | } | |
374 | ||
375 | int send_file(const string& filename, const string& dest) | |
376 | { | |
377 | int rc = 0; | |
378 | FILE* f = fopen(filename.c_str(), "r"); | |
379 | if (!f) | |
380 | return 1; | |
381 | ||
382 | struct stat fs; | |
383 | rc = fstat(fileno(f), &fs); | |
384 | if (!rc) | |
385 | { | |
386 | ostringstream cmd; | |
387 | cmd << "file " << fs.st_size << " " << dest << "\n"; | |
388 | rc = send_command(cmd.str()); | |
389 | } | |
390 | ||
391 | off_t i = 0; | |
392 | while (!rc && i < fs.st_size) | |
393 | { | |
394 | char buf[4096]; | |
395 | size_t r = sizeof(buf); | |
396 | if (fs.st_size - i < (off_t)r) | |
397 | r = fs.st_size - i; | |
398 | r = fread(buf, 1, r, f); | |
399 | if (r == 0) | |
400 | rc = 1; | |
401 | else | |
402 | { | |
403 | size_t w = fwrite(buf, 1, r, IN); | |
404 | if (w != r) | |
405 | rc = 1; | |
406 | else | |
407 | i += w; | |
408 | } | |
409 | } | |
410 | if (!rc) | |
411 | rc = fflush(IN); | |
412 | ||
413 | fclose(f); | |
35df6b43 JS |
414 | |
415 | if (!rc) | |
416 | { | |
417 | string reply = get_reply(); | |
418 | if (reply != "OK\n") | |
419 | { | |
420 | rc = 1; | |
a5923b0e | 421 | if (s->verbose > 0) |
35df6b43 JS |
422 | { |
423 | if (reply.empty()) | |
f3f6443c | 424 | clog << _("stapsh file ERROR: no reply") << endl; |
35df6b43 | 425 | else |
f3f6443c | 426 | clog << _F("stapsh file replied %s", reply.c_str()); |
35df6b43 JS |
427 | } |
428 | } | |
429 | } | |
430 | ||
e96f2257 JS |
431 | return rc; |
432 | } | |
433 | ||
bf9c0b28 | 434 | static string qpencode(const string& str) |
e96f2257 | 435 | { |
bf9c0b28 JS |
436 | ostringstream o; |
437 | o << setfill('0') << hex; | |
438 | for (const char* s = str.c_str(); *s; ++s) | |
439 | if (*s >= 33 && *s <= 126 && *s != 61) | |
440 | o << *s; | |
441 | else | |
442 | o << '=' << setw(2) << (unsigned)(unsigned char) *s; | |
443 | return o.str(); | |
e96f2257 JS |
444 | } |
445 | ||
004f2b9c JL |
446 | static bool is_valid_data_cmd(const vector<string>& cmd) |
447 | { | |
448 | bool err = false; | |
449 | ||
450 | err = err || (cmd[0] != "data"); | |
451 | err = err || (cmd[1] != "stdout" && cmd[1] != "stderr"); | |
452 | ||
453 | if (!err) // try to cast as size_t | |
454 | try { lex_cast<size_t>(cmd[2]); } | |
455 | catch (...) { err = true; } | |
456 | ||
457 | return !err; | |
458 | } | |
459 | ||
460 | virtual void printout(const char *buf, size_t size) | |
461 | { | |
462 | static string last_prefix; | |
463 | static bool on_same_line; | |
464 | ||
465 | if (size == 0) | |
466 | return; | |
467 | ||
468 | // don't prefix for stderr to be more consistent with ssh | |
469 | if (!prefix.empty() && target_stream != "stderr") | |
470 | { | |
471 | vector<pair<const char*,int> > lines = split_lines(buf, size); | |
472 | for (vector<pair<const char*,int> >::iterator it = lines.begin(); | |
473 | it != lines.end(); ++it) | |
474 | { | |
475 | if (last_prefix != prefix) | |
476 | { | |
477 | if (on_same_line) | |
478 | cout << endl; | |
479 | cout << prefix; | |
480 | } | |
481 | else if (!on_same_line) | |
482 | cout << prefix; | |
483 | cout.write(it->first, it->second); | |
484 | } | |
485 | cout.flush(); | |
486 | const char *last_line = lines.back().first; | |
487 | const char last_char = last_line[lines.back().second-1]; | |
488 | on_same_line = !lines.empty() && last_char != '\n'; | |
489 | last_prefix = prefix; | |
490 | } | |
491 | else | |
492 | { | |
493 | // Otherwise dump the whole block | |
494 | // NB: The buf could contain binary data, | |
495 | // including \0, so write as a block instead of | |
496 | // the usual << string. | |
497 | if (target_stream == "stdout") | |
498 | { | |
499 | cout.write(buf, size); | |
500 | cout.flush(); | |
501 | } | |
502 | else // stderr | |
503 | clog.write(buf, size); | |
504 | } | |
505 | } | |
506 | ||
e96f2257 JS |
507 | protected: |
508 | stapsh(systemtap_session& s) | |
509 | : remote(s), interrupts_sent(0), | |
004f2b9c JL |
510 | fdin(-1), fdout(-1), IN(0), OUT(0), |
511 | data_size(0), target_stream("stdout"), // default to stdout for schemes | |
512 | stream_state(STAPSH_READY) // that don't pipe stderr (e.g. ssh) | |
e96f2257 JS |
513 | {} |
514 | ||
55881ad4 JL |
515 | vector<string> options; |
516 | ||
3fe776ae | 517 | virtual int prepare() |
e96f2257 JS |
518 | { |
519 | int rc = 0; | |
520 | ||
f78ee21b AM |
521 | string extension; |
522 | if (s->runtime_mode == systemtap_session::dyninst_runtime) | |
523 | extension = ".so"; | |
524 | else if (s->runtime_mode == systemtap_session::bpf_runtime) | |
525 | extension = ".bo"; | |
526 | else | |
527 | extension = ".ko"; | |
528 | ||
529 | string localmodule = s->tmpdir + "/" + s->module_name + extension; | |
dd981a9d | 530 | string remotemodule = s->module_name + extension; |
f78ee21b | 531 | |
e96f2257 JS |
532 | if ((rc = send_file(localmodule, remotemodule))) |
533 | return rc; | |
534 | ||
599c80df JS |
535 | if (file_exists(localmodule + ".sgn") && |
536 | (rc = send_file(localmodule + ".sgn", remotemodule + ".sgn"))) | |
537 | return rc; | |
538 | ||
539 | if (!s->uprobes_path.empty()) | |
540 | { | |
541 | string remoteuprobes = basename(s->uprobes_path.c_str()); | |
542 | if ((rc = send_file(s->uprobes_path, remoteuprobes))) | |
543 | return rc; | |
544 | ||
545 | if (file_exists(s->uprobes_path + ".sgn") && | |
546 | (rc = send_file(s->uprobes_path + ".sgn", remoteuprobes + ".sgn"))) | |
547 | return rc; | |
548 | } | |
e96f2257 | 549 | |
be7e131b | 550 | args = make_run_command(*s, ".", remote_version); |
5137a7a9 FCE |
551 | |
552 | // PR13354: identify our remote index/url | |
553 | if (strverscmp("1.7", remote_version.c_str()) <= 0 && // -r supported? | |
554 | ! staprun_r_arg.empty()) | |
15700f9b CS |
555 | { |
556 | if (s->runtime_mode == systemtap_session::dyninst_runtime) | |
be7e131b | 557 | args.insert(args.end(), { "_stp_dyninst_remote=" + staprun_r_arg}); |
f78ee21b | 558 | else if (s->runtime_mode == systemtap_session::kernel_runtime) |
be7e131b | 559 | args.insert(args.end(), { "-r", staprun_r_arg }); |
f78ee21b | 560 | // leave args empty for bpf_runtime |
15700f9b | 561 | } |
5137a7a9 | 562 | |
be7e131b MC |
563 | // Dump args to a file, where parent can read it. |
564 | put_args(); | |
565 | ||
566 | return rc; | |
567 | } | |
568 | ||
569 | virtual int start() | |
570 | { | |
571 | // Send the staprun args | |
572 | // NB: The remote is left to decide its own staprun path | |
573 | ostringstream run("run", ios::out | ios::ate); | |
574 | ||
575 | if (args.size() == 0) | |
576 | get_args(); | |
577 | ||
578 | for (unsigned i = 1; i < args.size(); ++i) | |
579 | run << ' ' << qpencode(args[i]); | |
bf9c0b28 | 580 | run << '\n'; |
e96f2257 | 581 | |
3fe776ae | 582 | int rc = send_command(run.str()); |
e96f2257 | 583 | |
35df6b43 JS |
584 | if (!rc) |
585 | { | |
586 | string reply = get_reply(); | |
587 | if (reply != "OK\n") | |
588 | { | |
589 | rc = 1; | |
a5923b0e | 590 | if (s->verbose > 0) |
35df6b43 JS |
591 | { |
592 | if (reply.empty()) | |
f3f6443c | 593 | clog << _("stapsh run ERROR: no reply") << endl; |
35df6b43 | 594 | else |
f3f6443c | 595 | clog << _F("stapsh run replied %s", reply.c_str()); |
35df6b43 JS |
596 | } |
597 | } | |
598 | } | |
599 | ||
e96f2257 JS |
600 | if (!rc) |
601 | { | |
92c41b4e JL |
602 | long flags; |
603 | if ((flags = fcntl(fdout, F_GETFL)) == -1 | |
604 | || fcntl(fdout, F_SETFL, flags | O_NONBLOCK) == -1) | |
605 | { | |
606 | clog << _("failed to change to non-blocking mode") << endl; | |
607 | rc = 1; | |
608 | } | |
e96f2257 | 609 | } |
92c41b4e JL |
610 | |
611 | if (rc) | |
a5923b0e JS |
612 | // If run failed for any reason, then this |
613 | // connection is effectively dead to us. | |
614 | close(); | |
e96f2257 JS |
615 | |
616 | return rc; | |
617 | } | |
618 | ||
619 | void close() | |
620 | { | |
621 | if (IN) fclose(IN); | |
622 | if (OUT) fclose(OUT); | |
623 | IN = OUT = NULL; | |
624 | fdin = fdout = -1; | |
625 | } | |
626 | ||
627 | virtual int finish() | |
628 | { | |
629 | close(); | |
630 | return 0; | |
631 | } | |
632 | ||
633 | void set_child_fds(int in, int out) | |
634 | { | |
635 | if (fdin >= 0 || fdout >= 0 || IN || OUT) | |
8d1a21b3 | 636 | throw runtime_error(_("stapsh file descriptors already set")); |
e96f2257 JS |
637 | |
638 | fdin = in; | |
639 | fdout = out; | |
640 | IN = fdopen(fdin, "w"); | |
641 | OUT = fdopen(fdout, "r"); | |
642 | if (!IN || !OUT) | |
8d1a21b3 | 643 | throw runtime_error(_("invalid file descriptors for stapsh")); |
e96f2257 JS |
644 | |
645 | if (send_command("stap " VERSION "\n")) | |
8d1a21b3 | 646 | throw runtime_error(_("error sending hello to stapsh")); |
e96f2257 | 647 | |
35df6b43 JS |
648 | string reply = get_reply(); |
649 | if (reply.empty()) | |
8d1a21b3 | 650 | throw runtime_error(_("error receiving hello from stapsh")); |
e96f2257 JS |
651 | |
652 | // stapsh VERSION MACHINE RELEASE | |
653 | vector<string> uname; | |
654 | tokenize(reply, uname, " \t\r\n"); | |
655 | if (uname.size() != 4 || uname[0] != "stapsh") | |
03501815 | 656 | throw runtime_error(_F("invalid hello from stapsh: %s", reply.c_str())); |
4112f219 JS |
657 | |
658 | // We assume that later versions will know how to talk to us. | |
659 | // Looking backward, we use this for make_run_command(). | |
660 | this->remote_version = uname[1]; | |
e96f2257 JS |
661 | |
662 | this->s = s->clone(uname[2], uname[3]); | |
55881ad4 JL |
663 | |
664 | // set any option requested | |
665 | if (!this->options.empty()) | |
666 | { | |
667 | // check first if the option command is supported | |
668 | if (strverscmp("2.4", this->remote_version.c_str()) > 0) | |
669 | throw runtime_error(_F("stapsh %s does not support options", | |
670 | this->remote_version.c_str())); | |
671 | ||
672 | for (vector<string>::iterator it = this->options.begin(); | |
673 | it != this->options.end(); ++it) | |
674 | { | |
92c41b4e JL |
675 | int rc = send_command("option " + *it + "\n"); |
676 | if (rc != 0) | |
677 | throw runtime_error(_F("could not set option %s: " | |
678 | "send_command returned %d", | |
679 | it->c_str(), rc)); | |
55881ad4 JL |
680 | string reply = get_reply(); |
681 | if (reply != "OK\n") | |
682 | throw runtime_error(_F("could not set option %s: %s", | |
683 | it->c_str(), reply.c_str())); | |
684 | } | |
685 | } | |
e96f2257 JS |
686 | } |
687 | ||
688 | public: | |
689 | virtual ~stapsh() { close(); } | |
690 | }; | |
691 | ||
692 | ||
88e08f53 JS |
693 | // direct_stapsh is meant only for testing, as a way to exercise the stapsh |
694 | // mechanisms without requiring test machines to have actual remote access. | |
e96f2257 JS |
695 | class direct_stapsh : public stapsh { |
696 | private: | |
697 | pid_t child; | |
698 | ||
699 | direct_stapsh(systemtap_session& s) | |
700 | : stapsh(s), child(0) | |
701 | { | |
8d1a21b3 | 702 | int in, out; |
d1735491 | 703 | vector<string> cmd { BINDIR "/stapsh" }; |
8d1a21b3 JS |
704 | if (s.perpass_verbose[4] > 1) |
705 | cmd.push_back("-v"); | |
706 | if (s.perpass_verbose[4] > 2) | |
707 | cmd.push_back("-v"); | |
708 | ||
88e08f53 JS |
709 | // mask signals while we spawn, so we can simulate manual signals to |
710 | // the "remote" target, as we must for the real ssh_remote case. | |
a69b4b55 JS |
711 | { |
712 | stap_sigmasker masked; | |
713 | child = stap_spawn_piped(s.verbose, cmd, &in, &out); | |
714 | } | |
8d1a21b3 | 715 | |
e96f2257 | 716 | if (child <= 0) |
8d1a21b3 | 717 | throw runtime_error(_("error launching stapsh")); |
e96f2257 JS |
718 | |
719 | try | |
720 | { | |
721 | set_child_fds(in, out); | |
722 | } | |
723 | catch (runtime_error&) | |
724 | { | |
725 | finish(); | |
726 | throw; | |
727 | } | |
728 | } | |
729 | ||
730 | virtual int finish() | |
731 | { | |
732 | int rc = stapsh::finish(); | |
733 | if (child <= 0) | |
734 | return rc; | |
735 | ||
736 | int rc2 = stap_waitpid(s->verbose, child); | |
737 | child = 0; | |
738 | return rc ?: rc2; | |
739 | } | |
740 | ||
741 | public: | |
742 | friend class remote; | |
743 | ||
10a5bce5 | 744 | virtual ~direct_stapsh() { finish(); } |
e96f2257 JS |
745 | }; |
746 | ||
747 | ||
e7e8737f JS |
748 | // Connect to an existing stapsh on a unix socket. |
749 | class unix_stapsh : public stapsh { | |
750 | private: | |
751 | ||
752 | unix_stapsh(systemtap_session& s, const uri_decoder& ud) | |
753 | : stapsh(s) | |
754 | { | |
004f2b9c JL |
755 | // Request that data be encapsulated since both stdout and stderr have |
756 | // to go over the same line. Also makes stapsh tell us when it quits. | |
757 | this->options.push_back("data"); | |
758 | ||
55881ad4 JL |
759 | // set verbosity to the requested level |
760 | for (unsigned i = 1; i < s.perpass_verbose[4]; i++) | |
761 | this->options.push_back("verbose"); | |
762 | ||
e7e8737f JS |
763 | sockaddr_un server; |
764 | server.sun_family = AF_UNIX; | |
765 | if (ud.path.empty()) | |
766 | throw runtime_error(_("unix target requires a /path")); | |
767 | if (ud.path.size() > sizeof(server.sun_path) - 1) | |
768 | throw runtime_error(_("unix target /path is too long")); | |
769 | strcpy(server.sun_path, ud.path.c_str()); | |
770 | ||
771 | if (ud.has_authority) | |
772 | throw runtime_error(_("unix target doesn't support a hostname")); | |
773 | if (ud.has_query) | |
774 | throw runtime_error(_("unix target URI doesn't support a ?query")); | |
775 | if (ud.has_fragment) | |
776 | throw runtime_error(_("unix target URI doesn't support a #fragment")); | |
777 | ||
778 | int fd = socket(AF_UNIX, SOCK_STREAM, 0); | |
779 | if (fd <= 0) | |
780 | throw runtime_error(_("error opening a socket")); | |
781 | ||
782 | if (connect(fd, (struct sockaddr *)&server, SUN_LEN(&server)) < 0) | |
783 | { | |
784 | const char *msg = strerror(errno); | |
785 | ::close(fd); | |
03501815 JL |
786 | throw runtime_error(_F("error connecting to socket %s: %s", |
787 | server.sun_path, msg)); | |
e7e8737f JS |
788 | } |
789 | ||
790 | // Try to dup it, so class stapsh can have truly separate fds for its | |
791 | // fdopen handles. If it fails for some reason, it can still work with | |
792 | // just one though. | |
793 | int fd2 = dup(fd); | |
794 | if (fd2 < 0) | |
795 | fd2 = fd; | |
796 | ||
797 | try | |
798 | { | |
799 | set_child_fds(fd, fd2); | |
800 | } | |
801 | catch (runtime_error&) | |
802 | { | |
803 | finish(); | |
804 | ::close(fd); | |
805 | ::close(fd2); | |
806 | throw; | |
807 | } | |
808 | } | |
809 | ||
810 | public: | |
811 | friend class remote; | |
812 | ||
813 | virtual ~unix_stapsh() { finish(); } | |
814 | }; | |
815 | ||
2246b6d3 JL |
816 | class libvirt_stapsh : public stapsh { |
817 | private: | |
818 | ||
819 | pid_t child; | |
820 | ||
821 | libvirt_stapsh(systemtap_session& s, const uri_decoder& ud) | |
822 | : stapsh(s) | |
823 | { | |
824 | string domain; | |
825 | string libvirt_uri; | |
826 | ||
827 | // Request that data be encapsulated since both stdout and stderr have | |
828 | // to go over the same line. Also makes stapsh tell us when it quits. | |
829 | this->options.push_back("data"); | |
830 | ||
831 | // set verbosity to the requested level | |
832 | for (unsigned i = 1; i < s.perpass_verbose[4]; i++) | |
833 | this->options.push_back("verbose"); | |
834 | ||
835 | // A valid libvirt URI has one of two forms: | |
836 | // - libvirt://DOMAIN/LIBVIRT_URI?LIBVIRT_QUERY | |
837 | // - libvirt:DOMAIN/LIBVIRT_URI?LIBVIRT_QUERY | |
838 | // We only advertise the first form, but to be nice, we also accept the | |
839 | // second form. In the first form, DOMAIN is authority, LIBVIRT_URI is | |
840 | // path, and LIBVIRT_QUERY is the query. In the second form, the DOMAIN | |
841 | // is part of the path. | |
842 | ||
843 | if (ud.has_fragment) | |
844 | throw runtime_error(_("libvirt target URI doesn't support a #fragment")); | |
845 | ||
846 | if (ud.has_authority) // first form | |
847 | { | |
848 | domain = ud.authority; | |
849 | if (!ud.path.empty()) | |
850 | { | |
851 | libvirt_uri = ud.path.substr(1); | |
852 | if (ud.has_query) | |
853 | libvirt_uri += "?" + ud.query; | |
854 | } | |
855 | } | |
856 | else // second form | |
857 | { | |
858 | if (ud.path.empty()) | |
859 | throw runtime_error(_("libvirt target URI requires a domain name")); | |
860 | ||
861 | size_t slash = ud.path.find_first_of('/'); | |
862 | if (slash == string::npos) | |
863 | domain = ud.path; | |
864 | else | |
865 | { | |
866 | domain = ud.path.substr(0, slash); | |
867 | libvirt_uri = ud.path.substr(slash+1); | |
868 | } | |
869 | } | |
870 | ||
871 | int in, out; | |
2246b6d3 JL |
872 | |
873 | string stapvirt = BINDIR "/stapvirt"; | |
874 | if (!file_exists(stapvirt)) | |
875 | throw runtime_error(_("stapvirt missing")); | |
876 | ||
d1735491 | 877 | vector<string> cmd { stapvirt }; |
2246b6d3 JL |
878 | |
879 | // carry verbosity into stapvirt | |
d1735491 JS |
880 | if (s.perpass_verbose[4] > 0) |
881 | cmd.insert(cmd.end(), s.perpass_verbose[4] - 1, "-v"); | |
2246b6d3 JL |
882 | |
883 | if (!libvirt_uri.empty()) | |
d1735491 | 884 | cmd.insert(cmd.end(), { "-c", libvirt_uri }); |
2246b6d3 | 885 | |
d1735491 | 886 | cmd.insert(cmd.end(), { "connect", domain }); |
2246b6d3 JL |
887 | |
888 | // mask signals for stapvirt since it relies instead on stap or stapsh | |
889 | // closing its connection to know when to exit (otherwise, stapvirt | |
890 | // would receive SIGINT on Ctrl-C and exit nonzero) | |
891 | ||
892 | // NB: There is an interesting issue to note here. Some libvirt URIs | |
893 | // will create child processes of stapvirt (e.g. qemu+ssh:// will | |
894 | // create an ssh process under stapvirt, also qemu+ext://, see | |
895 | // <libvirt.org/remote.html> for more info). These processes do not | |
896 | // keep the mask of stapvirt we are creating here, but are still part | |
897 | // of the same process group. | |
898 | // This means that e.g. SIGINT will not be blocked. Thus, stapvirt | |
899 | // explicitly adds a handler for SIGCHLD and forcefully disconnects | |
900 | // upon receiving it (otherwise it would await indefinitely). | |
901 | { | |
902 | stap_sigmasker masked; | |
903 | child = stap_spawn_piped(s.verbose, cmd, &in, &out); | |
904 | } | |
905 | ||
906 | if (child <= 0) | |
907 | throw runtime_error(_("error launching stapvirt")); | |
908 | ||
909 | try | |
910 | { | |
911 | set_child_fds(in, out); | |
912 | } | |
913 | catch (runtime_error&) | |
914 | { | |
915 | finish(); | |
916 | throw; | |
917 | } | |
918 | } | |
919 | ||
920 | int finish() | |
921 | { | |
922 | int rc = stapsh::finish(); | |
923 | if (child <= 0) | |
924 | return rc; | |
925 | ||
926 | int rc2 = stap_waitpid(s->verbose, child); | |
927 | child = 0; | |
928 | return rc ?: rc2; | |
929 | } | |
930 | ||
931 | public: | |
932 | friend class remote; | |
933 | ||
934 | virtual ~libvirt_stapsh() { finish(); } | |
935 | }; | |
e7e8737f | 936 | |
8d1a21b3 | 937 | // stapsh-based ssh_remote |
e96f2257 JS |
938 | class ssh_remote : public stapsh { |
939 | private: | |
940 | pid_t child; | |
941 | ||
8d1a21b3 | 942 | ssh_remote(systemtap_session& s): stapsh(s), child(0) {} |
e96f2257 | 943 | |
eacb2780 | 944 | int connect(const string& host, const string& port) |
e96f2257 | 945 | { |
8d1a21b3 JS |
946 | int rc = 0; |
947 | int in, out; | |
d1735491 | 948 | vector<string> cmd { "ssh", "-q", host }; |
eacb2780 | 949 | if (!port.empty()) |
d1735491 | 950 | cmd.insert(cmd.end(), { "-p", port }); |
8d1a21b3 JS |
951 | |
952 | // This is crafted so that we get a silent failure with status 127 if | |
953 | // the command is not found. The combination of -P and $cmd ensures | |
954 | // that we pull the command out of the PATH, not aliases or such. | |
75daad84 | 955 | string stapsh_cmd = "cmd=`type -P stapsh || exit 127` && exec \"$cmd\""; |
8d1a21b3 | 956 | if (s->perpass_verbose[4] > 1) |
75daad84 | 957 | stapsh_cmd.append(" -v"); |
8d1a21b3 | 958 | if (s->perpass_verbose[4] > 2) |
75daad84 JS |
959 | stapsh_cmd.append(" -v"); |
960 | // NB: We need to explicitly choose bash, as $SHELL may be weird... | |
961 | cmd.push_back("/bin/bash -c '" + stapsh_cmd + "'"); | |
e96f2257 | 962 | |
e96f2257 JS |
963 | // mask signals while we spawn, so we can manually send even tty |
964 | // signals *through* ssh rather than to ssh itself | |
a69b4b55 JS |
965 | { |
966 | stap_sigmasker masked; | |
967 | child = stap_spawn_piped(s->verbose, cmd, &in, &out); | |
968 | } | |
8d1a21b3 | 969 | |
e96f2257 | 970 | if (child <= 0) |
8d1a21b3 | 971 | throw runtime_error(_("error launching stapsh")); |
e96f2257 JS |
972 | |
973 | try | |
974 | { | |
975 | set_child_fds(in, out); | |
976 | } | |
977 | catch (runtime_error&) | |
978 | { | |
c00db46e JS |
979 | rc = finish(); |
980 | ||
981 | // ssh itself signals errors with 255 | |
982 | if (rc == 255) | |
983 | throw runtime_error(_("error establishing ssh connection")); | |
984 | ||
8d1a21b3 JS |
985 | // If rc == 127, that's command-not-found, so we let ::create() |
986 | // try again in legacy mode. But only do this if there's a single | |
987 | // remote, as the old code didn't handle ttys well with multiple | |
988 | // remotes. Otherwise, throw up again. *barf* | |
8d1a21b3 JS |
989 | if (rc != 127 || s->remote_uris.size() > 1) |
990 | throw; | |
e96f2257 | 991 | } |
8d1a21b3 JS |
992 | |
993 | return rc; | |
e96f2257 JS |
994 | } |
995 | ||
996 | int finish() | |
997 | { | |
998 | int rc = stapsh::finish(); | |
999 | if (child <= 0) | |
1000 | return rc; | |
1001 | ||
1002 | int rc2 = stap_waitpid(s->verbose, child); | |
1003 | child = 0; | |
1004 | return rc ?: rc2; | |
1005 | } | |
1006 | ||
1007 | public: | |
8d1a21b3 JS |
1008 | |
1009 | static remote* create(systemtap_session& s, const string& host); | |
1010 | static remote* create(systemtap_session& s, const uri_decoder& ud); | |
e96f2257 | 1011 | |
10a5bce5 | 1012 | virtual ~ssh_remote() { finish(); } |
e96f2257 | 1013 | }; |
8d1a21b3 JS |
1014 | |
1015 | ||
1016 | // ssh connection without stapsh, for legacy stap installations | |
1017 | // NB: ssh commands use a tty (-t) so signals are passed along to the remote. | |
1018 | // It does this by putting the local tty in raw mode, so it only works for tty | |
1019 | // signals, and only for a single remote at a time. | |
1020 | class ssh_legacy_remote : public remote { | |
daa75206 | 1021 | private: |
20f90026 JS |
1022 | vector<string> ssh_args, scp_args; |
1023 | string ssh_control; | |
eacb2780 | 1024 | string host, port, tmpdir; |
ebff2ed0 JS |
1025 | pid_t child; |
1026 | ||
eacb2780 JS |
1027 | ssh_legacy_remote(systemtap_session& s, const string& host, const string& port) |
1028 | : remote(s), host(host), port(port), child(0) | |
1544bdf9 | 1029 | { |
4c156a0e | 1030 | open_control_master(); |
1544bdf9 JS |
1031 | try |
1032 | { | |
1033 | get_uname(); | |
1034 | } | |
1035 | catch (runtime_error&) | |
1036 | { | |
1037 | close_control_master(); | |
1038 | throw; | |
1039 | } | |
91fa953d JS |
1040 | } |
1041 | ||
d1735491 JS |
1042 | vector<string> ssh_command(initializer_list<string> ilist) |
1043 | { | |
1044 | vector<string> cmd = ssh_args; | |
1045 | cmd.insert(cmd.end(), ilist); | |
1046 | return cmd; | |
1047 | } | |
1048 | ||
1049 | int copy_to_remote(const string& local, const string& remote) | |
1050 | { | |
1051 | vector<string> cmd = scp_args; | |
1052 | cmd.insert(cmd.end(), { local, host + ":" + remote }); | |
1053 | return stap_system(s->verbose, cmd); | |
1054 | } | |
1055 | ||
4c156a0e JS |
1056 | void open_control_master() |
1057 | { | |
1058 | static unsigned index = 0; | |
1059 | ||
1060 | if (s->tmpdir.empty()) // sanity check, shouldn't happen | |
58a834b1 | 1061 | throw runtime_error(_("No tmpdir available for ssh control master")); |
4c156a0e JS |
1062 | |
1063 | ssh_control = s->tmpdir + "/ssh_remote_control_" + lex_cast(++index); | |
20f90026 | 1064 | |
d1735491 JS |
1065 | scp_args = { "scp", "-q", "-o", "ControlPath=" + ssh_control }; |
1066 | ssh_args = { "ssh", "-q", "-o", "ControlPath=" + ssh_control, host }; | |
4c156a0e | 1067 | |
eacb2780 JS |
1068 | if (!port.empty()) |
1069 | { | |
d1735491 JS |
1070 | scp_args.insert(scp_args.end(), { "-P", port }); |
1071 | ssh_args.insert(ssh_args.end(), { "-p", port }); | |
eacb2780 JS |
1072 | } |
1073 | ||
4c156a0e JS |
1074 | // NB: ssh -f will stay in the foreground until authentication is |
1075 | // complete and the control socket is created, so we know it's ready to | |
1076 | // go when stap_system returns. | |
d1735491 | 1077 | auto cmd = ssh_command({ "-f", "-N", "-M" }); |
4c156a0e JS |
1078 | int rc = stap_system(s->verbose, cmd); |
1079 | if (rc != 0) | |
8d1a21b3 JS |
1080 | throw runtime_error(_F("failed to create an ssh control master for %s : rc= %d", |
1081 | host.c_str(), rc)); | |
4c156a0e JS |
1082 | |
1083 | if (s->verbose>1) | |
58a834b1 LB |
1084 | clog << _F("Created ssh control master at %s", |
1085 | lex_cast_qstring(ssh_control).c_str()) << endl; | |
4c156a0e JS |
1086 | } |
1087 | ||
1088 | void close_control_master() | |
1089 | { | |
1090 | if (ssh_control.empty()) | |
1091 | return; | |
1092 | ||
d1735491 | 1093 | auto cmd = ssh_command({ "-O", "exit" }); |
2323da5e | 1094 | int rc = stap_system(s->verbose, cmd, true, true); |
4c156a0e | 1095 | if (rc != 0) |
58a834b1 LB |
1096 | cerr << _F("failed to stop the ssh control master for %s : rc=%d", |
1097 | host.c_str(), rc) << endl; | |
4c156a0e JS |
1098 | |
1099 | ssh_control.clear(); | |
20f90026 JS |
1100 | scp_args.clear(); |
1101 | ssh_args.clear(); | |
4c156a0e JS |
1102 | } |
1103 | ||
91fa953d | 1104 | void get_uname() |
daa75206 JS |
1105 | { |
1106 | ostringstream out; | |
1107 | vector<string> uname; | |
d1735491 | 1108 | auto cmd = ssh_command({ "-t", "uname -rm" }); |
20f90026 | 1109 | int rc = stap_system_read(s->verbose, cmd, out); |
daa75206 JS |
1110 | if (rc == 0) |
1111 | tokenize(out.str(), uname, " \t\r\n"); | |
1112 | if (uname.size() != 2) | |
58a834b1 | 1113 | throw runtime_error(_F("failed to get uname from %s : rc= %d", host.c_str(), rc)); |
4c156a0e JS |
1114 | const string& release = uname[0]; |
1115 | const string& arch = uname[1]; | |
1116 | // XXX need to deal with command-line vs. implied arch/release | |
1117 | this->s = s->clone(arch, release); | |
daa75206 JS |
1118 | } |
1119 | ||
ebff2ed0 | 1120 | int start() |
daa75206 JS |
1121 | { |
1122 | int rc; | |
ebff2ed0 JS |
1123 | string localmodule = s->tmpdir + "/" + s->module_name + ".ko"; |
1124 | string tmpmodule; | |
daa75206 JS |
1125 | |
1126 | // Make a remote tempdir. | |
1127 | { | |
1128 | ostringstream out; | |
1129 | vector<string> vout; | |
d1735491 | 1130 | auto cmd = ssh_command({ "-t", "mktemp -d -t stapXXXXXX" }); |
ebff2ed0 | 1131 | rc = stap_system_read(s->verbose, cmd, out); |
daa75206 | 1132 | if (rc == 0) |
b2cbfd40 | 1133 | tokenize(out.str(), vout, "\r\n"); |
daa75206 JS |
1134 | if (vout.size() != 1) |
1135 | { | |
58a834b1 LB |
1136 | cerr << _F("failed to make a tempdir on %s : rc=%d", |
1137 | host.c_str(), rc) << endl; | |
daa75206 JS |
1138 | return -1; |
1139 | } | |
1140 | tmpdir = vout[0]; | |
ebff2ed0 | 1141 | tmpmodule = tmpdir + "/" + s->module_name + ".ko"; |
daa75206 JS |
1142 | } |
1143 | ||
18630fb8 JS |
1144 | // Transfer the module. |
1145 | if (rc == 0) | |
1146 | { | |
d1735491 | 1147 | rc = copy_to_remote(localmodule, tmpmodule); |
18630fb8 JS |
1148 | if (rc != 0) |
1149 | cerr << _F("failed to copy the module to %s : rc=%d", | |
1150 | host.c_str(), rc) << endl; | |
1151 | } | |
1152 | ||
1153 | // Transfer the module signature. | |
1154 | if (rc == 0 && file_exists(localmodule + ".sgn")) | |
1155 | { | |
d1735491 | 1156 | rc = copy_to_remote(localmodule + ".sgn", tmpmodule + ".sgn"); |
18630fb8 JS |
1157 | if (rc != 0) |
1158 | cerr << _F("failed to copy the module signature to %s : rc=%d", | |
1159 | host.c_str(), rc) << endl; | |
1160 | } | |
1161 | ||
1162 | // What about transfering uprobes.ko? In this ssh "legacy" mode, we | |
1163 | // don't the remote systemtap version, but -uPATH wasn't added until | |
1164 | // 1.4. Rather than risking a getopt error, we'll just assume that | |
1165 | // this isn't supported. The remote will just have to provide its own | |
1166 | // uprobes.ko in SYSTEMTAP_RUNTIME or already loaded. | |
daa75206 JS |
1167 | |
1168 | // Run the module on the remote. | |
1169 | if (rc == 0) { | |
4112f219 | 1170 | // We don't know the actual version, but all <=1.3 are approx equal. |
18630fb8 JS |
1171 | vector<string> staprun_cmd = make_run_command(*s, tmpdir, "1.3"); |
1172 | staprun_cmd[0] = "staprun"; // NB: The remote decides its own path | |
5137a7a9 FCE |
1173 | // NB: PR13354: we assume legacy installations don't have |
1174 | // staprun -r support, so we ignore staprun_r_arg. | |
d1735491 | 1175 | auto cmd = ssh_command({ "-t", cmdstr_join(staprun_cmd) }); |
ebff2ed0 JS |
1176 | pid_t pid = stap_spawn(s->verbose, cmd); |
1177 | if (pid > 0) | |
4c156a0e JS |
1178 | child = pid; |
1179 | else | |
1180 | { | |
58a834b1 LB |
1181 | cerr << _F("failed to run the module on %s : ret=%d", |
1182 | host.c_str(), pid) << endl; | |
4c156a0e JS |
1183 | rc = -1; |
1184 | } | |
daa75206 JS |
1185 | } |
1186 | ||
4c156a0e | 1187 | return rc; |
ebff2ed0 | 1188 | } |
daa75206 | 1189 | |
ebff2ed0 JS |
1190 | int finish() |
1191 | { | |
4c156a0e | 1192 | int rc = 0; |
ebff2ed0 | 1193 | |
4c156a0e JS |
1194 | if (child > 0) |
1195 | { | |
1196 | rc = stap_waitpid(s->verbose, child); | |
1197 | child = 0; | |
1198 | } | |
ebff2ed0 JS |
1199 | |
1200 | if (!tmpdir.empty()) | |
4c156a0e JS |
1201 | { |
1202 | // Remove the tempdir. | |
1203 | // XXX need to make sure this runs even with e.g. CTRL-C exits | |
d1735491 | 1204 | auto cmd = ssh_command({ "-t", "rm -r " + cmdstr_quoted(tmpdir) }); |
4c156a0e JS |
1205 | int rc2 = stap_system(s->verbose, cmd); |
1206 | if (rc2 != 0) | |
58a834b1 LB |
1207 | cerr << _F("failed to delete the tempdir on %s : rc=%d", |
1208 | host.c_str(), rc2) << endl; | |
4c156a0e JS |
1209 | if (rc == 0) |
1210 | rc = rc2; | |
20f90026 | 1211 | tmpdir.clear(); |
4c156a0e JS |
1212 | } |
1213 | ||
1214 | close_control_master(); | |
1215 | ||
1216 | return rc; | |
daa75206 | 1217 | } |
4f244435 JS |
1218 | |
1219 | public: | |
8d1a21b3 | 1220 | friend class ssh_remote; |
4f244435 | 1221 | |
8d1a21b3 | 1222 | virtual ~ssh_legacy_remote() |
4f244435 JS |
1223 | { |
1224 | close_control_master(); | |
1225 | } | |
daa75206 | 1226 | }; |
8d1a21b3 JS |
1227 | |
1228 | ||
1229 | // Try to establish a stapsh connection to the remote, but fallback | |
1230 | // to the older mechanism if the command is not found. | |
1231 | remote* | |
eacb2780 | 1232 | ssh_remote::create(systemtap_session& s, const string& target) |
8d1a21b3 | 1233 | { |
eacb2780 JS |
1234 | string port, host = target; |
1235 | size_t i = host.find(':'); | |
1236 | if (i != string::npos) | |
1237 | { | |
1238 | port = host.substr(i + 1); | |
1239 | host.erase(i); | |
1240 | } | |
1241 | ||
1b6156ad | 1242 | unique_ptr<ssh_remote> p (new ssh_remote(s)); |
eacb2780 | 1243 | int rc = p->connect(host, port); |
8d1a21b3 JS |
1244 | if (rc == 0) |
1245 | return p.release(); | |
1246 | else if (rc == 127) // stapsh command not found | |
eacb2780 | 1247 | return new ssh_legacy_remote(s, host, port); // try legacy instead |
8d1a21b3 JS |
1248 | return NULL; |
1249 | } | |
1250 | ||
1251 | remote* | |
1252 | ssh_remote::create(systemtap_session& s, const uri_decoder& ud) | |
1253 | { | |
1254 | if (!ud.has_authority || ud.authority.empty()) | |
1255 | throw runtime_error(_("ssh target requires a hostname")); | |
1256 | if (!ud.path.empty() && ud.path != "/") | |
1257 | throw runtime_error(_("ssh target URI doesn't support a /path")); | |
1258 | if (ud.has_query) | |
1259 | throw runtime_error(_("ssh target URI doesn't support a ?query")); | |
1260 | if (ud.has_fragment) | |
1261 | throw runtime_error(_("ssh target URI doesn't support a #fragment")); | |
1262 | ||
1263 | return create(s, ud.authority); | |
1264 | } | |
daa75206 JS |
1265 | |
1266 | ||
1267 | remote* | |
5137a7a9 | 1268 | remote::create(systemtap_session& s, const string& uri, int idx) |
daa75206 | 1269 | { |
5137a7a9 | 1270 | remote *it = 0; |
daa75206 JS |
1271 | try |
1272 | { | |
2bce499b | 1273 | if (uri.find(':') != string::npos) |
91fa953d JS |
1274 | { |
1275 | const uri_decoder ud(uri); | |
eacb2780 JS |
1276 | |
1277 | // An ssh "host:port" is ambiguous with a URI "scheme:path". | |
1278 | // So if it looks like a number, just assume ssh. | |
1279 | if (!ud.has_authority && !ud.has_query && | |
1280 | !ud.has_fragment && !ud.path.empty() && | |
1281 | ud.path.find_first_not_of("1234567890") == string::npos) | |
5137a7a9 FCE |
1282 | it = ssh_remote::create(s, uri); |
1283 | else if (ud.scheme == "direct") | |
1284 | it = new direct(s); | |
2bce499b | 1285 | else if (ud.scheme == "stapsh") |
5137a7a9 | 1286 | it = new direct_stapsh(s); |
e7e8737f | 1287 | else if (ud.scheme == "unix") |
5137a7a9 | 1288 | it = new unix_stapsh(s, ud); |
2246b6d3 JL |
1289 | else if (ud.scheme == "libvirt") |
1290 | it = new libvirt_stapsh(s, ud); | |
5137a7a9 FCE |
1291 | else if (ud.scheme == "ssh") |
1292 | it = ssh_remote::create(s, ud); | |
91fa953d | 1293 | else |
8d1a21b3 JS |
1294 | throw runtime_error(_F("unrecognized URI scheme '%s' in remote: %s", |
1295 | ud.scheme.c_str(), uri.c_str())); | |
91fa953d | 1296 | } |
daa75206 JS |
1297 | else |
1298 | // XXX assuming everything else is ssh for now... | |
5137a7a9 | 1299 | it = ssh_remote::create(s, uri); |
daa75206 JS |
1300 | } |
1301 | catch (std::runtime_error& e) | |
1302 | { | |
8d1a21b3 | 1303 | cerr << e.what() << " on remote '" << uri << "'" << endl; |
5137a7a9 | 1304 | it = 0; |
daa75206 | 1305 | } |
5137a7a9 | 1306 | |
15700f9b | 1307 | if (it && idx >= 0) // PR13354: remote metadata for staprun -r IDX:URI |
5137a7a9 FCE |
1308 | { |
1309 | stringstream r_arg; | |
1310 | r_arg << idx << ":" << uri; | |
1311 | it->staprun_r_arg = r_arg.str(); | |
1312 | } | |
1313 | ||
1314 | return it; | |
daa75206 JS |
1315 | } |
1316 | ||
4f244435 JS |
1317 | int |
1318 | remote::run(const vector<remote*>& remotes) | |
1319 | { | |
1320 | // NB: the first failure "wins" | |
1321 | int ret = 0, rc = 0; | |
1322 | ||
1323 | for (unsigned i = 0; i < remotes.size() && !pending_interrupts; ++i) | |
3fe776ae | 1324 | { |
756cfd49 JS |
1325 | remote *r = remotes[i]; |
1326 | r->s->verbose = r->s->perpass_verbose[4]; | |
1327 | if (r->s->use_remote_prefix) | |
1328 | r->prefix = lex_cast(i) + ": "; | |
1329 | rc = r->prepare(); | |
3fe776ae JS |
1330 | if (rc) |
1331 | return rc; | |
1332 | } | |
1333 | ||
1334 | for (unsigned i = 0; i < remotes.size() && !pending_interrupts; ++i) | |
4f244435 JS |
1335 | { |
1336 | rc = remotes[i]->start(); | |
1337 | if (!ret) | |
1338 | ret = rc; | |
1339 | } | |
1340 | ||
e96f2257 | 1341 | // mask signals while we're preparing to poll |
a69b4b55 JS |
1342 | { |
1343 | stap_sigmasker masked; | |
e96f2257 | 1344 | |
a69b4b55 JS |
1345 | // polling loop for remotes that have fds to watch |
1346 | for (;;) | |
1347 | { | |
1348 | vector<pollfd> fds; | |
1349 | for (unsigned i = 0; i < remotes.size(); ++i) | |
1350 | remotes[i]->prepare_poll (fds); | |
1351 | if (fds.empty()) | |
1352 | break; | |
1353 | ||
1354 | rc = ppoll (&fds[0], fds.size(), NULL, &masked.old); | |
1355 | if (rc < 0 && errno != EINTR) | |
1356 | break; | |
1357 | ||
1358 | for (unsigned i = 0; i < remotes.size(); ++i) | |
1359 | remotes[i]->handle_poll (fds); | |
1360 | } | |
1361 | } | |
e96f2257 | 1362 | |
4f244435 JS |
1363 | for (unsigned i = 0; i < remotes.size(); ++i) |
1364 | { | |
1365 | rc = remotes[i]->finish(); | |
1366 | if (!ret) | |
1367 | ret = rc; | |
1368 | } | |
1369 | ||
1370 | return ret; | |
1371 | } | |
1372 | ||
be7e131b MC |
1373 | int |
1374 | remote::run1(const vector<remote*>& remotes) | |
1375 | { | |
1376 | // NB: the first failure "wins" | |
1377 | int ret = 0, rc = 0; | |
1378 | ||
1379 | // Only prepare the staprun commandline, don't spawn staprun. | |
1380 | // Run under unprivileged user. | |
1381 | ||
1382 | for (unsigned i = 0; i < remotes.size() && !pending_interrupts; ++i) | |
1383 | { | |
1384 | remote *r = remotes[i]; | |
1385 | r->s->verbose = r->s->perpass_verbose[4]; | |
1386 | if (r->s->use_remote_prefix) | |
1387 | r->prefix = lex_cast(i) + ": "; | |
1388 | rc = r->prepare(); | |
1389 | if (rc) | |
1390 | return rc; | |
1391 | } | |
1392 | return ret; | |
1393 | } | |
1394 | ||
1395 | int | |
1396 | remote::run2(const vector<remote*>& remotes) | |
1397 | { | |
1398 | // NB: the first failure "wins" | |
1399 | int ret = 0, rc = 0; | |
1400 | ||
1401 | ||
1402 | // The staprun commandline is already prepared. | |
1403 | // Spawn staprun using it. | |
1404 | // Run under privileged user. | |
1405 | ||
1406 | ||
1407 | for (unsigned i = 0; i < remotes.size() && !pending_interrupts; ++i) | |
1408 | { | |
1409 | rc = remotes[i]->start(); | |
1410 | if (!ret) | |
1411 | ret = rc; | |
1412 | } | |
1413 | ||
1414 | // mask signals while we're preparing to poll | |
1415 | { | |
1416 | stap_sigmasker masked; | |
1417 | ||
1418 | // polling loop for remotes that have fds to watch | |
1419 | for (;;) | |
1420 | { | |
1421 | vector<pollfd> fds; | |
1422 | for (unsigned i = 0; i < remotes.size(); ++i) | |
1423 | remotes[i]->prepare_poll (fds); | |
1424 | if (fds.empty()) | |
1425 | break; | |
1426 | ||
1427 | rc = ppoll (&fds[0], fds.size(), NULL, &masked.old); | |
1428 | if (rc < 0 && errno != EINTR) | |
1429 | break; | |
1430 | ||
1431 | for (unsigned i = 0; i < remotes.size(); ++i) | |
1432 | remotes[i]->handle_poll (fds); | |
1433 | } | |
1434 | } | |
1435 | ||
1436 | for (unsigned i = 0; i < remotes.size(); ++i) | |
1437 | { | |
1438 | rc = remotes[i]->finish(); | |
1439 | if (!ret) | |
1440 | ret = rc; | |
1441 | } | |
1442 | ||
1443 | return ret; | |
1444 | } | |
1445 | ||
1446 | ||
daa75206 JS |
1447 | |
1448 | /* vim: set sw=2 ts=8 cino=>4,n-2,{2,^-2,t0,(0,u0,w1,M1 : */ |