Skip to content

Commit 713becd

Browse files
committed
feat: add supervisor and error dump
1 parent 83b0c79 commit 713becd

File tree

9 files changed

+198
-4
lines changed

9 files changed

+198
-4
lines changed

cmd/main.go

Lines changed: 147 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,37 @@ package main
33
import (
44
"flag"
55
"fmt"
6+
"io"
67
"os"
8+
"os/exec"
9+
"os/signal"
10+
"path/filepath"
11+
"syscall"
12+
"time"
713

14+
"github.com/erikdubbelboer/gspt"
815
"github.com/jetkvm/kvm"
916
)
1017

18+
const (
19+
envChildID = "JETKVM_CHILD_ID"
20+
errorDumpDir = "/userdata/jetkvm/"
21+
errorDumpStateFile = ".has_error_dump"
22+
errorDumpTemplate = "jetkvm-%s.log"
23+
)
24+
25+
func program() {
26+
gspt.SetProcTitle(os.Args[0] + " [app]")
27+
kvm.Main()
28+
}
29+
1130
func main() {
1231
versionPtr := flag.Bool("version", false, "print version and exit")
13-
versionJsonPtr := flag.Bool("version-json", false, "print version as json and exit")
32+
versionJSONPtr := flag.Bool("version-json", false, "print version as json and exit")
1433
flag.Parse()
1534

16-
if *versionPtr || *versionJsonPtr {
17-
versionData, err := kvm.GetVersionData(*versionJsonPtr)
35+
if *versionPtr || *versionJSONPtr {
36+
versionData, err := kvm.GetVersionData(*versionJSONPtr)
1837
if err != nil {
1938
fmt.Printf("failed to get version data: %v\n", err)
2039
os.Exit(1)
@@ -23,5 +42,129 @@ func main() {
2342
return
2443
}
2544

26-
kvm.Main()
45+
childID := os.Getenv(envChildID)
46+
switch childID {
47+
case "":
48+
doSupervise()
49+
case kvm.GetBuiltAppVersion():
50+
program()
51+
default:
52+
fmt.Printf("Invalid build version: %s != %s\n", childID, kvm.GetBuiltAppVersion())
53+
os.Exit(1)
54+
}
55+
}
56+
57+
func supervise() error {
58+
// check binary path
59+
binPath, err := os.Executable()
60+
if err != nil {
61+
return fmt.Errorf("failed to get executable path: %w", err)
62+
}
63+
64+
// check if binary is same as current binary
65+
if info, statErr := os.Stat(binPath); statErr != nil {
66+
return fmt.Errorf("failed to get executable info: %w", statErr)
67+
// check if binary is empty
68+
} else if info.Size() == 0 {
69+
return fmt.Errorf("binary is empty")
70+
// check if it's executable
71+
} else if info.Mode().Perm()&0111 == 0 {
72+
return fmt.Errorf("binary is not executable")
73+
}
74+
// run the child binary
75+
cmd := exec.Command(binPath)
76+
77+
cmd.Env = append(os.Environ(), []string{envChildID + "=" + kvm.GetBuiltAppVersion()}...)
78+
cmd.Args = os.Args
79+
80+
logFile, err := os.CreateTemp("", "jetkvm-stdout.log")
81+
defer func() {
82+
// we don't care about the errors here
83+
_ = logFile.Close()
84+
_ = os.Remove(logFile.Name())
85+
}()
86+
if err != nil {
87+
return fmt.Errorf("failed to create log file: %w", err)
88+
}
89+
90+
// Use io.MultiWriter to write to both the original streams and our buffers
91+
cmd.Stdout = io.MultiWriter(os.Stdout, logFile)
92+
cmd.Stderr = io.MultiWriter(os.Stderr, logFile)
93+
if startErr := cmd.Start(); startErr != nil {
94+
return fmt.Errorf("failed to start command: %w", startErr)
95+
}
96+
97+
go func() {
98+
sigChan := make(chan os.Signal, 1)
99+
signal.Notify(sigChan, syscall.SIGTERM)
100+
101+
sig := <-sigChan
102+
_ = cmd.Process.Signal(sig)
103+
}()
104+
105+
gspt.SetProcTitle(os.Args[0] + " [sup]")
106+
107+
cmdErr := cmd.Wait()
108+
if cmdErr == nil {
109+
return nil
110+
}
111+
112+
if exiterr, ok := cmdErr.(*exec.ExitError); ok {
113+
createErrorDump(logFile)
114+
os.Exit(exiterr.ExitCode())
115+
}
116+
117+
return nil
118+
}
119+
120+
func createErrorDump(logFile *os.File) {
121+
logFile.Close()
122+
123+
// touch the error dump state file
124+
if err := os.WriteFile(filepath.Join(errorDumpDir, errorDumpStateFile), []byte{}, 0644); err != nil {
125+
return
126+
}
127+
128+
fileName := fmt.Sprintf(errorDumpTemplate, time.Now().Format("20060102150405"))
129+
filePath := filepath.Join(errorDumpDir, fileName)
130+
if err := os.Rename(logFile.Name(), filePath); err == nil {
131+
fmt.Printf("error dump created: %s\n", filePath)
132+
return
133+
}
134+
135+
fnSrc, err := os.Open(logFile.Name())
136+
if err != nil {
137+
return
138+
}
139+
defer fnSrc.Close()
140+
141+
fnDst, err := os.Create(filePath)
142+
if err != nil {
143+
return
144+
}
145+
defer fnDst.Close()
146+
147+
buf := make([]byte, 1024*1024)
148+
for {
149+
n, err := fnSrc.Read(buf)
150+
if err != nil && err != io.EOF {
151+
return
152+
}
153+
if n == 0 {
154+
break
155+
}
156+
157+
if _, err := fnDst.Write(buf[:n]); err != nil {
158+
return
159+
}
160+
}
161+
162+
fmt.Printf("error dump created: %s\n", filePath)
163+
}
164+
165+
func doSupervise() {
166+
err := supervise()
167+
if err == nil {
168+
return
169+
}
27170
}

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,22 +38,27 @@ require (
3838
replace github.com/pojntfx/go-nbd v0.3.2 => github.com/chemhack/go-nbd v0.0.0-20241006125820-59e45f5b1e7b
3939

4040
require (
41+
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
4142
github.com/beorn7/perks v1.0.1 // indirect
4243
github.com/bytedance/sonic v1.13.3 // indirect
4344
github.com/bytedance/sonic/loader v0.2.4 // indirect
4445
github.com/cespare/xxhash/v2 v2.3.0 // indirect
4546
github.com/cloudwego/base64x v0.1.5 // indirect
4647
github.com/creack/goselect v0.1.2 // indirect
4748
github.com/davecgh/go-spew v1.1.1 // indirect
49+
github.com/erikdubbelboer/gspt v0.0.0-20210805194459-ce36a5128377 // indirect
4850
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
4951
github.com/gin-contrib/sse v1.1.0 // indirect
5052
github.com/go-jose/go-jose/v4 v4.1.0 // indirect
53+
github.com/go-ole/go-ole v1.2.4 // indirect
5154
github.com/go-playground/locales v0.14.1 // indirect
5255
github.com/go-playground/universal-translator v0.18.1 // indirect
5356
github.com/go-playground/validator/v10 v10.26.0 // indirect
5457
github.com/goccy/go-json v0.10.5 // indirect
5558
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
5659
github.com/jonboulle/clockwork v0.5.0 // indirect
60+
github.com/jpillora/overseer v1.1.6 // indirect
61+
github.com/jpillora/s3 v1.1.4 // indirect
5762
github.com/json-iterator/go v1.1.12 // indirect
5863
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
5964
github.com/leodido/go-urn v1.4.0 // indirect

go.sum

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
22
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
3+
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
4+
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
35
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
46
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
57
github.com/beevik/ntp v1.4.3 h1:PlbTvE5NNy4QHmA4Mg57n7mcFTmr1W1j3gcK7L1lqho=
@@ -32,6 +34,8 @@ github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfv
3234
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3335
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3436
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
37+
github.com/erikdubbelboer/gspt v0.0.0-20210805194459-ce36a5128377 h1:gT+RM6gdTIAzMT7HUvmT5mL8SyG8Wx7iS3+L0V34Km4=
38+
github.com/erikdubbelboer/gspt v0.0.0-20210805194459-ce36a5128377/go.mod h1:v6o7m/E9bfvm79dE1iFiF+3T7zLBnrjYjkWMa1J+Hv0=
3539
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
3640
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
3741
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
@@ -46,6 +50,8 @@ github.com/go-co-op/gocron/v2 v2.16.5 h1:j228Jxk7bb9CF8LKR3gS+bK3rcjRUINjlVI+ZMp
4650
github.com/go-co-op/gocron/v2 v2.16.5/go.mod h1:zAfC/GFQ668qHxOVl/D68Jh5Ce7sDqX6TJnSQyRkRBc=
4751
github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY=
4852
github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw=
53+
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
54+
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
4955
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
5056
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
5157
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@@ -70,6 +76,10 @@ github.com/gwatts/rootcerts v0.0.0-20250901182336-dc5ae18bd79f h1:08t2PbrkDgW2+m
7076
github.com/gwatts/rootcerts v0.0.0-20250901182336-dc5ae18bd79f/go.mod h1:5Kt9XkWvkGi2OHOq0QsGxebHmhCcqJ8KCbNg/a6+n+g=
7177
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
7278
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
79+
github.com/jpillora/overseer v1.1.6 h1:3ygYfNcR3FfOr22miu3vR1iQcXKMHbmULBh98rbkIyo=
80+
github.com/jpillora/overseer v1.1.6/go.mod h1:aPXQtxuVb9PVWRWTXpo+LdnC/YXQ0IBLNXqKMJmgk88=
81+
github.com/jpillora/s3 v1.1.4 h1:YCCKDWzb/Ye9EBNd83ATRF/8wPEy0xd43Rezb6u6fzc=
82+
github.com/jpillora/s3 v1.1.4/go.mod h1:yedE603V+crlFi1Kl/5vZJaBu9pUzE9wvKegU/lF2zs=
7383
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
7484
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
7585
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
@@ -160,6 +170,8 @@ github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
160170
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
161171
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
162172
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
173+
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
174+
github.com/smartystreets/gunit v1.1.3/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ=
163175
github.com/sourcegraph/tf-dag v0.2.2-0.20250131204052-3e8ff1477b4f h1:VgoRCP1efSCEZIcF2THLQ46+pIBzzgNiaUBe9wEDwYU=
164176
github.com/sourcegraph/tf-dag v0.2.2-0.20250131204052-3e8ff1477b4f/go.mod h1:pzro7BGorij2WgrjEammtrkbo3+xldxo+KaGLGUiD+Q=
165177
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

internal/native/cgo/ctrl.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,4 +411,10 @@ int jetkvm_video_init() {
411411

412412
void jetkvm_video_shutdown() {
413413
video_shutdown();
414+
}
415+
416+
void jetkvm_crash() {
417+
// let's call a function that will crash the program
418+
int* p = 0;
419+
*p = 0;
414420
}

internal/native/cgo/ctrl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ void jetkvm_set_indev_handler(jetkvm_indev_handler_t *handler);
2626
void jetkvm_set_rpc_handler(jetkvm_rpc_handler_t *handler);
2727
void jetkvm_call_rpc_handler(const char *method, const char *params);
2828
void jetkvm_set_video_state_handler(jetkvm_video_state_handler_t *handler);
29+
void jetkvm_crash();
2930

3031
void jetkvm_ui_set_var(const char *name, const char *value);
3132
const char *jetkvm_ui_get_var(const char *name);

internal/native/cgo_linux.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,3 +389,9 @@ func videoSetEDID(edid string) error {
389389
C.jetkvm_video_set_edid(edidCStr)
390390
return nil
391391
}
392+
393+
// DO NOT USE THIS FUNCTION IN PRODUCTION
394+
// This is only for testing purposes
395+
func crash() {
396+
C.jetkvm_crash()
397+
}

internal/native/cgo_notlinux.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,7 @@ func videoSetEDID(edid string) error {
122122
panicPlatformNotSupported()
123123
return nil
124124
}
125+
126+
func crash() {
127+
panicPlatformNotSupported()
128+
}

internal/native/native.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,15 @@ func (n *Native) Start() {
9999

100100
close(n.ready)
101101
}
102+
103+
// DoNotUseThisIsForCrashTestingOnly
104+
// will crash the program in cgo code
105+
func (n *Native) DoNotUseThisIsForCrashTestingOnly() {
106+
defer func() {
107+
if r := recover(); r != nil {
108+
n.l.Error().Msg("recovered from crash")
109+
}
110+
}()
111+
112+
crash()
113+
}

native.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package kvm
22

33
import (
4+
"os"
45
"sync"
56
"time"
67

@@ -56,4 +57,8 @@ func initNative(systemVersion *semver.Version, appVersion *semver.Version) {
5657
},
5758
})
5859
nativeInstance.Start()
60+
61+
if os.Getenv("JETKVM_CRASH_TESTING") == "1" {
62+
nativeInstance.DoNotUseThisIsForCrashTestingOnly()
63+
}
5964
}

0 commit comments

Comments
 (0)