Remove old daemonization code

We will be able do it using fork+exec
This commit is contained in:
Frank Denis 2018-06-13 17:31:07 +02:00
parent 09e39c785a
commit 7f8b8d043e
12 changed files with 2 additions and 693 deletions

10
Gopkg.lock generated
View File

@ -13,12 +13,6 @@
revision = "b24eb346a94c3ba12c1da1e564dbac1b498a77ce"
version = "v1.1.1"
[[projects]]
branch = "master"
name = "github.com/VividCortex/godaemon"
packages = ["."]
revision = "3d9f6e0b234fe7d17448b345b2e14ac05814a758"
[[projects]]
branch = "master"
name = "github.com/aead/chacha20"
@ -185,7 +179,7 @@
"windows/svc/eventlog",
"windows/svc/mgr"
]
revision = "f4b713d59635e9af9041b0e1b1fd102d95812dbe"
revision = "8ee9f3e146b708d082f4bab861e5759d1edf8c00"
[[projects]]
name = "golang.org/x/text"
@ -217,6 +211,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "d5f42f6f996d902d4c18ce227affb4baf70ff089470d3f63f41679e74756f18c"
inputs-digest = "c2408310ad6e3a3b8987a6b1aeac25d10b74502190596d85af5d27ebbf8896d0"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -1,9 +0,0 @@
// +build linux
package main
import "github.com/VividCortex/godaemon"
func Daemonize() {
godaemon.MakeDaemon(&godaemon.DaemonAttr{})
}

View File

@ -1,6 +0,0 @@
// +build !linux
package main
func Daemonize() {
}

View File

