mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-11 13:16:53 +00:00
GODT-2039: bridge monitors bridge-gui via its PID (port from v2.4)
This commit is contained in:
@ -69,15 +69,16 @@ const (
|
|||||||
|
|
||||||
// Hidden flags.
|
// Hidden flags.
|
||||||
const (
|
const (
|
||||||
flagLauncher = "launcher"
|
flagLauncher = "launcher"
|
||||||
flagNoWindow = "no-window"
|
flagNoWindow = "no-window"
|
||||||
|
flagParentPID = "parent-pid"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
appUsage = "Proton Mail IMAP and SMTP Bridge"
|
appUsage = "Proton Mail IMAP and SMTP Bridge"
|
||||||
)
|
)
|
||||||
|
|
||||||
func New() *cli.App {
|
func New() *cli.App { //nolint:funlen
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
|
|
||||||
app.Name = constants.FullAppName
|
app.Name = constants.FullAppName
|
||||||
@ -133,6 +134,11 @@ func New() *cli.App {
|
|||||||
Usage: "The launcher used to start the app",
|
Usage: "The launcher used to start the app",
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
},
|
},
|
||||||
|
&cli.IntFlag{
|
||||||
|
Name: flagParentPID,
|
||||||
|
Usage: "Process ID of the parent",
|
||||||
|
Hidden: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Action = run
|
app.Action = run
|
||||||
@ -203,9 +209,14 @@ func run(c *cli.Context) error { //nolint:funlen
|
|||||||
b.PushError(bridge.ErrVaultCorrupt)
|
b.PushError(bridge.ErrVaultCorrupt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parentPID := -1
|
||||||
|
if pid := c.Int(flagParentPID); pid != 0 {
|
||||||
|
parentPID = pid
|
||||||
|
}
|
||||||
|
|
||||||
// Run the frontend.
|
// Run the frontend.
|
||||||
return runFrontend(c, crashHandler, restarter,
|
return runFrontend(c, crashHandler, restarter,
|
||||||
locations, b, eventCh,
|
locations, b, eventCh, parentPID,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@ -38,6 +38,7 @@ func runFrontend(
|
|||||||
locations *locations.Locations,
|
locations *locations.Locations,
|
||||||
bridge *bridge.Bridge,
|
bridge *bridge.Bridge,
|
||||||
eventCh <-chan events.Event,
|
eventCh <-chan events.Event,
|
||||||
|
parentPID int,
|
||||||
) error {
|
) error {
|
||||||
switch {
|
switch {
|
||||||
case c.Bool(flagCLI):
|
case c.Bool(flagCLI):
|
||||||
@ -47,7 +48,7 @@ func runFrontend(
|
|||||||
select {}
|
select {}
|
||||||
|
|
||||||
case c.Bool(flagGRPC):
|
case c.Bool(flagGRPC):
|
||||||
service, err := grpc.NewService(crashHandler, restarter, locations, bridge, eventCh, !c.Bool(flagNoWindow))
|
service, err := grpc.NewService(crashHandler, restarter, locations, bridge, eventCh, !c.Bool(flagNoWindow), parentPID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not create service: %w", err)
|
return fmt.Errorf("could not create service: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,7 +36,7 @@ const (
|
|||||||
MaxCompressedFilesCount = 6
|
MaxCompressedFilesCount = 6
|
||||||
)
|
)
|
||||||
|
|
||||||
func (bridge *Bridge) ReportBug(ctx context.Context, osType, osVersion, description, username, email, client string, attachLogs bool) error {
|
func (bridge *Bridge) ReportBug(ctx context.Context, osType, osVersion, description, username, email, client string, attachLogs bool) error { //nolint:funlen
|
||||||
var account string
|
var account string
|
||||||
|
|
||||||
if info, err := bridge.QueryUserInfo(username); err == nil {
|
if info, err := bridge.QueryUserInfo(username); err == nil {
|
||||||
@ -59,15 +59,15 @@ func (bridge *Bridge) ReportBug(ctx context.Context, osType, osVersion, descript
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
guiLogs, err := getMatchingLogs(bridge.locator, func(filename string) bool {
|
crashes, err := getMatchingLogs(bridge.locator, func(filename string) bool {
|
||||||
return logging.MatchGUILogName(filename) && !logging.MatchStackTraceName(filename)
|
return logging.MatchLogName(filename) && logging.MatchStackTraceName(filename)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
crashes, err := getMatchingLogs(bridge.locator, func(filename string) bool {
|
guiLogs, err := getMatchingLogs(bridge.locator, func(filename string) bool {
|
||||||
return logging.MatchLogName(filename) && logging.MatchStackTraceName(filename)
|
return logging.MatchGUILogName(filename) && !logging.MatchStackTraceName(filename)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@ -257,7 +257,10 @@ void launchBridge(QStringList const &args)
|
|||||||
else
|
else
|
||||||
app().log().debug(QString("Bridge executable path: %1").arg(QDir::toNativeSeparators(bridgeExePath)));
|
app().log().debug(QString("Bridge executable path: %1").arg(QDir::toNativeSeparators(bridgeExePath)));
|
||||||
|
|
||||||
overseer = std::make_unique<Overseer>(new ProcessMonitor(bridgeExePath, QStringList("--grpc") + args, nullptr), nullptr);
|
qint64 const pid = qApp->applicationPid();
|
||||||
|
QStringList const params = QStringList { "--grpc", "--parent-pid", QString::number(pid) } + args ;
|
||||||
|
app().log().info(QString("Launching bridge process with command \"%1\" %2").arg(bridgeExePath, params.join(" ")));
|
||||||
|
overseer = std::make_unique<Overseer>(new ProcessMonitor(bridgeExePath, params , nullptr), nullptr);
|
||||||
overseer->startWorker(true);
|
overseer->startWorker(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,9 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v2/internal/events"
|
"github.com/ProtonMail/proton-bridge/v2/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/safe"
|
"github.com/ProtonMail/proton-bridge/v2/internal/safe"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
||||||
|
"github.com/bradenaw/juniper/xslices"
|
||||||
|
"github.com/elastic/go-sysinfo"
|
||||||
|
sysinfotypes "github.com/elastic/go-sysinfo/types"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"gitlab.protontech.ch/go/liteapi"
|
"gitlab.protontech.ch/go/liteapi"
|
||||||
@ -80,8 +83,9 @@ type Service struct { // nolint:structcheck
|
|||||||
initializing sync.WaitGroup
|
initializing sync.WaitGroup
|
||||||
initializationDone sync.Once
|
initializationDone sync.Once
|
||||||
firstTimeAutostart sync.Once
|
firstTimeAutostart sync.Once
|
||||||
|
parentPID int
|
||||||
showOnStartup bool
|
parentPIDDoneCh chan struct{}
|
||||||
|
showOnStartup bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService returns a new instance of the service.
|
// NewService returns a new instance of the service.
|
||||||
@ -94,6 +98,7 @@ func NewService(
|
|||||||
bridge *bridge.Bridge,
|
bridge *bridge.Bridge,
|
||||||
eventCh <-chan events.Event,
|
eventCh <-chan events.Event,
|
||||||
showOnStartup bool,
|
showOnStartup bool,
|
||||||
|
parentPID int,
|
||||||
) (*Service, error) {
|
) (*Service, error) {
|
||||||
tlsConfig, certPEM, err := newTLSConfig()
|
tlsConfig, certPEM, err := newTLSConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -137,7 +142,9 @@ func NewService(
|
|||||||
initializationDone: sync.Once{},
|
initializationDone: sync.Once{},
|
||||||
firstTimeAutostart: sync.Once{},
|
firstTimeAutostart: sync.Once{},
|
||||||
|
|
||||||
showOnStartup: showOnStartup,
|
parentPID: parentPID,
|
||||||
|
parentPIDDoneCh: make(chan struct{}),
|
||||||
|
showOnStartup: showOnStartup,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initializing.Done is only called sync.Once. Please keep the increment set to 1
|
// Initializing.Done is only called sync.Once. Please keep the increment set to 1
|
||||||
@ -170,6 +177,12 @@ func (s *Service) initAutostart() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Loop() error {
|
func (s *Service) Loop() error {
|
||||||
|
if s.parentPID < 0 {
|
||||||
|
s.log.Info("Not monitoring parent PID")
|
||||||
|
} else {
|
||||||
|
go s.monitorParentPID()
|
||||||
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = s.bridge.SetFirstStartGUI(false)
|
_ = s.bridge.SetFirstStartGUI(false)
|
||||||
}()
|
}()
|
||||||
@ -486,3 +499,41 @@ func newStreamTokenValidator(wantToken string) grpc.StreamServerInterceptor {
|
|||||||
return handler(srv, stream)
|
return handler(srv, stream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// monitorParentPID check at regular intervals that the parent process is still alive, and if not shuts down the server
|
||||||
|
// and the applications.
|
||||||
|
func (s *Service) monitorParentPID() {
|
||||||
|
s.log.Infof("Starting to monitor parent PID %v", s.parentPID)
|
||||||
|
ticker := time.NewTicker(5 * time.Second)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
if s.parentPID < 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
processes, err := sysinfo.Processes() // sysinfo.Process(pid) does not seem to work on Windows.
|
||||||
|
if err != nil {
|
||||||
|
s.log.Debug("Could not retrieve process list")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !xslices.Any(processes, func(p sysinfotypes.Process) bool { return p != nil && p.PID() == s.parentPID }) {
|
||||||
|
s.log.Info("Parent process does not exist anymore. Initiating shutdown")
|
||||||
|
// quit will write to the parentPIDDoneCh, so we launch a goroutine.
|
||||||
|
go func() {
|
||||||
|
if err := s.quit(); err != nil {
|
||||||
|
logrus.WithError(err).Error("Error on quit")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
s.log.Tracef("Parent process %v is still alive", s.parentPID)
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-s.parentPIDDoneCh:
|
||||||
|
s.log.Infof("Stopping process monitoring for PID %v", s.parentPID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -104,6 +104,10 @@ func (s *Service) Quit(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empt
|
|||||||
func (s *Service) quit() error {
|
func (s *Service) quit() error {
|
||||||
// Windows is notably slow at Quitting. We do it in a goroutine to speed things up a bit.
|
// Windows is notably slow at Quitting. We do it in a goroutine to speed things up a bit.
|
||||||
go func() {
|
go func() {
|
||||||
|
if s.parentPID >= 0 {
|
||||||
|
s.parentPIDDoneCh <- struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if s.isStreamingEvents() {
|
if s.isStreamingEvents() {
|
||||||
if err = s.stopEventStream(); err != nil {
|
if err = s.stopEventStream(); err != nil {
|
||||||
|
|||||||
@ -72,7 +72,7 @@ func (restarter *Restarter) Restart() {
|
|||||||
delete(env, BridgeCrashCount)
|
delete(env, BridgeCrashCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := execabs.Command(restarter.exe, xslices.Join(os.Args[1:], restarter.flags)...) //nolint:gosec
|
cmd := execabs.Command(restarter.exe, xslices.Join(removeFlagWithValue(os.Args[1:], "parent-pid"), restarter.flags)...) //nolint:gosec
|
||||||
l := logrus.WithFields(logrus.Fields{
|
l := logrus.WithFields(logrus.Fields{
|
||||||
"exe": restarter.exe,
|
"exe": restarter.exe,
|
||||||
"crashCount": env[BridgeCrashCount],
|
"crashCount": env[BridgeCrashCount],
|
||||||
@ -130,3 +130,27 @@ func increment(value string) string {
|
|||||||
|
|
||||||
return strconv.Itoa(valueInt + 1)
|
return strconv.Itoa(valueInt + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// removeFlagWithValue removes a flag that requires a value from a list of command line parameters.
|
||||||
|
// The flag can be of the following form `-flag value`, `--flag value`, `-flag=value` or `--flags=value`.
|
||||||
|
func removeFlagWithValue(argList []string, flag string) []string {
|
||||||
|
var result []string
|
||||||
|
|
||||||
|
for i := 0; i < len(argList); i++ {
|
||||||
|
arg := argList[i]
|
||||||
|
// "detect the parameter form "-flag value" or "--paramName value"
|
||||||
|
if (arg == "-"+flag) || (arg == "--"+flag) {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// "detect the form "--flag=value" or "--flag=value"
|
||||||
|
if strings.HasPrefix(arg, "-"+flag+"=") || (strings.HasPrefix(arg, "--"+flag+"=")) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
43
pkg/restarter/restarter_test.go
Normal file
43
pkg/restarter/restarter_test.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (c) 2022 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail 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.
|
||||||
|
//
|
||||||
|
// Proton Mail 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package restarter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRemoveFlagWithValue(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
argList []string
|
||||||
|
flag string
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{[]string{}, "b", nil},
|
||||||
|
{[]string{"-a", "-b=value", "-c"}, "b", []string{"-a", "-c"}},
|
||||||
|
{[]string{"-a", "--b=value", "-c"}, "b", []string{"-a", "-c"}},
|
||||||
|
{[]string{"-a", "-b", "value", "-c"}, "b", []string{"-a", "-c"}},
|
||||||
|
{[]string{"-a", "--b", "value", "-c"}, "b", []string{"-a", "-c"}},
|
||||||
|
{[]string{"-a", "-B=value", "-c"}, "b", []string{"-a", "-B=value", "-c"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
require.Equal(t, removeFlagWithValue(tt.argList, tt.flag), tt.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -203,6 +203,7 @@ func (t *testCtx) initFrontendService(eventCh <-chan events.Event) error {
|
|||||||
t.bridge,
|
t.bridge,
|
||||||
eventCh,
|
eventCh,
|
||||||
true,
|
true,
|
||||||
|
-1,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not create service: %w", err)
|
return fmt.Errorf("could not create service: %w", err)
|
||||||
|
|||||||
Reference in New Issue
Block a user