@@ -3,18 +3,37 @@ package main
3
3
import (
4
4
"flag"
5
5
"fmt"
6
+ "io"
6
7
"os"
8
+ "os/exec"
9
+ "os/signal"
10
+ "path/filepath"
11
+ "syscall"
12
+ "time"
7
13
14
+ "github.com/erikdubbelboer/gspt"
8
15
"github.com/jetkvm/kvm"
9
16
)
10
17
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
+
11
30
func main () {
12
31
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" )
14
33
flag .Parse ()
15
34
16
- if * versionPtr || * versionJsonPtr {
17
- versionData , err := kvm .GetVersionData (* versionJsonPtr )
35
+ if * versionPtr || * versionJSONPtr {
36
+ versionData , err := kvm .GetVersionData (* versionJSONPtr )
18
37
if err != nil {
19
38
fmt .Printf ("failed to get version data: %v\n " , err )
20
39
os .Exit (1 )
@@ -23,5 +42,129 @@ func main() {
23
42
return
24
43
}
25
44
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
+ }
27
170
}
0 commit comments