github.com/jlmucb/cloudproxy@v0.0.0-20170830161738-b5aa0b619bc4/go/apps/tao_launch/tao_launch.go (about) 1 // Copyright (c) 2014, Google, Inc. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package main 16 17 // Job control, signals, and stdio for running hosted programs: 18 // 19 // If stdin is a terminal, we proxy input over a pipe instead of handing it 20 // directly to the hosted program. We also proxy a few signals, like SIGINT, 21 // SIGKILL, SIGHUP, SIGTSTP, and SIGCONT. This mostly lets you use normal job 22 // control with Control-C, Control-V, &, fg, and bg. 23 // 24 // Known bugs and limitations: 25 // - SIGTTOU and `stty stop` is not supported at all 26 // - Attempting to run in the background as the head of a pipeline is broken; 27 // the hosted program will run, but all other processes in the pipeline will 28 // probably stop due to a misleading SIGTTIN. 29 // - Running in the background (not as part of a pipeline) works, but the shell 30 // (and ps) will report that we are stopped waiting for input. 31 // - There are various race conditions if signals arrive back-to-back. 32 // 33 // Otherwise, simple job control, signals, and redirection mostly kinda works. 34 // But the implementation is all a bit fragile and broken. Here is why. 35 // 36 // Handing stdin to a hosted program causes problems with job control. Normally, 37 // a process gets a SIGTTIN (which, by default, causes it to stop) when it 38 // writes to a stream if all of these are true: 39 // (1) the stream is a tty, 40 // (2) the tty is the controlling terminal of the process, 41 // (3) the process is in the background (or maybe non-foreground?) group for 42 // the session associated with that tty 43 // When we hand stdin to a hosted program, (2) and maybe (3) are false, so it 44 // never gets the signal. The terminal is associated with our session, and there 45 // isn't a reliable way for the hosted program to join our session. We could 46 // possibly create a new pty, but that seems complex. So we aren't going to ever 47 // meet conditions (2) and (3). There is one problematic scenario: if stdin is 48 // our controlling tty, and when we are put into the background, then the hosted 49 // program will steal input from the console. Unfortunately, this seems like a 50 // common scenario. 51 // 52 // If we proxy stdin, the hosted program will read from our pipe, while we 53 // continously read from the tty and write to the pipe. When we are put into the 54 // background, we will get SIGTTIN almost immediately, because we are 55 // continuously reading. We have a few options for handling the SIGTTIN: 56 // (a) We can stop ourselves with a SIGSTOP. The shell will report that we are 57 // stopped waiting for input. That won't stop the hosted program 58 // immediately, but it will probably block on stdin eventually. If we 59 // proxy stdout and stderr, those would stop too, unfortunately, breaking 60 // background mode. So we shouldn't proxy stdout and stderr in this case. 61 // There is a downside to this, explained below. And, the shell's notice 62 // that we are stopped waiting for input will be misleading, since the 63 // output will keep going. Moreover, the SIGTTIN will cause other 64 // processes in our process group (e.g. other parts of a pipeline) to 65 // stop. 66 // (b) We can try to stop only the input proxying when we get a SIGTTIN. The 67 // The downside is that the shell will not realize that the hosted program is 68 // blocked waiting for input, since we don't actually stop. This seems 69 // like a minor problem. 70 // Sadly, (b) is apparently impossible in go: go installs the SIGTTIN handler as 71 // SA_RESTART, there is no way to suspend the proxying goroutine nor a way to 72 // cause its read of stdin to return EINTR or EIO, and there is no way within go 73 // to ignore SIGTTIN at the OS level. 74 // 75 // Handing stdout or stderr to a hosted program may cause slight problems. 76 // Normally, a process gets a SIGTTOU (which, by default, causes it to stop) 77 // when it writes to a stream if the above (1), (2), and (3) are true, and: 78 // (4) and 'stty tostop' is in effect for that tty. 79 // Again, we aren't going to meet conditions (2) and (3). Fortunately, (4) is 80 // probably false in most cases anyway. Or, at least, I have never heard of it 81 // before and have never set that option. So having broken SIGTTOU delivery is 82 // probably okay. 83 // 84 // If we proxy stdout and stderr instead, when the hosted program writes to our 85 // pipe, we will read it, then write to the tty. If a SIGTTOU is needed, it will 86 // be delivered to us. We can catch it, stop the hosted program with a SIGTTOU, 87 // and stop ourselves with a SIGSTOP. The hosted program can ignore the SIGTTOU 88 // and keep writing, but it will get buffered in the pipe and not appear on 89 // screen. 90 // 91 // In summary, our best options are: 92 // 93 // Option 1: Don't proxy stdout or stderr. If stdin is our controlling 94 // terminal, then proxy it. When put into the background, the shell will 95 // immediately report that we are stopped waiting for input, but confusingly 96 // the output may keep going. SIGTTOU is not supported at all. The code is at 97 // least somewhat clean. 98 // 99 // Option 2: Proxy anything that is our controlling terminal. When put into 100 // the background, then shell will never report that we are stopped waiting 101 // for input. When we get SIGTTIN, catch the signal and cause the proxying on 102 // stdin to stop, but allow the proxying on stdout and stderr to continue. 103 // Unfortunately, this is tricky in go. There is a goroutine that continuously 104 // does read() on stdin. When we catch SIGTTIN, the read() on stdin seems to 105 // get restarted (via ERESTART?) immediately, leading to another SIGTTIN, and 106 // so on, in a tight loop. If we could set SIGTTIN to SIG_IGN, then I believe 107 // read() would fail with EIO instead of restarting, allowing the goroutine to 108 // detect the error and stop trying to read() the tty. Sadly, it isn't obvious 109 // how to call sigaction SIG_IGN in go, and it isn't clear if that would 110 // interfere with go's own signal handling efforts. 111 112 import ( 113 "flag" 114 "fmt" 115 "io" 116 "io/ioutil" 117 "net" 118 "os" 119 "os/signal" 120 "path" 121 "regexp" 122 "strings" 123 "syscall" 124 "text/tabwriter" 125 "time" 126 "unsafe" 127 128 "github.com/jlmucb/cloudproxy/go/tao" 129 "github.com/jlmucb/cloudproxy/go/tao/auth" 130 "github.com/jlmucb/cloudproxy/go/util" 131 "github.com/jlmucb/cloudproxy/go/util/options" 132 ) 133 134 var opts = []options.Option{ 135 // Flags for all commands 136 {"tao_domain", "", "<dir>", "Tao domain configuration directory", "all,all+run"}, 137 {"host", "", "<dir>", "Host configuration, relative to domain directory or absolute", "all,all+run"}, 138 } 139 140 var run_opts = []options.Option{ 141 // Flags for run 142 {"pidfile", "", "<file>", "Write hosted program pid to this file", "run,all+run"}, 143 {"namefile", "", "<file>", "Write hosted program subprin name to this file", "run,all+run"}, 144 {"disown", false, "", "Don't wait for hosted program to exit", "run,all+run"}, 145 {"daemon", false, "", "Don't pipe stdio or wait for hosted program to exit", "run,all+run"}, 146 {"verbose", false, "", "Be more verbose", "run,all+run"}, 147 } 148 149 func init() { 150 options.Add(opts...) 151 switch path.Base(os.Args[0]) { 152 case "tao_stop", "tao_kill", "tao_list": 153 case "tao_run": 154 options.Add(run_opts...) 155 default: 156 options.Add(run_opts...) 157 } 158 } 159 160 var noise = ioutil.Discard 161 162 func help() { 163 w := new(tabwriter.Writer) 164 w.Init(os.Stderr, 4, 0, 2, ' ', 0) 165 av0 := path.Base(os.Args[0]) 166 167 switch av0 { 168 case "tao_stop", "tao_kill": 169 fmt.Fprintf(w, "Usage: %s [options] <subprin> [subprin...]\n", av0) 170 categories := []options.Category{ 171 {"all", "Options"}, 172 {"logging", "Options to control log output"}, 173 } 174 options.ShowRelevant(w, categories...) 175 case "tao_list": 176 fmt.Fprintf(w, "Usage: %s [options]\n", av0) 177 categories := []options.Category{ 178 {"all", "Options"}, 179 {"logging", "Options to control log output"}, 180 } 181 options.ShowRelevant(w, categories...) 182 case "tao_run": 183 fmt.Fprintf(w, "Usage: %s [options] <prog> [args...]\n", av0) 184 categories := []options.Category{ 185 {"all+run", "Options"}, 186 {"logging", "Options to control log output"}, 187 } 188 options.ShowRelevant(w, categories...) 189 default: 190 fmt.Fprintf(w, "Tao Hosted Program Utility\n") 191 fmt.Fprintf(w, "Usage:\n") 192 fmt.Fprintf(w, " %s run [options] [process:]<prog> [args...]\t Run a new hosted process\n", av0) 193 fmt.Fprintf(w, " %s run [options] docker:<img> [dockerargs...] [-- [imgargs...]]\t Run a new hosted docker image\n", av0) 194 fmt.Fprintf(w, " %s run [options] kvm_coreos:<img> [dockerargs...] [-- [imgargs...]]\t Run a new hosted QEMU/kvm CoreOS image\n", av0) 195 fmt.Fprintf(w, " %s run [options] kvm_custom:<img> [-- <kernel image path> <initram image path> <SSH port>]\t Run a new hosted QEMU/kvm custom instance\n", av0) 196 fmt.Fprintf(w, " %s list [options]\t List hosted programs\n", av0) 197 fmt.Fprintf(w, " %s stop [options] subprin [subprin...]\t Stop hosted programs\n", av0) 198 fmt.Fprintf(w, " %s stop [options] subprin [subprin...]\t Kill hosted programs\n", av0) 199 categories := []options.Category{ 200 {"all", "Basic options for all commands"}, 201 {"run", "Options for 'run' command"}, 202 {"logging", "Options to control log output"}, 203 } 204 options.ShowRelevant(w, categories...) 205 } 206 w.Flush() 207 } 208 209 func main() { 210 flag.Usage = help 211 212 cmd := "help" 213 switch av0 := path.Base(os.Args[0]); av0 { 214 case "tao_run", "tao_list", "tao_stop", "tao_kill": 215 cmd = av0[4:] 216 flag.Parse() 217 default: 218 // Get options before the command verb 219 flag.Parse() 220 // Get command verb 221 if flag.NArg() > 0 { 222 cmd = flag.Arg(0) 223 } 224 // Get options after the command verb 225 if flag.NArg() > 1 { 226 flag.CommandLine.Parse(flag.Args()[1:]) 227 } 228 } 229 230 if b, ok := options.Bool["verbose"]; ok && *b { 231 noise = os.Stderr 232 } 233 234 sockPath := path.Join(hostPath(), "admin_socket") 235 conn, err := net.DialUnix("unix", nil, &net.UnixAddr{Name: sockPath, Net: "unix"}) 236 options.FailIf(err, "Can't connect to host admin socket") 237 defer conn.Close() 238 239 client := tao.NewLinuxHostAdminClient(conn) 240 switch cmd { 241 case "help": 242 help() 243 case "run": 244 runHosted(&client, flag.Args()) 245 case "stop": 246 for _, s := range flag.Args() { 247 var subprin auth.SubPrin 248 _, err := fmt.Sscanf(s, "%v", &subprin) 249 options.FailIf(err, "Not a subprin: %s", s) 250 err = client.StopHostedProgram(subprin) 251 options.FailIf(err, "Could not stop %s", s) 252 } 253 case "kill": 254 for _, s := range flag.Args() { 255 var subprin auth.SubPrin 256 options.FailIf(err, "Not a subprin: %s", s) 257 err = client.KillHostedProgram(subprin) 258 options.FailIf(err, "Could not kill %s", s) 259 } 260 case "list": 261 names, pids, err := client.ListHostedPrograms() 262 options.FailIf(err, "Can't list hosted programs") 263 for i, p := range pids { 264 fmt.Printf("pid=%d subprin=%v\n", p, names[i]) 265 } 266 fmt.Printf("%d hosted programs\n", len(pids)) 267 default: 268 options.Usage("Unrecognized command: %s", cmd) 269 } 270 271 return 272 } 273 274 const moment = 100 * time.Millisecond 275 276 func split(a []string, delim string) (before []string, after []string) { 277 for i, s := range a { 278 if s == delim { 279 before = append(before, a[0:i]...) 280 after = append(after, a[i+1:]...) 281 return 282 } 283 } 284 before = append(before, a...) 285 return 286 } 287 288 func runHosted(client *tao.LinuxHostAdminClient, args []string) { 289 var err error 290 291 if len(args) == 0 { 292 options.Usage("Missing program path and arguments") 293 } 294 295 spec := new(tao.HostedProgramSpec) 296 297 ctype := "process" 298 spec.Path = args[0] 299 for _, prefix := range []string{"process", "docker", "kvm_coreos", "kvm_custom"} { 300 if strings.HasPrefix(spec.Path, prefix+":") { 301 ctype = prefix 302 spec.Path = strings.TrimPrefix(spec.Path, prefix+":") 303 } 304 } 305 306 switch ctype { 307 case "process": 308 dirs := util.LiberalSearchPath() 309 binary := util.FindExecutable(args[0], dirs) 310 if binary == "" { 311 options.Fail(nil, "Can't find `%s` on path '%s'", args[0], strings.Join(dirs, ":")) 312 } 313 spec.ContainerArgs = []string{spec.Path} 314 spec.Args = args[1:] 315 spec.Path = binary 316 case "docker", "kvm_coreos": 317 // args contains [ "docker:argv0", docker_args..., "--", prog_args... ] 318 spec.ContainerArgs, spec.Args = split(args, "--") 319 // Replace docker arg 0 with valid image name constructed from // base(argv[0]). 320 r, _ := regexp.Compile("[^a-zA-Z0-9_.]+") 321 spec.ContainerArgs[0] = r.ReplaceAllLiteralString(path.Base(spec.Path), "_") 322 case "kvm_custom": 323 _, spec.Args = split(args, "--") 324 } 325 326 pidfile := *options.String["pidfile"] 327 var pidOut *os.File 328 if pidfile == "-" { 329 pidOut = os.Stdout 330 } else if pidfile != "" { 331 pidOut, err = os.Create(pidfile) 332 options.FailIf(err, "Can't open pid file") 333 } 334 335 namefile := *options.String["namefile"] 336 var nameOut *os.File 337 if namefile == "-" { 338 nameOut = os.Stdout 339 } else if namefile != "" { 340 nameOut, err = os.Create(namefile) 341 options.FailIf(err, "Can't open name file") 342 } 343 344 daemon := *options.Bool["daemon"] 345 disown := *options.Bool["disown"] 346 347 var pr, pw *os.File 348 proxying := false 349 tty := isCtty(int(os.Stdin.Fd())) 350 if daemon { 351 // stdio is nil 352 } else if disown { 353 // We are assuming that if stdin is a terminal, it is our controlling 354 // terminal. I don't know any way to verify it, but it seems likely. 355 if tty { 356 // stdin is nil, else they would steal input from tty 357 } else { 358 spec.Stdin = os.Stdin 359 } 360 spec.Stdout = os.Stdout 361 spec.Stderr = os.Stderr 362 } else { 363 // interactive 364 proxying = tty 365 if proxying { 366 pr, pw, err = os.Pipe() 367 options.FailIf(err, "Can't pipe") 368 spec.Stdin = pr 369 } else { 370 spec.Stdin = os.Stdin 371 } 372 spec.Stdout = os.Stdout 373 spec.Stderr = os.Stderr 374 fmt.Fprintf(noise, "[proxying stdin]\n") 375 } 376 377 spec.Dir, err = os.Getwd() 378 options.FailIf(err, "Can't get working directory") 379 380 // Start catching signals early, buffering a few, so we don't miss any. We 381 // don't proxy SIGTTIN. However, we do catch it and stop ourselves, rather 382 // than letting the OS stop us. This is necessary so that we can send 383 // SIGCONT to the child at the right times. 384 // Here is the easy case 385 // we start in background 386 // we fork (output starts going) 387 // we are background, so leave SIGTTIN handling at the default 388 // we read and get SIGTTIN, so are stopped 389 // child is not stopped, it keeps outputting, as desired 390 // upon fg, we get SIGCONT, start dropping SIGTTIN and looping for input and signals 391 // Here is the tricky case: 392 // we start in foreground 393 // we fork (output starts going) 394 // we are foreground, so catch and drop SIGTTIN (we use SIGTSTP instead) 395 // we get SIGTSTP via ctrl-z 396 // we send child SIGTSTP, so it stops 397 // [we are still dropping SIGTTIN] 398 // we send ourselves SIGSTOP, so we stop 399 // we get SIGCONT via either bg or fg 400 // [if bg, now furiously catching and dropping SIGTTIN] 401 // [if fg, dropping too, but there should not be any SIGTTIN] 402 // send child the SIGCONT 403 // if we are foreground, so go back to top of loop 404 // if we are background, reset SIGTTIN which causes us to stop 405 // 406 // The basic invariant we are trying to maintain is that when we are 407 // foreground we catch and drop SIGTTIN, allowing us to properly handle 408 // SIGTSTP events. There shouldn't be any SIGTTIN anyway, except for the 409 // brief moments when we are transitioning to stopped. 410 // And when the child is supposed to be running in the background, we should 411 // leave the default SIGTTIN behavior, so that the OS will stop our read 412 // loop. 413 414 signals := make(chan os.Signal, 10) // some buffering 415 signal.Notify(signals, 416 syscall.SIGINT, // Ctrl-C 417 syscall.SIGTERM, // SIGINT wannabe (e.g. via kill) 418 syscall.SIGQUIT, // Ctrl-\ 419 syscall.SIGTSTP, // Ctrl-Z 420 syscall.SIGHUP, // tty hangup (e.g. via disown) 421 syscall.SIGABRT, // abort (e.g. via kill) 422 syscall.SIGUSR1, // user-defined (e.g. via kill) 423 syscall.SIGUSR2, // user-defined (e.g. via kill) 424 ) 425 426 // Start the hosted program 427 subprin, pid, err := client.StartHostedProgram(spec) 428 options.FailIf(err, "Can't start hosted program") 429 fmt.Fprintf(noise, "[started hosted program with pid %d]\n", pid) 430 fmt.Fprintf(noise, "[subprin is %v]\n", subprin) 431 432 if pidOut != nil { 433 fmt.Fprintln(pidOut, pid) 434 pidOut.Close() 435 } 436 437 if nameOut != nil { 438 fmt.Fprintln(nameOut, subprin) 439 nameOut.Close() 440 } 441 442 if disown || daemon { 443 return 444 } 445 446 // Listen for exit status from host 447 status := make(chan int, 1) 448 go func() { 449 s, _ := client.WaitHostedProgram(pid, subprin) 450 // For short programs, we often lose the race, so 451 // we get a "no such hosted program" error. Ignore it. 452 status <- s 453 }() 454 455 wasForeground := false 456 if proxying && isForeground() { 457 fmt.Fprintf(noise, "[in foreground]\n") 458 dropSIGTTIN() 459 wasForeground = true 460 } 461 462 // Proxy stdin, if needed 463 if proxying { 464 pr.Close() 465 go func() { 466 _, err := io.Copy(pw, os.Stdin) 467 options.WarnIf(err, "Can't copy from stdin to pipe") 468 pw.Close() 469 }() 470 } 471 472 // If we are proxying and (were) background, we should probably 473 // have done a read() by now and gotten SIGTTIN and stopped. Let's 474 // pause a moment to be sure the read() happens. 475 time.Sleep(moment) 476 477 // By this point, if we had been foreground, we might still be. Or, we might 478 // have been foreground but just gotten SIGTSTP and are now madly dropping 479 // SIGTTIN until we get into the loop below to handle the SIGTSTP. 480 // 481 // Alternatively, if we had been background, we would have been stopped by 482 // the default SIGTTIN, so the only way we would be here is if we later got 483 // pulled foreground via fg. We want to be dropping SIGTTIN in case we get a 484 // SIGTSTP. 485 if proxying && !wasForeground { 486 dropSIGTTIN() 487 } 488 489 next := cont 490 for next != done { 491 select { 492 case s := <-status: 493 fmt.Fprintf(noise, "[hosted program exited, %s]\n", exitCode(s)) 494 next = done 495 case sig := <-signals: 496 next = handle(sig, pid) 497 } 498 if next == resumed && proxying && !isForeground() { 499 // Need to toggle SIGTTIN handling and block (that's the only way to 500 // stop spinning on SIGTTIN), but only after handling all pending 501 // signals (e.g. SIGCONT then SIGHUP, or SIGCONT then SIGTERM). 502 for next == resumed { 503 select { 504 case s := <-status: 505 fmt.Fprintf(noise, "[hosted program exited, %s]\n", exitCode(s)) 506 next = done 507 case sig := <-signals: 508 next = handle(sig, pid) 509 if next == cont { 510 next = resumed 511 } 512 default: 513 next = cont 514 defaultSIGTTIN() 515 time.Sleep(moment) 516 dropSIGTTIN() 517 } 518 } 519 } 520 } 521 signal.Stop(signals) 522 } 523 524 type todo int 525 526 const ( 527 done todo = iota // we are done, time to exit 528 resumed // we just woke up 529 cont // neither of the above 530 ) 531 532 func handle(sig os.Signal, pid int) todo { 533 switch sig { 534 case syscall.SIGTSTP: 535 send(pid, syscall.SIGTSTP) 536 fmt.Fprintf(noise, "[stopping]\n") 537 syscall.Kill(syscall.Getpid(), syscall.SIGSTOP) 538 time.Sleep(moment) 539 fmt.Fprintf(noise, "[resuming]\n") 540 send(pid, syscall.SIGCONT) 541 return resumed 542 case syscall.SIGHUP: // tty hangup (e.g. via disown) 543 noise = ioutil.Discard 544 os.Stdin.Close() 545 os.Stdout.Close() 546 os.Stderr.Close() 547 send(pid, sig.(syscall.Signal)) 548 // Our tty is gone, so there is little left to do. We could hang 549 // around proxying signals (e.g. those sent via kill). But those 550 // could be just as easily sent directly to the hosted program, 551 // so let's not bother. 552 return done 553 default: 554 send(pid, sig.(syscall.Signal)) 555 } 556 return cont 557 } 558 559 var discard = make(chan os.Signal, 1) // minimal buffering 560 561 func defaultSIGTTIN() { 562 fmt.Fprintf(noise, "[default SIGTTIN handling]\n") 563 signal.Stop(discard) 564 } 565 566 func dropSIGTTIN() { 567 fmt.Fprintf(noise, "[dropping SIGTTIN]\n") 568 signal.Notify(discard, syscall.SIGTTIN) 569 } 570 571 func isForeground() bool { 572 tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) 573 if err != nil { 574 575 } 576 fpgrp := 0 577 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, tty.Fd(), syscall.TIOCGPGRP, uintptr(unsafe.Pointer(&fpgrp))) 578 if errno != 0 { 579 } 580 return syscall.Getpgrp() == fpgrp 581 } 582 583 // Note: There is a slight race here. If pids are reused very quickly, we might 584 // end up sending a signal to the wrong child just after the hosted program 585 // exits. That seems unlikely. To fix it, we would have to coordinate with 586 // linux_host, e.g. have linux_host send the signal on our behalf. 587 func send(pid int, sig syscall.Signal) { 588 fmt.Fprintf(noise, "[sending %s to hosted program]\n", sigName(sig)) 589 syscall.Kill(pid, sig) 590 } 591 592 func sigName(sig syscall.Signal) string { 593 var name string 594 switch sig { 595 case syscall.SIGINT: 596 name = "SIGINT" 597 case syscall.SIGTERM: 598 name = "SIGTERM" 599 case syscall.SIGQUIT: 600 name = "SIGQUIT" 601 case syscall.SIGTSTP: 602 name = "SIGTSTP" 603 case syscall.SIGHUP: 604 name = "SIGHUP" 605 case syscall.SIGABRT: 606 name = "SIGABRT" 607 case syscall.SIGUSR1: 608 name = "SIGUSR1" 609 case syscall.SIGUSR2: 610 name = "SIGUSR2" 611 case syscall.SIGCONT: 612 name = "SIGCONT" 613 case syscall.SIGSTOP: 614 name = "SIGSTOP" 615 } 616 return fmt.Sprintf("%s (%d)", name, int(sig)) 617 } 618 619 func exitCode(s int) string { 620 ws := syscall.WaitStatus(s) 621 if ws.Exited() { 622 return fmt.Sprintf("status %d", ws.ExitStatus()) 623 } else if ws.Signaled() && ws.CoreDump() { 624 return fmt.Sprintf("signal %v, with core dump", ws.Signal()) 625 } else if ws.Signaled() { 626 return fmt.Sprintf("signal %v", ws.Signal()) 627 } else if ws.Stopped() { 628 return fmt.Sprintf("stopped by %v", ws.StopSignal()) 629 } else if ws.Continued() { 630 return fmt.Sprintf("continued") 631 } else { 632 return fmt.Sprintf("exit status unknown") 633 } 634 } 635 636 func domainPath() string { 637 if path := *options.String["tao_domain"]; path != "" { 638 return path 639 } 640 if path := os.Getenv("TAO_DOMAIN"); path != "" { 641 return path 642 } 643 options.Usage("Must supply -tao_domain or set $TAO_DOMAIN") 644 return "" 645 } 646 647 func hostPath() string { 648 hostPath := *options.String["host"] 649 if hostPath == "" { 650 hostPath = "linux_tao_host" 651 } 652 if !path.IsAbs(hostPath) { 653 hostPath = path.Join(domainPath(), hostPath) 654 } 655 return hostPath 656 }