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