Skip to content

Commit

Permalink
Refactoring for support of different platforms
Browse files Browse the repository at this point in the history
  • Loading branch information
sevlyar committed Sep 25, 2016
1 parent f84331e commit 5de16e8
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 93 deletions.
File renamed without changes.
28 changes: 23 additions & 5 deletions daemon_windows.go → daemon.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
package daemon

import (
"errors"
"os"
"syscall"
)

var errNotSupported = errors.New("daemon: Non-POSIX OS is not supported")

// Mark of daemon process - system environment variable _GO_DAEMON=1
const (
MARK_NAME = "_GO_DAEMON"
MARK_VALUE = "1"
)

// Default file permissions for log and pid files.
const FILE_PERM = os.FileMode(0640)

// A Context describes daemon context.
type Context struct {
// If PidFileName is non-empty, parent process will try to create and lock
Expand Down Expand Up @@ -34,37 +47,42 @@ type Context struct {
Args []string

// Credential holds user and group identities to be assumed by a daemon-process.
Credential *int
Credential *syscall.Credential
// If Umask is non-zero, the daemon-process call Umask() func with given value.
Umask int

// Struct contains only serializable public fields (!!!)
abspath string
pidFile *int
pidFile *LockFile
logFile *os.File
nullFile *os.File

rpipe, wpipe *os.File
}

// WasReborn returns true in child process (daemon) and false in parent process.
func WasReborn() bool {
return os.Getenv(MARK_NAME) == MARK_VALUE
}

// Reborn runs second copy of current process in the given context.
// function executes separate parts of code in child process and parent process
// and provides demonization of child process. It look similar as the
// fork-daemonization, but goroutine-safe.
// In success returns *os.Process in parent process and nil in child process.
// Otherwise returns error.
func (d *Context) Reborn() (child *os.Process, err error) {
return
return d.reborn()
}

// Search search daemons process by given in context pid file name.
// If success returns pointer on daemons os.Process structure,
// else returns error. Returns nil if filename is empty.
func (d *Context) Search() (daemon *os.Process, err error) {
return
return d.search()
}

// Release provides correct pid-file release in daemon.
func (d *Context) Release() (err error) {
return
return d.release()
}
19 changes: 19 additions & 0 deletions daemon_stub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!plan9,!solaris

package daemon

import (
"os"
)

func (d *Context) reborn() (child *os.Process, err error) {
return nil, errNotSupported
}

func (d *Context) search() (daemon *os.Process, err error) {
return nil, errNotSupported
}

func (d *Context) release() (err error) {
return nil, errNotSupported
}
75 changes: 5 additions & 70 deletions daemon_linux.go → daemon_unix.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// +build darwin dragonfly freebsd linux netbsd openbsd plan9 solaris

package daemon

import (
Expand All @@ -9,65 +11,7 @@ import (
"github.com/kardianos/osext"
)

// Mark of daemon process - system environment variable _GO_DAEMON=1
const (
MARK_NAME = "_GO_DAEMON"
MARK_VALUE = "1"
)

// Default file permissions for log and pid files.
const FILE_PERM = os.FileMode(0640)

// A Context describes daemon context.
type Context struct {
// If PidFileName is non-empty, parent process will try to create and lock
// pid file with given name. Child process writes process id to file.
PidFileName string
// Permissions for new pid file.
PidFilePerm os.FileMode

// If LogFileName is non-empty, parent process will create file with given name
// and will link to fd 2 (stderr) for child process.
LogFileName string
// Permissions for new log file.
LogFilePerm os.FileMode

// If WorkDir is non-empty, the child changes into the directory before
// creating the process.
WorkDir string
// If Chroot is non-empty, the child changes root directory
Chroot string

// If Env is non-nil, it gives the environment variables for the
// daemon-process in the form returned by os.Environ.
// If it is nil, the result of os.Environ will be used.
Env []string
// If Args is non-nil, it gives the command-line args for the
// daemon-process. If it is nil, the result of os.Args will be used
// (without program name).
Args []string

// Credential holds user and group identities to be assumed by a daemon-process.
Credential *syscall.Credential
// If Umask is non-zero, the daemon-process call Umask() func with given value.
Umask int

// Struct contains only serializable public fields (!!!)
abspath string
pidFile *LockFile
logFile *os.File
nullFile *os.File

rpipe, wpipe *os.File
}

// Reborn runs second copy of current process in the given context.
// function executes separate parts of code in child process and parent process
// and provides demonization of child process. It look similar as the
// fork-daemonization, but goroutine-safe.
// In success returns *os.Process in parent process and nil in child process.
// Otherwise returns error.
func (d *Context) Reborn() (child *os.Process, err error) {
func (d *Context) reborn() (child *os.Process, err error) {
if !WasReborn() {
child, err = d.parent()
} else {
Expand All @@ -76,10 +20,7 @@ func (d *Context) Reborn() (child *os.Process, err error) {
return
}

// Search search daemons process by given in context pid file name.
// If success returns pointer on daemons os.Process structure,
// else returns error. Returns nil if filename is empty.
func (d *Context) Search() (daemon *os.Process, err error) {
func (d *Context) search() (daemon *os.Process, err error) {
if len(d.PidFileName) > 0 {
var pid int
if pid, err = ReadPidFile(d.PidFileName); err != nil {
Expand All @@ -90,11 +31,6 @@ func (d *Context) Search() (daemon *os.Process, err error) {
return
}

// WasReborn returns true in child process (daemon) and false in parent process.
func WasReborn() bool {
return os.Getenv(MARK_NAME) == MARK_VALUE
}

func (d *Context) parent() (child *os.Process, err error) {
if err = d.prepareEnv(); err != nil {
return
Expand Down Expand Up @@ -254,8 +190,7 @@ func (d *Context) child() (err error) {
return
}

// Release provides correct pid-file release in daemon.
func (d *Context) Release() (err error) {
func (d *Context) release() (err error) {
if !initialized {
return
}
Expand Down
24 changes: 6 additions & 18 deletions lock_file_linux.go → lock_file.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package daemon

import (
"errors"
"fmt"
"os"
"syscall"
)

var (
// ErrWoldBlock indicates on locking pid-file by another process.
ErrWouldBlock = syscall.EWOULDBLOCK
ErrWouldBlock = errors.New("Daemon: Resource temporarily unavailable")
)

// LockFile wraps *os.File and provide functions for locking of files.
Expand Down Expand Up @@ -49,12 +50,12 @@ func OpenLockFile(name string, perm os.FileMode) (lock *LockFile, err error) {

// Lock apply exclusive lock on an open file. If file already locked, returns error.
func (file *LockFile) Lock() error {
return syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
return lockFile(file.Fd())
}

// Unlock remove exclusive lock on an open file.
func (file *LockFile) Unlock() error {
return syscall.Flock(int(file.Fd()), syscall.LOCK_UN)
return unlockFile(file.Fd())
}

// ReadPidFile reads process id from file with give name and returns pid.
Expand Down Expand Up @@ -105,6 +106,7 @@ func (file *LockFile) Remove() error {
return err
}

// TODO(yar): keep filename?
name, err := GetFdName(file.Fd())
if err != nil {
return err
Expand All @@ -116,19 +118,5 @@ func (file *LockFile) Remove() error {

// GetFdName returns file name for given descriptor.
func GetFdName(fd uintptr) (name string, err error) {
path := fmt.Sprintf("/proc/self/fd/%d", int(fd))

var (
fi os.FileInfo
n int
)
if fi, err = os.Lstat(path); err != nil {
return
}
buf := make([]byte, fi.Size()+1)

if n, err = syscall.Readlink(path, buf); err == nil {
name = string(buf[:n])
}
return
return getFdName(fd)
}
15 changes: 15 additions & 0 deletions lock_file_stub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!plan9,!solaris

package daemon

func lockFile(fd uintptr) error {
return errNotSupported
}

func unlockFile(fd uintptr) error {
return errNotSupported
}

func getFdName(fd uintptr) (name string, err error) {
return "", errNotSupported
}
44 changes: 44 additions & 0 deletions lock_file_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// +build darwin dragonfly freebsd linux netbsd openbsd plan9 solaris

package daemon

import (
"fmt"
"os"
"syscall"
)

func lockFile(fd uintptr) error {
err := syscall.Flock(int(fd), syscall.LOCK_EX|syscall.LOCK_NB)
if err == syscall.EWOULDBLOCK {
err = ErrWouldBlock
}
return err
}

func unlockFile(fd uintptr) error {
err := syscall.Flock(int(fd), syscall.LOCK_UN)
if err == syscall.EWOULDBLOCK {
err = ErrWouldBlock
}
return err
}

func getFdName(fd uintptr) (name string, err error) {
// TODO(yar): This way does not work on darwin, use fcntl
path := fmt.Sprintf("/proc/self/fd/%d", int(fd))

var (
fi os.FileInfo
n int
)
if fi, err = os.Lstat(path); err != nil {
return
}
buf := make([]byte, fi.Size()+1)

if n, err = syscall.Readlink(path, buf); err == nil {
name = string(buf[:n])
}
return
}
File renamed without changes.

0 comments on commit 5de16e8

Please sign in to comment.