]> sourceware.org Git - systemtap.git/blame - remote.cxx
remote: Split preparation from the actual start
[systemtap.git] / remote.cxx
CommitLineData
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
9extern "C" {
e96f2257
JS
10#include <fcntl.h>
11#include <sys/types.h>
12#include <sys/stat.h>
13#include <unistd.h>
daa75206
JS
14}
15
e96f2257 16#include <cstdio>
bf9c0b28 17#include <iomanip>
daa75206
JS
18#include <stdexcept>
19#include <sstream>
20#include <string>
21#include <vector>
22
23#include "buildrun.h"
24#include "remote.h"
25#include "util.h"
26
27using namespace std;
28
91fa953d
JS
29// Decode URIs as per RFC 3986, though not bothering to be strict
30class uri_decoder {
31 public:
32 const string uri;
33 string scheme, authority, path, query, fragment;
34 bool has_authority, has_query, has_fragment;
35
36 uri_decoder(const string& uri):
37 uri(uri), has_authority(false), has_query(false), has_fragment(false)
38 {
39 const string re =
40 "^([^:]+):(//[^/?#]*)?([^?#]*)(\\?[^#]*)?(#.*)?$";
41
42 vector<string> matches;
43 if (regexp_match(uri, re, matches) != 0)
44 throw runtime_error("string doesn't appear to be a URI: " + uri);
45
46 scheme = matches[1];
47
48 if (!matches[2].empty())
49 {
50 has_authority = true;
51 authority = matches[2].substr(2);
52 }
53
54 path = matches[3];
55
56 if (!matches[4].empty())
57 {
58 has_query = true;
59 query = matches[4].substr(1);
60 }
61
62 if (!matches[5].empty())
63 {
64 has_fragment = true;
65 fragment = matches[5].substr(1);
66 }
67 }
68};
69
70
daa75206
JS
71// loopback target for running locally
72class direct : public remote {
ebff2ed0
JS
73 private:
74 pid_t child;
75 direct(systemtap_session& s): remote(s), child(0) {}
daa75206 76
ebff2ed0
JS
77 int start()
78 {
5eea6ed1 79 pid_t pid = stap_spawn (s->verbose, make_run_command (*s));
4c156a0e
JS
80 if (pid <= 0)
81 return 1;
82 child = pid;
83 return 0;
ebff2ed0
JS
84 }
85
86 int finish()
87 {
4c156a0e
JS
88 if (child <= 0)
89 return 1;
ebff2ed0 90
4c156a0e
JS
91 int ret = stap_waitpid(s->verbose, child);
92 child = 0;
93 return ret;
ebff2ed0 94 }
4f244435
JS
95
96 public:
97 friend class remote;
98
99 virtual ~direct() {}
daa75206
JS
100};
101
e96f2257
JS
102
103class stapsh : public remote {
104 private:
105 int interrupts_sent;
106 int fdin, fdout;
107 FILE *IN, *OUT;
108
109 virtual void prepare_poll(vector<pollfd>& fds)
110 {
111 if (fdout >= 0 && OUT)
112 {
113 pollfd p = { fdout, POLLIN };
114 fds.push_back(p);
115 }
116
117 // need to send a signal?
118 if (fdin >= 0 && IN && interrupts_sent < pending_interrupts)
119 {
120 pollfd p = { fdin, POLLOUT };
121 fds.push_back(p);
122 }
123 }
124
125 virtual void handle_poll(vector<pollfd>& fds)
126 {
127 for (unsigned i=0; i < fds.size(); ++i)
128 if (fds[i].revents)
129 {
130 if (fdout >= 0 && OUT && fds[i].fd == fdout)
131 {
132 if (fds[i].revents & POLLIN)
133 {
134 // XXX should we do line-buffering?
135 char buf[4096];
136 size_t rc = fread(buf, 1, sizeof(buf), OUT);
137 if (rc > 0)
138 {
139 cout.write(buf, rc);
140 continue;
141 }
142 }
143 close();
144 }
145
146 // need to send a signal?
147 if (fdin >= 0 && IN && fds[i].fd == fdin &&
148 interrupts_sent < pending_interrupts)
149 {
150 if (fds[i].revents & POLLOUT)
151 {
152 ostringstream cmd;
153 cmd << "signal " << SIGINT << "\n";
154 if (send_command(cmd.str()) == 0)
155 {
156 ++interrupts_sent;
157 continue;
158 }
159 }
160 close();
161 }
162 }
163 }
164
165 int send_command(const string& cmd)
166 {
167 if (!IN)
168 return 2;
169 if (fputs(cmd.c_str(), IN) < 0 ||
170 fflush(IN) != 0)
171 return 1;
172 return 0;
173 }
174
175 int send_file(const string& filename, const string& dest)
176 {
177 int rc = 0;
178 FILE* f = fopen(filename.c_str(), "r");
179 if (!f)
180 return 1;
181
182 struct stat fs;
183 rc = fstat(fileno(f), &fs);
184 if (!rc)
185 {
186 ostringstream cmd;
187 cmd << "file " << fs.st_size << " " << dest << "\n";
188 rc = send_command(cmd.str());
189 }
190
191 off_t i = 0;
192 while (!rc && i < fs.st_size)
193 {
194 char buf[4096];
195 size_t r = sizeof(buf);
196 if (fs.st_size - i < (off_t)r)
197 r = fs.st_size - i;
198 r = fread(buf, 1, r, f);
199 if (r == 0)
200 rc = 1;
201 else
202 {
203 size_t w = fwrite(buf, 1, r, IN);
204 if (w != r)
205 rc = 1;
206 else
207 i += w;
208 }
209 }
210 if (!rc)
211 rc = fflush(IN);
212
213 fclose(f);
214 return rc;
215 }
216
bf9c0b28 217 static string qpencode(const string& str)
e96f2257 218 {
bf9c0b28
JS
219 ostringstream o;
220 o << setfill('0') << hex;
221 for (const char* s = str.c_str(); *s; ++s)
222 if (*s >= 33 && *s <= 126 && *s != 61)
223 o << *s;
224 else
225 o << '=' << setw(2) << (unsigned)(unsigned char) *s;
226 return o.str();
e96f2257
JS
227 }
228
229 protected:
230 stapsh(systemtap_session& s)
231 : remote(s), interrupts_sent(0),
232 fdin(-1), fdout(-1), IN(0), OUT(0)
233 {}
234
3fe776ae 235 virtual int prepare()
e96f2257
JS
236 {
237 int rc = 0;
238
239 string localmodule = s->tmpdir + "/" + s->module_name + ".ko";
240 string remotemodule = s->module_name + ".ko";
241 if ((rc = send_file(localmodule, remotemodule)))
242 return rc;
243
244 // XXX upload module.sig [and uprobes, uprobes.sig]
245
3fe776ae
JS
246 return rc;
247 }
248
249 virtual int start()
250 {
e96f2257
JS
251 // Send the staprun args
252 // NB: The remote is left to decide its own staprun path
bf9c0b28 253 ostringstream run("run", ios::out | ios::ate);
3fe776ae 254 vector<string> cmd = make_run_command(*s, s->module_name + ".ko");
e96f2257 255 for (unsigned i = 1; i < cmd.size(); ++i)
bf9c0b28
JS
256 run << ' ' << qpencode(cmd[i]);
257 run << '\n';
e96f2257 258
3fe776ae 259 int rc = send_command(run.str());
e96f2257
JS
260
261 if (!rc)
262 {
263 long flags = fcntl(fdout, F_GETFL) | O_NONBLOCK;
264 fcntl(fdout, F_SETFL, flags);
265 }
266
267 return rc;
268 }
269
270 void close()
271 {
272 if (IN) fclose(IN);
273 if (OUT) fclose(OUT);
274 IN = OUT = NULL;
275 fdin = fdout = -1;
276 }
277
278 virtual int finish()
279 {
280 close();
281 return 0;
282 }
283
284 void set_child_fds(int in, int out)
285 {
286 if (fdin >= 0 || fdout >= 0 || IN || OUT)
287 throw runtime_error("stapsh file descriptors already set!");
288
289 fdin = in;
290 fdout = out;
291 IN = fdopen(fdin, "w");
292 OUT = fdopen(fdout, "r");
293 if (!IN || !OUT)
294 throw runtime_error("invalid file descriptors for stapsh!");
295
296 if (send_command("stap " VERSION "\n"))
297 throw runtime_error("error sending hello to stapsh!");
298
299 char reply[1024];
300 if (!fgets(reply, sizeof(reply), OUT))
301 throw runtime_error("error receiving hello from stapsh!");
302
303 // stapsh VERSION MACHINE RELEASE
304 vector<string> uname;
305 tokenize(reply, uname, " \t\r\n");
306 if (uname.size() != 4 || uname[0] != "stapsh")
307 throw runtime_error("failed to get uname from stapsh");
308 // XXX check VERSION compatibility
309
310 this->s = s->clone(uname[2], uname[3]);
311 }
312
313 public:
314 virtual ~stapsh() { close(); }
315};
316
317
318class direct_stapsh : public stapsh {
319 private:
320 pid_t child;
321
322 direct_stapsh(systemtap_session& s)
323 : stapsh(s), child(0)
324 {
325 int in, out;
326 vector<string> cmd;
327 cmd.push_back(BINDIR "/stapsh");
328 child = stap_spawn_piped(s.verbose, cmd, &in, &out);
329 if (child <= 0)
330 throw runtime_error("error launching stapsh!");
331
332 try
333 {
334 set_child_fds(in, out);
335 }
336 catch (runtime_error&)
337 {
338 finish();
339 throw;
340 }
341 }
342
343 virtual int finish()
344 {
345 int rc = stapsh::finish();
346 if (child <= 0)
347 return rc;
348
349 int rc2 = stap_waitpid(s->verbose, child);
350 child = 0;
351 return rc ?: rc2;
352 }
353
354 public:
355 friend class remote;
356
357 virtual ~direct_stapsh() {}
358};
359
360
361#if 1 // stapsh-based ssh_remote
362class ssh_remote : public stapsh {
363 private:
364 pid_t child;
365
366 ssh_remote(systemtap_session& s, const string& host)
367 : stapsh(s), child(0)
368 {
369 init(host);
370 }
371
372 ssh_remote(systemtap_session& s, const uri_decoder& ud)
373 : stapsh(s), child(0)
374 {
375 if (!ud.has_authority || ud.authority.empty())
376 throw runtime_error("ssh target requires a hostname");
377 if (!ud.path.empty() && ud.path != "/")
378 throw runtime_error("ssh target URI doesn't support a /path");
379 if (ud.has_query)
380 throw runtime_error("ssh target URI doesn't support a ?query");
381 if (ud.has_fragment)
382 throw runtime_error("ssh target URI doesn't support a #fragment");
383
384 init(ud.authority);
385 }
386
387 void init(const string& host)
388 {
389 // mask signals while we spawn, so we can manually send even tty
390 // signals *through* ssh rather than to ssh itself
391 sigset_t mask, oldmask;
392 sigemptyset (&mask);
393 sigaddset (&mask, SIGHUP);
394 sigaddset (&mask, SIGPIPE);
395 sigaddset (&mask, SIGINT);
396 sigaddset (&mask, SIGTERM);
397 sigprocmask (SIG_BLOCK, &mask, &oldmask);
398
399 int in, out;
400 vector<string> cmd;
401 cmd.push_back("ssh");
402 cmd.push_back("-q");
403 cmd.push_back(host);
404 cmd.push_back("stapsh"); // NB: relies on remote $PATH
405 child = stap_spawn_piped(s->verbose, cmd, &in, &out);
406 sigprocmask (SIG_SETMASK, &oldmask, NULL); // back to normal signals
407 if (child <= 0)
408 throw runtime_error("error launching stapsh!");
409
410 try
411 {
412 set_child_fds(in, out);
413 }
414 catch (runtime_error&)
415 {
416 finish();
417 throw;
418 }
419 }
420
421 int finish()
422 {
423 int rc = stapsh::finish();
424 if (child <= 0)
425 return rc;
426
427 int rc2 = stap_waitpid(s->verbose, child);
428 child = 0;
429 return rc ?: rc2;
430 }
431
432 public:
433 friend class remote;
434
435 virtual ~ssh_remote() { }
436};
437#else // !stapsh-based ssh_remote
daa75206 438class ssh_remote : public remote {
b2cbfd40 439 // NB: ssh commands use a tty (-t) so signals are passed along to the remote
daa75206 440 private:
20f90026
JS
441 vector<string> ssh_args, scp_args;
442 string ssh_control;
91fa953d 443 string host, tmpdir;
ebff2ed0
JS
444 pid_t child;
445
91fa953d
JS
446 ssh_remote(systemtap_session& s, const string& host)
447 : remote(s), host(host), child(0)
448 {
1544bdf9 449 init();
91fa953d
JS
450 }
451
452 ssh_remote(systemtap_session& s, const uri_decoder& ud)
453 : remote(s), child(0)
454 {
455 if (!ud.has_authority || ud.authority.empty())
456 throw runtime_error("ssh target requires a hostname");
457 if (!ud.path.empty() && ud.path != "/")
458 throw runtime_error("ssh target URI doesn't support a /path");
459 if (ud.has_query)
460 throw runtime_error("ssh target URI doesn't support a ?query");
461 if (ud.has_fragment)
462 throw runtime_error("ssh target URI doesn't support a #fragment");
463
464 host = ud.authority;
1544bdf9
JS
465 init();
466 }
467
468 void init()
469 {
4c156a0e 470 open_control_master();
1544bdf9
JS
471 try
472 {
473 get_uname();
474 }
475 catch (runtime_error&)
476 {
477 close_control_master();
478 throw;
479 }
91fa953d
JS
480 }
481
4c156a0e
JS
482 void open_control_master()
483 {
484 static unsigned index = 0;
485
486 if (s->tmpdir.empty()) // sanity check, shouldn't happen
487 throw runtime_error("No tmpdir available for ssh control master");
488
489 ssh_control = s->tmpdir + "/ssh_remote_control_" + lex_cast(++index);
20f90026
JS
490
491 scp_args.clear();
492 scp_args.push_back("scp");
493 scp_args.push_back("-q");
494 scp_args.push_back("-o");
495 scp_args.push_back("ControlPath=" + ssh_control);
496
497 ssh_args = scp_args;
498 ssh_args[0] = "ssh";
499 ssh_args.push_back(host);
4c156a0e
JS
500
501 // NB: ssh -f will stay in the foreground until authentication is
502 // complete and the control socket is created, so we know it's ready to
503 // go when stap_system returns.
20f90026
JS
504 vector<string> cmd = ssh_args;
505 cmd.push_back("-f");
506 cmd.push_back("-N");
507 cmd.push_back("-M");
4c156a0e
JS
508 int rc = stap_system(s->verbose, cmd);
509 if (rc != 0)
510 {
511 ostringstream err;
512 err << "failed to create an ssh control master for " << host
513 << " : rc=" << rc;
514 throw runtime_error(err.str());
515 }
516
517 if (s->verbose>1)
518 clog << "Created ssh control master at "
519 << lex_cast_qstring(ssh_control) << endl;
520 }
521
522 void close_control_master()
523 {
524 if (ssh_control.empty())
525 return;
526
20f90026
JS
527 vector<string> cmd = ssh_args;
528 cmd.push_back("-O");
529 cmd.push_back("exit");
2323da5e 530 int rc = stap_system(s->verbose, cmd, true, true);
4c156a0e
JS
531 if (rc != 0)
532 cerr << "failed to stop the ssh control master for " << host
533 << " : rc=" << rc << endl;
534
535 ssh_control.clear();
20f90026
JS
536 scp_args.clear();
537 ssh_args.clear();
4c156a0e
JS
538 }
539
91fa953d 540 void get_uname()
daa75206
JS
541 {
542 ostringstream out;
543 vector<string> uname;
20f90026
JS
544 vector<string> cmd = ssh_args;
545 cmd.push_back("-t");
546 cmd.push_back("uname -rm");
547 int rc = stap_system_read(s->verbose, cmd, out);
daa75206
JS
548 if (rc == 0)
549 tokenize(out.str(), uname, " \t\r\n");
550 if (uname.size() != 2)
91fa953d 551 throw runtime_error("failed to get uname from " + host
daa75206 552 + " : rc=" + lex_cast(rc));
4c156a0e
JS
553 const string& release = uname[0];
554 const string& arch = uname[1];
555 // XXX need to deal with command-line vs. implied arch/release
556 this->s = s->clone(arch, release);
daa75206
JS
557 }
558
ebff2ed0 559 int start()
daa75206
JS
560 {
561 int rc;
ebff2ed0
JS
562 string localmodule = s->tmpdir + "/" + s->module_name + ".ko";
563 string tmpmodule;
daa75206
JS
564
565 // Make a remote tempdir.
566 {
567 ostringstream out;
568 vector<string> vout;
20f90026
JS
569 vector<string> cmd = ssh_args;
570 cmd.push_back("-t");
571 cmd.push_back("mktemp -d -t stapXXXXXX");
ebff2ed0 572 rc = stap_system_read(s->verbose, cmd, out);
daa75206 573 if (rc == 0)
b2cbfd40 574 tokenize(out.str(), vout, "\r\n");
daa75206
JS
575 if (vout.size() != 1)
576 {
91fa953d 577 cerr << "failed to make a tempdir on " << host
daa75206
JS
578 << " : rc=" << rc << endl;
579 return -1;
580 }
581 tmpdir = vout[0];
ebff2ed0 582 tmpmodule = tmpdir + "/" + s->module_name + ".ko";
daa75206
JS
583 }
584
585 // Transfer the module. XXX and uprobes.ko, sigs, etc.
586 if (rc == 0) {
20f90026
JS
587 vector<string> cmd = scp_args;
588 cmd.push_back(localmodule);
589 cmd.push_back(host + ":" + tmpmodule);
ebff2ed0 590 rc = stap_system(s->verbose, cmd);
daa75206 591 if (rc != 0)
91fa953d 592 cerr << "failed to copy the module to " << host
daa75206
JS
593 << " : rc=" << rc << endl;
594 }
595
596 // Run the module on the remote.
597 if (rc == 0) {
20f90026
JS
598 vector<string> cmd = ssh_args;
599 cmd.push_back("-t");
5eea6ed1 600 cmd.push_back(cmdstr_join(make_run_command(*s, tmpmodule)));
ebff2ed0
JS
601 pid_t pid = stap_spawn(s->verbose, cmd);
602 if (pid > 0)
4c156a0e
JS
603 child = pid;
604 else
605 {
606 cerr << "failed to run the module on " << host
607 << " : ret=" << pid << endl;
608 rc = -1;
609 }
daa75206
JS
610 }
611
4c156a0e 612 return rc;
ebff2ed0 613 }
daa75206 614
ebff2ed0
JS
615 int finish()
616 {
4c156a0e 617 int rc = 0;
ebff2ed0 618
4c156a0e
JS
619 if (child > 0)
620 {
621 rc = stap_waitpid(s->verbose, child);
622 child = 0;
623 }
ebff2ed0
JS
624
625 if (!tmpdir.empty())
4c156a0e
JS
626 {
627 // Remove the tempdir.
628 // XXX need to make sure this runs even with e.g. CTRL-C exits
20f90026
JS
629 vector<string> cmd = ssh_args;
630 cmd.push_back("-t");
631 cmd.push_back("rm -r " + cmdstr_quoted(tmpdir));
4c156a0e
JS
632 int rc2 = stap_system(s->verbose, cmd);
633 if (rc2 != 0)
634 cerr << "failed to delete the tempdir on " << host
635 << " : rc=" << rc2 << endl;
636 if (rc == 0)
637 rc = rc2;
20f90026 638 tmpdir.clear();
4c156a0e
JS
639 }
640
641 close_control_master();
642
643 return rc;
daa75206 644 }
4f244435
JS
645
646 public:
647 friend class remote;
648
649 virtual ~ssh_remote()
650 {
651 close_control_master();
652 }
daa75206 653};
e96f2257 654#endif
daa75206
JS
655
656
657remote*
658remote::create(systemtap_session& s, const string& uri)
659{
660 try
661 {
662 if (uri == "direct")
663 return new direct(s);
e96f2257
JS
664 else if (uri == "stapsh")
665 return new direct_stapsh(s);
91fa953d
JS
666 else if (uri.find(':') != string::npos)
667 {
668 const uri_decoder ud(uri);
669 if (ud.scheme == "ssh")
670 return new ssh_remote(s, ud);
671 else
672 {
673 ostringstream msg;
674 msg << "unrecognized URI scheme '" << ud.scheme
675 << "' in remote: " << uri;
676 throw runtime_error(msg.str());
677 }
678 }
daa75206
JS
679 else
680 // XXX assuming everything else is ssh for now...
681 return new ssh_remote(s, uri);
682 }
683 catch (std::runtime_error& e)
684 {
685 cerr << e.what() << endl;
686 return NULL;
687 }
688}
689
4f244435
JS
690int
691remote::run(const vector<remote*>& remotes)
692{
693 // NB: the first failure "wins"
694 int ret = 0, rc = 0;
695
696 for (unsigned i = 0; i < remotes.size() && !pending_interrupts; ++i)
3fe776ae
JS
697 {
698 rc = remotes[i]->prepare();
699 if (rc)
700 return rc;
701 }
702
703 for (unsigned i = 0; i < remotes.size() && !pending_interrupts; ++i)
4f244435
JS
704 {
705 rc = remotes[i]->start();
706 if (!ret)
707 ret = rc;
708 }
709
e96f2257
JS
710 // mask signals while we're preparing to poll
711 sigset_t mask, oldmask;
712 sigemptyset (&mask);
713 sigaddset (&mask, SIGHUP);
714 sigaddset (&mask, SIGPIPE);
715 sigaddset (&mask, SIGINT);
716 sigaddset (&mask, SIGTERM);
717 sigprocmask (SIG_BLOCK, &mask, &oldmask);
718
719 for (;;) // polling loop for remotes that have fds to watch
720 {
721 vector<pollfd> fds;
722 for (unsigned i = 0; i < remotes.size(); ++i)
723 remotes[i]->prepare_poll (fds);
724 if (fds.empty())
725 break;
726
727 rc = ppoll (&fds[0], fds.size(), NULL, &oldmask);
728 if (rc < 0 && errno != EINTR)
729 break;
730
731 for (unsigned i = 0; i < remotes.size(); ++i)
732 remotes[i]->handle_poll (fds);
733 }
734
735 sigprocmask (SIG_SETMASK, &oldmask, NULL);
736
4f244435
JS
737 for (unsigned i = 0; i < remotes.size(); ++i)
738 {
739 rc = remotes[i]->finish();
740 if (!ret)
741 ret = rc;
742 }
743
744 return ret;
745}
746
daa75206
JS
747
748/* vim: set sw=2 ts=8 cino=>4,n-2,{2,^-2,t0,(0,u0,w1,M1 : */
This page took 0.1022 seconds and 5 git commands to generate.