// Copyright (c) 2020 Proton Technologies AG // // This file is part of ProtonMail Bridge. // // ProtonMail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // ProtonMail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . package cmd import ( "fmt" "os" "os/exec" "strconv" "strings" "github.com/ProtonMail/proton-bridge/internal/frontend" "github.com/ProtonMail/proton-bridge/pkg/config" "github.com/urfave/cli" ) const ( // After how many crashes app gives up starting. maxAllowedCrashes = 10 ) var ( // How many crashes happened so far in a row. // It will be filled from args by `filterRestartNumberFromArgs`. // Every call of `HandlePanic` will increase this number. // Then it will be passed as argument to the next try by `RestartApp`. numberOfCrashes = 0 //nolint[gochecknoglobals] ) // filterRestartNumberFromArgs removes flag with a number how many restart we already did. // See restartApp how that number is used. func filterRestartNumberFromArgs() { tmp := os.Args[:0] for i, arg := range os.Args { if !strings.HasPrefix(arg, "--restart_") { tmp = append(tmp, arg) continue } var err error numberOfCrashes, err = strconv.Atoi(os.Args[i][10:]) if err != nil { numberOfCrashes = maxAllowedCrashes } } os.Args = tmp } // DisableRestart disables restart once `RestartApp` is called. func DisableRestart() { numberOfCrashes = maxAllowedCrashes } // RestartApp starts a new instance in background. func RestartApp() { if numberOfCrashes >= maxAllowedCrashes { log.Error("Too many crashes") return } if exeFile, err := os.Executable(); err == nil { arguments := append(os.Args[1:], fmt.Sprintf("--restart_%d", numberOfCrashes)) cmd := exec.Command(exeFile, arguments...) //nolint[gosec] cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Stdin = os.Stdin if err := cmd.Start(); err != nil { log.Error("Restart failed: ", err) } } } // PanicHandler defines HandlePanic which can be used anywhere in defer. type PanicHandler struct { AppName string Config *config.Config Err *error // Pointer to error of cli action. } // HandlePanic should be called in defer to ensure restart of app after error. func (ph *PanicHandler) HandlePanic() { r := recover() if r == nil { return } config.HandlePanic(ph.Config, fmt.Sprintf("Recover: %v", r)) frontend.HandlePanic(ph.AppName) *ph.Err = cli.NewExitError("Panic and restart", 255) numberOfCrashes++ log.Error("Restarting after panic") RestartApp() os.Exit(255) }