VMBackup/debughelper/main.go (156 lines of code) (raw):

package main import ( "bytes" "context" "flag" "fmt" "io" "log" "os" "os/exec" "os/signal" "runtime" "strings" "sync" "syscall" "time" "github.com/oklog/ulid/v2" ) var ( working_directory = flag.String( "wd", "./", "Location which this application will use for all it's processing and persisting data. Please make sure this location does not get frozen during a snapshot operation", ) extension_command = flag.String("extcmd", "", "The command to execute extensions with") run_diagnosis = flag.Bool("diagnose", false, "Daignose the system") with_strace = flag.Bool("strace", false, "The tool will run with strace enabled") strace_pid = flag.Int64("tracepid", 0, "The PID to apply strace on") log_to_mem = flag.Bool("logtomem", true, "Will temporarily log to memory before moving all log files to working directory") ) func wrapErr(err error, msgs ...string) error { pc := make([]uintptr, 15) n := runtime.Callers(2, pc) frames := runtime.CallersFrames(pc[:n]) frame, _ := frames.Next() src := frame.Function s := strings.Join(append([]string{src}, msgs...), " -> ") return fmt.Errorf("%s -> %s", s, err.Error()) } func checkBinExistence(c string) bool { if len(c) == 0 { return false } cmd := exec.Command("which", c) bs, err := cmd.CombinedOutput() if err != nil { log.Println(wrapErr(err, "CombinedOutput failed")) return false } if cmd.ProcessState.ExitCode() != 0 { return false } if len(bs) == 0 { return false } return true } func checkSvcExistence(s string) bool { if len(s) == 0 { return false } cmd := exec.Command("systemctl", "list-unit-files", "--type", "service") cmd2 := exec.Command("grep", "-e", s) r, w := io.Pipe() cmd.Stdout = w cmd2.Stdin = r var b2 bytes.Buffer cmd2.Stdout = &b2 cmd.Start() cmd2.Start() cmd.Wait() w.Close() cmd2.Wait() bs := b2.Bytes() if len(bs) == 0 { return false } return true } func envVarExists(v string) bool { return len(os.Getenv(v)) > 0 } func databaseText(d string) string { return fmt.Sprintf("Unsupported database detected: \"%s\". Please make sure the database is not in use during a snapshot operation. The heavy disk IO behavior of databases can conflict with disk freezing", d) } func avText(a string) string { return fmt.Sprintf("Anitivirus detected: \"%s\". Make sure no files, directories, or mountpoints are being scanned during a snapshot operation", a) } func diagnoseDbs() []string { dbreport := []string{} if checkSvcExistence("postgresql.service") { dbreport = append(dbreport, databaseText("PostgreSQL")) } if checkSvcExistence("mongod") { dbreport = append(dbreport, databaseText("MongoDB")) } if checkBinExistence("mysqld") || checkBinExistence("mysql") { dbreport = append(dbreport, databaseText("MySQL")) } return dbreport } func diagnoseAvs() []string { clamAVExists := checkBinExistence("clamscan") bitDefenderExists := checkSvcExistence("bdsec*") avreport := []string{} if clamAVExists { avreport = append(avreport, avText("ClamAV")) } if bitDefenderExists { avreport = append(avreport, avText("Bitdefender")) } return avreport } func main() { flag.Parse() opID := fmt.Sprintf("%s", ulid.Make()) if *with_strace && *strace_pid == 0 { log.Printf("Cannot trace PID: 0") return } r, rf, err := NewRun(*working_directory, opID, *with_strace, *strace_pid, *log_to_mem) if err != nil { log.Println(wrapErr(err)) return } defer rf.Close() if *run_diagnosis { lf := r.diagnose() log.Printf("Diagnosis has been written to:\n%s\n", lf) return } wg := sync.WaitGroup{} ctx, cancel := context.WithCancel(context.Background()) wg.Add(1) go func() { defer wg.Done() r.monitor(ctx) }() inter := make(chan os.Signal, 1) // Auto kill after 20 minutes go func(inter chan os.Signal) { i := 0 ticker := time.NewTicker(time.Second) for range ticker.C { i++ if i >= (20 * 60) { break } } ticker.Stop() inter <- syscall.SIGINT }(inter) signal.Notify(inter, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) <-inter cancel() wg.Wait() }