mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
Fix Fleet Desktop bugs on Windows (#16402)
#15821
This PR is adding two improvements and fixing two Windows bugs in Fleet
Desktop:
## Improvement
- We are now capturing the stderr of Fleet Desktop. This helped me find
bug (1) below (otherwise the panic output below was hidden from us).
- To reduce complexity I'm removing the "Theme detection" routine
because we made the decision to use the colored icon for both themes...,
see here:
415d1f493b/orbit/cmd/desktop/desktop_windows.go (L21-L27)
## Bug fixes
1. Fleet Desktop icon not showing in the task bar. This was fixed by
updating to use the latest version of `fyne.io/systray`. (See
https://github.com/fyne-io/systray/issues/22#issuecomment-1173157898.)
2. Orbit now properly detects if Fleet Desktop isn't running on Windows.
Bug (1)'s panic output
```
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x0 pc=0x72b14b]
goroutine 23 [running]:
fyne.io/systray.(*winTray).setTooltip(0x1eb5d40, {0x126923f?, 0x0?})
/Users/luk/gopath/pkg/mod/fyne.io/systray@v1.10.0/systray_windows.go:260 +0xcb
fyne.io/systray.SetTooltip({0x126923f?, 0x125fc16?})
/Users/luk/gopath/pkg/mod/fyne.io/systray@v1.10.0/systray_windows.go:961 +0x29
main.main.func1()
/Users/luk/fleetdm/git/fleet/orbit/cmd/desktop/desktop.go:103 +0xba
fyne.io/systray.Register.func2()
/Users/luk/gopath/pkg/mod/fyne.io/systray@v1.10.0/systray.go:98 +0x2f
created by fyne.io/systray.Register in goroutine 1
/Users/luk/gopath/pkg/mod/fyne.io/systray@v1.10.0/systray.go:96 +0xb1
```
- [X] Changes file added for user-visible changes in `changes/` or
`orbit/changes/`.
See [Changes
files](https://fleetdm.com/docs/contributing/committing-changes#changes-files)
for more information.
- [X] Manual QA for all new/changed functionality
- For Orbit and Fleet Desktop changes:
- [X] Manual QA must be performed in the three main OSs, macOS, Windows
and Linux.
- [x] Auto-update manual QA, from released version of component to new
version (see [tools/tuf/test](../tools/tuf/test/README.md)).
This commit is contained in:
parent
3c5d52a5ff
commit
ea25ce4e9e
16 changed files with 304 additions and 166 deletions
3
go.mod
3
go.mod
|
|
@ -4,7 +4,7 @@ go 1.21
|
|||
|
||||
require (
|
||||
cloud.google.com/go/pubsub v1.33.0
|
||||
fyne.io/systray v1.10.0
|
||||
fyne.io/systray v1.10.1-0.20240111184411-11c585fff98d
|
||||
github.com/AbGuthrie/goquery/v2 v2.0.1
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
|
|
@ -289,7 +289,6 @@ require (
|
|||
github.com/subosito/gotenv v1.2.0 // indirect
|
||||
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
|
||||
github.com/tevino/abool v1.2.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||
github.com/trivago/tgo v1.0.7 // indirect
|
||||
|
|
|
|||
6
go.sum
6
go.sum
|
|
@ -84,8 +84,8 @@ contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0Wk
|
|||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
fyne.io/systray v1.10.0 h1:Yr1D9Lxeiw3+vSuZWPlaHC8BMjIHZXJKkek706AfYQk=
|
||||
fyne.io/systray v1.10.0/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE=
|
||||
fyne.io/systray v1.10.1-0.20240111184411-11c585fff98d h1:NjHwOOuOgGswUOPzDlsEDJOqKdjOjwL8Vi1mj9qx9+o=
|
||||
fyne.io/systray v1.10.1-0.20240111184411-11c585fff98d/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
|
||||
github.com/AbGuthrie/goquery/v2 v2.0.1 h1:h0tIhmeRroyqYjT9zxXPXOrheNp1xqNTV+XFWuDI+eA=
|
||||
github.com/AbGuthrie/goquery/v2 v2.0.1/go.mod h1:xpDLF4kUr+TRFXogclRa7Zzc8bMAB/fYm1zG/XX1WOA=
|
||||
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
|
||||
|
|
@ -1171,8 +1171,6 @@ github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BG
|
|||
github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
|
||||
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
|
||||
github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
|
||||
github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
|
||||
github.com/theupdateframework/go-tuf v0.5.2 h1:habfDzTmpbzBLIFGWa2ZpVhYvFBoK0C1onC3a4zuPRA=
|
||||
github.com/theupdateframework/go-tuf v0.5.2/go.mod h1:SyMV5kg5n4uEclsyxXJZI2UxPFJNDc4Y+r7wv+MlvTA=
|
||||
github.com/throttled/throttled/v2 v2.8.0 h1:B5VfdM8BE+ClI2Ji238SbNOTWfYcocvuAhgT27lvwrE=
|
||||
|
|
|
|||
2
orbit/changes/15821-fix-desktop-windows-tray-icon
Normal file
2
orbit/changes/15821-fix-desktop-windows-tray-icon
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
* Fixed bug on Windows where Fleet Desktop tray icon was not showing in the task bar.
|
||||
* Fixed bug on Windows where Orbit was not bringing the Fleet Desktop process up (when it was detected as not running).
|
||||
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"fyne.io/systray"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/constant"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/go-paniclog"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/profiles"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/token"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/update"
|
||||
|
|
@ -59,6 +60,7 @@ func setupRunners() {
|
|||
|
||||
func main() {
|
||||
setupLogs()
|
||||
setupStderr()
|
||||
|
||||
// Our TUF provided targets must support launching with "--help".
|
||||
if len(os.Args) > 1 && os.Args[1] == "--help" {
|
||||
|
|
@ -101,25 +103,11 @@ func main() {
|
|||
log.Info().Msg("ready")
|
||||
|
||||
systray.SetTooltip("Fleet Desktop")
|
||||
|
||||
// Default to dark theme icon because this seems to be a better fit on Linux (Ubuntu at
|
||||
// least). On macOS this is used as a template icon anyway.
|
||||
systray.SetTemplateIcon(iconDark, iconDark)
|
||||
|
||||
// Theme detection is currently only on Windows. On macOS we use template icons (which
|
||||
// automatically change), and on Linux we don't handle it yet (Ubuntu doesn't seem to change
|
||||
// systray colors in the default configuration when toggling light/dark).
|
||||
if runtime.GOOS == "windows" {
|
||||
// Set the initial theme, and watch for theme changes.
|
||||
theme, err := getSystemTheme()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("get system theme")
|
||||
}
|
||||
iconManager := newIconManager(theme)
|
||||
go func() {
|
||||
watchSystemTheme(iconManager)
|
||||
}()
|
||||
}
|
||||
|
||||
// Add a disabled menu item with the current version
|
||||
versionItem := systray.AddMenuItem(fmt.Sprintf("Fleet Desktop v%s", version), "")
|
||||
versionItem.Disable()
|
||||
|
|
@ -438,14 +426,12 @@ func (m *mdmMigrationHandler) ShowInstructions() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// setupLogs configures our logging system to write logs to rolling files and
|
||||
// stderr, if for some reason we can't write a log file the logs are still
|
||||
// printed to stderr.
|
||||
// setupLogs configures our logging system to write logs to rolling files, if for some
|
||||
// reason we can't write a log file the logs are still printed to stderr.
|
||||
func setupLogs() {
|
||||
stderrOut := zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339Nano, NoColor: true}
|
||||
|
||||
dir, err := logDir()
|
||||
if err != nil {
|
||||
stderrOut := zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339Nano, NoColor: true}
|
||||
log.Logger = log.Output(stderrOut)
|
||||
log.Error().Err(err).Msg("find directory for logs")
|
||||
return
|
||||
|
|
@ -454,6 +440,7 @@ func setupLogs() {
|
|||
dir = filepath.Join(dir, "Fleet")
|
||||
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
stderrOut := zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339Nano, NoColor: true}
|
||||
log.Logger = log.Output(stderrOut)
|
||||
log.Error().Err(err).Msg("make directories for log files")
|
||||
return
|
||||
|
|
@ -466,10 +453,40 @@ func setupLogs() {
|
|||
MaxAge: 28, // days
|
||||
}
|
||||
|
||||
log.Logger = log.Output(zerolog.MultiLevelWriter(
|
||||
zerolog.ConsoleWriter{Out: logFile, TimeFormat: time.RFC3339Nano, NoColor: true},
|
||||
stderrOut,
|
||||
))
|
||||
consoleWriter := zerolog.ConsoleWriter{Out: logFile, TimeFormat: time.RFC3339Nano, NoColor: true}
|
||||
log.Logger = log.Output(consoleWriter)
|
||||
}
|
||||
|
||||
// setupStderr redirects stderr output to a file.
|
||||
func setupStderr() {
|
||||
dir, err := logDir()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("find directory for stderr")
|
||||
return
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
log.Error().Err(err).Msg("make directories for stderr")
|
||||
return
|
||||
}
|
||||
|
||||
stderrFile, err := os.OpenFile(filepath.Join(dir, "Fleet", "fleet-desktop.err"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o666)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("create file to redirect stderr")
|
||||
return
|
||||
}
|
||||
defer stderrFile.Close()
|
||||
|
||||
if _, err := stderrFile.Write([]byte(time.Now().UTC().Format("2006-01-02T15-04-05") + "\n")); err != nil {
|
||||
log.Error().Err(err).Msg("write to stderr file")
|
||||
}
|
||||
|
||||
// We need to use this method to properly capture golang's panic stderr output.
|
||||
// Just setting os.Stderr to a file doesn't work (Go's runtime is probably using os.Stderr
|
||||
// very early).
|
||||
if _, err := paniclog.RedirectStderr(stderrFile); err != nil {
|
||||
log.Error().Err(err).Msg("redirect stderr to file")
|
||||
}
|
||||
}
|
||||
|
||||
// logDir returns the default root directory to use for application-level logs.
|
||||
|
|
@ -512,29 +529,3 @@ func logDir() (string, error) {
|
|||
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
type iconManager struct {
|
||||
theme theme
|
||||
}
|
||||
|
||||
func newIconManager(theme theme) *iconManager {
|
||||
m := &iconManager{
|
||||
theme: theme,
|
||||
}
|
||||
m.UpdateTheme(theme)
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *iconManager) UpdateTheme(theme theme) {
|
||||
m.theme = theme
|
||||
switch theme {
|
||||
case themeDark:
|
||||
systray.SetIcon(iconDark)
|
||||
case themeLight:
|
||||
systray.SetIcon(iconLight)
|
||||
case themeUnknown:
|
||||
log.Debug().Msg("theme unknown, using dark theme")
|
||||
default:
|
||||
log.Error().Str("theme", string(theme)).Msg("tried to set invalid theme")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,21 +9,9 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
//go:embed icon_light.png
|
||||
var iconLight []byte
|
||||
|
||||
//go:embed icon_dark.png
|
||||
var iconDark []byte
|
||||
|
||||
func getSystemTheme() (theme, error) {
|
||||
log.Debug().Msg("get system theme not implemented for this platform")
|
||||
return themeUnknown, nil
|
||||
}
|
||||
|
||||
func watchSystemTheme(_ *iconManager) {
|
||||
log.Debug().Msg("watch system theme not implemented for this platform")
|
||||
}
|
||||
|
||||
func blockWaitForStopEvent(channelId string) error {
|
||||
log.Debug().Msg("communication channel helpers are not implemented for this platform")
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -7,103 +7,25 @@ import (
|
|||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
// For Windows we must use ico format for the icon,
|
||||
// see https://github.com/getlantern/systray/blob/6065fda28be8c8d91aeb5e20de25e1600b8664a3/systray_windows.go#L850-L856.
|
||||
|
||||
// since watchSystemTheme is currently buggy, we are using the same icon for both themes
|
||||
//
|
||||
// In the past we implemented some logic to detect the Windows theme but it was buggy,
|
||||
// so as a temporary fix we are using the same colored icon for both themes.
|
||||
// Such theme detection logic was removed in this PR: https://github.com/fleetdm/fleet/pull/16402.
|
||||
|
||||
//go:embed windows_app.ico
|
||||
var iconLight []byte
|
||||
|
||||
//go:embed windows_app.ico
|
||||
var iconDark []byte
|
||||
|
||||
// Adapted from MIT licensed code in
|
||||
// https://github.com/WireGuard/wireguard-go/commit/7e962a9932667f4a161b20aba5ff1c75ab8e578a
|
||||
// and https://gist.github.com/jerblack/1d05bbcebb50ad55c312e4d7cf1bc909
|
||||
|
||||
// mkwinsyscall generates the Window syscall from the //sys comment below.
|
||||
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output generated_syscall_windows.go desktop_windows.go
|
||||
//sys regNotifyChangeKeyValue(key windows.Handle, watchSubtree bool, notifyFilter uint32, event windows.Handle, asynchronous bool) (regerrno error) = advapi32.RegNotifyChangeKeyValue
|
||||
|
||||
const (
|
||||
// Registry path where theme value is stored.
|
||||
registryPath = `Software\Microsoft\Windows\CurrentVersion\Themes\Personalize`
|
||||
registryKey = "AppsUseLightTheme"
|
||||
// REG_NOTIFY_CHANGE_LAST_SET notifies the caller of changes to a value of the key. This can include adding or deleting a value, or changing an existing value.
|
||||
REG_NOTIFY_CHANGE_LAST_SET uint32 = 0x00000004
|
||||
)
|
||||
|
||||
func getSystemTheme() (theme, error) {
|
||||
// Adapted from https://stackoverflow.com/a/58494769/491710
|
||||
key, err := registry.OpenKey(registry.CURRENT_USER, registryPath, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return themeDark, err
|
||||
}
|
||||
defer key.Close()
|
||||
|
||||
val, _, err := key.GetIntegerValue(registryKey)
|
||||
if err != nil {
|
||||
return themeDark, err
|
||||
}
|
||||
|
||||
switch val {
|
||||
case 0:
|
||||
return themeDark, nil
|
||||
case 1:
|
||||
return themeLight, nil
|
||||
default:
|
||||
return themeUnknown, fmt.Errorf("unknown theme value %d", val)
|
||||
}
|
||||
}
|
||||
|
||||
// since this logic is currently buggy, we are currently using the same icon for both themes
|
||||
func watchSystemTheme(iconManager *iconManager) {
|
||||
for {
|
||||
// Function call for proper defer semantics.
|
||||
func() {
|
||||
// Open the key within the loop, because "If the specified key is closed, the event is
|
||||
// signaled. This means that an application should not depend on the key being open after
|
||||
// returning from a wait operation on the event." -
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regnotifychangekeyvalue
|
||||
key, err := registry.OpenKey(registry.CURRENT_USER, registryPath, syscall.KEY_NOTIFY)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("open registry key")
|
||||
return
|
||||
}
|
||||
defer key.Close()
|
||||
|
||||
err = regNotifyChangeKeyValue(windows.Handle(key), false, REG_NOTIFY_CHANGE_LAST_SET, windows.Handle(0), false)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("change notification on registry value")
|
||||
}
|
||||
|
||||
theme, err := getSystemTheme()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("get system theme")
|
||||
return
|
||||
}
|
||||
log.Debug().Str("theme", string(theme)).Msg("got theme update")
|
||||
|
||||
// The systray library has a timeout issue trying to change the icon sometimes. As a
|
||||
// cheap workaround, do this update a handful of times hoping one will work. Sadly the
|
||||
// API doesn't return an error in these cases.
|
||||
for i := 0; i < 10; i++ {
|
||||
iconManager.UpdateTheme(theme)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// blockWaitForStopEvent waits for the named event kernel object to be signalled
|
||||
func blockWaitForStopEvent(channelId string) error {
|
||||
if channelId == "" {
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
package main
|
||||
|
||||
type theme string
|
||||
|
||||
const (
|
||||
themeDark theme = "dark"
|
||||
themeLight = "light"
|
||||
themeUnknown = "unknown"
|
||||
)
|
||||
21
orbit/pkg/go-paniclog/LICENSE
Normal file
21
orbit/pkg/go-paniclog/LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
Copyright (C) 2019 by Dustin Spicuzza (dustin@virtualroadside.com)
|
||||
Copyright (C) 2012 by Nick Craig-Wood http://www.craig-wood.com/nick/
|
||||
|
||||
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.
|
||||
|
||||
34
orbit/pkg/go-paniclog/README.md
Normal file
34
orbit/pkg/go-paniclog/README.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
> This package was copied from https://github.com/virtuald/go-paniclog
|
||||
|
||||
go-paniclog
|
||||
===========
|
||||
|
||||
By default, panics in golang are sent to stderr. Unfortunately, there isn't a
|
||||
direct builtin global mechanism to capture/send the output of the panic to
|
||||
a file or really do anything with it other than to write to stderr.
|
||||
|
||||
One possible solution is that you can redirect stderr to a file, and that's
|
||||
all that this package does. Of course, once you redirect stderr to file,
|
||||
anything else you write to stderr will also end up in that file. v2.0 now
|
||||
includes a function you can use to undo the redirection if you wanted to do
|
||||
that for some reason.
|
||||
|
||||
Reference: https://stackoverflow.com/questions/34772012/capturing-panic-in-golang
|
||||
|
||||
|
||||
Alternatives
|
||||
------------
|
||||
|
||||
* [panicwrap](https://github.com/mitchellh/panicwrap) may be a better solution
|
||||
for many programs
|
||||
|
||||
Author
|
||||
------
|
||||
|
||||
I can't claim any credit for this idea or the code, it is entirely taken from
|
||||
the [rclone](https://github.com/ncw/rclone.git) program by Nick Craig-Wood.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
MIT License
|
||||
31
orbit/pkg/go-paniclog/example/main.go
Normal file
31
orbit/pkg/go-paniclog/example/main.go
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/go-paniclog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
f, err := os.Create("test.log")
|
||||
if err != nil {
|
||||
fmt.Println("Error creating file:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
undo, err := paniclog.RedirectStderr(f)
|
||||
if err != nil {
|
||||
fmt.Println("Error redirecting stderr:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
f.Close()
|
||||
|
||||
if os.Getenv("UNDO_PANICLOG") != "" {
|
||||
// demonstrates undoing the stderr redirect
|
||||
undo() //nolint:errcheck
|
||||
}
|
||||
|
||||
panic("this should end up in the file instead of the console")
|
||||
}
|
||||
19
orbit/pkg/go-paniclog/paniclog.go
Normal file
19
orbit/pkg/go-paniclog/paniclog.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package paniclog
|
||||
|
||||
import "os"
|
||||
|
||||
// UndoFunction will reverse the redirection
|
||||
type UndoFunction func() error
|
||||
|
||||
// RedirectStderr to the file passed in, so that the output of any panics that
|
||||
// occur will be sent to that file. The caller may close the file after
|
||||
// this function returns.
|
||||
//
|
||||
// Of course, anything else written to stderr will also be sent to that file,
|
||||
// so don't do that unless that's your intent.
|
||||
//
|
||||
// Returns a function that can be used to revert stderr back to the console,
|
||||
// or an error if stderr could not be redirected
|
||||
func RedirectStderr(f *os.File) (UndoFunction, error) {
|
||||
return redirectStderr(f)
|
||||
}
|
||||
15
orbit/pkg/go-paniclog/paniclog_other.go
Normal file
15
orbit/pkg/go-paniclog/paniclog_other.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Log the panic to the log file - for oses which can't do this
|
||||
|
||||
//go:build !windows && !darwin && !dragonfly && !freebsd && !linux && !nacl && !netbsd && !openbsd
|
||||
// +build !windows,!darwin,!dragonfly,!freebsd,!linux,!nacl,!netbsd,!openbsd
|
||||
|
||||
package paniclog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
func redirectStderr(f *os.File) (UndoFunction, error) {
|
||||
return nil, errors.New("Can't redirect stderr to file")
|
||||
}
|
||||
39
orbit/pkg/go-paniclog/paniclog_unix.go
Normal file
39
orbit/pkg/go-paniclog/paniclog_unix.go
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// Log the panic under unix to the log file
|
||||
|
||||
//go:build !windows && !solaris && !plan9
|
||||
// +build !windows,!solaris,!plan9
|
||||
|
||||
package paniclog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func redirectStderr(f *os.File) (UndoFunction, error) {
|
||||
stderrFd := int(os.Stderr.Fd())
|
||||
oldfd, err := unix.Dup(stderrFd)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to redirect stderr to file: " + err.Error())
|
||||
}
|
||||
|
||||
err = unix.Dup2(int(f.Fd()), stderrFd)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to redirect stderr to file: " + err.Error())
|
||||
}
|
||||
|
||||
undo := func() error {
|
||||
undoErr := unix.Dup2(oldfd, stderrFd)
|
||||
unix.Close(oldfd)
|
||||
|
||||
if undoErr != nil {
|
||||
return errors.New("Failed to reverse stderr redirection: " + err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return undo, nil
|
||||
}
|
||||
84
orbit/pkg/go-paniclog/paniclog_windows.go
Normal file
84
orbit/pkg/go-paniclog/paniclog_windows.go
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
// Log the panic under windows to the log file
|
||||
//
|
||||
// Code from minix, via
|
||||
//
|
||||
// https://play.golang.org/p/kLtct7lSUg
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package paniclog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32 = syscall.MustLoadDLL("kernel32.dll")
|
||||
procSetStdHandle = kernel32.MustFindProc("SetStdHandle")
|
||||
procGetStdHandle = kernel32.MustFindProc("GetStdHandle")
|
||||
)
|
||||
|
||||
func dupFD(fd uintptr) (syscall.Handle, error) {
|
||||
// Cribbed from https://github.com/golang/go/blob/go1.8/src/syscall/exec_windows.go#L303.
|
||||
p, err := syscall.GetCurrentProcess()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var h syscall.Handle
|
||||
return h, syscall.DuplicateHandle(p, syscall.Handle(fd), p, &h, 0, true, syscall.DUPLICATE_SAME_ACCESS)
|
||||
}
|
||||
|
||||
func getStdHandle(stdHandle int32) (syscall.Handle, error) {
|
||||
r0, _, e1 := syscall.Syscall(procGetStdHandle.Addr(), 2, uintptr(stdHandle), 0, 0)
|
||||
rh0 := syscall.Handle(r0)
|
||||
if rh0 == syscall.InvalidHandle {
|
||||
if e1 != 0 {
|
||||
return syscall.InvalidHandle, error(e1)
|
||||
}
|
||||
return syscall.InvalidHandle, syscall.EINVAL
|
||||
}
|
||||
return syscall.Handle(r0), nil
|
||||
}
|
||||
|
||||
func setStdHandle(stdhandle int32, handle syscall.Handle) error {
|
||||
r0, _, e1 := syscall.Syscall(procSetStdHandle.Addr(), 2, uintptr(stdhandle), uintptr(handle), 0)
|
||||
if r0 == 0 {
|
||||
if e1 != 0 {
|
||||
return error(e1)
|
||||
}
|
||||
return syscall.EINVAL
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func redirectStderr(f *os.File) (UndoFunction, error) {
|
||||
stderrFd, err := getStdHandle(syscall.STD_ERROR_HANDLE)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to redirect stderr to file: " + err.Error())
|
||||
}
|
||||
|
||||
// duplicate the handle to match unix behavior
|
||||
fHandle, err := dupFD(f.Fd())
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to duplicate file: " + err.Error())
|
||||
}
|
||||
|
||||
err = setStdHandle(syscall.STD_ERROR_HANDLE, fHandle)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to redirect stderr to file: " + err.Error())
|
||||
}
|
||||
|
||||
undo := func() error {
|
||||
err := setStdHandle(syscall.STD_ERROR_HANDLE, stderrFd)
|
||||
if err != nil {
|
||||
return errors.New("Failed to redirect stderr to file: " + err.Error())
|
||||
}
|
||||
syscall.CloseHandle(fHandle)
|
||||
return nil
|
||||
}
|
||||
|
||||
return undo, nil
|
||||
}
|
||||
|
|
@ -54,7 +54,8 @@ func SignalProcessBeforeTerminate(processName string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// GetProcessByName gets a single process object by its name
|
||||
// GetProcessByName gets a single running process object by its name.
|
||||
// Returns ErrProcessNotFound if the process was not found running.
|
||||
func GetProcessByName(name string) (*gopsutil_process.Process, error) {
|
||||
if name == "" {
|
||||
return nil, errors.New("process name should not be empty")
|
||||
|
|
|
|||
|
|
@ -136,16 +136,14 @@ func SignalProcessBeforeTerminate(processName string) error {
|
|||
return fmt.Errorf("get process: %w", err)
|
||||
}
|
||||
|
||||
isRunning, err := foundProcess.IsRunning()
|
||||
if (err == nil) && (isRunning) {
|
||||
if err := foundProcess.Kill(); err != nil {
|
||||
return fmt.Errorf("kill process %d: %w", foundProcess.Pid, err)
|
||||
}
|
||||
if err := foundProcess.Kill(); err != nil {
|
||||
return fmt.Errorf("kill process %d: %w", foundProcess.Pid, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetProcessByName gets a single process object by its name
|
||||
// GetProcessByName gets a single running process object by its name.
|
||||
// Returns ErrProcessNotFound if the process was not found running.
|
||||
func GetProcessByName(name string) (*gopsutil_process.Process, error) {
|
||||
if name == "" {
|
||||
return nil, errors.New("process name should not be empty")
|
||||
|
|
@ -198,6 +196,11 @@ func GetProcessByName(name string) (*gopsutil_process.Process, error) {
|
|||
return nil, fmt.Errorf("NewProcess: %w", err)
|
||||
}
|
||||
|
||||
isRunning, err := process.IsRunning()
|
||||
if err != nil || !isRunning {
|
||||
return nil, ErrProcessNotFound
|
||||
}
|
||||
|
||||
return process, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue