diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..55b4021 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +reniced diff --git a/.golangci.yaml b/.golangci.yaml index b0eb507..05cbe50 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -26,7 +26,6 @@ linters: - errchkjson - errorlint - exhaustive - - exhaustruct - exportloopref - ginkgolinter - gci diff --git a/.reniced.yaml.example b/.reniced.yaml.example index e4d0c8c..cab73c3 100644 --- a/.reniced.yaml.example +++ b/.reniced.yaml.example @@ -1,57 +1,85 @@ +# Fork and run as real unix daemon. +daemon: true +# PID file for daemon. +pidfile: /var/run/reniced.pid +# Delay between subsequent renices or kills, to lower cpu load peaks. cmd_delay: 33 +# Delay among iterations. loop_delay: 5000 +# Wheather to print debug messages to stdout. debug: true kill: - sig: stop name: + # Backup program. I don't really want it, but have to keep. - commvault + # Distributed notification service. Sometimes can be naughty. - distnoted + # This service can be greedy and may make kernel_tak to hig cpu too - coreduetd prio: - nice: -11 name: + # Main audio service, to play sounds smoothly, must have high prio. - coreaudiod + # Window manager service. - WindowServer + # Isolation helper. - "com.apple.audio.SandboxHelper" + # Audio driver helper. - "com.apple.audio.DriverHelper" + # Used for syncing timers for different audio sources. - audioclocksyncd - nice: -5 name: + # Video playback helper. - VTDecoderXPCService + # Corporate messenger. - "VK Teams" + # Responds to apple broadcast dns requests. In linux world it is avahi. - mDNSResponder + # Helper service for mDNSResponder. - mDNSResponderHelper + # Part of AudioToolbox framework. - AudioComponentRegistrar + # Mac OS X Core Virtual Machine Server. Something related to isolation of system processes. - CVMServer - nice: -3 name: + # 3-rd party tool to make tricks with keyboard. (remap/swap buttons) - "Karabiner-Elements" + # 3-rd party tool to automate routine actions. - Hammerspoon + # Compiler for metal shaders, that can pass information directly to the GPU. - MTLCompilerService - - "com.apple.appkit.xpc.openAndSavePanelService" - nice: 3 name: + # System logger. - logd + # System logger. - syslogd + # Something related to binary signature checks. To ensure system integrity. Part of DRM protection. + # It rubs berfore application starts, so we definitely don't want to low its prio heavily. - syspolicyd + # Unix periodic task scheduler. - cron + # Ditto. - softwareupdated + # Handles the scheduling of the periodic command - "periodic-wrapper" - - iconservicesagent - - "com.apple.quicklook.ThumbnailsAgent" - appstored - nice: 5 name: + # Dynamic brightness tuning service. - corebrightnessd - airportd - containermanagerd - - searchpartyd - corespeechd - peopled - AssetCacheLocatorService @@ -121,7 +149,5 @@ prio: - biomesyncd - siriknowledged - triald - - syspolicyd - # vim: set ft=yaml et ai ts=2 sw=2 sts=2: diff --git a/README.md b/README.md index 32f0a4b..82044ed 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ # Reniced Helper utility intended to run as a service or as background process (say in tmux) that can make system more respositive -under certain load. +under certain type of load. ## Why it was made -In general case os can handle load well fairly share resources among processes. But in some cases it make it poorly. -As an exanple it can be ill-minded pseudo-security stuff like antiviruses or mediaanalysers that searches forbidden -content on your mac (due to gov.polices that apple have to accept). Or it can be backup that crawls in background. +In general case os can handle load well, fairly sharing resources among processes. But in some cases it makes it poorly. +As an example it can be ill-minded pseudo-security stuff like antiviruses or mediaanalysers that searches forbidden +content on your mac (due to gov.polices that apple have to accept). Or it can be backup that crawls fs in background. -To help resolve such situation this utility was made. Some malicious processes can be renice to lower nicelevels (say, +To help resolve such situation this utility was made. Some malicious processes can be reniced to lower nicelevels (say, 19) and for some good processes nicelevel can be rised to higher values(-19 if reniced run as superuser or 0 if as normal). diff --git a/go.mod b/go.mod index ef759f6..8866eca 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,14 @@ module reniced go 1.21.4 require ( + github.com/sevlyar/go-daemon v0.1.6 github.com/shirou/gopsutil/v3 v3.24.2 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/go-ole/go-ole v1.3.0 // indirect + github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect diff --git a/go.sum b/go.sum index c18cef6..0a366cc 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a h1:3Bm7EwfUQUvhNeKIkUct/gl9eod1TcXuj8stxvi/GoI= github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= @@ -16,6 +18,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/sevlyar/go-daemon v0.1.6 h1:EUh1MDjEM4BI109Jign0EaknA2izkOyi0LV3ro3QQGs= +github.com/sevlyar/go-daemon v0.1.6/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE= github.com/shirou/gopsutil/v3 v3.24.2 h1:kcR0erMbLg5/3LcInpw0X/rrPSqq4CDPyI6A6ZRC18Y= github.com/shirou/gopsutil/v3 v3.24.2/go.mod h1:tSg/594BcA+8UdQU2XcW803GWYgdtauFFPgJCJKZlVk= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= diff --git a/main.go b/main.go index 2c4c73a..15360f9 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "syscall" "time" + "github.com/sevlyar/go-daemon" proc "github.com/shirou/gopsutil/v3/process" ) @@ -22,7 +23,33 @@ func main() { log.Fatalf("unable to parse config: %s", err) } - // Основной цикл. + if cnf.Daemon { + cntxt := &daemon.Context{ + WorkDir: "/", + Args: []string{"reniced"}, + } + + if cnf.Pidfile != "" { + cntxt.PidFileName = cnf.Pidfile + } + + if d, err := cntxt.Reborn(); err != nil { + log.Fatal("Unable to run: ", err) + + return + } else if d != nil { + return + } + + defer cntxt.Release() //nolint: errcheck + } + + // То, ради чего всё затевалось. + reniced(cnf) +} + +// reniced основная логика. +func reniced(cnf Config) { for { processList, err := proc.Processes() diff --git a/types.go b/types.go index 3df96be..4df76a1 100644 --- a/types.go +++ b/types.go @@ -1,11 +1,17 @@ package main type Config struct { + // Weather we fork to backgoround or stay as-is. + Daemon bool `yaml:"daemon,omitempty"` + + // Where to put pid-file. + Pidfile string `yaml:"pidfile,omitempty"` + // LoopDelay in milliseconds - whole set of action delay period. - LoopDelay int `yaml:"loop_delay"` + LoopDelay int `yaml:"loop_delay,omitempty"` // CmdDelay in milliseconds - delay between each change application. - CmdDelay int `yaml:"cmd_delay"` + CmdDelay int `yaml:"cmd_delay,omitempty"` // Whether to print debug info on stdout. Debug bool `yaml:"debug,omitempty"` diff --git a/util.go b/util.go index 4ecc478..58a3500 100644 --- a/util.go +++ b/util.go @@ -52,8 +52,6 @@ func readConf(path string) (Config, error) { if cfg.Debug { log.Println("debug set to true") - } else { - log.Println("debug set to false") } newPrio := make(Prio, 0) diff --git a/vendor/github.com/kardianos/osext/LICENSE b/vendor/github.com/kardianos/osext/LICENSE new file mode 100644 index 0000000..7448756 --- /dev/null +++ b/vendor/github.com/kardianos/osext/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/kardianos/osext/README.md b/vendor/github.com/kardianos/osext/README.md new file mode 100644 index 0000000..15cbc3d --- /dev/null +++ b/vendor/github.com/kardianos/osext/README.md @@ -0,0 +1,21 @@ +### Extensions to the "os" package. + +[![GoDoc](https://godoc.org/github.com/kardianos/osext?status.svg)](https://godoc.org/github.com/kardianos/osext) + +## Find the current Executable and ExecutableFolder. + +As of go1.8 the Executable function may be found in `os`. The Executable function +in the std lib `os` package is used if available. + +There is sometimes utility in finding the current executable file +that is running. This can be used for upgrading the current executable +or finding resources located relative to the executable file. Both +working directory and the os.Args[0] value are arbitrary and cannot +be relied on; os.Args[0] can be "faked". + +Multi-platform and supports: + * Linux + * OS X + * Windows + * Plan 9 + * BSDs. diff --git a/vendor/github.com/kardianos/osext/osext.go b/vendor/github.com/kardianos/osext/osext.go new file mode 100644 index 0000000..17f380f --- /dev/null +++ b/vendor/github.com/kardianos/osext/osext.go @@ -0,0 +1,33 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Extensions to the standard "os" package. +package osext // import "github.com/kardianos/osext" + +import "path/filepath" + +var cx, ce = executableClean() + +func executableClean() (string, error) { + p, err := executable() + return filepath.Clean(p), err +} + +// Executable returns an absolute path that can be used to +// re-invoke the current program. +// It may not be valid after the current program exits. +func Executable() (string, error) { + return cx, ce +} + +// Returns same path as Executable, returns just the folder +// path. Excludes the executable name and any trailing slash. +func ExecutableFolder() (string, error) { + p, err := Executable() + if err != nil { + return "", err + } + + return filepath.Dir(p), nil +} diff --git a/vendor/github.com/kardianos/osext/osext_go18.go b/vendor/github.com/kardianos/osext/osext_go18.go new file mode 100644 index 0000000..009d8a9 --- /dev/null +++ b/vendor/github.com/kardianos/osext/osext_go18.go @@ -0,0 +1,9 @@ +//+build go1.8,!openbsd + +package osext + +import "os" + +func executable() (string, error) { + return os.Executable() +} diff --git a/vendor/github.com/kardianos/osext/osext_plan9.go b/vendor/github.com/kardianos/osext/osext_plan9.go new file mode 100644 index 0000000..95e2371 --- /dev/null +++ b/vendor/github.com/kardianos/osext/osext_plan9.go @@ -0,0 +1,22 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//+build !go1.8 + +package osext + +import ( + "os" + "strconv" + "syscall" +) + +func executable() (string, error) { + f, err := os.Open("/proc/" + strconv.Itoa(os.Getpid()) + "/text") + if err != nil { + return "", err + } + defer f.Close() + return syscall.Fd2path(int(f.Fd())) +} diff --git a/vendor/github.com/kardianos/osext/osext_procfs.go b/vendor/github.com/kardianos/osext/osext_procfs.go new file mode 100644 index 0000000..e1f16f8 --- /dev/null +++ b/vendor/github.com/kardianos/osext/osext_procfs.go @@ -0,0 +1,36 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.8,android !go1.8,linux !go1.8,netbsd !go1.8,solaris !go1.8,dragonfly + +package osext + +import ( + "errors" + "fmt" + "os" + "runtime" + "strings" +) + +func executable() (string, error) { + switch runtime.GOOS { + case "linux", "android": + const deletedTag = " (deleted)" + execpath, err := os.Readlink("/proc/self/exe") + if err != nil { + return execpath, err + } + execpath = strings.TrimSuffix(execpath, deletedTag) + execpath = strings.TrimPrefix(execpath, deletedTag) + return execpath, nil + case "netbsd": + return os.Readlink("/proc/curproc/exe") + case "dragonfly": + return os.Readlink("/proc/curproc/file") + case "solaris": + return os.Readlink(fmt.Sprintf("/proc/%d/path/a.out", os.Getpid())) + } + return "", errors.New("ExecPath not implemented for " + runtime.GOOS) +} diff --git a/vendor/github.com/kardianos/osext/osext_sysctl.go b/vendor/github.com/kardianos/osext/osext_sysctl.go new file mode 100644 index 0000000..33cee25 --- /dev/null +++ b/vendor/github.com/kardianos/osext/osext_sysctl.go @@ -0,0 +1,126 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.8,darwin !go1.8,freebsd openbsd + +package osext + +import ( + "os" + "os/exec" + "path/filepath" + "runtime" + "syscall" + "unsafe" +) + +var initCwd, initCwdErr = os.Getwd() + +func executable() (string, error) { + var mib [4]int32 + switch runtime.GOOS { + case "freebsd": + mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1} + case "darwin": + mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1} + case "openbsd": + mib = [4]int32{1 /* CTL_KERN */, 55 /* KERN_PROC_ARGS */, int32(os.Getpid()), 1 /* KERN_PROC_ARGV */} + } + + n := uintptr(0) + // Get length. + _, _, errNum := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0) + if errNum != 0 { + return "", errNum + } + if n == 0 { // This shouldn't happen. + return "", nil + } + buf := make([]byte, n) + _, _, errNum = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0) + if errNum != 0 { + return "", errNum + } + if n == 0 { // This shouldn't happen. + return "", nil + } + + var execPath string + switch runtime.GOOS { + case "openbsd": + // buf now contains **argv, with pointers to each of the C-style + // NULL terminated arguments. + var args []string + argv := uintptr(unsafe.Pointer(&buf[0])) + Loop: + for { + argp := *(**[1 << 20]byte)(unsafe.Pointer(argv)) + if argp == nil { + break + } + for i := 0; uintptr(i) < n; i++ { + // we don't want the full arguments list + if string(argp[i]) == " " { + break Loop + } + if argp[i] != 0 { + continue + } + args = append(args, string(argp[:i])) + n -= uintptr(i) + break + } + if n < unsafe.Sizeof(argv) { + break + } + argv += unsafe.Sizeof(argv) + n -= unsafe.Sizeof(argv) + } + execPath = args[0] + // There is no canonical way to get an executable path on + // OpenBSD, so check PATH in case we are called directly + if execPath[0] != '/' && execPath[0] != '.' { + execIsInPath, err := exec.LookPath(execPath) + if err == nil { + execPath = execIsInPath + } + } + default: + for i, v := range buf { + if v == 0 { + buf = buf[:i] + break + } + } + execPath = string(buf) + } + + var err error + // execPath will not be empty due to above checks. + // Try to get the absolute path if the execPath is not rooted. + if execPath[0] != '/' { + execPath, err = getAbs(execPath) + if err != nil { + return execPath, err + } + } + // For darwin KERN_PROCARGS may return the path to a symlink rather than the + // actual executable. + if runtime.GOOS == "darwin" { + if execPath, err = filepath.EvalSymlinks(execPath); err != nil { + return execPath, err + } + } + return execPath, nil +} + +func getAbs(execPath string) (string, error) { + if initCwdErr != nil { + return execPath, initCwdErr + } + // The execPath may begin with a "../" or a "./" so clean it first. + // Join the two paths, trailing and starting slashes undetermined, so use + // the generic Join function. + return filepath.Join(initCwd, filepath.Clean(execPath)), nil +} diff --git a/vendor/github.com/kardianos/osext/osext_windows.go b/vendor/github.com/kardianos/osext/osext_windows.go new file mode 100644 index 0000000..074b3b3 --- /dev/null +++ b/vendor/github.com/kardianos/osext/osext_windows.go @@ -0,0 +1,36 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//+build !go1.8 + +package osext + +import ( + "syscall" + "unicode/utf16" + "unsafe" +) + +var ( + kernel = syscall.MustLoadDLL("kernel32.dll") + getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW") +) + +// GetModuleFileName() with hModule = NULL +func executable() (exePath string, err error) { + return getModuleFileName() +} + +func getModuleFileName() (string, error) { + var n uint32 + b := make([]uint16, syscall.MAX_PATH) + size := uint32(len(b)) + + r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size)) + n = uint32(r0) + if n == 0 { + return "", e1 + } + return string(utf16.Decode(b[0:n])), nil +} diff --git a/vendor/github.com/sevlyar/go-daemon/.travis.yml b/vendor/github.com/sevlyar/go-daemon/.travis.yml new file mode 100644 index 0000000..03d4305 --- /dev/null +++ b/vendor/github.com/sevlyar/go-daemon/.travis.yml @@ -0,0 +1,15 @@ +language: go + +go: + - 1.3 + - 1.5 + - tip + +before_install: + - go get -t -v ./... + +script: + - go test -v -coverprofile=coverage.txt -covermode=atomic + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/vendor/github.com/sevlyar/go-daemon/LICENSE b/vendor/github.com/sevlyar/go-daemon/LICENSE new file mode 100644 index 0000000..6923f2f --- /dev/null +++ b/vendor/github.com/sevlyar/go-daemon/LICENSE @@ -0,0 +1,7 @@ +Copyright (C) 2013 Sergey Yarmonov + +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. diff --git a/vendor/github.com/sevlyar/go-daemon/README.md b/vendor/github.com/sevlyar/go-daemon/README.md new file mode 100644 index 0000000..af5b768 --- /dev/null +++ b/vendor/github.com/sevlyar/go-daemon/README.md @@ -0,0 +1,70 @@ +# go-daemon [![Build Status](https://travis-ci.org/sevlyar/go-daemon.svg?branch=master)](https://travis-ci.org/sevlyar/go-daemon) [![GoDoc](https://godoc.org/github.com/sevlyar/go-daemon?status.svg)](https://godoc.org/github.com/sevlyar/go-daemon) + +Library for writing system daemons in Go. + +Now supported only UNIX-based OS (Windows is not supported). But the library was tested only on Linux +and OSX, so that if you have an ability to test the library on other platforms, give me feedback, please (#26). + +*Please, feel free to send me bug reports and fixes. Many thanks to all contributors.* + +## Features + +* Goroutine-safe daemonization; +* Out of box work with pid-files; +* Easy handling of system signals; +* The control of a daemon. + +## Installation + + go get github.com/sevlyar/go-daemon + +You can use [gopkg.in](http://labix.org/gopkg.in): + + go get gopkg.in/sevlyar/go-daemon.v0 + +If you want to use the library in production project, please use vendoring, +because i can not ensure backward compatibility before release v1.0. + +## Examples + +* [Simple](examples/cmd/gd-simple/) +* [Log rotation](examples/cmd/gd-log-rotation/) +* [Signal handling](examples/cmd/gd-signal-handling/) + +## Documentation + +[godoc.org/github.com/sevlyar/go-daemon](https://godoc.org/github.com/sevlyar/go-daemon) + +## How it works + +We can not use `fork` syscall in Golang's runtime, because child process doesn't inherit +threads and goroutines in that case. The library uses a simple trick: it runs its own copy with +a mark - a predefined environment variable. Availability of the variable for the process means +an execution in the child's copy. So that if the mark is not setted - the library executes +parent's operations and runs its own copy with mark, and if the mark is setted - the library +executes child's operations: + +```go +import "log" + +func main() { + Pre() + + context := new(Context) + child, _ := context.Reborn() + + if child != nil { + PostParent() + } else { + defer func() { + if err := context.Release(); err != nil { + log.Printf("Unable to release pid-file: %s", err.Error()) + } + }() + + PostChild() + } +} +``` + +![](img/idea.png) diff --git a/vendor/github.com/sevlyar/go-daemon/command.go b/vendor/github.com/sevlyar/go-daemon/command.go new file mode 100644 index 0000000..07d23c8 --- /dev/null +++ b/vendor/github.com/sevlyar/go-daemon/command.go @@ -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 +} diff --git a/vendor/github.com/sevlyar/go-daemon/daemon.go b/vendor/github.com/sevlyar/go-daemon/daemon.go new file mode 100644 index 0000000..81c7507 --- /dev/null +++ b/vendor/github.com/sevlyar/go-daemon/daemon.go @@ -0,0 +1,44 @@ +package daemon + +import ( + "errors" + "os" +) + +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) + +// 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 d.reborn() +} + +// Search searches 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 d.search() +} + +// Release provides correct pid-file release in daemon. +func (d *Context) Release() error { + return d.release() +} diff --git a/vendor/github.com/sevlyar/go-daemon/daemon_stub.go b/vendor/github.com/sevlyar/go-daemon/daemon_stub.go new file mode 100644 index 0000000..9b2fbc2 --- /dev/null +++ b/vendor/github.com/sevlyar/go-daemon/daemon_stub.go @@ -0,0 +1,53 @@ +//go:build !darwin && !dragonfly && !freebsd && !linux &&!netbsd && !openbsd && !plan9 && !solaris +// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!plan9,!solaris + +package daemon + +import ( + "os" +) + +// 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 + + // If Umask is non-zero, the daemon-process call Umask() func with given value. + Umask int +} + +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 errNotSupported +} diff --git a/vendor/github.com/sevlyar/go-daemon/daemon_unix.go b/vendor/github.com/sevlyar/go-daemon/daemon_unix.go new file mode 100644 index 0000000..57c37b1 --- /dev/null +++ b/vendor/github.com/sevlyar/go-daemon/daemon_unix.go @@ -0,0 +1,270 @@ +//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || plan9 || solaris +// +build darwin dragonfly freebsd linux netbsd openbsd plan9 solaris + +package daemon + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "syscall" +) + +// 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. + 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 +} + +func (d *Context) reborn() (child *os.Process, err error) { + if !WasReborn() { + child, err = d.parent() + } else { + err = d.child() + } + return +} + +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) + if err == nil && daemon != nil { + // Send a test signal to test if this daemon is actually alive or dead + // An error means it is dead + if daemon.Signal(syscall.Signal(0)) != nil { + daemon = nil + } + } + } + return +} + +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) + if err = encoder.Encode(d); err != nil { + return + } + _, err = fmt.Fprint(d.wpipe, "\n\n") + 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.PidFileName, err = filepath.Abs(d.PidFileName); err != nil { + return err + } + if d.pidFile, err = OpenLockFile(d.PidFileName, d.PidFilePerm); err != nil { + return + } + if err = d.pidFile.Lock(); err != nil { + return + } + if len(d.Chroot) > 0 { + // Calculate PID-file absolute path in child's environment + if d.PidFileName, err = filepath.Rel(d.Chroot, d.PidFileName); err != nil { + return err + } + d.PidFileName = "/" + d.PidFileName + } + } + + if len(d.LogFileName) > 0 { + if d.LogFileName == "/dev/stdout" { + d.logFile = os.Stdout + } else if d.LogFileName == "/dev/stderr" { + d.logFile = os.Stderr + } else 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 = osExecutable(); 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 + } + + // create PID file after context decoding to know PID file full path. + if len(d.PidFileName) > 0 { + d.pidFile = NewLockFile(os.NewFile(4, d.PidFileName)) + if err = d.pidFile.WritePid(); err != nil { + return + } + defer func() { + if err != nil { + d.pidFile.Remove() + } + }() + } + + if err = syscallDup(3, 0); err != nil { + return + } + + if d.Umask != 0 { + syscall.Umask(int(d.Umask)) + } + if len(d.Chroot) > 0 { + err = syscall.Chroot(d.Chroot) + if err != nil { + return + } + } + + return +} + +func (d *Context) release() error { + if !initialized || d.pidFile == nil { + return nil + } + + return d.pidFile.Remove() +} diff --git a/vendor/github.com/sevlyar/go-daemon/lock_file.go b/vendor/github.com/sevlyar/go-daemon/lock_file.go new file mode 100644 index 0000000..1ec81db --- /dev/null +++ b/vendor/github.com/sevlyar/go-daemon/lock_file.go @@ -0,0 +1,109 @@ +package daemon + +import ( + "errors" + "fmt" + "os" +) + +var ( + // ErrWouldBlock indicates on locking pid-file by another process. + ErrWouldBlock = errors.New("daemon: Resource temporarily unavailable") +) + +// LockFile wraps *os.File and provide functions for locking of files. +type LockFile struct { + *os.File +} + +// NewLockFile returns a new LockFile with the given File. +func NewLockFile(file *os.File) *LockFile { + return &LockFile{file} +} + +// CreatePidFile opens the named file, applies exclusive lock and writes +// current process id to file. +func CreatePidFile(name string, perm os.FileMode) (lock *LockFile, err error) { + if lock, err = OpenLockFile(name, perm); err != nil { + return + } + if err = lock.Lock(); err != nil { + lock.Remove() + return + } + if err = lock.WritePid(); err != nil { + lock.Remove() + } + return +} + +// OpenLockFile opens the named file with flags os.O_RDWR|os.O_CREATE and specified perm. +// If successful, function returns LockFile for opened file. +func OpenLockFile(name string, perm os.FileMode) (lock *LockFile, err error) { + var file *os.File + if file, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE, perm); err == nil { + lock = &LockFile{file} + } + return +} + +// Lock apply exclusive lock on an open file. If file already locked, returns error. +func (file *LockFile) Lock() error { + return lockFile(file.Fd()) +} + +// Unlock remove exclusive lock on an open file. +func (file *LockFile) Unlock() error { + return unlockFile(file.Fd()) +} + +// ReadPidFile reads process id from file with give name and returns pid. +// If unable read from a file, returns error. +func ReadPidFile(name string) (pid int, err error) { + var file *os.File + if file, err = os.OpenFile(name, os.O_RDONLY, 0640); err != nil { + return + } + defer file.Close() + + lock := &LockFile{file} + pid, err = lock.ReadPid() + return +} + +// WritePid writes current process id to an open file. +func (file *LockFile) WritePid() (err error) { + if _, err = file.Seek(0, os.SEEK_SET); err != nil { + return + } + var fileLen int + if fileLen, err = fmt.Fprint(file, os.Getpid()); err != nil { + return + } + if err = file.Truncate(int64(fileLen)); err != nil { + return + } + err = file.Sync() + return +} + +// ReadPid reads process id from file and returns pid. +// If unable read from a file, returns error. +func (file *LockFile) ReadPid() (pid int, err error) { + if _, err = file.Seek(0, os.SEEK_SET); err != nil { + return + } + _, err = fmt.Fscan(file, &pid) + return +} + +// Remove removes lock, closes and removes an open file. +func (file *LockFile) Remove() error { + defer file.Close() + + if err := file.Unlock(); err != nil { + return err + } + + return os.Remove(file.Name()) +} diff --git a/vendor/github.com/sevlyar/go-daemon/lock_file_solaris.go b/vendor/github.com/sevlyar/go-daemon/lock_file_solaris.go new file mode 100644 index 0000000..6578fd0 --- /dev/null +++ b/vendor/github.com/sevlyar/go-daemon/lock_file_solaris.go @@ -0,0 +1,41 @@ +//go:build solaris +// +build solaris + +package daemon + +import ( + "io" + "syscall" +) + +func lockFile(fd uintptr) error { + lockInfo := syscall.Flock_t{ + Type: syscall.F_WRLCK, + Whence: io.SeekStart, + Start: 0, + Len: 0, + } + if err := syscall.FcntlFlock(fd, syscall.F_SETLK, &lockInfo); err != nil { + if err == syscall.EAGAIN { + err = ErrWouldBlock + } + return err + } + return nil +} + +func unlockFile(fd uintptr) error { + lockInfo := syscall.Flock_t{ + Type: syscall.F_UNLCK, + Whence: io.SeekStart, + Start: 0, + Len: 0, + } + if err := syscall.FcntlFlock(fd, syscall.F_GETLK, &lockInfo); err != nil { + if err == syscall.EAGAIN { + err = ErrWouldBlock + } + return err + } + return nil +} diff --git a/vendor/github.com/sevlyar/go-daemon/lock_file_stub.go b/vendor/github.com/sevlyar/go-daemon/lock_file_stub.go new file mode 100644 index 0000000..baf43d8 --- /dev/null +++ b/vendor/github.com/sevlyar/go-daemon/lock_file_stub.go @@ -0,0 +1,12 @@ +//go:build !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !plan9 && !solaris +// +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 +} diff --git a/vendor/github.com/sevlyar/go-daemon/lock_file_unix.go b/vendor/github.com/sevlyar/go-daemon/lock_file_unix.go new file mode 100644 index 0000000..94673e9 --- /dev/null +++ b/vendor/github.com/sevlyar/go-daemon/lock_file_unix.go @@ -0,0 +1,24 @@ +//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || plan9 +// +build darwin dragonfly freebsd linux netbsd openbsd plan9 + +package daemon + +import ( + "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 +} diff --git a/vendor/github.com/sevlyar/go-daemon/os_executable.go b/vendor/github.com/sevlyar/go-daemon/os_executable.go new file mode 100644 index 0000000..ddebe3d --- /dev/null +++ b/vendor/github.com/sevlyar/go-daemon/os_executable.go @@ -0,0 +1,12 @@ +//go:build go1.8 +// +build go1.8 + +package daemon + +import ( + "os" +) + +func osExecutable() (string, error) { + return os.Executable() +} diff --git a/vendor/github.com/sevlyar/go-daemon/os_executable_pre18.go b/vendor/github.com/sevlyar/go-daemon/os_executable_pre18.go new file mode 100644 index 0000000..281a82c --- /dev/null +++ b/vendor/github.com/sevlyar/go-daemon/os_executable_pre18.go @@ -0,0 +1,12 @@ +//go:build !go1.8 +// +build !go1.8 + +package daemon + +import ( + "github.com/kardianos/osext" +) + +func osExecutable() (string, error) { + return osext.Executable() +} diff --git a/vendor/github.com/sevlyar/go-daemon/signal.go b/vendor/github.com/sevlyar/go-daemon/signal.go new file mode 100644 index 0000000..fe512da --- /dev/null +++ b/vendor/github.com/sevlyar/go-daemon/signal.go @@ -0,0 +1,59 @@ +package daemon + +import ( + "errors" + "os" + "os/signal" + "syscall" +) + +// ErrStop should be returned signal handler function +// for termination of handling signals. +var ErrStop = errors.New("stop serve signals") + +// SignalHandlerFunc is the interface for signal handler functions. +type SignalHandlerFunc func(sig os.Signal) (err error) + +// SetSigHandler sets handler for the given signals. +// SIGTERM has the default handler, he returns ErrStop. +func SetSigHandler(handler SignalHandlerFunc, signals ...os.Signal) { + for _, sig := range signals { + handlers[sig] = handler + } +} + +// ServeSignals calls handlers for system signals. +func ServeSignals() (err error) { + signals := make([]os.Signal, 0, len(handlers)) + for sig := range handlers { + signals = append(signals, sig) + } + + ch := make(chan os.Signal, 8) + signal.Notify(ch, signals...) + + for sig := range ch { + err = handlers[sig](sig) + if err != nil { + break + } + } + + signal.Stop(ch) + + if err == ErrStop { + err = nil + } + + return +} + +var handlers = make(map[os.Signal]SignalHandlerFunc) + +func init() { + handlers[syscall.SIGTERM] = sigtermDefaultHandler +} + +func sigtermDefaultHandler(sig os.Signal) error { + return ErrStop +} diff --git a/vendor/github.com/sevlyar/go-daemon/syscall_dup.go b/vendor/github.com/sevlyar/go-daemon/syscall_dup.go new file mode 100644 index 0000000..654d6b8 --- /dev/null +++ b/vendor/github.com/sevlyar/go-daemon/syscall_dup.go @@ -0,0 +1,13 @@ +//go:build (!linux || !arm64) && (!linux || !riscv64) && !windows && go1.7 +// +build !linux !arm64 +// +build !linux !riscv64 +// +build !windows +// +build go1.7 + +package daemon + +import "golang.org/x/sys/unix" + +func syscallDup(oldfd int, newfd int) (err error) { + return unix.Dup2(oldfd, newfd) +} diff --git a/vendor/github.com/sevlyar/go-daemon/syscall_dup3.go b/vendor/github.com/sevlyar/go-daemon/syscall_dup3.go new file mode 100644 index 0000000..16bddc9 --- /dev/null +++ b/vendor/github.com/sevlyar/go-daemon/syscall_dup3.go @@ -0,0 +1,13 @@ +//go:build linux && (riscv64 || arm64) +// +build linux +// +build riscv64 arm64 + +package daemon + +import "syscall" + +func syscallDup(oldfd int, newfd int) (err error) { + // linux_arm64 platform doesn't have syscall.Dup2 + // so use the nearly identical syscall.Dup3 instead. + return syscall.Dup3(oldfd, newfd, 0) +} diff --git a/vendor/github.com/sevlyar/go-daemon/syscall_dup_pre17.go b/vendor/github.com/sevlyar/go-daemon/syscall_dup_pre17.go new file mode 100644 index 0000000..381db75 --- /dev/null +++ b/vendor/github.com/sevlyar/go-daemon/syscall_dup_pre17.go @@ -0,0 +1,14 @@ +//go:build (!linux || !arm64) && !windows && !go1.7 +// +build !linux !arm64 +// +build !windows +// +build !go1.7 + +package daemon + +import ( + "syscall" +) + +func syscallDup(oldfd int, newfd int) (err error) { + return syscall.Dup2(oldfd, newfd) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 3f1b494..2c72242 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -2,12 +2,18 @@ ## explicit; go 1.12 github.com/go-ole/go-ole github.com/go-ole/go-ole/oleutil +# github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 +## explicit +github.com/kardianos/osext # github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a ## explicit; go 1.16 github.com/lufia/plan9stats # github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 ## explicit; go 1.14 github.com/power-devops/perfstat +# github.com/sevlyar/go-daemon v0.1.6 +## explicit +github.com/sevlyar/go-daemon # github.com/shirou/gopsutil/v3 v3.24.2 ## explicit; go 1.15 github.com/shirou/gopsutil/v3/common