github.com/ladydascalie/elvish@v0.0.0-20170703214355-2964dd3ece7f/daemon/daemon.go (about)

     1  // Package exec provides the entry point of the daemon sub-program and helpers
     2  // to spawn a daemon process.
     3  package daemon
     4  
     5  import (
     6  	"errors"
     7  	"log"
     8  	"os"
     9  	"path"
    10  	"path/filepath"
    11  	"strconv"
    12  	"strings"
    13  	"syscall"
    14  
    15  	"github.com/elves/elvish/util"
    16  )
    17  
    18  // Daemon keeps configurations for the daemon process.
    19  type Daemon struct {
    20  	Forked        int
    21  	BinPath       string
    22  	DbPath        string
    23  	SockPath      string
    24  	LogPathPrefix string
    25  }
    26  
    27  // closeFd is used in syscall.ProcAttr.Files to signify closing a fd.
    28  const closeFd = ^uintptr(0)
    29  
    30  // Main is the entry point of the daemon sub-program.
    31  func (d *Daemon) Main(serve func(string, string)) int {
    32  	switch d.Forked {
    33  	case 0:
    34  		errored := false
    35  		absify := func(f string, s *string) {
    36  			if *s == "" {
    37  				log.Println("flag", f, "is required for daemon")
    38  				errored = true
    39  				return
    40  			}
    41  			p, err := filepath.Abs(*s)
    42  			if err != nil {
    43  				log.Println("abs:", err)
    44  				errored = true
    45  			} else {
    46  				*s = p
    47  			}
    48  		}
    49  		absify("-bin", &d.BinPath)
    50  		absify("-db", &d.DbPath)
    51  		absify("-sock", &d.SockPath)
    52  		absify("-logprefix", &d.LogPathPrefix)
    53  		if errored {
    54  			return 2
    55  		}
    56  
    57  		syscall.Umask(0077)
    58  		return d.pseudoFork(
    59  			&syscall.ProcAttr{
    60  				// cd to /
    61  				Dir: "/",
    62  				// empty environment
    63  				Env: nil,
    64  				// inherit stderr only for logging
    65  				Files: []uintptr{closeFd, closeFd, 2},
    66  				Sys:   &syscall.SysProcAttr{Setsid: true},
    67  			})
    68  	case 1:
    69  		return d.pseudoFork(
    70  			&syscall.ProcAttr{
    71  				Files: []uintptr{closeFd, closeFd, 2},
    72  			})
    73  	case 2:
    74  		serve(d.SockPath, d.DbPath)
    75  		return 0
    76  	default:
    77  		return 2
    78  	}
    79  }
    80  
    81  // Spawn spawns a daemon in the background. It is supposed to be called from a
    82  // client.
    83  func (d *Daemon) Spawn() error {
    84  	binPath := d.BinPath
    85  	// Determine binPath.
    86  	if binPath == "" {
    87  		if len(os.Args) > 0 && path.IsAbs(os.Args[0]) {
    88  			binPath = os.Args[0]
    89  		} else {
    90  			// Find elvish in PATH
    91  			paths := strings.Split(os.Getenv("PATH"), ":")
    92  			result, err := util.Search(paths, "elvish")
    93  			if err != nil {
    94  				return errors.New("cannot find elvish: " + err.Error())
    95  			}
    96  			binPath = result
    97  		}
    98  	}
    99  	return forkExec(nil, 0, binPath, d.DbPath, d.SockPath, d.LogPathPrefix)
   100  }
   101  
   102  // pseudoFork forks a daemon. It is supposed to be called from the daemon.
   103  func (d *Daemon) pseudoFork(attr *syscall.ProcAttr) int {
   104  	err := forkExec(attr, d.Forked+1, d.BinPath, d.DbPath, d.SockPath, d.LogPathPrefix)
   105  	if err != nil {
   106  		return 2
   107  	}
   108  	return 0
   109  }
   110  
   111  func forkExec(attr *syscall.ProcAttr, forkLevel int, binPath, dbPath, sockPath, logPathPrefix string) error {
   112  	_, err := syscall.ForkExec(binPath, []string{
   113  		binPath,
   114  		"-daemon",
   115  		"-forked", strconv.Itoa(forkLevel),
   116  		"-bin", binPath,
   117  		"-db", dbPath,
   118  		"-sock", sockPath,
   119  		"-logprefix", logPathPrefix,
   120  	}, attr)
   121  	return err
   122  }