-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
127 lines (113 loc) · 3.61 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/*
* Copyright (c) 2016-2017, Randy Westlund. All rights reserved.
* This code is under the BSD-2-Clause license.
*
* This is the main file. Run it to launch the application.
*/
package main
import (
"flag"
"fmt"
"golang.org/x/sys/unix"
"log"
"os"
"os/signal"
"text/tabwriter"
)
// The location of the configuration file to read. Compile with .e.g.
// -ldflags="-X main.localbase=/usr/local" if the config file will not be under
// /etc/.
func printYN(b bool) string {
if b {
return "Yes"
} else {
return "No"
}
}
var localbase = ""
func main() {
// A dictionary of all processes.
procs := make(map[string]*process)
// Count the children we have so we know when to exit.
runningProcesses := 0
// Listen on this channel to know when a program has started.
runningChan := make(chan launchStatus)
// Listen on this channel to know when a program is done.
doneChan := make(chan launchStatus)
// This writer will be used to print info in response to SIGINFO.
writer := tabwriter.NewWriter(os.Stderr, 0, 8, 0, '\t', 0)
// Read the config file.
var configFile = flag.String("f", localbase+"/etc/paladin.conf",
"The configuration file to read from.")
flag.Parse()
var config *configOptions = parseConfigFile(*configFile)
// Change logger output destination, if necessary.
if config.LogFile != "" {
logfile, err := os.OpenFile(config.LogFile,
os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0664)
if err != nil {
log.Fatal("Failed to open log file ", config.LogFile, "\n", err)
}
defer logfile.Close()
log.SetOutput(logfile)
}
// Build the process dictionary.
for _, pc := range config.Process {
procs[pc.Name] = &process{Config: pc}
}
// Launch only leaf node processes; those that don't depend on any others.
// The remaining processes will be launched when the events from these
// starting are received.
for i := range procs {
if len(procs[i].Config.SoftDepends) == 0 {
// Launch the process in a new goroutine.
go launchProcess(procs[i].Config, runningChan, doneChan)
runningProcesses++
}
}
// Setup notification for signals.
var sigs = make(chan os.Signal)
signal.Notify(sigs, unix.SIGTERM, unix.SIGINT, unix.SIGINFO)
// Use this var to avoid restarting processes as we kill them during
// shutdown.
var shutdown bool
for runningProcesses > 0 {
select {
// Listen for events fired when a child starts.
case status := <-runningChan:
procs[status.Name].Status = status
handleRunning(procs, runningChan, doneChan, status, &runningProcesses)
// Listen for events fired when a child exits.
case status := <-doneChan:
procs[status.Name].Status = status
runningProcesses--
handleDone(procs, runningChan, doneChan, status, &runningProcesses, shutdown)
// Listen for OS signals.
case sig := <-sigs:
switch sig {
// In the case of a kill signal, terminate all
// children before exiting.
case unix.SIGTERM, unix.SIGINT:
log.Println("Signal", sig, " received, termiating children...")
shutdown = true
for i := range procs {
if procs[i].Running {
log.Println("killing", procs[i].Config.Name)
unix.Kill(procs[i].Status.Pid, unix.SIGTERM)
}
}
case unix.SIGINFO:
fmt.Println("The following children are running:")
fmt.Fprintln(writer, "Process\tRunning\tPath\tArgs\tUser\tGroup")
fmt.Fprintln(writer, "-------\t-------\t----\t----\t----\t-----")
for i := range procs {
fmt.Fprintf(writer, "%s\t%s\t%s\t%s\t%s\t%s\n", procs[i].Config.Name,
printYN(procs[i].Running), procs[i].Config.Path,
procs[i].Config.Args, procs[i].Config.User, procs[i].Config.Group)
writer.Flush()
}
}
}
}
log.Println("done")
}