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