@ -20,6 +20,7 @@ package main // import "tailscale.com/cmd/tailscaled"
// to C:\ to run it, like tswin does.
import (
"bufio"
"context"
"encoding/json"
"errors"
@ -27,6 +28,10 @@ import (
"log"
"net/netip"
"os"
"os/exec"
"os/signal"
"sync"
"syscall"
"time"
"github.com/dblohm7/wingoes/com"
@ -38,6 +43,7 @@ import (
"tailscale.com/ipn/ipnserver"
"tailscale.com/ipn/store"
"tailscale.com/logpolicy"
"tailscale.com/logtail/backoff"
"tailscale.com/net/dns"
"tailscale.com/net/tsdial"
"tailscale.com/net/tstun"
@ -129,7 +135,7 @@ func (service *ipnService) Execute(args []string, r <-chan svc.ChangeRequest, ch
// writer that logpolicy already installed as the global
// output.
logger := log . New ( log . Default ( ) . Writer ( ) , "" , 0 )
ipnserver . B abysitProc( ctx , args , logger . Printf )
b abysitProc( ctx , args , logger . Printf )
} ( )
changes <- svc . Status { State : svc . Running , Accepts : svcAccepts }
@ -427,3 +433,126 @@ func windowsUptime() time.Duration {
r , _ , _ := getTickCount64Proc . Call ( )
return time . Duration ( int64 ( r ) ) * time . Millisecond
}
// babysitProc runs the current executable as a child process with the
// provided args, capturing its output, writing it to files, and
// restarting the process on any crashes.
func babysitProc ( ctx context . Context , args [ ] string , logf logger . Logf ) {
executable , err := os . Executable ( )
if err != nil {
panic ( "cannot determine executable: " + err . Error ( ) )
}
var proc struct {
mu sync . Mutex
p * os . Process
}
done := make ( chan struct { } )
go func ( ) {
interrupt := make ( chan os . Signal , 1 )
signal . Notify ( interrupt , syscall . SIGINT , syscall . SIGTERM )
var sig os . Signal
select {
case sig = <- interrupt :
logf ( "babysitProc: got signal: %v" , sig )
close ( done )
case <- ctx . Done ( ) :
logf ( "babysitProc: context done" )
sig = os . Kill
close ( done )
}
proc . mu . Lock ( )
proc . p . Signal ( sig )
proc . mu . Unlock ( )
} ( )
bo := backoff . NewBackoff ( "babysitProc" , logf , 30 * time . Second )
for {
startTime := time . Now ( )
log . Printf ( "exec: %#v %v" , executable , args )
cmd := exec . Command ( executable , args ... )
// Create a pipe object to use as the subproc's stdin.
// When the writer goes away, the reader gets EOF.
// A subproc can watch its stdin and exit when it gets EOF;
// this is a very reliable way to have a subproc die when
// its parent (us) disappears.
// We never need to actually write to wStdin.
rStdin , wStdin , err := os . Pipe ( )
if err != nil {
log . Printf ( "os.Pipe 1: %v" , err )
return
}
// Create a pipe object to use as the subproc's stdout/stderr.
// We'll read from this pipe and send it to logf, line by line.
// We can't use os.exec's io.Writer for this because it
// doesn't care about lines, and thus ends up merging multiple
// log lines into one or splitting one line into multiple
// logf() calls. bufio is more appropriate.
rStdout , wStdout , err := os . Pipe ( )
if err != nil {
log . Printf ( "os.Pipe 2: %v" , err )
}
go func ( r * os . File ) {
defer r . Close ( )
rb := bufio . NewReader ( r )
for {
s , err := rb . ReadString ( '\n' )
if s != "" {
logf ( "%s" , s )
}
if err != nil {
break
}
}
} ( rStdout )
cmd . Stdin = rStdin
cmd . Stdout = wStdout
cmd . Stderr = wStdout
err = cmd . Start ( )
// Now that the subproc is started, get rid of our copy of the
// pipe reader. Bad things happen on Windows if more than one
// process owns the read side of a pipe.
rStdin . Close ( )
wStdout . Close ( )
if err != nil {
log . Printf ( "starting subprocess failed: %v" , err )
} else {
proc . mu . Lock ( )
proc . p = cmd . Process
proc . mu . Unlock ( )
err = cmd . Wait ( )
log . Printf ( "subprocess exited: %v" , err )
}
// If the process finishes, clean up the write side of the
// pipe. We'll make a new one when we restart the subproc.
wStdin . Close ( )
if os . Getenv ( "TS_DEBUG_RESTART_CRASHED" ) == "0" {
log . Fatalf ( "Process ended." )
}
if time . Since ( startTime ) < 60 * time . Second {
bo . BackOff ( ctx , fmt . Errorf ( "subproc early exit: %v" , err ) )
} else {
// Reset the timeout, since the process ran for a while.
bo . BackOff ( ctx , nil )
}
select {
case <- done :
return
default :
}
}
}