diff --git a/internal/app/wwclient/root.go b/internal/app/wwclient/root.go index 9d9c8c21..7d911c8a 100644 --- a/internal/app/wwclient/root.go +++ b/internal/app/wwclient/root.go @@ -16,7 +16,7 @@ import ( "github.com/google/uuid" "github.com/talos-systems/go-smbios/smbios" - "github.com/hpcng/warewulf/internal/pkg/util" + "github.com/hpcng/warewulf/internal/pkg/pidfile" "github.com/hpcng/warewulf/internal/pkg/warewulfconf" "github.com/hpcng/warewulf/internal/pkg/wwlog" "github.com/spf13/cobra" @@ -53,16 +53,12 @@ func CobraRunE(cmd *cobra.Command, args []string) error { return err } - if util.IsFile(PIDFile) { + pid, err := pidfile.Write(PIDFile) + if err != nil && pid == -1 { + wwlog.Printf(wwlog.WARN, "%v. starting new wwclient", err) + } else if err != nil && pid > 0 { return errors.New("found pidfile " + PIDFile + " not starting") } - p, err := os.OpenFile(PIDFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) - if err != nil { - return err - } - defer p.Close() - - fmt.Fprintf(p, "%d", os.Getpid()) if os.Args[0] == "/warewulf/bin/wwclient" { err := os.Chdir("/") @@ -137,20 +133,20 @@ func CobraRunE(cmd *cobra.Command, args []string) error { wwid := strings.Split(wwid_tmp[1], " ")[0] // listen on SIGHUP - sigs := make(chan os.Signal) + sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGINT) go func() { - for sig := range sigs { - switch sig { - case syscall.SIGHUP: - log.Printf("Received SIGNAL: %s\n", sig) - updateSystem(conf.Ipaddr, conf.Warewulf.Port, wwid, tag, localUUID) - case syscall.SIGTERM, syscall.SIGINT: - cleanUp() - os.Exit(0) - } + sig := <-sigs + switch sig { + case syscall.SIGHUP: + log.Printf("Received SIGNAL: %s\n", sig) + updateSystem(conf.Ipaddr, conf.Warewulf.Port, wwid, tag, localUUID) + case syscall.SIGTERM, syscall.SIGINT: + wwlog.Printf(wwlog.INFO, "termination wwclient!, %v", sig) + cleanUp() + os.Exit(0) } }() @@ -200,9 +196,8 @@ func updateSystem(ipaddr string, port int, wwid string, tag string, localUUID uu } func cleanUp() { - err := os.Remove(PIDFile) + err := pidfile.Remove(PIDFile) if err != nil { - log.Printf("could not remove pidfile: %s\n", err) + wwlog.Printf(wwlog.ERROR, "could not remove pidfile: %s\n", err) } - } diff --git a/internal/pkg/pidfile/pidfile.go b/internal/pkg/pidfile/pidfile.go new file mode 100644 index 00000000..9541ea99 --- /dev/null +++ b/internal/pkg/pidfile/pidfile.go @@ -0,0 +1,84 @@ +package pidfile + +// based on original work found here, github.com/soellman/pidfile + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "strconv" + "strings" + "syscall" +) + +var ( + ErrProcessRunning = errors.New("process is running") + ErrFileStale = errors.New("pidfile exists but process is not running") + ErrFileInvalid = errors.New("pidfile has invalid contents") +) + +// Remove a pidfile +func Remove(filename string) error { + return os.RemoveAll(filename) +} + +// Write writes a pidfile, returning an error +// if the process is already running or pidfile is orphaned +func Write(filename string) (int, error) { + return WriteControl(filename, os.Getpid(), false) +} + +func WriteControl(filename string, pid int, overwrite bool) (int, error) { + // Check for existing pid + oldpid, err := pidfileContents(filename) + if err != nil && !os.IsNotExist(err) { + return oldpid, err + } + + // We have a pid + if err == nil { + if pidIsRunning(oldpid) { + return oldpid, ErrProcessRunning + } + if !overwrite { + return -1, ErrFileStale + } + } + + // We're clear to (over)write the file + return pid, ioutil.WriteFile(filename, []byte(fmt.Sprintf("%d\n", pid)), 0644) +} + +func pidfileContents(filename string) (int, error) { + contents, err := ioutil.ReadFile(filename) + if err != nil { + return 0, err + } + + pid, err := strconv.Atoi(strings.TrimSpace(string(contents))) + if err != nil { + return 0, ErrFileInvalid + } + + return pid, nil +} + +func pidIsRunning(pid int) bool { + process, err := os.FindProcess(pid) + if err != nil { + return false + } + + err = process.Signal(syscall.Signal(0)) + + if err != nil && err.Error() == "no such process" { + return false + } + + if err != nil && err.Error() == "os: process already finished" { + return false + } + + return true +}