/
main.go
379 lines (305 loc) · 9.51 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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
// Copyright (2012) Sandia Corporation.
// Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
// the U.S. Government retains certain rights in this software.
package main
import (
"flag"
"fmt"
"os"
"os/signal"
"os/user"
"path/filepath"
"runtime"
"runtime/pprof"
"strconv"
"strings"
"sync"
"syscall"
"time"
"github.com/sandia-minimega/minimega/v2/internal/version"
"github.com/sandia-minimega/minimega/v2/internal/vlans"
"github.com/sandia-minimega/minimega/v2/pkg/minicli"
"github.com/sandia-minimega/minimega/v2/pkg/miniclient"
log "github.com/sandia-minimega/minimega/v2/pkg/minilog"
"github.com/sandia-minimega/minimega/v2/pkg/minipager"
"github.com/peterh/liner"
)
const (
BASE_PATH = "/tmp/minimega"
IOM_PATH = "/tmp/minimega/files"
Wildcard = "all"
Localhost = "localhost"
)
var (
f_base = flag.String("base", BASE_PATH, "base path for minimega data")
f_degree = flag.Uint("degree", 0, "meshage starting degree")
f_msaTimeout = flag.Uint("msa", 10, "meshage MSA timeout")
f_broadcastIP = flag.String("broadcast", "255.255.255.255", "meshage broadcast address to use")
f_vlanRange = flag.String("vlanrange", "101-4096", "default VLAN range for namespaces")
f_port = flag.Int("port", 9000, "meshage port to listen on")
f_force = flag.Bool("force", false, "force minimega to run even if it appears to already be running")
f_recover = flag.Bool("recover", false, "attempt to recover from a previously running instance (only if -force is not set)")
f_nostdin = flag.Bool("nostdin", false, "disable reading from stdin, useful for putting minimega in the background")
f_version = flag.Bool("version", false, "print the version and copyright notices")
f_context = flag.String("context", "minimega", "meshage context for discovery")
f_iomBase = flag.String("filepath", IOM_PATH, "directory to serve files from")
f_cli = flag.Bool("cli", false, "validate and print the minimega cli, in JSON, to stdout and exit")
f_panic = flag.Bool("panic", false, "panic on quit, producing stack traces for debugging")
f_cgroup = flag.String("cgroup", "/sys/fs/cgroup", "path to cgroup mount")
f_pipe = flag.String("pipe", "", "read/write to or from a named pipe")
f_headnode = flag.String("headnode", "", "mesh node to send all logs to and get all files from")
f_hashfiles = flag.Bool("hashfiles", false, "hash files to be served by iomeshage")
f_e = flag.Bool("e", false, "execute command on running minimega")
f_attach = flag.Bool("attach", false, "attach the minimega command line to a running instance of minimega")
f_namespace = flag.String("namespace", "", "prepend namespace to all -attach and -e commands")
hostname string
reserved = []string{Wildcard}
attached *miniclient.Conn
// channel to shutdown minimega
shutdown = make(chan os.Signal, 1)
shutdownMu sync.Mutex
)
const (
banner = `minimega, Copyright (2014) Sandia Corporation.
Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
the U.S. Government retains certain rights in this software.`
poeticDeath = `Willst du immer weiterschweifen?
Sieh, das Gute liegt so nah.
Lerne nur das Glück ergreifen,
denn das Glück ist immer da.`
)
func usage() {
fmt.Println(banner)
fmt.Println("usage: minimega [option]... [file]...")
flag.PrintDefaults()
}
func main() {
var err error
flag.Usage = usage
flag.Parse()
log.Init()
logLevel = log.LevelFlag
// see containerShim()
if flag.NArg() > 1 && flag.Arg(0) == CONTAINER_MAGIC {
containerShim()
}
cliSetup()
if *f_cli {
if err := minicli.Validate(); err != nil {
log.Fatalln(err)
}
doc, err := minicli.Doc()
if err != nil {
log.Fatal("failed to generate docs: %v", err)
}
fmt.Println(doc)
os.Exit(0)
}
if *f_version {
fmt.Println("minimega", version.Version, version.Revision, version.Date)
fmt.Println(version.Copyright)
os.Exit(0)
}
// see pipeMMHandler in plumber.go
if *f_pipe != "" {
pipeMMHandler()
return
}
// warn if we're not root
user, err := user.Current()
if err != nil {
log.Fatalln(err)
}
if user.Uid != "0" {
log.Warnln("not running as root")
}
// set global for hostname
hostname, err = os.Hostname()
if err != nil {
log.Fatalln(err)
}
if isReserved(hostname) {
log.Warn("hostname `%s` is a reserved word -- abandon all hope, ye who enter here", hostname)
}
// Don't allow a minimega instance to point to itself as the headnode, which
// would break mesh logging and iomeshage.
if *f_headnode == hostname {
headnode := ""
f_headnode = &headnode
}
// special case, catch -e and execute a command on an already running
// minimega instance
if *f_e || *f_attach {
// try to connect to the local minimega
mm, err := miniclient.Dial(*f_base)
if err != nil {
log.Fatalln(err)
}
mm.Pager = minipager.DefaultPager
if *f_e {
parts := []string{}
if *f_namespace != "" {
parts = append(parts, "namespace", *f_namespace)
}
parts = append(parts, flag.Args()...)
cmd := quoteJoin(parts, " ")
if len(parts) == 1 {
cmd = parts[0]
}
log.Info("got command: `%v`", cmd)
mm.RunAndPrint(cmd, false)
} else {
attached = mm
mm.Attach(*f_namespace)
}
return
}
fmt.Println(banner)
// check all the external dependencies
if err := checkExternal(); err != nil {
log.Warnln(err.Error())
}
// rebase f_iomBase if f_base changed but iomBase did not
if *f_base != BASE_PATH && *f_iomBase == IOM_PATH {
*f_iomBase = filepath.Join(*f_base, "files")
}
// check for a running instance of minimega
if _, err := os.Stat(filepath.Join(*f_base, "minimega")); err == nil {
if *f_force {
log.Warn("minimega may already be running, proceed with caution")
if err := os.Remove(filepath.Join(*f_base, "minimega")); err != nil {
log.Fatalln(err)
}
} else if *f_recover {
if err := os.Remove(filepath.Join(*f_base, "minimega")); err != nil {
log.Fatalln(err)
}
// an attempt to recover will happen later after the mesh is initialized
} else {
log.Fatalln("minimega appears to already be running, override with -force")
}
}
// attempt to set up the base path
if err := os.MkdirAll(*f_base, os.FileMode(0770)); err != nil {
log.Fatal("mkdir base path: %v", err)
}
pid := strconv.Itoa(os.Getpid())
mustWrite(filepath.Join(*f_base, "minimega.pid"), pid)
// fan out to the number of cpus on the system if GOMAXPROCS env variable
// is not set.
if os.Getenv("GOMAXPROCS") == "" {
runtime.GOMAXPROCS(runtime.NumCPU())
}
// start services
// NOTE: the plumber needs a reference to the meshage node, and cc
// needs a reference to the plumber, so the order here counts
tapReaperStart()
if err := meshageStart(hostname, *f_context, *f_degree, *f_msaTimeout, *f_broadcastIP, *f_port); err != nil {
// TODO: we've created the PID file...
log.Fatal("unable to start meshage: %v", err)
}
// wait a bit to let mesh settle
time.Sleep(500 * time.Millisecond)
plumberStart(meshageNode)
if err := setupMeshageLogging(*f_headnode); err != nil {
log.Fatal("unable to setup mesh logging: %v", err)
}
if *f_recover { // has to happen after meshageNode is created
if err := recover(); err != nil {
log.Fatal("recovery failed: %v", err)
}
}
// has to happen after meshageNode is created
GetOrCreateNamespace(DefaultNamespace)
SetNamespace(DefaultNamespace)
// set default VLAN range
vlanRange := strings.Split(*f_vlanRange, "-")
if len(vlanRange) != 2 {
log.Fatal("invalid VLAN range provided")
}
min, err := strconv.Atoi(vlanRange[0])
if err != nil {
log.Fatal("expected integer values for VLAN range")
}
max, err := strconv.Atoi(vlanRange[1])
if err != nil {
log.Fatal("expected integer values for VLAN range")
}
if max <= min {
log.Fatal("expected min < max for VLAN range")
}
if err := vlans.SetRange("", min, max); err != nil {
log.Fatal("unable to set default VLAN range to %s: %v", *f_vlanRange, err)
}
commandSocketStart()
// set up signal handling
signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM)
if !*f_nostdin {
// start status handler
go func() {
status := make(chan string)
addStatusMessageChannel("localcli", status)
for {
select {
case <-shutdown:
delStatusMessageChannel("localcli")
return
case s := <-status:
minipager.DefaultPager.Page(s)
}
}
}()
// start CLI
input := liner.NewLiner()
defer input.Close()
go func() {
cliLocal(input)
Shutdown("quitting")
}()
}
sig := <-shutdown
if sig != nil {
log.Warn("caught Ctrl-C, shutting down")
if *f_panic {
// panic if we got a signal
panic("teardown")
}
}
teardown()
}
func Shutdown(format string, args ...interface{}) {
// make sure we only close the shutdown channel once. There's no reason to
// unlock since we can only shutdown once.
shutdownMu.Lock()
msg := fmt.Sprintf(format, args...)
// Some callstack voodoo magic
if pc, _, _, ok := runtime.Caller(1); ok {
if fn := runtime.FuncForPC(pc); fn != nil {
file, line := fn.FileLine(pc)
file = filepath.Base(file)
log.Warn("shutdown initiated by %v:%v: %v", file, line, msg)
}
}
close(shutdown)
// block forever
<-make(chan int)
}
func teardown() {
// destroy all namespaces
DestroyNamespace(Wildcard)
// clean-up non-namespace things
dnsmasqKillAll()
ksmDisable()
containerTeardown()
if err := bridgesDestroy(); err != nil {
log.Errorln(err)
}
commandSocketRemove()
if err := os.Remove(filepath.Join(*f_base, "minimega.pid")); err != nil {
log.Errorln(err)
}
if cpuProfileOut != nil {
pprof.StopCPUProfile()
cpuProfileOut.Close()
}
}