diff --git a/internal/app/app.go b/internal/app/app.go index abc34800..8916c95c 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -166,6 +166,8 @@ func run(c *cli.Context) error { //nolint:funlen exe = os.Args[0] } + migrationErr := migrateOldVersions() + // Run with profiling if requested. return withProfiler(c, func() error { // Restart the app if requested. @@ -176,6 +178,9 @@ func run(c *cli.Context) error { //nolint:funlen return WithLocations(func(locations *locations.Locations) error { // Initialize logging. return withLogging(c, crashHandler, locations, func() error { + if migrationErr != nil { + logrus.WithError(migrationErr).Error("Migration failed") + } // Ensure we are the only instance running. return withSingleInstance(locations, version, func() error { // Unlock the encrypted vault. diff --git a/internal/app/migration.go b/internal/app/migration.go new file mode 100644 index 00000000..097f3907 --- /dev/null +++ b/internal/app/migration.go @@ -0,0 +1,106 @@ +// 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 . + +package app + +import ( + "os" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/allan-simon/go-singleinstance" + "github.com/hashicorp/go-multierror" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +func migrateOldVersions() (allErrors error) { + cacheDir, cacheError := os.UserCacheDir() + if cacheError != nil { + allErrors = multierror.Append(allErrors, errors.Wrap(cacheError, "cannot get os cache")) + return // not need to continue for now (with more migrations might be still ok to continue) + } + + if err := killV2AppAndRemoveV2LockFiles(filepath.Join(cacheDir, "protonmail", "bridge", "bridge.lock")); err != nil { + allErrors = multierror.Append(allErrors, errors.Wrap(err, "cannot migrate lockfiles")) + } + + return +} + +func killV2AppAndRemoveV2LockFiles(lockFilePathV2 string) error { + l := logrus.WithField("path", lockFilePathV2) + + if _, err := os.Stat(lockFilePathV2); os.IsNotExist(err) { + l.Debug("no v2 lockfile") + return nil + } + + lock, err := singleinstance.CreateLockFile(lockFilePathV2) + + if err == nil { + l.Debug("no other v2 instance is running") + + if errClose := lock.Close(); errClose != nil { + l.WithError(errClose).Error("Cannot close lock file") + } + + return os.Remove(lockFilePathV2) + } + + // The other instance is an older version, so we should kill it. + pid, err := getPID(lockFilePathV2) + if err != nil { + return errors.Wrap(err, "cannot get v2 pid") + } + + if err := killPID(pid); err != nil { + return errors.Wrapf(err, "cannot kill v2 app (PID %d)", pid) + } + + // Need to wait some time to release file lock + time.Sleep(time.Second) + + return nil +} + +func getPID(lockFilePath string) (int, error) { + file, err := os.Open(filepath.Clean(lockFilePath)) + if err != nil { + return 0, err + } + defer func() { _ = file.Close() }() + + rawPID := make([]byte, 10) // PID is probably up to 7 digits long, 10 should be enough + n, err := file.Read(rawPID) + if err != nil { + return 0, err + } + + return strconv.Atoi(strings.TrimSpace(string(rawPID[:n]))) +} + +func killPID(pid int) error { + p, err := os.FindProcess(pid) + if err != nil { + return err + } + + return p.Kill() +} diff --git a/internal/app/singleinstance_unix.go b/internal/app/singleinstance.go similarity index 71% rename from internal/app/singleinstance_unix.go rename to internal/app/singleinstance.go index f7c98130..0009a88f 100644 --- a/internal/app/singleinstance_unix.go +++ b/internal/app/singleinstance.go @@ -15,24 +15,17 @@ // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . -//go:build !windows -// +build !windows - package app import ( "fmt" "os" - "path/filepath" - "strconv" - "strings" "time" "github.com/Masterminds/semver/v3" "github.com/ProtonMail/proton-bridge/v2/internal/focus" "github.com/allan-simon/go-singleinstance" "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" ) // checkSingleInstance checks if another instance of the application is already running. @@ -66,7 +59,7 @@ func checkSingleInstance(lockFilePath string, curVersion *semver.Version) (*os.F return nil, err } - if err := unix.Kill(pid, unix.SIGTERM); err != nil { + if err := killPID(pid); err != nil { return nil, err } @@ -75,39 +68,3 @@ func checkSingleInstance(lockFilePath string, curVersion *semver.Version) (*os.F return singleinstance.CreateLockFile(lockFilePath) } - -func getPID(lockFilePath string) (int, error) { - file, err := os.Open(filepath.Clean(lockFilePath)) - if err != nil { - return 0, err - } - defer func() { _ = file.Close() }() - - rawPID := make([]byte, 10) // PID is probably up to 7 digits long, 10 should be enough - n, err := file.Read(rawPID) - if err != nil { - return 0, err - } - - return strconv.Atoi(strings.TrimSpace(string(rawPID[:n]))) -} - -/* -func runningVersionIsOlder() error { - currentVer, err := semver.StrictNewVersion(constants.Version) - if err != nil { - return err - } - - runningVer, err := semver.StrictNewVersion(settingsObj.Get(settings.LastVersionKey)) - if err != nil { - return err - } - - if !runningVer.LessThan(currentVer) { - return errors.New("running version is not older") - } - - return nil -} -*/ diff --git a/internal/app/singleinstance_windows.go b/internal/app/singleinstance_windows.go deleted file mode 100644 index 6efaf0c0..00000000 --- a/internal/app/singleinstance_windows.go +++ /dev/null @@ -1,32 +0,0 @@ -// 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 . - -//go:build windows -// +build windows - -package app - -import ( - "os" - - "github.com/Masterminds/semver/v3" - "github.com/allan-simon/go-singleinstance" -) - -func checkSingleInstance(lockFilePath string, _ *semver.Version) (*os.File, error) { - return singleinstance.CreateLockFile(lockFilePath) -}