@ -83,9 +83,6 @@ func (app *App) Start(service service.Service) error {
if err := InitPluginsGlobals(&proxy.pluginsGlobals, proxy); err != nil {
dlog.Fatal(err)
}
if proxy.daemonize {
Daemonize()
}
app.quit = make(chan struct{})
app.wg.Add(1)
if service != nil {

View File

@ -1,19 +0,0 @@
Copyright (c) 2013 VividCortex
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,84 +0,0 @@
godaemon
========
Daemonize Go applications with `exec()` instead of `fork()`. Read our [blog post](https://vividcortex.com/blog/2013/08/27/godaemon-a-library-to-daemonize-go-apps/) on the subject.
You can't daemonize the usual way in Go. Daemonizing is a Unix concept that requires
some [specific things](http://goo.gl/vTUsVy) you can't do
easily in Go. But you can still accomplish the same goals
if you don't mind that your program will start copies of itself
several times, as opposed to using `fork()` the way many programmers are accustomed to doing.
It is somewhat controversial whether it's even a good idea to make programs daemonize themselves,
or how to do it correctly (and whether it's even possible to do correctly in Go).
Read [here](https://code.google.com/p/go/issues/detail?id=227),
[here](http://www.ryanday.net/2012/09/04/the-problem-with-a-golang-daemon/),
and [here](http://stackoverflow.com/questions/14537045/how-i-should-run-my-golang-process-in-background)
for more on this topic. However, at [VividCortex](https://vividcortex.com/) we do need to run one of our processes as a
daemon with the usual attributes of a daemon, and we chose the approach implemented in this package.
Because of the factors mentioned in the first link just given, you should take great care when
using this package's approach. It works for us, because we don't do anything like starting up
goroutines in our `init()` functions, or other things that are perfectly legal in Go in general.
## Getting Started
View the [package documentation](http://godoc.org/github.com/VividCortex/godaemon)
for details about how it works. Briefly, to make your program into a daemon,
do the following as soon as possible in your `main()` function:
```go
import (
"github.com/VividCortex/godaemon"
)
func main() {
godaemon.MakeDaemon(&godaemon.DaemonAttr{})
}
```
Use the `CaptureOutput` attribute if you need to capture your program's
standard output and standard error streams. In that case, the function returns
two valid readers (`io.Reader`) that you can read from the program itself.
That's particularly useful for functions that write error or diagnosis messages
right to the error output, which are normally lost in a daemon.
Use the `Files` attribute if you need to inherit open files into the daemon.
This is primarily intended for avoiding race conditions when holding locks on
those files (flocks). Releasing and re-acquiring locks between successive fork
calls opens up the chance for another program to steal the lock. However, by
declaring your file descriptors in the `Files` attribute, `MakeDaemon()` will
guarantee that locks are not released throughout the whole process. Your daemon
will inherit the file still holding the same locks, with no other process having
intervened in between. See the
[package documentation](http://godoc.org/github.com/VividCortex/godaemon) for
more details and sample code. (Note that you shouldn't use this feature to
inherit TTY descriptors; otherwise what you get is technically not a daemon.)
## Contribute
Contributions are welcome. Please open pull requests or issue reports!
## License
This repository is Copyright (c) 2013 VividCortex, Inc. All rights reserved.
It is licensed under the MIT license. Please see the LICENSE file for applicable
license terms.
## Authors
The primary author is [Gustavo Kristic](https://github.com/gkristic), with some
documentation and other minor contributions by others at VividCortex.
## History
An earlier version of this concept with a slightly different interface was
developed internally at VividCortex.
## Cats
A Go Daemon is a good thing, and so we present an angelic cat picture:
![Angelic Cat](http://f.cl.ly/items/2b0y0n3W2W1H0S1K3g0g/angelic-cat.jpg)

View File

@ -1,386 +0,0 @@
// +build darwin freebsd linux
// Package godaemon runs a program as a Unix daemon.
package godaemon
// Copyright (c) 2013-2015 VividCortex, Inc. All rights reserved.
// Please see the LICENSE file for applicable license terms.
import (
"bytes"
"crypto/sha1"
"encoding/hex"
"fmt"
"io"
"os"
"strconv"
"strings"
"syscall"
"time"
)
// Environment variables to support this process
const (
stageVar = "__DAEMON_STAGE"
fdVarPrefix = "__DAEMON_FD_"
)
// DaemonAttr describes the options that apply to daemonization
type DaemonAttr struct {
ProgramName string // child's os.Args[0]; copied from parent if empty
CaptureOutput bool // whether to capture stdout/stderr
Files []**os.File // files to keep open in the daemon
}
/*
MakeDaemon turns the process into a daemon. But given the lack of Go's
support for fork(), MakeDaemon() is forced to run the process all over again,
from the start. Hence, this should probably be your first call after main
begins, unless you understand the effects of calling from somewhere else.
Keep in mind that the PID changes after this function is called, given
that it only returns in the child; the parent will exit without returning.
Options are provided as a DaemonAttr structure. In particular, setting the
CaptureOutput member to true will make the function return two io.Reader
streams to read the process' standard output and standard error, respectively.
That's useful if you want to capture things you'd normally lose given the
lack of console output for a daemon. Some libraries can write error conditions
to standard error or make use of Go's log package, that defaults to standard
error too. Having these streams allows you to capture them as required. (Note
that this function takes no action whatsoever on any of the streams.)
NOTE: If you use them, make sure NOT to take one of these readers and write
the data back again to standard output/error, or you'll end up with a loop.
Also, note that data will be flushed on a line-by-line basis; i.e., partial
lines will be buffered until an end-of-line is seen.
By using the Files member of DaemonAttr you can inherit open files that will
still be open once the program is running as a daemon. This may be convenient in
general, but it's primarily intended to avoid race conditions while forking, in
case a lock (flock) was held on that file. Repeatedly releasing and re-locking
while forking is subject to race conditions, cause a different process could
lock the file in between. But locks held on files declared at DaemonAttr.Files
are guaranteed NOT to be released during the whole process, and still be held by
the daemon. To use this feature you should open the file(s), lock if required
and then call MakeDaemon using pointers to that *os.File objects; i.e., you'd be
passing **os.File objects to MakeDaemon(). However, opening the files (and
locking if required) should only be attempted at the parent. (Recall that
MakeDaemon() will run the code coming "before" it three times; see the
explanation above.) You can filter that by calling Stage() and looking for a
godaemon.StageParent result. The last call to MakeDaemon() at the daemon itself
will actually *load* the *os.File objects for you; that's why you need to
provide a pointer to them. So here's how you'd use it:
var (
f *os.File
err error
)
if godaemon.Stage() == godaemon.StageParent {
f, err = os.OpenFile(name, opts, perm)
if err != nil {
os.Exit(1)
}
err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX)
if err != nil {
os.Exit(1)
}
}
_, _, err = godaemon.MakeDaemon(&godaemon.DaemonAttr{
Files: []**os.File{&f},
})
// Only the daemon will reach this point, where f will be a valid descriptor
// pointing to your file "name", still holding the lock (which will have
// never been released during successive forks). You can operate on f as you
// normally would, like:
f.Close()
NOTE: Do not abuse this feature. Even though you could, it's obviously not a
good idea to use this mechanism to keep a terminal device open, for instance.
Otherwise, what you get is not strictly a daemon.
Daemonizing is a 3-stage process. In stage 0, the program increments the
magical environment variable and starts a copy of itself that's a session
leader, with its STDIN, STDOUT, and STDERR disconnected from any tty. It
then exits.
In stage 1, the (new copy of) the program starts another copy that's not
a session leader, and then exits.
In stage 2, the (new copy of) the program chdir's to /, then sets the umask
and reestablishes the original value for the environment variable.
*/
func MakeDaemon(attrs *DaemonAttr) (io.Reader, io.Reader, error) {
stage, advanceStage, resetEnv := getStage()
// This is a handy wrapper to do the proper thing in case of fatal
// conditions. For the first stage you may want to recover, so it will
// return the error. Otherwise it will exit the process, cause you'll be
// half-way with some descriptors already changed. There's no chance to
// write to stdout or stderr in the later case; they'll be already closed.
fatal := func(err error) (io.Reader, io.Reader, error) {
if stage > 0 {
os.Exit(1)
}
resetEnv()
return nil, nil, err
}
fileCount := 3 + len(attrs.Files)
files := make([]*os.File, fileCount, fileCount+2)
if stage == 0 {
// Descriptors 0, 1 and 2 are fixed in the "os" package. If we close
// them, the process may choose to open something else there, with bad
// consequences if some write to os.Stdout or os.Stderr follows (even
// from Go's library itself, through the default log package). We thus
// reserve these descriptors to avoid that.
nullDev, err := os.OpenFile("/dev/null", 0, 0)
if err != nil {
return fatal(err)
}
files[0], files[1], files[2] = nullDev, nullDev, nullDev
fd := 3
for _, fPtr := range attrs.Files {
files[fd] = *fPtr
saveFileName(fd, (*fPtr).Name())
fd++
}
} else {
files[0], files[1], files[2] = os.Stdin, os.Stdout, os.Stderr
fd := 3
for _, fPtr := range attrs.Files {
*fPtr = os.NewFile(uintptr(fd), getFileName(fd))
syscall.CloseOnExec(fd)
files[fd] = *fPtr
fd++
}
}
if stage < 2 {
// getExecutablePath() is OS-specific.
procName, err := GetExecutablePath()
if err != nil {
return fatal(fmt.Errorf("can't determine full path to executable: %s", err))
}
// If getExecutablePath() returns "" but no error, determinating the
// executable path is not implemented on the host OS, so daemonization
// is not supported.
if len(procName) == 0 {
return fatal(fmt.Errorf("can't determine full path to executable"))
}
if stage == 1 && attrs.CaptureOutput {
files = files[:fileCount+2]
// stdout: write at fd:1, read at fd:fileCount
if files[fileCount], files[1], err = os.Pipe(); err != nil {
return fatal(err)
}
// stderr: write at fd:2, read at fd:fileCount+1
if files[fileCount+1], files[2], err = os.Pipe(); err != nil {
return fatal(err)
}
}
if err := advanceStage(); err != nil {
return fatal(err)
}
dir, _ := os.Getwd()
osAttrs := os.ProcAttr{Dir: dir, Env: os.Environ(), Files: files}
if stage == 0 {
sysattrs := syscall.SysProcAttr{Setsid: true}
osAttrs.Sys = &sysattrs
}
progName := attrs.ProgramName
if len(progName) == 0 {
progName = os.Args[0]
}
args := append([]string{progName}, os.Args[1:]...)
proc, err := os.StartProcess(procName, args, &osAttrs)
if err != nil {
return fatal(fmt.Errorf("can't create process %s: %s", procName, err))
}
proc.Release()
os.Exit(0)
}
os.Chdir("/")
syscall.Umask(0)
resetEnv()
for fd := 3; fd < fileCount; fd++ {
resetFileName(fd)
}
currStage = DaemonStage(stage)
var stdout, stderr *os.File
if attrs.CaptureOutput {
stdout = os.NewFile(uintptr(fileCount), "stdout")
stderr = os.NewFile(uintptr(fileCount+1), "stderr")
}
return stdout, stderr, nil
}
func saveFileName(fd int, name string) {
// We encode in hex to avoid issues with filename encoding, and to be able
// to separate it from the original variable value (if set) that we want to
// keep. Otherwise, all non-zero characters are valid in the name, and we
// can't insert a zero in the var as a separator.
fdVar := fdVarPrefix + fmt.Sprint(fd)
value := fmt.Sprintf("%s:%s",
hex.EncodeToString([]byte(name)), os.Getenv(fdVar))
if err := os.Setenv(fdVar, value); err != nil {
fmt.Fprintf(os.Stderr, "can't set %s: %s\n", fdVar, err)
os.Exit(1)
}
}
func getFileName(fd int) string {
fdVar := fdVarPrefix + fmt.Sprint(fd)
value := os.Getenv(fdVar)
sep := bytes.IndexByte([]byte(value), ':')
if sep < 0 {
fmt.Fprintf(os.Stderr, "bad fd var %s\n", fdVar)
os.Exit(1)
}
name, err := hex.DecodeString(value[:sep])
if err != nil {
fmt.Fprintf(os.Stderr, "error decoding %s\n", fdVar)
os.Exit(1)
}
return string(name)
}
func resetFileName(fd int) {
fdVar := fdVarPrefix + fmt.Sprint(fd)
value := os.Getenv(fdVar)
sep := bytes.IndexByte([]byte(value), ':')
if sep < 0 {
fmt.Fprintf(os.Stderr, "bad fd var %s\n", fdVar)
os.Exit(1)
}
if err := os.Setenv(fdVar, value[sep+1:]); err != nil {
fmt.Fprintf(os.Stderr, "can't reset %s\n", fdVar)
os.Exit(1)
}
}
// Daemonize is equivalent to MakeDaemon(&DaemonAttr{}). It is kept only for
// backwards API compatibility, but it's usage is otherwise discouraged. Use
// MakeDaemon() instead. The child parameter, previously used to tell whether
// to reset the environment or not (see MakeDaemon()), is currently ignored.
// The environment is reset in all cases.
func Daemonize(child ...bool) {
MakeDaemon(&DaemonAttr{})
}
// DaemonStage tells in what stage in the process we are. See Stage().
type DaemonStage int
// Stages in the daemonizing process.
const (
StageParent = DaemonStage(iota) // Original process
StageChild // MakeDaemon() called once: first child
StageDaemon // MakeDaemon() run twice: final daemon
stageUnknown = DaemonStage(-1)
)
// currStage keeps the current stage. This is used only as a cache for Stage(),
// in order to extend a valid result after MakeDaemon() has returned, where the
// environment variable would have already been reset. (Also, this is faster
// than repetitive calls to getStage().) Note that this approach is valid cause
// the stage doesn't change throughout any single process execution. It does
// only for the next process after the MakeDaemon() call.
var currStage = stageUnknown
// Stage returns the "stage of daemonizing", i.e., it allows you to know whether
// you're currently working in the parent, first child, or the final daemon.
// This is useless after the call to MakeDaemon(), cause that call will only
// return for the daemon stage. However, you can still use Stage() to tell
// whether you've daemonized or not, in case you have a running path that may
// exclude the call to MakeDaemon().
func Stage() DaemonStage {
if currStage == stageUnknown {
s, _, _ := getStage()
currStage = DaemonStage(s)
}
return currStage
}
// String returns a humanly readable daemonization stage.
func (s DaemonStage) String() string {
switch s {
case StageParent:
return "parent"
case StageChild:
return "first child"
case StageDaemon:
return "daemon"
default:
return "unknown"
}
}
// Returns the current stage in the "daemonization process", that's kept in
// an environment variable. The variable is instrumented with a digital
// signature, to avoid misbehavior if it was present in the user's
// environment. The original value is restored after the last stage, so that
// there's no final effect on the environment the application receives.
func getStage() (stage int, advanceStage func() error, resetEnv func() error) {
var origValue string
stage = 0
daemonStage := os.Getenv(stageVar)
stageTag := strings.SplitN(daemonStage, ":", 2)
stageInfo := strings.SplitN(stageTag[0], "/", 3)
if len(stageInfo) == 3 {
stageStr, tm, check := stageInfo[0], stageInfo[1], stageInfo[2]
hash := sha1.New()
hash.Write([]byte(stageStr + "/" + tm + "/"))
if check != hex.EncodeToString(hash.Sum([]byte{})) {
// This whole chunk is original data
origValue = daemonStage
} else {
stage, _ = strconv.Atoi(stageStr)
if len(stageTag) == 2 {
origValue = stageTag[1]
}
}
} else {
origValue = daemonStage
}
advanceStage = func() error {
base := fmt.Sprintf("%d/%09d/", stage+1, time.Now().Nanosecond())
hash := sha1.New()
hash.Write([]byte(base))
tag := base + hex.EncodeToString(hash.Sum([]byte{}))
if err := os.Setenv(stageVar, tag+":"+origValue); err != nil {
return fmt.Errorf("can't set %s: %s", stageVar, err)
}
return nil
}
resetEnv = func() error {
return os.Setenv(stageVar, origValue)
}
return stage, advanceStage, resetEnv
}

View File

@ -1,40 +0,0 @@
package godaemon
// Copyright (c) 2013 VividCortex, Inc. All rights reserved.
// Please see the LICENSE file for applicable license terms.
//#include <mach-o/dyld.h>
import "C"
import (
"bytes"
"fmt"
"path/filepath"
"unsafe"
)
// GetExecutablePath returns the absolute path to the currently running
// executable. It is used internally by the godaemon package, and exported
// publicly because it's useful outside of the package too.
func GetExecutablePath() (string, error) {
PATH_MAX := 1024 // From <sys/syslimits.h>
exePath := make([]byte, PATH_MAX)
exeLen := C.uint32_t(len(exePath))
status, err := C._NSGetExecutablePath((*C.char)(unsafe.Pointer(&exePath[0])), &exeLen)
if err != nil {
return "", fmt.Errorf("_NSGetExecutablePath: %v", err)
}
// Not sure why this might happen with err being nil, but...
if status != 0 {
return "", fmt.Errorf("_NSGetExecutablePath returned %d", status)
}
// Convert from null-padded []byte to a clean string. (Can't simply cast!)
exePathStringLen := bytes.Index(exePath, []byte{0})
exePathString := string(exePath[:exePathStringLen])
return filepath.Clean(exePathString), nil
}

View File

@ -1,49 +0,0 @@
package godaemon
// Copyright (c) 2013 VividCortex, Inc. All rights reserved.
// Please see the LICENSE file for applicable license terms.
//#include <sys/types.h>
//#include <sys/sysctl.h>
import "C"
import (
"bytes"
"fmt"
"path/filepath"
"unsafe"
)
// GetExecutablePath returns the absolute path to the currently running
// executable. It is used internally by the godaemon package, and exported
// publicly because it's useful outside of the package too.
func GetExecutablePath() (string, error) {
PATH_MAX := 1024 // From <sys/syslimits.h>
exePath := make([]byte, PATH_MAX)
exeLen := C.size_t(len(exePath))
// Beware: sizeof(int) != sizeof(C.int)
var mib [4]C.int
// From <sys/sysctl.h>
mib[0] = 1 // CTL_KERN
mib[1] = 14 // KERN_PROC
mib[2] = 12 // KERN_PROC_PATHNAME
mib[3] = -1
status, err := C.sysctl((*C.int)(unsafe.Pointer(&mib[0])), 4, unsafe.Pointer(&exePath[0]), &exeLen, nil, 0)
if err != nil {
return "", fmt.Errorf("sysctl: %v", err)
}
// Not sure why this might happen with err being nil, but...
if status != 0 {
return "", fmt.Errorf("sysctl returned %d", status)
}
// Convert from null-padded []byte to a clean string. (Can't simply cast!)
exePathStringLen := bytes.Index(exePath, []byte{0})
exePathString := string(exePath[:exePathStringLen])
return filepath.Clean(exePathString), nil
}

View File

@ -1,22 +0,0 @@
package godaemon
// Copyright (c) 2013 VividCortex, Inc. All rights reserved.
// Please see the LICENSE file for applicable license terms.
import (
"fmt"
"path/filepath"
)
// GetExecutablePath returns the absolute path to the currently running
// executable. It is used internally by the godaemon package, and exported
// publicly because it's useful outside of the package too.
func GetExecutablePath() (string, error) {
exePath, err := Readlink("/proc/self/exe")
if err != nil {
err = fmt.Errorf("can't read /proc/self/exe: %v", err)
}
return filepath.Clean(exePath), err
}

View File

@ -1,29 +0,0 @@
package godaemon
// Copyright (c) 2013-2015 VividCortex, Inc. All rights reserved.
// Please see the LICENSE file for applicable license terms.
import (
"fmt"
"syscall"
"unicode/utf16"
"unsafe"
)
var (
getModuleFileName = syscall.MustLoadDLL("kernel32.dll").MustFindProc("GetModuleFileNameW")
)
// GetExecutablePath returns the absolute path to the currently running
// executable. It is used internally by the godaemon package, and exported
// publicly because it's useful outside of the package too.
func GetExecutablePath() (string, error) {
buf := make([]uint16, syscall.MAX_PATH+1)
res, _, err := getModuleFileName.Call(0, uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)))
if res == 0 || res >= syscall.MAX_PATH || buf[0] == 0 || buf[res-1] == 0 {
return "", fmt.Errorf("GetModuleFileNameW returned %d errno=%d", res, err)
}
return string(utf16.Decode(buf[:res])), nil
}

View File

@ -1,38 +0,0 @@
package godaemon
import (
"bytes"
"os"
"syscall"
)
// Readlink returns the file pointed to by the given soft link, or an error of
// type PathError otherwise. This mimics the os.Readlink() function, but works
// around a bug we've seen in CentOS 5.10 (kernel 2.6.27.10 on x86_64) where the
// underlying OS function readlink() returns a wrong number of bytes for the
// result (see man readlink). Here we don't rely blindly on that value; if
// there's a zero byte among that number of bytes, then we keep only up to that
// point.
//
// NOTE: We chose not to use os.Readlink() and then search on its result to
// avoid an extra overhead of converting back to []byte. The function to search
// for a byte over the string itself (strings.IndexByte()) is only available
// starting with Go 1.2. Also, we're not searching at every iteration to save
// some CPU time, even though that could mean extra iterations for systems
// affected with this bug. But it's wiser to optimize for the general case
// (i.e., those not affected).
func Readlink(name string) (string, error) {
for len := 128; ; len *= 2 {
b := make([]byte, len)
n, e := syscall.Readlink(name, b)
if e != nil {
return "", &os.PathError{"readlink", name, e}
}
if n < len {
if z := bytes.IndexByte(b[:n], 0); z >= 0 {
n = z
}
return string(b[:n]), nil
}
}
}