Add PID file support
Add PID file support

--- a/doh-client/main.go
+++ b/doh-client/main.go
@@ -25,13 +25,80 @@
 
 import (
 	"flag"
+	"fmt"
+	"io"
+	"io/ioutil"
 	"log"
+	"os"
+	"runtime"
+	"strconv"
 )
+
+func checkPIDFile(pidFile string) (bool, error) {
+retry:
+	f, err := os.OpenFile(pidFile, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666)
+	if os.IsExist(err) {
+		pidStr, err := ioutil.ReadFile(pidFile)
+		if err != nil {
+			return false, err
+		}
+		pid, err := strconv.ParseUint(string(pidStr), 10, 0)
+		if err != nil {
+			return false, err
+		}
+		_, err = os.Stat(fmt.Sprintf("/proc/%d", pid))
+		if os.IsNotExist(err) {
+			err = os.Remove(pidFile)
+			if err != nil {
+				return false, err
+			}
+			goto retry
+		} else if err != nil {
+			return false, err
+		}
+		log.Printf("Already running on PID %d, exiting.\n", pid)
+		return false, nil
+	} else if err != nil {
+		return false, err
+	}
+	defer f.Close()
+	_, err = io.WriteString(f, strconv.FormatInt(int64(os.Getpid()), 10))
+	if err != nil {
+		return false, err
+	}
+	return true, nil
+}
 
 func main() {
 	confPath := flag.String("conf", "doh-client.conf", "Configuration file")
 	verbose := flag.Bool("verbose", false, "Enable logging")
+	showVersion := flag.Bool("version", false, "Show software version and exit")
+	var pidFile *string
+
+	// I really want to push the technology forward by recommending cgroup-based
+	// process tracking. But I understand some cloud service providers have
+	// their own monitoring system. So this feature is only enabled on Linux and
+	// BSD series platforms which lacks functionality similar to cgroup.
+	if runtime.GOOS == "dragonfly" || runtime.GOOS == "freebsd" || runtime.GOOS == "linux" || runtime.GOOS == "netbsd" || runtime.GOOS == "openbsd" {
+		pidFile = flag.String("pid-file", "", "PID file for legacy supervision systems lacking support for reliable cgroup-based process tracking")
+	}
+
 	flag.Parse()
+
+	if *showVersion {
+		fmt.Printf("doh-server %s\nHomepage: https://github.com/m13253/dns-over-https\n", VERSION)
+		return
+	}
+
+	if pidFile != nil && *pidFile != "" {
+		ok, err := checkPIDFile(*pidFile)
+		if err != nil {
+			log.Printf("Error checking PID file: %v\n", err)
+		}
+		if !ok {
+			return
+		}
+	}
 
 	conf, err := loadConfig(*confPath)
 	if err != nil {

--- a/doh-server/main.go
+++ b/doh-server/main.go
@@ -25,13 +25,80 @@
 
 import (
 	"flag"
+	"fmt"
+	"io"
+	"io/ioutil"
 	"log"
+	"os"
+	"runtime"
+	"strconv"
 )
+
+func checkPIDFile(pidFile string) (bool, error) {
+retry:
+	f, err := os.OpenFile(pidFile, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666)
+	if os.IsExist(err) {
+		pidStr, err := ioutil.ReadFile(pidFile)
+		if err != nil {
+			return false, err
+		}
+		pid, err := strconv.ParseUint(string(pidStr), 10, 0)
+		if err != nil {
+			return false, err
+		}
+		_, err = os.Stat(fmt.Sprintf("/proc/%d", pid))
+		if os.IsNotExist(err) {
+			err = os.Remove(pidFile)
+			if err != nil {
+				return false, err
+			}
+			goto retry
+		} else if err != nil {
+			return false, err
+		}
+		log.Printf("Already running on PID %d, exiting.\n", pid)
+		return false, nil
+	} else if err != nil {
+		return false, err
+	}
+	defer f.Close()
+	_, err = io.WriteString(f, strconv.FormatInt(int64(os.Getpid()), 10))
+	if err != nil {
+		return false, err
+	}
+	return true, nil
+}
 
 func main() {
 	confPath := flag.String("conf", "doh-server.conf", "Configuration file")
 	verbose := flag.Bool("verbose", false, "Enable logging")
+	showVersion := flag.Bool("version", false, "Show software version and exit")
+	var pidFile *string
+
+	// I really want to push the technology forward by recommending cgroup-based
+	// process tracking. But I understand some cloud service providers have
+	// their own monitoring system. So this feature is only enabled on Linux and
+	// BSD series platforms which lacks functionality similar to cgroup.
+	if runtime.GOOS == "dragonfly" || runtime.GOOS == "freebsd" || runtime.GOOS == "linux" || runtime.GOOS == "netbsd" || runtime.GOOS == "openbsd" {
+		pidFile = flag.String("pid-file", "", "PID file for legacy supervision systems lacking support for reliable cgroup-based process tracking")
+	}
+
 	flag.Parse()
+
+	if *showVersion {
+		fmt.Printf("doh-server %s\nHomepage: https://github.com/m13253/dns-over-https\n", VERSION)
+		return
+	}
+
+	if pidFile != nil && *pidFile != "" {
+		ok, err := checkPIDFile(*pidFile)
+		if err != nil {
+			log.Printf("Error checking PID file: %v\n", err)
+		}
+		if !ok {
+			return
+		}
+	}
 
 	conf, err := loadConfig(*confPath)
 	if err != nil {