Skip to content

Commit

Permalink
Merge pull request #1 from stunndard/devel
Browse files Browse the repository at this point in the history
daemon mode added for Linux
  • Loading branch information
stunndard committed Jun 10, 2016
2 parents fa8cfe6 + e6f6741 commit 7583030
Show file tree
Hide file tree
Showing 8 changed files with 672 additions and 2 deletions.
5 changes: 4 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ type Config struct {
StreamGenre string `ini:"genre"`
StreamPublic bool `ini:"public"`
IsDaemon bool `ini:"daemon"`
PidFile string
FFMPEGPath string
}

const Version = "0.1"
const Version = "0.2"

var Cfg Config

Expand Down Expand Up @@ -78,6 +79,8 @@ func LoadConfig(filename string) error {
Cfg.NpFile = ini.Section("misc").Key("npfile").Value()
Cfg.LogFile = ini.Section("misc").Key("logfile").Value()
Cfg.LogLevel, _ = ini.Section("misc").Key("loglevel").Int()
Cfg.IsDaemon, _ = ini.Section("misc").Key("daemon").Bool()
Cfg.PidFile = ini.Section("misc").Key("pidfile").Value()

return nil
}
Expand Down
99 changes: 99 additions & 0 deletions daemon/command_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package daemon

import (
"os"
)

// AddCommand is wrapper on AddFlag and SetSigHandler functions.
func AddCommand(f Flag, sig os.Signal, handler SignalHandlerFunc) {
if f != nil {
AddFlag(f, sig)
}
if handler != nil {
SetSigHandler(handler, sig)
}
}

// Flag is the interface implemented by an object that has two state:
// 'set' and 'unset'.
type Flag interface {
IsSet() bool
}

// BoolFlag returns new object that implements interface Flag and
// has state 'set' when var with the given address is true.
func BoolFlag(f *bool) Flag {
return &boolFlag{f}
}

// StringFlag returns new object that implements interface Flag and
// has state 'set' when var with the given address equals given value of v.
func StringFlag(f *string, v string) Flag {
return &stringFlag{f, v}
}

type boolFlag struct {
b *bool
}

func (f *boolFlag) IsSet() bool {
if f == nil {
return false
}
return *f.b
}

type stringFlag struct {
s *string
v string
}

func (f *stringFlag) IsSet() bool {
if f == nil {
return false
}
return *f.s == f.v
}

var flags = make(map[Flag]os.Signal)

// Flags returns flags that was added by the function AddFlag.
func Flags() map[Flag]os.Signal {
return flags
}

// AddFlag adds the flag and signal to the internal map.
func AddFlag(f Flag, sig os.Signal) {
flags[f] = sig
}

// SendCommands sends active signals to the given process.
func SendCommands(p *os.Process) (err error) {
for _, sig := range signals() {
if err = p.Signal(sig); err != nil {
return
}
}
return
}

// ActiveFlags returns flags that has the state 'set'.
func ActiveFlags() (ret []Flag) {
ret = make([]Flag, 0, 1)
for f := range flags {
if f.IsSet() {
ret = append(ret, f)
}
}
return
}

func signals() (ret []os.Signal) {
ret = make([]os.Signal, 0, 1)
for f, sig := range flags {
if f.IsSet() {
ret = append(ret, sig)
}
}
return
}
266 changes: 266 additions & 0 deletions daemon/daemon_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
package daemon

import (
"encoding/json"
"fmt"
"os"
"syscall"

"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) {
if !WasReborn() {
child, err = d.parent()
} else {
err = d.child()
}
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) {
if len(d.PidFileName) > 0 {
var pid int
if pid, err = ReadPidFile(d.PidFileName); err != nil {
return
}
daemon, err = os.FindProcess(pid)
}
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
}

defer d.closeFiles()
if err = d.openFiles(); err != nil {
return
}

attr := &os.ProcAttr{
Dir: d.WorkDir,
Env: d.Env,
Files: d.files(),
Sys: &syscall.SysProcAttr{
//Chroot: d.Chroot,
Credential: d.Credential,
Setsid: true,
},
}

if child, err = os.StartProcess(d.abspath, d.Args, attr); err != nil {
if d.pidFile != nil {
d.pidFile.Remove()
}
return
}

d.rpipe.Close()
encoder := json.NewEncoder(d.wpipe)
err = encoder.Encode(d)

return
}

func (d *Context) openFiles() (err error) {
if d.PidFilePerm == 0 {
d.PidFilePerm = FILE_PERM
}
if d.LogFilePerm == 0 {
d.LogFilePerm = FILE_PERM
}

if d.nullFile, err = os.Open(os.DevNull); err != nil {
return
}

if len(d.PidFileName) > 0 {
if d.pidFile, err = OpenLockFile(d.PidFileName, d.PidFilePerm); err != nil {
return
}
if err = d.pidFile.Lock(); err != nil {
return
}
}

if len(d.LogFileName) > 0 {
if d.logFile, err = os.OpenFile(d.LogFileName,
os.O_WRONLY|os.O_CREATE|os.O_APPEND, d.LogFilePerm); err != nil {
return
}
}

d.rpipe, d.wpipe, err = os.Pipe()
return
}

func (d *Context) closeFiles() (err error) {
cl := func(file **os.File) {
if *file != nil {
(*file).Close()
*file = nil
}
}
cl(&d.rpipe)
cl(&d.wpipe)
cl(&d.logFile)
cl(&d.nullFile)
if d.pidFile != nil {
d.pidFile.Close()
d.pidFile = nil
}
return
}

func (d *Context) prepareEnv() (err error) {
if d.abspath, err = osext.Executable(); err != nil {
return
}

if len(d.Args) == 0 {
d.Args = os.Args
}

mark := fmt.Sprintf("%s=%s", MARK_NAME, MARK_VALUE)
if len(d.Env) == 0 {
d.Env = os.Environ()
}
d.Env = append(d.Env, mark)

return
}

func (d *Context) files() (f []*os.File) {
log := d.nullFile
if d.logFile != nil {
log = d.logFile
}

f = []*os.File{
d.rpipe, // (0) stdin
log, // (1) stdout
log, // (2) stderr
d.nullFile, // (3) dup on fd 0 after initialization
}

if d.pidFile != nil {
f = append(f, d.pidFile.File) // (4) pid file
}
return
}

var initialized = false

func (d *Context) child() (err error) {
if initialized {
return os.ErrInvalid
}
initialized = true

decoder := json.NewDecoder(os.Stdin)
if err = decoder.Decode(d); err != nil {
return
}

if err = syscall.Close(0); err != nil {
return
}
if err = syscall.Dup2(3, 0); err != nil {
return
}

if len(d.PidFileName) > 0 {
d.pidFile = NewLockFile(os.NewFile(4, d.PidFileName))
if err = d.pidFile.WritePid(); err != nil {
return
}
}

if d.Umask != 0 {
syscall.Umask(int(d.Umask))
}
if len(d.Chroot) > 0 {
err = syscall.Chroot(d.Chroot)
}

return
}

// Release provides correct pid-file release in daemon.
func (d *Context) Release() (err error) {
if !initialized {
return
}
if d.pidFile != nil {
err = d.pidFile.Remove()
}
return
}
Loading

0 comments on commit 7583030

Please sign in to comment.